3 – Zombies like PPTP
Our intel shows us that the Zombies use a MS-PPTP like protocol and luckily we could intercept a challenge-response transmission of one of the Zombie outposts. The important thing for Zombies in this war is mass! Not only brain mass but their mass. So they built their PPTP protocol compatible to all older Zombie soldiers. Luckily our science team could extract the algorithm of the challenge-response system out of a captured Zombie brain … I spare you the details, let’s just say it was not a pretty sight. And here comes your part soldier: we need the password of this intercepted transmission. With this password we were finally able to turn this war to our favor. So move your ass soldier and good luck!
https://ctf.fluxfingers.net/challenges/pptp.tar.gz
pptp.tar.gz
Summary: bruteforce LM response
There are three files here – pptp.py, pyDes.py, transmission.pcap.
Let’s start with pcap. It’s really simple, there’s only one session here:
Looks simple. Now pptp.py: it contains 4 methods: lm_hash, response_lm, newTechnologie_hash, response_newTechnologie
.
response_lm
uses lm_hash
so we should consider only response_ methods. We can think about conversation as
challenge: dead234a1f13beef response_newTechnologie: 200 41787c9f6ffde56919ca3cd8d8944590a9fff68468e2bcb6 incompatible # seems nt is not supported response_lm: 200 78165eccbf53cdb11085e8e5e3626ba9bdefd5e9de62ce91 |
Now we have both responses. response_lm method uses DES as a hash function:
DES(key=password.upper(), msg="Trololol") for lm_hash and DES(key=lm_hash, msg=challenge) for response_lm |
Here’s the scheme:
We can bruteforce both parts of the password separately:
- first 8-byte part can be checked against LM Response 1
- second 6-byte part can be checked agains LM Response 2
and LM Response 3
We can bruteforce first the second part of the key, because it’s faster and we can obtain some information about the first part (possible charset for example). This way we don’t know LM hash 1 so we’ll bruteforce its two last bytes for each valid LM Response 3 and check against LM Response 2 (there will be collisions because LM response 3 depends only on two bytes of the lm_hash2).
Recall that password is uppercased first and that DES’s low bits are meaningless, we can narrow the charset. So:
#include <openssl/des.h> #include "brute.h" char input[] = "Trololol"; char chall[] = "\xde\xad\x23\x4a\x1f\x13\xbe\xef"; char lm_resp1[] = "\x78\x16\x5e\xcc\xbf\x53\xcd\xb1"; char lm_resp2[] = "\x10\x85\xe8\xe5\xe3\x62\x6b\xa9"; char lm_resp3[] = "\xbd\xef\xd5\xe9\xde\x62\xce\x91"; void checker_second(char *pw, int len) { DES_key_schedule k; char buf_hash2[16]; char buf_resp2[8]; char buf_resp3[8]; DES_set_key_unchecked(pw, &k); DES_ecb_encrypt(input, buf_hash2, &k, 1); bzero(&buf_hash2[8], 6); DES_set_key_unchecked(&buf_hash2[6], &k); DES_ecb_encrypt(chall, buf_resp3, &k, 1); if (memcmp(buf_resp3, lm_resp3, 8)) return; char key_for_resp2[8]; memcpy(&key_for_resp2[2], buf_hash2, 6); unsigned int low; for (low = 0; low < 65535; low++) { *(unsigned short*)key_for_resp2 = low; DES_set_key_unchecked((void*)key_for_resp2, &k); DES_ecb_encrypt(chall, buf_resp2, &k, 1); if (!memcmp(buf_resp2, lm_resp2, 8)) { printf("6-byte part: %s\n", pw); return; } } } int main(int argc, char *argv) { bruteforce_set_debug(1); bruteforce_set_key("", 6); bruteforce_set_charset("ABDFHJLNPRTVXZ02468_-", 20); bruteforce(6, checker_second); } |
Launch it and get in 1 minute:
6-byte part: DNRDLR |
Seems charset is A-Z (maybe digits too but we’ll try A-Z first) for first block:
void checker_first(char *pw, int len) { DES_key_schedule k; char buf_hash1[8]; char buf_resp1[8]; DES_set_key_unchecked(pw, &k); DES_ecb_encrypt(input, buf_hash1, &k, 1); DES_set_key_unchecked(buf_hash1, &k); DES_ecb_encrypt(chall, buf_resp1, &k, 1); if (!memcmp(buf_resp1, lm_resp1, 8)) { printf("8-byte part: %s\n", pw); return; } } int main(int argc, char *argv) { bruteforce_set_debug(1); bruteforce_set_key("", 8); bruteforce_set_charset("ABDFHJLNPRTVXZ", 14); bruteforce(6, checker_first); } |
In ~6 minutes (30 minutes with one thread):
8-byte part: ZNLBHDRA |
Now we have passwords but we’ve lost two bits in each byte: case bit and the lowest bit. Let’s brute force them agains response_newTechnologie, which is similar to response_lm but uses sha1 as first hash function:
DES_key_schedule k; char nt_resp1[] = "\x41\x78\x7c\x9f\x6f\xfd\xe5\x69"; char pw[] = "ZNLBHDRADNRDLR"; char res[40]; char resp1[8]; unsigned int bits, i, acc; for (acc = 0; acc < 0x10000000; acc++) { // 2**28 bits = acc; for (i = 0; i < 14; i++) { pw[i] = pw[i] & (0xff ^ 0x20 ^ 1); pw[i] |= bits & 1; pw[i] |= (bits & 2) << 4; bits >>= 2; } SHA1(pw, 14, res); DES_set_key_unchecked(res, &k); DES_ecb_encrypt(chall, resp1, &k, 1); if (!memcmp(resp1, nt_resp1, 8)) { printf("PWNED: %s\n", pw); return; } } |
$ time ./a.out PWNED: ZomBIEsAdOReMS real 1m0.745s user 1m0.636s sys 0m0.000s |
The flag: ZomBIEsAdOReMS
4 comments
Skip to comment form
Isn’t the ordering wrong? The first response is NT, the second is LM – also according to your code shown here.
Author
oh, really! Thanks :)
fixd
I’m so sorry but would you mind posting also #1 Zombie Talk? I was all in with my friend to that question but we couldn’t solve it. If you don’t mind, please send the solution to my e-mail uyw4687@naver.com Thank you!!
Author
Just convert to base36 all the numbers (you can use http://www.efunda.com/units/base_n.cfm?base_from=16 for example)