__run_exit_handlers free hook overwrite

https://www.notion.so/lv2_Overwrite-_rtld_global-227364c9a3f18012acf5f8aecd34ba24?source=copy_link

해당 문제를 푸는 도중 원래 의도는 rtld_global의 _rtld_lock_recursive 함수를 overwrite 한뒤 푸는 문제였으나 신기한 풀이를 발견했다.

https://dreamhack.io/wargame/writeups/7548

이 문제는 실행파일에 free 함수가 존재하지 않는다. 근데 이 분은 갑자기 _free_hook overwrite를 하기 시작한다. hook overwrite야 쉽지만 없는 free 함수를 실행시키는 것은 어렵다. 나는 여기에 꽂혀서 어떻게 free 함수를 실행시키는지 분석을 시작해보았다.

이 글은 분석에 대한 내용이다.

먼저, 우리가 프로그램이 끝날때 _dl_fini 함수를 거쳐 __run_exit_handlers 함수를 사용하게 된다. 여기서 보면

함수의 끝에

C
     *listp = cur->next;
      if (*listp != NULL)
	/* Don't free the last element in the chain, this is the statically
	   allocate element.  */
	free (cur);

      __libc_lock_unlock (__exit_funcs_lock);

free (cur); 으로 놀랍게도 free를 사용하게 된다.

이때 조건이 조금 있는데 일단 가장 중요한 건 if (*listp != NULL). 을 반드시 통과해야 된다.

또한 그 위에 있는 코드인

C
while (cur->idx > 0) {

이 while 문에 걸리면 안된다.

*listp 는 cur->next; 을 의미한다.

cur 은

struct exit_function_list *cur;

exit_function_list 구조체를 의미한다.

C
struct exit_function_list
  {
    struct exit_function_list *next;
    size_t idx;
    struct exit_function fns[32];
  };

해당 구조체는 다음과 같이 정의되어있으며

cur->idx 는 offset + 0x8 이며

cur→next 는 offset + 0x0 이다. 그럼 이제 offset을 알 수 있기만 한다면,

cur->idx 에 0을 넣고 cur→next 에는 “/bin/sh” 을 u64(b'/bin/sh\0') 다음과 같이 넣으면

free (cur); 여기서 hook에 overwrite 된 system(”/bin/sh”) 을 실행 시킬 수 있게된다.

추가적으로

C
static struct exit_function_list initial;
struct exit_function_list *__exit_funcs = &initial;

첫번째의 exit_function_list 구조체는 initial 이라는 전역변수를 가진다.

따라서 우리는 gdb에서 편하게

C
pwndbg> p &initial
$1 = (struct exit_function_list *) 0x7c357ccfdd80 <initial>
pwndbg> p initial
$2 = {
  next = 0x0,
  idx = 1,
  fns = {{
      flavor = 4,
      func = {
        at = 0x8831e592f5c780b3,
        on = {
          fn = 0x8831e592f5c780b3,
          arg = 0x0
        },
        cxa = {
          fn = 0x8831e592f5c780b3,
          arg = 0x0,
          dso_handle = 0x0
        }
      }
    }, {
      flavor = 0,
      func = {
        at = 0x0,
        on = {
          fn = 0x0,
          arg = 0x0
        },
        cxa = {
          fn = 0x0,
          arg = 0x0,
          dso_handle = 0x0
        }
      }
    } <repeats 31 times>}
}

initial 이라는 심볼로 사용 할 수 있다.

idx = 1, 을 0으로 바꾸고

next = 0x0, 을 채워넣으면 우리의 준비는 끝나는 것이다.

C
pwndbg> vmmap &initial
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
             Start                End Perm     Size Offset File
    0x7c357ccfc000     0x7c357ccfe000 rw-p     2000 1eb000 /home/ow_rtld/libc-2.27.so +0x1d80

당연히 libc에 위치하므로 libc의 디버깅 심볼이 있어야 한다.