Tech/exploit

exit함수를 이용한 exploit

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