Download as pdf or txt
Download as pdf or txt
You are on page 1of 15

Get early access to Blockset’s Wallet-as-a-Service.

Build your own


customized crypto wallet today.

Get early access to Blockset’s Wallet-as-a-


Service. Build your own customized crypto
wallet today.

ads via Carbon ads via Carbon

pwnable.kr - collision

Introduction
Hey guys this is my write-up for a challenge called collision from pwnable.kr. It’s a
very simple challenge, we need a password to make the program read the flag, the
function that validates the given password is vulnerable to hash collision so we will exploit
it.
Challenge Description :

1 Daddy told me about cool MD5 hash collision today.


2 I wanna do something like that too!
3
4 ssh col@pwnable.kr -p2222 (pw:guest)

ads via Carbon


Code Analysis, Tests
col.c :

1 #include <stdio.h>
2 #include <string.h>
3 unsigned long hashcode = 0x21DD09EC;
4 unsigned long check_password(const char* p){
5 int* ip = (int*)p;
6 int i;
7 int res=0;
8 for(i=0; i<5; i++){
9 res += ip[i];
10 }
11 return res;
12 }
13
14 int main(int argc, char* argv[]){
15 if(argc<2){
16 printf("usage : %s [passcode]\n", argv[0]);
17 return 0;
18 }
19 if(strlen(argv[1]) != 20){
20 printf("passcode length should be 20 bytes\n");
21 return 0;
22 }
23
24 if(hashcode == check_password( argv[1] )){
25 system("/bin/cat flag");
26 return 0;
27 }
28 else
29 printf("wrong passcode.\n");
30 return 0;
31 }
main() :

Starting by the main function it checks if we have given the program an input and it
checks if our input’s length is exactly 20 bytes. Then it checks if the return value of
check_password(our input) is equal to hashcode , if we pass that check it will read the
flag, otherwise it will print wrong passcode. and exit.

1 int main(int argc, char* argv[]){


2 if(argc<2){
3 printf("usage : %s [passcode]\n", argv[0]);
4 return 0;
5 }
6 if(strlen(argv[1]) != 20){
7 printf("passcode length should be 20 bytes\n");
8 return 0;
9 }
10
11 if(hashcode == check_password( argv[1] )){
12 system("/bin/cat flag");
13 return 0;
14 }
15 else
16 printf("wrong passcode.\n");
17 return 0;
18 }

Looking up, we can see the declaration of the variable hashcode :

1 unsigned long hashcode = 0x21DD09EC;

That’s a hex value, let’s convert it to decimal with python :

1 >>> 0x21DD09EC
2 568134124

So we need our input to be 20 bytes length and we also need to make the function
check_password return 568134124 when our input is given to it.
Let’s quickly try to simulate that in gdb .
I ran the program and set a breakpoint at main :

1 gef➤ break main


2 Breakpoint 1 at 0x11b3
3 gef➤ r "AAAAAAAAAAAAAAAAAAAA"
4 Starting program: /root/Desktop/pwnable.kr/collision/col "AAAAAAAAAAAAAAAA
5
6 Breakpoint 1, 0x00005555555551b3 in main ()
7 [ Legend: Modified register | Code | Heap | Stack | String ]
8 ──────────────────────────────────────────────────────────────────────────
9 $rax : 0x00005555555551af → <main+0> push rbp
10 $rbx : 0x0
11 $rcx : 0x00007ffff7fa9718 → 0x00007ffff7faad80 → 0x0000000000000000
12 $rdx : 0x00007fffffffe160 → 0x00007fffffffe48b → "SHELL=/bin/bash"
13 $rsp : 0x00007fffffffe060 → 0x0000555555555260 → <__libc_csu_init+
14 $rbp : 0x00007fffffffe060 → 0x0000555555555260 → <__libc_csu_init+
15 $rsi : 0x00007fffffffe148 → 0x00007fffffffe44f → "/root/Desktop/pwna
16 $rdi : 0x2
17 $rip : 0x00005555555551b3 → <main+4> sub rsp, 0x10
18 $r8 : 0x00007ffff7faad80 → 0x0000000000000000
19 $r9 : 0x00007ffff7faad80 → 0x0000000000000000
20 $r10 : 0x0
21 $r11 : 0x00007ffff7f6b1b0 → 0x0000800003400468
22 $r12 : 0x0000555555555080 → <_start+0> xor ebp, ebp
23 $r13 : 0x00007fffffffe140 → 0x0000000000000002
24 $r14 : 0x0
25 $r15 : 0x0
26 $eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow
27 $cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
28 ──────────────────────────────────────────────────────────────────────────
29 0x00007fffffffe060│+0x0000: 0x0000555555555260 → <__libc_csu_init+0> pus
30 0x00007fffffffe068│+0x0008: 0x00007ffff7e1209b → <__libc_start_main+
31 0x00007fffffffe070│+0x0010: 0x0000000000000000
32 0x00007fffffffe078│+0x0018: 0x00007fffffffe148 → 0x00007fffffffe44f →
33 0x00007fffffffe080│+0x0020: 0x0000000200040000
34 0x00007fffffffe088│+0x0028: 0x00005555555551af → <main+0> push rbp
35 0x00007fffffffe090│+0x0030: 0x0000000000000000
36 0x00007fffffffe098│+0x0038: 0xf6e7f80b45a87e3d
37 ──────────────────────────────────────────────────────────────────────────
38 0x5555555551ae <check_password+73> ret
39 0x5555555551af <main+0> push rbp
40 0x5555555551b0 <main+1> mov rbp, rsp
41 → 0x5555555551b3 <main+4> sub rsp, 0x10
42 0x5555555551b7 <main+8> mov DWORD PTR [rbp-0x4], edi
43 0x5555555551ba <main+11> mov QWORD PTR [rbp-0x10], rsi
44 0x5555555551be <main+15> cmp DWORD PTR [rbp-0x4], 0x1
45 0x5555555551c2 <main+19> jg 0x5555555551e6 <main+55>
46 0x5555555551c4 <main+21> mov rax, QWORD PTR [rbp-0x10]
47 ──────────────────────────────────────────────────────────────────────────
48 [#0] Id 1, Name: "col", stopped, reason: BREAKPOINT
49 ──────────────────────────────────────────────────────────────────────────
50 [#0] 0x5555555551b3 → main()
51 ──────────────────────────────────────────────────────────────────────────
52 gef➤

Then I set a breakpoint before the return instruction in check_password() and continued
the execution :

1 gef➤ disas check_password


2 Dump of assembler code for function check_password:
3 0x0000555555555165 <+0>: push rbp
4 0x0000555555555166 <+1>: mov rbp,rsp
5 0x0000555555555169 <+4>: mov QWORD PTR [rbp-0x18],rdi
6 0x000055555555516d <+8>: mov rax,QWORD PTR [rbp-0x18]
7 0x0000555555555171 <+12>: mov QWORD PTR [rbp-0x10],rax
8 0x0000555555555175 <+16>: mov DWORD PTR [rbp-0x8],0x0
9 0x000055555555517c <+23>: mov DWORD PTR [rbp-0x4],0x0
10 0x0000555555555183 <+30>: jmp 0x5555555551a2 <check_password+
11 0x0000555555555185 <+32>: mov eax,DWORD PTR [rbp-0x4]
12 0x0000555555555188 <+35>: cdqe
13 0x000055555555518a <+37>: lea rdx,[rax*4+0x0]
14 0x0000555555555192 <+45>: mov rax,QWORD PTR [rbp-0x10]
15 0x0000555555555196 <+49>: add rax,rdx
16 0x0000555555555199 <+52>: mov eax,DWORD PTR [rax]
17 0x000055555555519b <+54>: add DWORD PTR [rbp-0x8],eax
18 0x000055555555519e <+57>: add DWORD PTR [rbp-0x4],0x1
19 0x00005555555551a2 <+61>: cmp DWORD PTR [rbp-0x4],0x4
20 0x00005555555551a6 <+65>: jle 0x555555555185 <check_password+
21 0x00005555555551a8 <+67>: mov eax,DWORD PTR [rbp-0x8]
22 0x00005555555551ab <+70>: cdqe
23 0x00005555555551ad <+72>: pop rbp
24 0x00005555555551ae <+73>: ret
25 End of assembler dump.
26 gef➤ break *0x00005555555551ae
27 Breakpoint 2 at 0x5555555551ae
28 gef➤ c
29 Continuing.
30
31 Breakpoint 2, 0x00005555555551ae in check_password ()
32 [ Legend: Modified register | Code | Heap | Stack | String ]
33 ──────────────────────────────────────────────────────────────────────────
34 $rax : 0x46464645
35 $rbx : 0x0
36 $rcx : 0x6
37 $rdx : 0x10
38 $rsp : 0x00007fffffffe048 → 0x0000555555555225 → <main+118> mov rdx,
39 $rbp : 0x00007fffffffe060 → 0x0000555555555260 → <__libc_csu_init+
40 $rsi : 0x00007fffffffe148 → 0x00007fffffffe44f → "/root/Desktop/pwna
41 $rdi : 0x00007fffffffe476 → "AAAAAAAAAAAAAAAAAAAA"
42 $rip : 0x00005555555551ae → <check_password+73> ret
43 $r8 : 0x400
44 $r9 : 0x00007ffff7faad80 → 0x0000000000000000
45 $r10 : 0xfffffffffffff479
46 $r11 : 0x00007ffff7e861e0 → <__strlen_sse2+0> pxor xmm0, xmm0
47 $r12 : 0x0000555555555080 → <_start+0> xor ebp, ebp
48 $r13 : 0x00007fffffffe140 → 0x0000000000000002
49 $r14 : 0x0
50 $r15 : 0x0
51 $eflags: [zero carry parity adjust sign trap INTERRUPT direction overflow
52 $cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
53 ──────────────────────────────────────────────────────────────────────────
54 0x00007fffffffe048│+0x0000: 0x0000555555555225 → <main+118> mov rdx, rax
55 0x00007fffffffe050│+0x0008: 0x00007fffffffe148 → 0x00007fffffffe44f →
56 0x00007fffffffe058│+0x0010: 0x0000000200000000
57 0x00007fffffffe060│+0x0018: 0x0000555555555260 → <__libc_csu_init+0> pus
58 0x00007fffffffe068│+0x0020: 0x00007ffff7e1209b → <__libc_start_main+
59 0x00007fffffffe070│+0x0028: 0x0000000000000000
60 0x00007fffffffe078│+0x0030: 0x00007fffffffe148 → 0x00007fffffffe44f →
61 0x00007fffffffe080│+0x0038: 0x0000000200040000
62 ──────────────────────────────────────────────────────────────────────────
63 0x5555555551a1 <check_password+60> add DWORD PTR [rbx+0x7e04fc7d
64 0x5555555551a7 <check_password+66> fisttp QWORD PTR [rbx-0x67b707bb
65 0x5555555551ad <check_password+72> pop rbp
66 → 0x5555555551ae <check_password+73> ret
67 ↳ 0x555555555225 <main+118> mov rdx, rax
68 0x555555555228 <main+121> mov rax, QWORD PTR [rip+0x2e19
69 0x55555555522f <main+128> cmp rdx, rax
70 0x555555555232 <main+131> jne 0x55555555524c <main+157>
71 0x555555555234 <main+133> lea rdi, [rip+0xe08] #
72 0x55555555523b <main+140> mov eax, 0x0
73 ──────────────────────────────────────────────────────────────────────────
74 [#0] Id 1, Name: "col", stopped, reason: BREAKPOINT
75 ──────────────────────────────────────────────────────────────────────────
76 [#0] 0x5555555551ae → check_password()
77 [#1] 0x555555555225 → main()
78 ──────────────────────────────────────────────────────────────────────────
79 gef➤

The return value of check_password() is saved in EAX we need it to be 568134124 :

1 gef➤ print $eax


2 $1 = 0x46464645
3 gef➤ set $eax=568134124

Now if we continue execution it should attempt to execute /bin/cat flag :

1 gef➤ c
2 Continuing.

3 [Detaching after fork from child process 3166]


4 /bin/cat: flag: No such file or directory
5 [Inferior 1 (process 3101) exited normally]
6 gef➤

Great, now we need to find out how to make check_password() return that value, let’s
look at the code.
check_password() :

1 unsigned long check_password(const char* p){


2 int* ip = (int*)p;
3 int i;
4 int res=0;
5 for(i=0; i<5; i++){
6 res += ip[i];
7 }
8 return res;
9 }

This function casts the given passcode ( p ) into integer, declares ip which is an array of
pointers starting with the pointer to p , and declares an int variable called res and
gives it a value of 0 then it loops 5 times through ip (because length of passcode is 20,
20/4 == 5 ) and adds each value to res , finally it returns res .
In case you’re confused, simply what happens is that it takes the given passcode which is
20 bytes length and divides it to 5 pieces (each piece 4 bytes) then it sums the decimal
value of the 5 pieces and returns that value. For example the result of giving
check_password() “AAAAAAAAAAAAAAAAAAAA” will be like this :

1 "AAAA" + "AAAA" + "AAAA" + "AAAA" + "AAAA"


2 0x41414141 + 0x41414141 + 0x41414141 + 0x41414141 + 0x41414141
3 1094795585 + 1094795585 + 1094795585 + 1094795585 + 1094795585
4 res = 5473977925

To verify that I took the main code and added some printf statements, test code looks
like this :
1 #include <stdio.h>

2 #include <string.h>
3 unsigned long hashcode = 0x21DD09EC;
4 unsigned long check_password(const char* p){
5 int* ip = (int*)p;
6 int i;
7 int res=0;
8 for(i=0; i<5; i++){
9 res += ip[i];
10 printf("\n--------------------------\n");
11 printf("loop : %i\n", i);
12 printf("piece value : %i\n",ip[i] );
13 printf("\n");
14 }
15 return res;
16 }
17
18 int main(int argc, char* argv[]){
19 if(argc<2){
20 printf("usage : %s [passcode]\n", argv[0]);
21 return 0;
22 }
23 if(strlen(argv[1]) != 20){
24 printf("passcode length should be 20 bytes\n");
25 return 0;
26 }
27 printf("hashcode : %i\n", hashcode);
28 if(hashcode == check_password( argv[1] )){
29 system("/bin/cat flag");
30 return 0;
31 }
32 else
33 printf("wrong passcode.\n");
34 return 0;
35 }
Let’s give it 20 A’s :

1 root@kali:~/Desktop/pwnable.kr/collision/test# ./test "AAAAAAAAAAAAAAAAAAA


2 hashcode : 568134124
3
4 --------------------------
5 loop : 0
6 piece value : 1094795585
7
8
9 --------------------------
10 loop : 1
11 piece value : 1094795585
12
13
14 --------------------------
15 loop : 2
16 piece value : 1094795585
17
18
19 --------------------------
20 loop : 3
21 piece value : 1094795585
22
23
24 --------------------------
25 loop : 4
26 piece value : 1094795585
27
28 wrong passcode.

You can see that the 5 pieces are of the same value which is the value of 0x41414141 (4
A’s) :

1 >>> 0x41414141
2 1094795585
And if we give it AAAABBBBCCCCDDDDEEEE :

1 root@kali:~/Desktop/pwnable.kr/collision/test# ./test "AAAABBBBCCCCDDDDEEE


2 hashcode : 568134124
3
4 --------------------------
5 loop : 0
6 piece value : 1094795585
7
8
9 --------------------------
10 loop : 1
11 piece value : 1111638594
12
13
14 --------------------------
15 loop : 2
16 piece value : 1128481603
17
18
19 --------------------------
20 loop : 3
21 piece value : 1145324612
22
23
24 --------------------------
25 loop : 4
26 piece value : 1162167621
27
28 wrong passcode.

1 >>> 0x41414141
2 1094795585
3 >>> 0x42424242
4 1111638594
5 >>> 0x43434343
6 1128481603
7 >>> 0x44444444

8 1145324612
9 >>> 0x45454545
10 1162167621

Exploitation
We need to come up with 5 pieces that add up to 568134124.
We can divide the original value by 5 :

1 >>> 568134124/5
2 113626824

But 568134124 isn’t divisible by 5 :

1 >>> 568134124%5
2 4

We can use 113626824 as the first 4 pieces, to get the last piece we will multiply
113626824 by 4 and subtract the result from 568134124 :

1 >>> 113626824 * 4
2 454507296
3 >>> 568134124 - 454507296
4 113626828

What’s left is to convert them to hex :

1 >>> hex(113626824)
2 '0x6c5cec8'
3 >>> hex(113626828)
4 '0x6c5cecc'

And because it’s little endian we will reverse the order, final payload will be :
1 python -c 'print "\xc8\xce\xc5\x06" * 4 + "\xcc\xce\xc5\x06"'

Let’s test it :

1 root@kali:~/Desktop/pwnable.kr/collision/test# ./test `python -c 'print "\


2 ip : 1329747125
3
4 --------------------------
5 loop : 0
6 piece value : 113626824
7
8 --------------------------
9 loop : 1
10 piece value : 113626824
11
12 --------------------------
13 loop : 2
14 piece value : 113626824
15
16 --------------------------
17 loop : 3
18 piece value : 113626824
19
20 --------------------------
21 loop : 4
22 piece value : 113626828
23
24 /bin/cat: flag: No such file or directory

It works.
And by the way what we did now is a hash collision, we made a hash function produce the
same output for different inputs.
I also wrote small python script using pwntools :

1 #!/usr/bin/python
2 from pwn import *
3
4 payload = p32(0x6c5cec8) * 4 + p32(0x6c5cecc)
5
6 r = ssh('col' ,'pwnable.kr' ,password='guest', port=2222)
7 p = r.process(executable='./col', argv=['col',payload])
8 flag = p.recv()
9 log.success("Flag: " + flag)
10 p.close()
11 r.close()

pwned !
That’s it , Feedback is appreciated !
Don’t forget to read the other write-ups , Tweet about the write-up if you liked it , follow on
twitter @Ahm3d_H3sham
Thanks for reading.
Previous pwn write-up : pwnable.kr - bof

You might also like