Hack.lu 2012 CTF Challenge #17 (400)

17 – Zombieshop

A new company offers a lot of anti zombie equipment to protect yourself. Unfortunately not everyone can buy the good stuff. Only privileged users may do so. We managed to create an account, but it is not privileged. Your mission is to buy “Anti zombie Spray”.

zomboy53:killthezombies

https://ctf.fluxfingers.net:2077/

Summary: bruteforce DES

Here you can add things to basket and order them. After some analysis it’s easy to notice an interesting cookie “data”:

%60%C8%9D%9B%AD%A4%8D%84%8Fs%DF%12cF%CDNQQ%16%F7%0B%C8E%CF%18%A6%DA%B0%3D%B8%BA%1D%B5U%DBq%DF%F8%3B%2F%BC%D6%8D%3D%C0%F3%24W.%EE%C7a4%A6%0C%08%F81%09%AF%C5%07%8B%19%A8%FF%9F%95%27%07%CCN%B1%B6w%AA%01%CA%A0i%D8%0Aj%A0RI%9F3%2F%60%B8%94%10%05%9D%8F7%22%9B%95%13%19%FB%BD%BB%88%83%E4%DDEL%D1%84%9D%DBX%B8-y%AE%9DS%CB+C%60%FE%81%7C%5B%A9%7EH%B0.%3B_f%C0+%BC%D8%FF%BCN%0A%3D%FBaX%0D%134%ED%C5%DE%22L%CDt%9B%D9%FC%90%22_b%83%94%3F%5C%2330%8C%E5%AF%D6%F1%C4%F1%CC%FA%8D%DCW%9A6%E7O%A3%12%80%CD%C3Gpm%ED%07%93%26%A2%CE%A0%D6x%19%E5%F5%81%8E%E2.%DE%92%14%FE%89%B3%5E%C6%98%9A%AF%02%1F%84%E7%86%B2%DF%B9%07%CB%23h%E5%12%0DWM%92%AE%13aU%16%F1L%AA%8E%02%12%DF%97%88%60%CD%84c%EA%F4%0B%AF%C9%88%26%12%F8%BF%EC0%A2%F6%81-%F8%92%04p.%AF%14T%2B%F9%DE%CC%F8G%B4%21%23%04id%F9%E2%B2%28%BE%FB%2C%F3%81AQ%DD%BFW%08%11%B8%02%90%A7%9B%DA%A5%12h%95%C2%05%8F%24%9B%DB%F9%23%A2%B8%CD%AC%9CG3%23%CB%F2dU%2Fc%3E%9C%0F%3A%EE%F7%B8%BB%F72%CAviL%BE%21%E9%FAD%F1%1B%AF%B3%F6%3A4%A2%E1%B7%0E+%DD%CC%EFZ%04%A2x%D0%DB6%F0%94%1C-%A9%7E%18%D2%1E%15%F4%F8%07%40%E4%3B%BD%10d%EAp%26%D0%F3%5E%2AC%9A%A2v%99%BA%D8%13%A4%AB%B1j

It’s large enough and contains some data (probably your orders). If we delete it and buy smth, it will appear again, but smaller one. Let’s try different orders:

0000000: dec8 aabc 7e39 277a 81fd c2f5 2920 86ad  ....~9'z....) ..
0000010: f76c 1978 f51b 404e f802 f8ee cde9 0819  .l.x..@N........
0000020: cf26 8484 1ec6 1fc6
0000000: dec8 aabc 7e39 277a 81fd c2f5 2920 86ad  ....~9'z....) ..
0000010: f76c 1978 f51b 404e 61d3 0d32 9c78 4e48  .l.x..@Na..2.xNH
0000020: cf26 8484 1ec6 1fc6

You can notice only 8 byte changed. DES with ECB mode comes on mind. But we don’t know anything about the plaintext format, so how can we generate a valid ciphertext for the product id 2?

The most straight forward way is bruteforce. We can bruteforce key and then encrypt anything we want.

Also if you play a bit with PHPSESSID cookie, you can notice it’s encrypted in the “data” cookie too:

set PHPSESSID="A"*48 and login:
...
0000130: f381 4151 ddbf 5708 11b8 0290 a79b daa5  ..AQ..W.........
0000140: 5dbb 36b4 79d2 771f 2d91 6672 a90c ed48  ].6.y.w.-.fr...H
0000150: 2d91 6672 a90c ed48 2d91 6672 a90c ed48  -.fr...H-.fr...H
0000160: 2d91 6672 a90c ed48 2d91 6672 a90c ed48  -.fr...H-.fr...H
0000170: 5495 2c3f 766b 297b 18d2 1e15 f4f8 0740  T.,?vk){.......@
0000180: e43b bd10 64ea 7026 9260 686f b828 d4e1  .;..d.p&.`ho.(..
...

See “AAAAAAAA” encrypted to “\x2d\x91\x66\x72\xa9\x0c\xed\x48”. Now let’s bruteforce:

Let’s try different charsets separately: 0-9, A-Z, a-z. Also, DES key’s low bits are meaningless, so the real charsets are 02468, ABDFHJLNPRTVXZ and abdfhjlnprtvxz.

brutelib.tar.gz

#include <stdio.h>
#include "brute.h"
#include <openssl/des.h>
 
void checker(char pw[8], int len) {
    DES_key_schedule k;
 
    unsigned char out[8] = "";
    DES_set_key_unchecked((const_DES_cblock *)pw, &k);
    DES_ecb_encrypt((const_DES_cblock *)"AAAAAAAA", (DES_cblock *)out, &k, 1);
    if (!memcmp(out, "\x2d\x91\x66\x72\xa9\x0c\xed\x48", 8)){
        printf("pwned: %s\n", pw);
    }
}
 
int main(int argc, char *argv) {
    bruteforce_set_debug(1);
    bruteforce_set_key("", 8);
 
    bruteforce_set_charset("02468", 5);
    bruteforce(6, (check_func_t)checker);
 
    bruteforce_set_charset("ABDFHJLNPRTVXZ", 14);
    bruteforce(6, (check_func_t)checker);
 
    bruteforce_set_charset("abdfhjlnprtvxz", 14);
    bruteforce(6, (check_func_t)checker);
}
$ gcc shop.c brute.c -lcrypto -O9 && time ./a.out
Starting bruteforce with 6 threads...
Charset: '02468' (5 bytes)
Key: up to 8 bytes
...
Starting bruteforce with 6 threads...
Charset: 'ABDFHJLNPRTVXZ' (14 bytes)
Key: up to 8 bytes
...
Starting bruteforce with 6 threads...
Charset: 'abdfhjlnprtvxz' (14 bytes)
Key: up to 8 bytes
...
pwned: rnntbddr
Bruteforce ended
 
real	5m42.424s
user	27m34.039s
sys	0m0.420s

Soo we have password: rnntbddr. Looks like the actual password is rootbeer. Let’s decrypt the cookie:

import requests
from urllib import *
from Crypto.Cipher import DES
 
r = requests.post("https://ctf.fluxfingers.net:2077/login.php",
    data={"user": "zomboy53", "pass": "killthezombies", "login": "Login"})
 
del r.cookies['data']  # make small cookie
 
r = requests.get("https://ctf.fluxfingers.net:2077/shop.php?buy=1",
    cookies=r.cookies)
 
print DES.new("rnntbddr").decrypt(unquote_plus(r.cookies['data']))
$ py wu.py 
a:1:{s:6:"basket";a:1:{i:1;i:1;}}

Oh, seems it’s a php-serialized data. We need product id 2, so:

r = requests.post("https://ctf.fluxfingers.net:2077/login.php",
    data={"user": "zomboy53", "pass": "killthezombies", "login": "Login"})
 
r.cookies['data'] = quote_plus(DES.new("rnntbddr").
    encrypt('a:1:{s:6:"basket";a:1:{i:2;i:1;}}\x00\x00\x00\x00\x00\x00\x00')
)
 
r = requests.get("https://ctf.fluxfingers.net:2077/order.php",
    cookies=r.cookies)
 
print r.content
The flag for this challenge is spr4y4me.

PS: dictionary bruteforce would be faster

Injection way

Supposed by awesie from PPP.

This much cooler way is injecting an encrypted block. The idea is to craft a ciphertext for message:

...sket";a:2:{i: | 00000002 | ;i:1;}}

We can encrypt “00000002” block using PHPSESSID cookie:

set PHPSESSID to xxAAAAAAA00000002AAAAAAAA (padding guessed experimentally)
 
0000140: 95d2 a231 d6a7 6cd4 2d91 6672 a90c ed48  ...1..l.-.fr...H
0000150: 9a1f 8712 a6d3 c9fa 2d91 6672 a90c ed48  ........-.fr...H
0000160: 6876 d91d 7655 0a35 8d0b 3643 4c8d 0ecf  hv..vU.5..6CL...
0000170: 4c38 ff8a 9c93 d994 200a                 L8...... .

Recall that “AAAAAAAA” is encrypted to “\x2d\x91\x66\x72\xa9\x0c\xed\x48”. Here you see “\x9a\x1f\x87\x12\xa6\xd3\xc9\xfa” between 2 encrypted “AAAAAAAA” blocks – it is encryption of “00000002”.

Now let’s get encryption of “;i:1;}}\x00”: we can adjust length of PHPSESSID cookie to make the last block be “;i:1;}}”.

We’ll encrypt 8 different lengths to see when new block appears:

for x in xrange(1, 9):
    r = requests.post("https://ctf.fluxfingers.net:2077/login.php",
    data={"user": "zomboy53", "pass": "killthezombies", "login": "Login"},
    cookies={"PHPSESSID": "A"*x})
 
    r = requests.get("https://ctf.fluxfingers.net:2077/shop.php?buy=1",
        cookies=r.cookies)
 
    c = unquote_plus(r.cookies['data'])[0x158:]
    print x, c.encode("hex")

Results:

1 d0f35e2a439aa27699bad813a4abb16a
2 405a89b7b4343ea02b50cabace0ad830
3 a27dc769d5a03b23d39919dcecc1cdcf
4 86a2a414eadac8cfdc278b150ebbec34
5 f76c1978f51b404ef802f8eecde90819cf2684841ec61fc6
6 e15762b5966480b0e0f4f96766468c86be48762cccbc8e70
7 5bbde0895be74e0a3c74c423c0347612f2b13147ee448e44
8 1f5c44958e5b8d692bd1c3d47ef078585eca063c66adf92d

4 is to full block, we need minus one byte for “;i:1;}}\x00” – so the encryption is “d39919dcecc1cdcf”.

Now, the last step. We need blocks to divide like this:
...;a:2:{i: | 1;i:1;}}

The last block is full so we use “A”*4 as PHPSESSID and then we’are replacing the last block with
"\x9a\x1f\x87\x12\xa6\xd3\xc9\xfa" + "\xd3\x99\x19\xdc\xec\xc1\xcd\xcf":

r = requests.post("https://ctf.fluxfingers.net:2077/login.php",
    data={"user": "zomboy53", "pass": "killthezombies", "login": "Login"},
    cookies={"PHPSESSID": "A"*4})
 
r = requests.get("https://ctf.fluxfingers.net:2077/shop.php?buy=1",
    cookies=r.cookies)
 
c = unquote_plus(r.cookies['data'])
c = c[:-8] + "\x9a\x1f\x87\x12\xa6\xd3\xc9\xfa" + \
             "\xd3\x99\x19\xdc\xec\xc1\xcd\xcf"
 
r.cookies['data'] = quote_plus(c)
r = requests.get("https://ctf.fluxfingers.net:2077/order.php",
    cookies=r.cookies)
 
print r.content

Result: The flag for this challenge is spr4y4me.

PS: It’s very hard to solve it in this way if you don’t know the format of the plaintext data.
Awesie (and all who solved this way) rocks :)

7 comments

Skip to comment form

    • gacon on October 29, 2012 at 10:18
    • Reply

    Hi,
    Can you help explain how can you get from the cookie “data”:

    %60%C8%9D%9B%AD%A4%8D…..

    To this stream:

    dec8 aabc 7e39 277a 81fd c2f5 2920….

    I have changed the order, but the cookie data always start with : “60C8 9D9B…”.
    Thanks.

    1. After your login the cookie is large and starts with “60C8 9D9B …”
      But if you *delete* this cookie and buy something, it will contain only data for your buy and will start with “dec8 aabc. …”

    • gacon on October 29, 2012 at 11:17
    • Reply

    Oh, i see, thanks.

    • Dan on October 29, 2012 at 12:20
    • Reply

    Hi,

    First off, thanks for sharing, really helpful. Could you please explain why ‘A’ and ‘a’ are part of the charset range? I got confused since those are odd numbers discarded by the low bit right?

    1. 0x40 @
      0x41 A
      0x42 B
      0x43 C

      B ^ 1 = C, so B is equal to C in des key.
      But we can’t drop out A, because @ is not in a charset (charset must contain either X or X | 1)

      Tha’s why we have 26 letters in alphabet, but we need to try 14 instead of 13.

      Digits are better – they start with 0x30 so can reduce 10 digits to 5: 02468

        • Dan on October 30, 2012 at 01:39
        • Reply

        Got it. Thanks for the explanation. Now I am ready for the next CTF, LoL!

    • Crystal on October 20, 2013 at 06:40
    • Reply

    i need some help with this really cool thing im trying to figure out(if you like money you may be interested and i dont mind shareing the info with you especially if we can figure it out together!) and no very little (close to nothing) about solving it because the answer seems to be in figuring out encoding and encryption or parsing or somthing, please get back to me thanks so much

Leave a Reply to gacon Cancel reply

Your email address will not be published.