배경 개념
c 언어에서 메모리의 동적 할당과 해제를 담당하는 함수에는 malloc, free, realloc 등이 있다. 이 함수들은 libc.so에 구현되어 있다.
libc에는 이 함수들의 디버깅 편의를 위해 훅 변수가 정의되어 있다. malloc 함수는 __malloc_hook을, free 함수는 __free_hook, realloc 함수는 __realloc_hook이라는 훅 변수를 사용한다. 이 함수들은 libc.so의 bss 및 data 섹션에 포함된다. bss, data 섹션은 쓰기가 가능하므로, 실행 중에 조작이 가능하다.
훅을 실행할 때 기존 함수에 전달한 인자를 그대로 전달해주기 때문에 __malloc_hook을 system 함수의 주소로 덮고, malloc("/bin/sh")를 호출해서 쉘을 따는 방식의 공격이 가능하다.
__free_hook이나 __malloc_hook과 같은 훅은 Full RELRO가 적용된 바이너리에도 라이브러리의 훅에는 쓰기 권한이 있기 때문에 공격에 악용되기 쉬워 Glibc 2.34 버전부터 제거되었다.
문제
문제 파일로 Dockerfile, fho, fho.c, flag, libc-2.27.so 파일을 제공해준다. fho.c를 보자.
// Name: fho.c
// Compile: gcc -o fho fho.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
char buf[0x30];
unsigned long long *addr;
unsigned long long value;
setvbuf(stdin, 0, _IONBF, 0);
setvbuf(stdout, 0, _IONBF, 0);
puts("[1] Stack buffer overflow");
printf("Buf: ");
read(0, buf, 0x100);
printf("Buf: %s\n", buf);
puts("[2] Arbitary-Address-Write");
printf("To write: ");
scanf("%llu", &addr);
printf("With: ");
scanf("%llu", &value);
printf("[%p] = %llu\n", addr, value);
*addr = value;
puts("[3] Arbitrary-Address-Free");
printf("To free: ");
scanf("%llu", &addr);
free(addr);
return 0;
}
친절하게 bof 일으키고, overwrite하고, free 할 수 있게 되어있다. bof하는 부분에서 버퍼의 내용을 출력해줌으로, 스택의 내용을 읽을 수 있다.
우리가 hook overwrite를 하는데 필요한 __free_hook, system, '/bin/sh/' 문자열은 libc에 정의되어 있으므로, libc_base만 알면 주소를 알 수 있다. main 함수는 __libc_start_main이라는 라이브러리 함수가 호출하는데, main 함수의 ret을 읽으면 그 주소를 기반으로 libc_base 계산이 가능하다.
$ gdb fho
pwndbg> b *main
Breakpoint 1 at 0x8ba
pwndbg> r
pwndbg> bt
#0 0x00005625b14008ba in main ()
#1 0x00007f5ae2f1cc87 in __libc_start_main (main=0x5625b14008ba <main>, argc=1, argv=0x7ffdf39f3ed8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7ffdf39f3ec8) at ../csu/libc-start.c:310
#2 0x00005625b14007da in _start ()
pwndbg> x/i 0x00007f5ae2f1cc87
0x7f5ae2f1cc87 <__libc_start_main+231>: mov edi,eax
#1 부분에서 볼 수 있듯이 main()의 반환 주소는 0x00007f5ae2f1cc87이고, x/i로 출력해보면 <__libc_start_main+231>이다.
$ readelf -s libc-2.27.so | grep " __libc_start_main@"
2203: 0000000000021b10 446 FUNC GLOBAL DEFAULT 13 __libc_start_main@@GLIBC_2.2.5
__libc_start_main 함수 자체의 libc_offset이 0x21b10이므로 main 함수의 ret를 leak하고, 그 값에서 0x21b10+231을 빼주면 libc_base를 구할 수 있다!!
풀이
는 드림핵 정책에 따라 공개하지 않습니다,,