CodeGate 2010 – Challenge 8: Bit-flipping attack on CBC mode
March 23, 2010 by vnsec · Leave a Comment
Writeup for CodeGate 2010 Challenge 8 by namnx
This is a web-based cryptography challenge. In this challenge, we were provided a URL and a hint ”the first part is just an IV“.
The URL is: http://ctf1.codegate.org/99b5f49189e5a688492f13b418474e7e/web4.php.
Analysis
Go to the challenge URL. It will ask you the username for the first time. After we enter a value, for example ‘namnx‘, it will return only a single message “Hello, namnx!“. Examine the HTTP payload, we will see the cookie returned:
web4_auth=1vf2EJ15hKzkIxqB27w0AA==|5X5A0e3r48gXhUXZHEKBa5dpC+XfdVv4oamlriyi5yM=
The cookie includes 2 parts delimited by character ‘|’. After base64 decode the first part of the cookie, we have a 16-byte value. According to the hint, this is the IV of the cipher. And because it has 16-byte length, I guess that this challenge used AES cipher, and the block size is 16 bytes. Moreover, the cipher has an IV, so it can’t be in ECB mode. I guessed it in CBC mode. The last part is the base64 of a 32-byte value. This is a cipher text. We will exploit this value later.
Browse the URL again, we will receive another message: “Welcome back, namnx! Your role is: user. You need admin role.” Take a look into this message, we can guess the operation of this app: it will receive the cookie from the client, decrypt it to get the user and role information and return the message to the client based on the user and role information. So, in order to get further information, we must have the admin role. This is our goal in this challenge.
Exploit
I wrote some Python to work on this challenge easier:
import urllib, urllib2
import base64, re
url = 'http://ctf1.codegate.org/99b5f49189e5a688492f13b418474e7e/web4.php'
user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
def get_cookie(user):
headers = { 'User-Agent' : user_agent}
values = {'username' : user, 'submit' : "Submit"}
data = urllib.urlencode(values)
request = urllib2.Request(url, data, headers)
response = urllib2.urlopen(request)
cookie = response.info().get('Set-Cookie')
groups = re.match("web4_auth=(.+)\|(.+);.+", cookie).groups()
iv = base64.b64decode(groups[0])
cipher = base64.b64decode(groups[1])
return iv, cipher
def get_message(iv, cipher):
cookie = base64.b64encode(iv) + '|' + base64.b64encode(cipher)
cookie = urllib.quote(cookie)
cookie = 'web4_auth=' + cookie
headers = { 'User-Agent' : user_agent, 'Cookie': cookie}
request = urllib2.Request(url, None, headers)
response = urllib2.urlopen(request)
data = response.read()
print repr(data)
groups = re.match(".+, (.*)! .+: (.*)\. You.+", data).groups()
return groups[0], groups[1]
The first function, get_cookie will submit a value as a username in the first visit to the page, get the returned cookie, and then parse it to get the IV and cipher. The second function, get_message, do the task like when you visit the page in later times, it parses the response message to get the returned username and role.
>>> iv, cipher = get_cookie('123456789012')
>>> len(cipher)
32
>>> iv, cipher = get_cookie('1234567890123')
>>> len(cipher)
48
When you input the user with a 12-byte value, the returned cipher will have 32 bytes (2 blocks). And when you enter a 13-byte value, the cipher will have 48 bytes (3 blocks). This means that beside the username value, the plain text of the cipher will be added more 20 bytes.
Try altering the cipher text to see how it is decrypted:
>>> iv, cipher = get_cookie('1234567890')
>>> cipher1 = cipher[:-1] + '\00'
>>> username, role = get_message(iv, cipher1)
'Welcome back, 1234567\xa2\xc2\xca\xfei\xdb\xee_c\xa7\xd7\x0c\xa9j\xe0\xbb! Your role is: . You need admin role.'
As you can see, the last block of the decrypted role is the first block of the plain text. So, the format of the plain text may be: ‘username=’ + username + [11 bytes].
To here, we can guess that the format of the plain text can be something like:
‘username=’ + username + [delimiter] + [param] + ‘=’ + [value]
The last 11 bytes of the plain text can be determined by the code below:
>>> iv, cipher = get_cookie('\x00')
>>> username, role = get_message(iv, cipher)
'Welcome back, \x00##role=user\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00! Your role is: . You need admin role.'
You can see the last 11 bytes of the plain text in the returned message. So, at this time, we can conclude format of the plain text is:
‘username=’ + username + ‘##role=’ + role
Now, the last thing we have to do is altering the role value to ‘admin’. Because we’ve already known the format of the plain text, we can choose to input the username close to the target plain text and try to alter the cipher text in the way that the decrypted value is what we want.
Let remind the operation of CBC mode in cryptographic ciphers. In encryption process:
y[1] = C(IV xor x[1])
y[n] = C(y[n-1] xor x[n])
and in the decryption:
x[1] = D(y[1]) xor IV
x[n] = D(y[n]) xor y[n-1]
Notice that if we flip one bit in the (n-1)th block of cipher text, the respective bit in the n-th block of plain text will be also flipped. So, we will you this fact to exploit the challenge:
>>> iv, cipher = get_cookie('012345678901234567890123#role=admin')
>>> s = cipher[:16] + chr(ord(cipher[16]) ^ 0x10) + cipher[17:]
>>> username, role = get_message(iv, s)
'Welcome back, 0123456L\xaa\x17m\xe9\x91\xdc\xe2`#z)\xd8m\xd8\x18! Your role is: admin. You need admin role. Congratulations! Here is your flag: the_magic_words_are_squeamish_ossifrage_^-^!!!!!'
Successful! Such an interesting challenge, isn’t it?
References
CodeGate 2010 – Challenge 7: Weak SSL Cracking
March 23, 2010 by vnsec · Leave a Comment
Writeup for CodeGate 2010 – Challenge 7 by namnx
Analysis
Firstly, I used Wireshark to load this file and start to analyze it:
from scapy.all import *
from M2Crypto import X509
def decode_serverhello(packet):
payload = packet.load
cert = payload[94:1141]
cert = X509.load_cert_string(cert, 0)
return cert
def get_pubkey(cert):
pubkey = cert.get_pubkey().get_rsa()
n = long(pubkey.n.encode('hex')[8:], 16)
e = long(pubkey.e.encode('hex')[9:], 16)
return n, e
packets = rdpcap('ssl.pcap')
cert = decode_serverhello(packets[15])
n,e = get_pubkey(cert)
Because this traffic used RSA as public key algorithm, the public key contains 2 components: n and e. We get their values from the above code:
n = 1230186684530117755130494958384962720772853569595334792197322452151726400507263657518745202199786469389956474942774063845925192557326303453731548268507917026122142913461670429214311602221240479274737794080665351419597459856902143413
e = 65537
RSA-768 = 33478071698956898786044169848212690817704794983713768568912431388982883793878002287614711652531743087737814467999489
× 36746043666799590428244633799627952632279158164343087642676032283815739666511279233373417143396810270092798736308917
So now, we have all components of the RSA keys.
n = 1230186684530117755130494958384962720772853569595334792197322452151726400507263657518745202199786469389956474942774063845925192557326303453731548268507917026122142913461670429214311602221240479274737794080665351419597459856902143413
e = 65537
p = 33478071698956898786044169848212690817704794983713768568912431388982883793878002287614711652531743087737814467999489
q = 36746043666799590428244633799627952632279158164343087642676032283815739666511279233373417143396810270092798736308917
d = 703813872109751212728960868893055483396831478279095442779477323396386489876250832944220079595968592852532432488202250497425262918616760886811596907743384527001944888359578241816763079495533278518938372814827410628647251148091159553
After having the private key, just import it to Wireshark to decrypt the SSL traffic:
References
- SSL/TLS: http://en.wikipedia.org/wiki/Transport_Layer_Security
- RSA: http://en.wikipedia.org/wiki/RSA
CodeGate 2010 Challenge 2 – Xbox pwned
March 19, 2010 by RD · 19 Comments
Summary
This is the most interesting challenge in CodeGate 2010 IMHO. The binary is a VM which loads the ‘codefile’ and execute it. The VM codefile is protected from being tampered with a TEA based hash algorithm. By exploiting the weakness of hash algorithm (similar to Xbox hack) together with a bug inside VM, we could change the execution flow of VM code to get back the secret key content.
Analysis
Challenge information
credentials: ssh hugh@ctf4.codegate.org -p 9474 password=takeitaway
Exploit /home/hugh/yboy to read secret.key
There are yboy, codefile and secret.key files in the home directory of hugh (you can download these files here if you want to try it by yourself)
-rw-r–r– 1 codegate codegate 1136 2010-03-12 14:45 codefile
-r——– 1 daryl daryl 140 2010-03-12 15:27 secret.key
-rwsr-xr-x 1 daryl root 22307 2010-03-12 16:07 yboy
a. Reverse Engineering yboy
yboy basically does the following things
- load VM codes from the codefile into memory (code[])
- load content of secret.key into memory (data[])
- check for the integrity of codefile using TEA based hash algorithm against a hard-coded hash value. Exit if the hash not matched
- parse/decode loaded VM codes and execute it accordingly
For the VM code inside codefile
- ask user to input password
- compare the input with flag inside secret.key
- if correct, print out the flag
- otherwise, print out access denied error and exit
b. Decompiler for codefile
Since yboy load VM code from codefile and execute it, I wrote a decompiler for it
#include <stdio.h>
#include <stdlib.h>
unsigned char *decode[32] = {
"halt", "push", "pop", "add", "sub", "or", "xor", "nor", "shl",
"shr", "not", "nop", "branch", "jumpreg", "callreg", "load",
"store", "halt", "inputchar", "outputchar", "set_imm", "reload",
"rrandom", "nop", "nop", "nop", "nop", "nop", "nop", "nop", "nop",
"nop",
};
unsigned long registers[64];
unsigned long code[32768];
unsigned int PC;
int main(int argc, char **argv)
{
unsigned int ins;
unsigned char reg;
unsigned char imm1, imm2;
unsigned char opcode;
unsigned int codesize;
int set_imm = 1;
FILE *f;
f = fopen(argv[1], "r");
codesize = fread(code, 1, sizeof(code), f);
fclose(f);
//check_code();
PC = 0;
while (PC < (codesize) / 4) {
set_imm = 1;
ins = code[PC];
opcode = (ins >> 24); //& 0x1f;
reg = (ins >> 16) & 0xFF;
imm1 = (ins >> 8) & 0xFF;
imm2 = (unsigned char) ins;
// set_imm
if (opcode == 20) {
printf("%04x: \tr%d = %s %x, %x", PC, reg,
decode[opcode & 0x1f], imm1, imm2);
if (imm2 && !imm1)
printf("\t; %c", imm2);
else if (imm1)
printf("\t; %04x", imm2 + (imm1 << 8));
printf("\n");
PC++;
continue;
}
reg = (ins >> 16) & 0xBF;
imm1 = (ins >> 8) & 0xBF;
imm2 = ins & 0xBF;
printf("%04x: \tr%d = %s r%d, r%d", PC, reg,
decode[opcode & 0x1f], imm1, imm2);
if (opcode == 12) // comment for branch
printf("\t; if (r%d) goto r%d\n", imm1, imm2);
else
printf("\n");
PC++;
}
return 0;
}
Here is the output of the decompiler (click to open)
rd@jps(~/working/ctf/codegate2010/2/)$ ./yboy-decompile codefile 0000: r1 = set_imm 0, 45 ; E 0001: r0 = outputchar r1, r0 0002: r1 = set_imm 0, 6e ; n 0003: r0 = outputchar r1, r0 0004: r1 = set_imm 0, 74 ; t 0005: r0 = outputchar r1, r0 0006: r1 = set_imm 0, 65 ; e 0007: r0 = outputchar r1, r0 0008: r1 = set_imm 0, 72 ; r 0009: r0 = outputchar r1, r0 000a: r1 = set_imm 0, 20 ; 000b: r0 = outputchar r1, r0 000c: r1 = set_imm 0, 70 ; p 000d: r0 = outputchar r1, r0 000e: r1 = set_imm 0, 61 ; a 000f: r0 = outputchar r1, r0 0010: r1 = set_imm 0, 73 ; s 0011: r0 = outputchar r1, r0 0012: r1 = set_imm 0, 73 ; s 0013: r0 = outputchar r1, r0 0014: r1 = set_imm 0, 77 ; w 0015: r0 = outputchar r1, r0 0016: r1 = set_imm 0, 6f ; o 0017: r0 = outputchar r1, r0 0018: r1 = set_imm 0, 72 ; r 0019: r0 = outputchar r1, r0 001a: r1 = set_imm 0, 64 ; d 001b: r0 = outputchar r1, r0 001c: r1 = set_imm 0, 3e ; > 001d: r0 = outputchar r1, r0 001e: r1 = set_imm 0, 3e ; > 001f: r0 = outputchar r1, r0 0020: r60 = set_imm 0, ff ; � 0021: r61 = set_imm 0, 1 ; 0022: r4 = set_imm 5, 39 ; 0539 0023: r3 = inputchar r0, r0 0024: r50 = set_imm 0, a ; 0025: r0 = store r4, r3 0026: r0 = nop r0, r0 0027: r10 = sub r3, r50 0028: r10 = not r10, r0 0029: r11 = set_imm 0, 2f ; / 002a: r0 = branch r10, r11 ; if (r10) goto r11 002b: r4 = add r61, r4 002c: r10 = sub r60, r3 002d: r11 = set_imm 0, 23 ; # 002e: r0 = branch r10, r11 ; if (r10) goto r11 002f: r19 = set_imm 5, 39 ; 0539 0030: r20 = set_imm 0, 0 0031: r21 = set_imm 0, 23 ; # 0032: r21 = sub r21, r20 0033: r21 = not r21, r0 0034: r22 = set_imm 0, 5e ; ^ 0035: r0 = branch r21, r22 ; if (r21) goto r22 0036: r21 = load r20, r0 0037: r25 = add r19, r20 0038: r0 = nop r0, r0 0039: r26 = load r25, r0 003a: r26 = sub r21, r26 003b: r22 = set_imm 0, 41 ; A 003c: r0 = branch r26, r22 ; if (r26) goto r22 003d: r23 = set_imm 0, 1 ; 003e: r20 = add r20, r23 003f: r22 = set_imm 0, 31 ; 1 0040: r0 = branch r22, r22 ; if (r22) goto r22 0041: r1 = set_imm 0, 41 ; A 0042: r0 = outputchar r1, r0 0043: r1 = set_imm 0, 63 ; c 0044: r0 = outputchar r1, r0 0045: r1 = set_imm 0, 63 ; c 0046: r0 = outputchar r1, r0 0047: r1 = set_imm 0, 65 ; e 0048: r0 = outputchar r1, r0 0049: r1 = set_imm 0, 73 ; s 004a: r0 = outputchar r1, r0 004b: r1 = set_imm 0, 73 ; s 004c: r0 = outputchar r1, r0 004d: r1 = set_imm 0, 20 ; 004e: r0 = outputchar r1, r0 004f: r1 = set_imm 0, 44 ; D 0050: r0 = outputchar r1, r0 0051: r1 = set_imm 0, 65 ; e 0052: r0 = outputchar r1, r0 0053: r1 = set_imm 0, 6e ; n 0054: r0 = outputchar r1, r0 0055: r1 = set_imm 0, 69 ; i 0056: r0 = outputchar r1, r0 0057: r1 = set_imm 0, 65 ; e 0058: r0 = outputchar r1, r0 0059: r1 = set_imm 0, 64 ; d 005a: r0 = outputchar r1, r0 005b: r1 = set_imm 0, a ; 005c: r0 = outputchar r1, r0 005d: r0 = halt r0, r0 005e: r1 = set_imm 0, 47 ; G 005f: r0 = outputchar r1, r0 0060: r1 = set_imm 0, 72 ; r 0061: r0 = outputchar r1, r0 0062: r1 = set_imm 0, 65 ; e 0063: r0 = outputchar r1, r0 0064: r1 = set_imm 0, 65 ; e 0065: r0 = outputchar r1, r0 0066: r1 = set_imm 0, 74 ; t 0067: r0 = outputchar r1, r0 0068: r1 = set_imm 0, 7a ; z 0069: r0 = outputchar r1, r0 006a: r1 = set_imm 0, 20 ; 006b: r0 = outputchar r1, r0 006c: r1 = set_imm 0, 68 ; h 006d: r0 = outputchar r1, r0 006e: r1 = set_imm 0, 61 ; a 006f: r0 = outputchar r1, r0 0070: r1 = set_imm 0, 63 ; c 0071: r0 = outputchar r1, r0 0072: r1 = set_imm 0, 6b ; k 0073: r0 = outputchar r1, r0 0074: r1 = set_imm 0, 65 ; e 0075: r0 = outputchar r1, r0 0076: r1 = set_imm 0, 72 ; r 0077: r0 = outputchar r1, r0 0078: r1 = set_imm 0, 73 ; s 0079: r0 = outputchar r1, r0 007a: r1 = set_imm 0, 2e ; . 007b: r0 = outputchar r1, r0 007c: r1 = set_imm 0, 20 ; 007d: r0 = outputchar r1, r0 007e: r1 = set_imm 0, 4b ; K 007f: r0 = outputchar r1, r0 0080: r1 = set_imm 0, 65 ; e 0081: r0 = outputchar r1, r0 0082: r1 = set_imm 0, 65 ; e 0083: r0 = outputchar r1, r0 0084: r1 = set_imm 0, 70 ; p 0085: r0 = outputchar r1, r0 0086: r1 = set_imm 0, 20 ; 0087: r0 = outputchar r1, r0 0088: r1 = set_imm 0, 75 ; u 0089: r0 = outputchar r1, r0 008a: r1 = set_imm 0, 70 ; p 008b: r0 = outputchar r1, r0 008c: r1 = set_imm 0, 20 ; 008d: r0 = outputchar r1, r0 008e: r1 = set_imm 0, 74 ; t 008f: r0 = outputchar r1, r0 0090: r1 = set_imm 0, 68 ; h 0091: r0 = outputchar r1, r0 0092: r1 = set_imm 0, 65 ; e 0093: r0 = outputchar r1, r0 0094: r1 = set_imm 0, 20 ; 0095: r0 = outputchar r1, r0 0096: r1 = set_imm 0, 67 ; g 0097: r0 = outputchar r1, r0 0098: r1 = set_imm 0, 6f ; o 0099: r0 = outputchar r1, r0 009a: r1 = set_imm 0, 6f ; o 009b: r0 = outputchar r1, r0 009c: r1 = set_imm 0, 64 ; d 009d: r0 = outputchar r1, r0 009e: r1 = set_imm 0, 20 ; 009f: r0 = outputchar r1, r0 00a0: r1 = set_imm 0, 77 ; w 00a1: r0 = outputchar r1, r0 00a2: r1 = set_imm 0, 6f ; o 00a3: r0 = outputchar r1, r0 00a4: r1 = set_imm 0, 72 ; r 00a5: r0 = outputchar r1, r0 00a6: r1 = set_imm 0, 6b ; k 00a7: r0 = outputchar r1, r0 00a8: r1 = set_imm 0, 2e ; . 00a9: r0 = outputchar r1, r0 00aa: r1 = set_imm 0, 20 ; 00ab: r0 = outputchar r1, r0 00ac: r1 = set_imm 0, 53 ; S 00ad: r0 = outputchar r1, r0 00ae: r1 = set_imm 0, 74 ; t 00af: r0 = outputchar r1, r0 00b0: r1 = set_imm 0, 61 ; a 00b1: r0 = outputchar r1, r0 00b2: r1 = set_imm 0, 79 ; y 00b3: r0 = outputchar r1, r0 00b4: r1 = set_imm 0, 20 ; 00b5: r0 = outputchar r1, r0 00b6: r1 = set_imm 0, 73 ; s 00b7: r0 = outputchar r1, r0 00b8: r1 = set_imm 0, 68 ; h 00b9: r0 = outputchar r1, r0 00ba: r1 = set_imm 0, 61 ; a 00bb: r0 = outputchar r1, r0 00bc: r1 = set_imm 0, 72 ; r 00bd: r0 = outputchar r1, r0 00be: r1 = set_imm 0, 70 ; p 00bf: r0 = outputchar r1, r0 00c0: r1 = set_imm 0, 2e ; . 00c1: r0 = outputchar r1, r0 00c2: r1 = set_imm 0, 20 ; 00c3: r0 = outputchar r1, r0 00c4: r1 = set_imm 0, 44 ; D 00c5: r0 = outputchar r1, r0 00c6: r1 = set_imm 0, 69 ; i 00c7: r0 = outputchar r1, r0 00c8: r1 = set_imm 0, 73 ; s 00c9: r0 = outputchar r1, r0 00ca: r1 = set_imm 0, 6f ; o 00cb: r0 = outputchar r1, r0 00cc: r1 = set_imm 0, 62 ; b 00cd: r0 = outputchar r1, r0 00ce: r1 = set_imm 0, 65 ; e 00cf: r0 = outputchar r1, r0 00d0: r1 = set_imm 0, 79 ; y 00d1: r0 = outputchar r1, r0 00d2: r1 = set_imm 0, 20 ; 00d3: r0 = outputchar r1, r0 00d4: r1 = set_imm 0, 6d ; m 00d5: r0 = outputchar r1, r0 00d6: r1 = set_imm 0, 69 ; i 00d7: r0 = outputchar r1, r0 00d8: r1 = set_imm 0, 73 ; s 00d9: r0 = outputchar r1, r0 00da: r1 = set_imm 0, 69 ; i 00db: r0 = outputchar r1, r0 00dc: r1 = set_imm 0, 6e ; n 00dd: r0 = outputchar r1, r0 00de: r1 = set_imm 0, 66 ; f 00df: r0 = outputchar r1, r0 00e0: r1 = set_imm 0, 6f ; o 00e1: r0 = outputchar r1, r0 00e2: r1 = set_imm 0, 72 ; r 00e3: r0 = outputchar r1, r0 00e4: r1 = set_imm 0, 6d ; m 00e5: r0 = outputchar r1, r0 00e6: r1 = set_imm 0, 61 ; a 00e7: r0 = outputchar r1, r0 00e8: r1 = set_imm 0, 74 ; t 00e9: r0 = outputchar r1, r0 00ea: r1 = set_imm 0, 69 ; i 00ec: r1 = set_imm 0, 6f ; o 00ed: r0 = outputchar r1, r0 00ee: r1 = set_imm 0, 6e ; n 00ef: r0 = outputchar r1, r0 00f0: r1 = set_imm 0, 2e ; . 00f1: r0 = outputchar r1, r0 00f2: r1 = set_imm 0, a ; 00f3: r0 = outputchar r1, r0 00f4: r1 = set_imm 0, 59 ; Y 00f5: r0 = outputchar r1, r0 00f6: r1 = set_imm 0, 6f ; o 00f7: r0 = outputchar r1, r0 00f8: r1 = set_imm 0, 75 ; u 00f9: r0 = outputchar r1, r0 00fa: r1 = set_imm 0, 72 ; r 00fb: r0 = outputchar r1, r0 00fc: r1 = set_imm 0, 20 ; 00fd: r0 = outputchar r1, r0 00fe: r1 = set_imm 0, 66 ; f 00ff: r0 = outputchar r1, r0 0100: r1 = set_imm 0, 6c ; l 0101: r0 = outputchar r1, r0 0102: r1 = set_imm 0, 61 ; a 0103: r0 = outputchar r1, r0 0104: r1 = set_imm 0, 67 ; g 0105: r0 = outputchar r1, r0 0106: r1 = set_imm 0, 20 ; 0107: r0 = outputchar r1, r0 0108: r1 = set_imm 0, 69 ; i 0109: r0 = outputchar r1, r0 010a: r1 = set_imm 0, 73 ; s 010b: r0 = outputchar r1, r0 010c: r1 = set_imm 0, 3a ; : 010d: r0 = outputchar r1, r0 010e: r1 = set_imm 0, 20 ; 010f: r0 = outputchar r1, r0 0110: r29 = set_imm 0, 1 ; 0111: r30 = xor r30, r30 0112: r1 = load r30, r0 0113: r0 = outputchar r1, r0 0114: r30 = add r29, r30 0115: r31 = set_imm 0, 26 ; & 0116: r31 = sub r30, r31 0117: r32 = set_imm 1, 12 ; 0112 0118: r0 = branch r31, r32 ; if (r31) goto r32 0119: r1 = set_imm 0, a ; 011a: r0 = outputchar r1, r0 011b: r0 = halt r0, r0
Pseudo C code of the decompiled codefile
// data is an int array - int data[0x2000/4]
// the first 140 bytes of data store the content of "secret.key" file
printf("Enter password>>");
r4 = 1337;
while (!EOF) {
r3 = getc();
data[r4] = r3;
if (r3 == '\n') break;
r4++;
}
r19 = 1337;
r20 = 0;
while (1) {
if (r20 == 0x23) goto correctpass;
if (data[r20] != data[r19+r20]) goto wrongpass;
r20++;
}
wrongpass:
printf("Access Denied\n");
exit(0);
correctpass:
printf("Greetz hackers. Keep up the good work. Stay sharp. Disobey misinformation.\n");
printf("Your flag is: ");
for(i=0; i<0x26; i++)
print("%c", data[i]);
printf("\n");
c. Xbox’s TEA hash collision
From the decompiled VM code above, if we could modify the content of codefile, it would be possible to print out the flag inside secret.key stored at data[0]. However, the codefile is protected from being tampered with a hash algorithm.
int
check_code()
{
int result;
unsigned int v1;
unsigned int v2;
unsigned int i;
hash_block(0x99999999, 0xBBBBBBBB, 0x44444444, 0x55555555, code[0],
code[1], &v2, &v1);
for (i = 2; i <= 32766; i += 2)
hash_block(v2, v1, v2, v1, code[i], code[i + 1], (int *) &v2,
(int *) &v1);
if (v2 != 0x1EC0A9F0 || (result = v1, v1 != 0x9217F034)) {
puts("Tampering detected. Prepare for imminent arrest.");
exit(0);
}
return result;
}
Google the constant 0×61C88647 and searching around, I found that it’s a TEA based hash algorithm. Using TEA hash is bad and there is a weakness in the algorithm in which by flipping the 32nd and 64th bit of a 64 bits block, the hash value will remain the same. Xbox was hacked because of this one. (Actually I didn’t know about Xbox’s TEA bits flipping attack. I found this collision by writing a tool doing the brute force on bits flipping of 64 bits block to find the collision. Later, I realized that Yboy is Xbox with two bits flipped)
Now, the next problem is to find how codefile should be patched to print out the flag.
d. Branch instruction handing bug
The 32nd and 64th bit of a 64 bits block are MSB bits of the opcode field of two consequence instructions. Since the code only uses the least 05 bits in opcode for instruction decode, changing the MSB of the opcode won’t affect the instruction decode part. However, there is a problem with branch instruction handling code which will help us to modify the behavior of branch.
If we look at the VM parsing code, an instruction (4 byes) structure is as the following
[ opcode ] [ output register ] [ imm1 ] [ imm2 ]
opcode = (ins >> 24); //& 0x1f;
reg = (ins >> 16) & 0xFF;
imm1 = (ins >> 8) & 0xFF;
imm2 = (unsigned char) ins;
The least 05 bits of opcode are being used as an index to lookup for the corresponding function from the decode function table (decode[opcode & 0x1f])
Lets look deeper at the code handling ‘branch’ instruction:
int branch(int imm1, int imm2)
{
if (imm1)
PC = imm2;
else
PC++;
return 0;
}
Inside main()
As we can see, it only uses the least five bits of opcode ((ins >> 24) & 0×1f) for decode while the full byte (ins >> 24) is used for comparing later (opcode value for branch is oxC).
If we set the MSB of opcode, the opcode would become 0×8c. In this case, the branch() function is still being called, however, the (opcode == 0xC) check in main() will be false and PC will be increased by 1 unexpectedly.
e. Subvert the code flow to print out the flag
Look back at the decompiled VM code
0020: r60 = set_imm 0, ff ; r60 = 255 0021: r61 = set_imm 0, 1 ; r61 = 1 0022: r4 = set_imm 5, 39 ; r4 = 0x539 (1337) 0023: r3 = inputchar r0, r0 ; r3 = getc() 0024: r50 = set_imm 0, a ; r50 = '\n' 0025: r0 = store r4, r3 ; data[r4] = r3 0026: r0 = nop r0, r0 0027: r10 = sub r3, r50 ; r10 = r3 - '\n' 0028: r10 = not r10, r0 ; !r10 0029: r11 = set_imm 0, 2f ; r11 = 0x002f 002a: r0 = branch r10, r11 ; if (r3 == '\n') goto 002f 002b: r4 = add r61, r4 ; r4++ 002c: r10 = sub r60, r3 ; r10 = 255 - r3 002d: r11 = set_imm 0, 23 ; 0x0023 002e: r0 = branch r10, r11 ; if (r3 != EOF) goto 0023 002f: r19 = set_imm 5, 39 ; r19 = 0x539 (1337) 0030: r20 = set_imm 0, 0 ; r20 = 0 0031: r21 = set_imm 0, 23 ; r21 = 0x23 (35) 0032: r21 = sub r21, r20 ; r21 = r21 - r20 0033: r21 = not r21, r0 ; !r21 0034: r22 = set_imm 0, 5e ; 0x005e 0035: r0 = branch r21, r22 ; if (r20 == 35) goto 005e //goodpassword 0036: r21 = load r20, r0 ; r21 = data[r20] 0037: r25 = add r19, r20 ; r25 = 0x539 + r20 0038: r0 = nop r0, r0 0039: r26 = load r25, r0 ; r26 = data[0x539 + r20] 003a: r26 = sub r21, r26 ; r26 = r26 - r21 003b: r22 = set_imm 0, 41 ; 0x0041 003c: r0 = branch r26, r22 ; if (data[r20] != data[0x539+r20) goto 0041 //badpassword 003d: r23 = set_imm 0, 1 ; r23 = 1 003e: r20 = add r20, r23 ; r20++ 003f: r22 = set_imm 0, 31 ; 0x0032 0040: r0 = branch r22, r22 ; goto 0032 //loop
The code above read password from stdin, stores it inside data array starting at data[0x539] then compares the input with the content of secret.key stored at the beginning of data[0] (35 DWORDS = 140 bytes).
What if we modify the MSB bit of branch instruction at 002a?
//modify code[2a] from 0x0c000a0b to 0x8c000a0b
002a: r0 = branch r10, r11 ; if (r3 == '\n') goto 002f
When '\n' is read, the branch() instruction will set PC to the password check code at 002f (002f: r19 = set_imm 5, 39 ; r19 = 0x539). However, because the opcode now is 0x8c instead of 0x0c, PC will be also increased by 1 unexpectedly due to the bug at main loop code mentioned above. Hence, the PC will point to instruction at 0030 (0030: r20 = set_imm 0, 0 ; r20 = 0) instead of 002f.
Since the instruction at 002f is skipped, r19 register will be 0 (default value) instead of 0x539. The VM code becomes
//r19 register value is 0 while it's expected to be 0x539
r20 = 0;
while (1) {
if (r20 == 0x23) goto correctpass;
if (data[r20] != data[r19+r20]) goto wrongpass;
r20++;
}
It's comparing identical data. Yboy Pwned!
Exploit
- Copy the codefile, edit it to set the 32nd and 64th bits at offset 0x2a
code[2a] 0x0c000a0b -> 0x8c000a0b
code[2b] 0x03043d04 -> 0x83043d04
- Run the yboy with the new codefile
- Press enter and get the flag
hugh@codegate-desktop:/tmp/rd$ ./yboy newcodefile
...
Enter password>>
Greetz hackers. Keep up the good work. Stay sharp. Disobey misinformation.
Your flag is: TEA - Toiletpaper Esque Aspirations
References
Keywords: TEA, VM, Xbox, codegate 2010
CodeGate 2010 Challenge 15 – SHA1 padding attack
March 16, 2010 by RD · 13 Comments
Summary
This is a web based crypto challenge vulnerable to padding/length extension attack in its sha1 based authentication scheme.
Analysis
Challenge URL: http://ctf1.codegate.org/03c1e338b6445c0f127319f5cb69920a/web1.php
This page will ask for submitting a username for the first time. Once a username is submited ( ‘aaaa’ for example), the script will set a cookie as the following:
web1_auth = YWFhYXwx|8f5c14cc7c1cd461f35b190af57927d1c377997e
The first part YWFhYXwx is the base64 encoded string of ‘aaaa|1′ (username|role). The second part 8f5c14cc7c1cd461f35b190af57927d1c377997e is the sha1(unknown_secretkey + username + role).
In the next visit, the web1.php script will check for the cookie and return the following message
“Welcome back, aaaa! You are not the administrator.”
We can guest that 1 is the role value for normal user and 0 for administrator.
Solution
If we try to modify to first part of the web1_auth cookie to something like base64_encode(’aaaa|0′), the script will return an error message saying that the data has been tampered due to the wrong signature.
As we know that popular hash functions including sha1 are vulnerable to length extension (or padding) attacks. This can be used to break naive authentication schemes based on hash functions.
I will not write the detail on how to do sha1 length extension attack, you can read papers in the References section below for more information. Basically, with padding attack, we can append arbitrary data to the cookie and generate a valid signature for it without knowing the secret key. In this challenge, we want to have ‘|0′ (administrator role) at the end of the first part of the cookie.
$ python sha-padding.py
usage: sha-padding.py <keylen> <original_message> <original_signature> <text_to_append>$ python sha-padding.py 25 ‘aaaa|1′ 8f5c14cc7c1cd461f35b190af57927d1c377997e ‘|0′
new msg: ‘aaaa|1\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf8|0′
base64: YWFhYXwxgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4fDA=
new sig: 70f8bf57aa6d7faaa70ef17e763ef2578cb8d839
And here is what we got with the web1_auth cookie using YWFhYXwxgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4fDA= and signature 70f8bf57aa6d7faaa70ef17e763ef2578cb8d839
Welcome back, aaaa! Congratulations! You did it! Here is your flag: CryptoNinjaCertified!!!!!
Source Codes
- http://force.vnsecurity.net/download/rd/shaext.py
- http://force.vnsecurity.net/download/rd/sha-padding.py
- http://force.vnsecurity.net/download/rd/sha.py (this one taken from pypy lib)
References
- http://en.wikipedia.org/wiki/Cryptographic_hash_function
- Flickr’s API Signature Forgery Vulnerability
- G. Tsudik, “Message authentication with one-way hash functions,” Proceedings of Info-com 92.
Keywords: sha1, padding, length extension attack, codegate 2010
Lỗ hổng nghiêm trọng của TLS/SSL
Một phát hiện hết sức thú vị:
The SSL 3.0+ and TLS 1.0+ protocols are vulnerable to a set of related attacks which allow a man-in-the-middle (MITM) operating at or below the TCP layer to inject a chosen plaintext prefix into the encrypted data stream, often without detection by either end of the connection. This is possible because an “authentication gap” exists during the renegotiation process at which the MitM may splice together disparate TLS connections in a completely standards-compliant way. This represents a serious security defect for many or all protocols which run on top of TLS, including HTTPS.
Thú vị ở chỗ bao nhiêu người, bao nhiêu chuyên gia, bao nhiêu năm qua dòm vô TLS/SSL mà không thấy được lỗ hổng có vẻ như rất hiển nhiên mà các tác giả ở trên phát hiện.
Có lẽ nguyên nhân nhiều người dòm nhưng không thấy là vì họ chỉ dòm TLS/SSL khi nó đứng một mình, mà không nhìn vào bức tranh lớn OSI, trong đó TLS/SSL chỉ là một layer. Chuyện gì sẽ xảy ra nếu TLS/SSL không hiểu rõ cơ chế hoạt động của các protocol bên trên nó, như HTTP, SMTP hay POP3? Nói cách khác, chuyện gì sẽ xảy ra nếu các protocol ở mức Application không hiểu rõ cơ chế vận hành của TLS/SSL để sử dụng cho đúng cách? Đó là lúc lỗ hổng xuất hiện.
Tổng quan thì lỗ hổng này nằm ở sự thiếu “ăn rơ” giữa TLS/SSL và các protocol trên nó như HTTP hay SMTP. Khai thác lỗ hổng này thì kẻ tấn công có thể chèn thêm một đoạn plaintext bất kỳ vào TLS/SSL encrypted stream giữa client và server mà cả client và server đều không thể phát hiện được.
Đây là một lỗ hổng cực kỳ nghiêm trọng, bởi vì nó phá vỡ hoàn toàn cam kết an toàn của bộ giao thức TLS/SSL. Nói một cách *hoành tráng* thì về mặt lý thuyết, nền tảng của thương mại điện tử đang chao đảo. Tôi dùng chữ lý thuyết vì để cho hướng tấn công này nguy hiểm hơn trong thực tế, thì còn có nhiều trở ngại phải vượt qua (và sẽ bị vượt qua).
Để minh họa cho câu chuyện, và để dễ giải thích, tôi đặt ra một ví dụ như sau:
0. Giả định:
* Ngân hàng A có cung cấp dịch vụ Internet Banking ở địa chỉ https://www.ebank.com. Máy chủ của của họ chạy phần mềm có lỗ hổng mà chúng ta đang bàn ở đây. Chúng ta gọi máy chủ này là server.* Để tăng cường an ninh, ngân hàng A yêu cầu khi khách hàng (giờ cứ gọi là client) sử dụng các tính năng có liên quan đến giao dịch tài chính nằm trong khu vực https://www.ebank.com/account/, thì (browser của) họ phải có cài đặt client certificate cho ngân hàng A cung cấp. Lưu ý là nhiều ngân hàng ở VN thực hiện cái này lắm nha.
* Ngoài ra ngân hàng A còn hỗ trợ khách hàng truy cập bằng (Safari trên) iPhone, lúc đó khách hàng sẽ được chuyển đến https://www.ebank.com/iphone/. Do iPhone có processor yếu, nên ngân hàng A cấu hình máy chủ web của họ để sử dụng một bộ ciphersuite yếu hơn bộ ciphersuite mà họ sử dụng cho các khách hàng thông thường. Cái này trong thực tế cũng có nhiều công ty triển khai.
Rồi bây giờ tôi sẽ sử dụng cái kỹ thuật vừa mới phát hiện để tấn công các khách hàng của ngân hàng A theo 3 hướng tấn công mà các tác giả nêu ra. Àh lưu ý là đây là loại tấn công MITM, nghĩa là attacker phải có quyền theo dõi, điều chỉnh dữ liệu truyền qua lại giữa client và server nha. Attacker có thể làm việc này thông qua các tấn công vào các giao thức ARP hay DNS.
1. Hướng tấn công số 1
Đối với hướng tấn công số 1, tôi sẽ lợi dụng việc khi truy cập vào https://www.ebank.com/account/ thì server sẽ yêu cầu client phải trình certificate.
Sơ đồ bên dưới là tôi lấy từ paper của các tác giả phát hiện ra lỗ hổng này. Tôi thấy cái sơ đồ này giải thích rất rõ lỗ hổng này và cách thức tấn công theo hướng thứ 1. Thật ra thì hướng thứ 2 và hướng thứ 3 cũng khá giống hướng thứ 1, nên tôi nghĩ nắm rõ hướng thứ 1 thì sẽ thấy các hướng kia cũng đơn giản.
Có 4 bước khi triển khai tấn công này:
* Bước 1: client truy cập vào https://www.ebank.com. Lúc này client sẽ kết nối đến attacker, và gửi CLIENT_HELLO để bắt đầu giao thức TLS/SSL. Attacker sẽ tạm dừng cái kết nối này và lưu msg CLIENT_HELLO lại để dùng trong bước 3.
* Bước 2: attacker mở kết nối đến server thật. Hai bên sẽ bắt tay theo giao thức TLS/SSL để tạo thành một session. Sau khi hoàn tất bắt tay, attacker gửi một HTTP request, đại loại như:
POST /account/transfer?amount=1000&receiver=attacker HTTP/1.1\r\n
* Bước 3: server thấy có một request đến khu vực /account/ nên nó tạm thời dừng xử lý request này lại và như đã nói ở trên, nó yêu cầu attacker phải đưa client certificate cho nó xem. Cái hay ở đây, mặc dầu attacker không có (private key của) certificate của client, nhưng hắn vẫn có thể *proxy* cái certificate đó từ client lên server, mà không bị bên nào phát hiện cả.
Server bắt đầu quá trình xác thực bằng việc gửi một msg HELLO_REQUEST ngược lại cho attacker. Attacker nhận được msg này thì hắn gửi CLIENT_HELLO mà hắn đã lưu ở bước 1 ngược lại cho server. Rồi cứ thế, attacker đứng giữa, chuyển msg qua lại giữa client và server cho đến khi quá trình xác thực bằng client certificate kết thúc thành công.
Lưu ý là có 2 loại msg mà attacker sẽ gửi. Loại thứ nhất (trên sơ đồ là những msg kết thúc hoặc bắt đầu từ cột m) là những msg mà hắn phải giải mã/mã hóa trước khi gửi đi. Ví dụ như hắn nhận “Certificate” từ phía client thì hắn sẽ mã hóa cái msg này lại, rồi mới gửi cho server. Loại thứ hai (trên sơ đồ là những msg màu hồng và đỏ) là những msg mà hắn không đọc được (vì không có key), hắn chỉ làm mỗi việc là nhận từ client thì gửi qua server và ngược lại.
* Bước 4: quá trình xác thực client certificate đã kết thúc thành công, server tiếp tục xử lý cái request của attacker ở trên, và trả kết quả lại cho attacker (lưu ý là attacker sẽ không đọc được kết quả này).
Điểm yếu là ở đây. Như chúng ta thấy, khi attacker gửi request ở bước 3, lúc đó hắn chưa được xác thực. Nói cách khác, lúc này request của hắn là unauthenticated request. Việc xác thực diễn ra sau đó, và sau khi xác thực rồi thì server lại quay lại xử lý tiếp cái unauthenticated request của attacker.
Lưu ý, ở bước này, để tránh bị tình nghi, attacker có thể tiếp tục trả kết quả về cho client để đóng kết nối lại một cách êm đẹp.
2. Hướng tấn công số 2
Trước khi bắt đầu giải thích hướng số 2, tôi muốn nhấn mạnh ý này: tất cả 3 hướng tấn công này đều hướng đến chôm credential của client để gửi các authenticated request đến server. Credential ở đây có thể là certificate (như ở hướng số 1) hay cookie/session (như ở hướng số 2 và số 3). Nếu chỉ áp dụng cho HTTPS, nhìn ở một góc độ nào đó, các hướng tấn công này rất giống với tấn công CSRF. Nên nếu ứng dụng của bạn đã có các phương thức phòng chống CSRF rồi hay nếu ứng dụng của bạn không chấp nhận thay đổi state bằng GET, thì tạm thời cũng không phải có gì lo lắng.
Đối với hướng số 1, tôi lợi dụng client certificate để gửi một authenticated request. Ở trường hợp các server không xác thực bằng certificate, tôi sẽ sử dụng hướng tấn cống số 2.
Hướng tấn công này cũng có 4 bước:
* Bước số 1: tương tự như hướng tấn công số 1.
* Bước 2: attacker mở kết nối đến server thật. Hai bên sẽ bắt tay theo giao thức TLS/SSL để tạo thành một session.
Sau khi hoàn tất bắt tay, attacker gửi một HTTP request, đại loại như:
GET /iphone/login HTTP/1.1\r\n Host: ebank.com\r\n Connection: keep-alive\r\n \r\n GET /account/transfer?amount=1000&receiver=attacker HTTP/1.1\r\n Host: ebank.com\r\n Connection: close\r\n X-ignore-this:
* Bước số 3: server thấy có request đến /iphone/ nên nó tạm thời dừng xử lý request này lại và, như đã nói ở phần giả định, server sẽ bắt đầu quá trình renegotiate lại để chọn một bộ ciphersuite yếu hơn. Vấn đề ở đây là server sẽ buffer lại toàn bộ nhóm unauthenticated request này, khi mà renegotiate xong thì lại quay lại xử lý hết tất cả.
Trong quá trình renogotiation, vai trò của attacker cũng tương tự như ở bước số 3 của hướng tấn công số 1, nghĩa là hắn cũng chỉ *proxy* msg qua lại giữa client và server, cho đến khi quá trình renegotiate kết thúc thành công.
* Bước số 4: lúc này, client thấy đã handshake xong rồi, nên bản thân nó sẽ gửi tiếp cái HTTP request của nó ở dạng:
GET /index HTTP/1.1\r\n Cookie: AuthMe=Now\r\n \r\n
Chuyện bất ngờ diễn ra ở đây. Server nó sẽ gom nhóm unauthenticated request ở bước 2 (do attacker gửi) và cái authenticated request này (do client gửi) rồi xử lý chung một lần. Nguyên nhân server xử lý như thế là do cái cờ keep-alive ở request đầu tiên. Thành ra lúc này nhóm request trở thành như sau (màu cam là attacker gửi, màu xanh là client gửi):
GET /iphone/login HTTP/1.1\r\n Host: ebank.com\r\n Connection: keep-alive\r\n \r\n GET /account/transfer?amount=1000&receiver=attacker HTTP/1.1\r\n Host: ebank.com\r\n Connection: close\r\n X-ignore-this:GET /index HTTP/1.1\r\n Cookie: AuthMe=Now\r\n \r\n
Ở đây cái header X-ignore-this đã vô hiệu hóa cái request GET /index HTTP/1.1 của client, đồng thời chôm luôn cookie của client để gắn vào cái unauthenticated request GET /account/transfer?amount=1000&receiver=attacker. Rất hay!
3. Hướng tấn công số 3
Đây là hướng tấn công mạnh nhất, không cần server phải có cấu hình đặc biệt gì để thực hiện. Sự khác biệt cơ bản giữa tấn công này với hai hướng tấn công vừa rồi là trong trường hợp này, client bắt đầu quy trình renegotiation.
Ý tưởng thực hiện tấn công rất giống với hướng 2, chỉ khác nhau ở bước số 2, attacker sẽ không gửi GET /iphone/login nữa mà gửi trực tiếp luôn request của hắn, kèm theo một cái “X-ignore-this” header.
Ngay sau khi gửi cái request đó, attacker sẽ forward cái CLIENT_HELLO thu được ở bước 1 sang cho phía server để bắt đầu quy trình renegotiation. Khi đã renegotiate xong, client sẽ gửi request ban đầu của mình đến server, lúc này toàn bộ request sẽ trông như sau (phần màu cam của attacker gửi, phần màu xanh của client gửi):
GET /account/transfer?amount=1000&receiver=attacker HTTP/1.1\r\n Host: ebank.com\r\n Connection: close\r\n X-ignore-this: GET /index HTTP/1.1\r\n Cookie: AuthMe=Now\r\n \r\n
Tương tự ở trên, X-ignore-this đã vô hiệu hóa request của client và chôm cookie để biến request của attacker thành authenticated. Không cần keep-alive, không cần server phải có cấu hình đặc biệt gì cả!








