hackctf adult_fsb 문제를 풀면서 알게 된 방법이다.
exit함수 내부의 free함수를 호출하는것을 이용하여(free_hook) exploit을 하는 방법을 소개하려고 한다.
Source Code
먼저 간단하게 exit함수에서 어떻게 free함수가 호출되고 있는지 알아보자.
exit()
void exit (int status) {
__run_exit_handlers (status, &__exit_funcs, true, true);
}
exit함수는 내부적으로 run_exit_handlers 함수를 호출하여 진행한다. 여기서 인자로 넘겨주는 &__eixt_funcs는 exit.h에 선언되어있다.
exit.h
extern struct exit_function_list *__exit_funcs attribute_hidden;
exit.h에 위와 같이 선언되어 있는데 extern으로 다른 소스 파일에 있는 전역 변수를 가져와서 사용하는 것을 볼 수 있다. 이는 cxa_atexit.c 에서 가져온다.
cxa_atexit.c
static struct exit_function_list initial;struct exit_function_list *__exit_funcs = &initial;
위 변수를 extern으로 exit.h에서 가져와서 사용한다.
struct exit_function_list
struct exit_function_list{
struct exit_function_list *next;
size_t idx;
struct exit_function fns[32];
};
위 변수에서 사용하는 exit_function_list 구조체는 위와 같은 구조로 이루어져 있다.
exit_function_list가 exit_function 구조체를 관리하고 있며, 다음 list 포인터나 idx는 index 역할을 하는 것 같다.
struct exit_function
struct exit_function{
long int flavor;
union
{
void (*at) (void);
struct
{
void (*fn) (int status, void *arg);
void *arg;
} on;
struct
{
void (*fn) (void *arg, int status);
void *arg;
void *dso_handle;
} cxa;
} func;
};
run_exit_handlers()
void attribute_hidden
__run_exit_handlers (int status, struct exit_function_list **listp,
bool run_list_atexit, bool run_dtors) {
#ifndef SHARED
if (&__call_tls_dtors != NULL)
#endif
if (run_dtors)
__call_tls_dtors ();
while (true) {
struct exit_function_list *cur;
__libc_lock_lock (__exit_funcs_lock);
restart:
cur = *listp;
if (cur == NULL) {
__exit_funcs_done = true;
__libc_lock_unlock (__exit_funcs_lock);
break;
}
while (cur->idx > 0) {
struct exit_function *const f = &cur->fns[--cur->idx];
const uint64_t new_exitfn_called = __new_exitfn_called;
__libc_lock_unlock (__exit_funcs_lock);
switch (f->flavor) {
void (*atfct) (void);
void (*onfct) (int status, void *arg);
void (*cxafct) (void *arg, int status);
case ef_free:
case ef_us:
break;
case ef_on:
onfct = f->func.on.fn;
#ifdef PTR_DEMANGLE
PTR_DEMANGLE (onfct);
#endif
onfct (status, f->func.on.arg);
break;
case ef_at:
atfct = f->func.at;
#ifdef PTR_DEMANGLE
PTR_DEMANGLE (atfct);
#endif
atfct ();
break;
case ef_cxa:
f->flavor = ef_free;
cxafct = f->func.cxa.fn;
#ifdef PTR_DEMANGLE
PTR_DEMANGLE (cxafct);
#endif
cxafct (f->func.cxa.arg, status);
break;
}
__libc_lock_lock (__exit_funcs_lock);
if (__glibc_unlikely (new_exitfn_called != __new_exitfn_called))
goto restart;
}
*listp = cur->next;
if (*listp != NULL)
free (cur);
__libc_lock_unlock (__exit_funcs_lock);
}
if (run_list_atexit)
RUN_HOOK (__libc_atexit, ());
_exit (status);
}
free함수 호출은 바로 이 run_exit_handlers() 함수에서 발생한다. 하지만 free함수를 호출하기 위해서는 2가지 조건을 만족해야 한다. while문을 들어가지 않기 위해 (cur->idx > 0)
와 if문 (*listp != NULL)
을 만족해야 free를 호출할 수 있다.
Debugging
소스코드를 통해 free가 호출되는 것을 확인해봤으니 이번에는 gdb로 실제 메모리와 free를 호출하는지 확인해보려고 한다.
p initial
을 통해 다음 그림처럼 gdb로 구조체를 확인할 수 있다.
exit_function_list
실제 메모리를 확인하면 다음과 같다.
여기서 *initial 은 cur->next, ( *initial+8 )은 cur->idx에 해당된다.
즉 free를 호출하려면 (cur->idx > 0)는 *(initial+8) = 0으로 만들어주고,
( *listp != NULL)`는 *(initial)에 아무 값을 넣어주면 조건을 만족할 수 있다.
우선 set 명령어를 이용해서 다음과 같이 값을 바꿔준다.
그러면 다음과 같이 free를 호출하는 것을 확인할 수 있다.
Reference
'Tech > exploit' 카테고리의 다른 글
64bit Format String Bug (1) | 2020.03.21 |
---|