내가 보려고 만든 ctf의 ctf를 위한 ctf에 의한 힙 익스 아이디어

=====================



일단 ctf에 자주나오는 하우스 시리즈 힙 유형을 살펴본다

기법들에 대해 자세히 설명하지는 않는다. 전체적으로 훑어 보기로 한다.

제일 잘나오는건 세 가지다




#### 1.fastbin dup attack


 정말 제일 만만하고 제일 많이 쓰인다. 0x80까지의 동적메모리의 해제가 자유롭고

 arbitrary overwrite이든 overflow든 해제한 메모리의 next bin ptr영역에 overwrite가 가능하다면 이 기법을 사용할 수 있다. 

fastbin dup으로 overwrite를 할때는 bin의 사이즈 단위가 중요한데(같은 단위가 아니면 abort 0x10단위로 존재) 


특히 0x70단위의 경우 0x0000007f가 써져있는 메모리 영역에 접근이 가능한데 64비트의 경우 0x7f는 libc주소의 앞자리임으로 요긴하게 사용할 수 있다.(0x7f는 malloc_hook 앞에도 늘 존재하기 떄문에 malloc_hook을 덮을 때 꿀을 빨자)



#### 2.1byte overflow로 발생하는 poison null byte


0x18 0x28이런식으로 8로 끝나는 주소로 할당(32bit의 경우 4)하는 경우 청크가 딱 붙게 된다. 

그래서 1byte라도 오버플로우나는 경우 또는 입력의 마지막을 null 처리 해주는데 그게 다른 청크로 침범이 가능할 경우에는 바로 이 기법이다.(후자는 거이 이 기법을 유도했다고 볼 수 있다)


마지막이 null이 된다는 건 chunk/bin의 flag를 건들일 수 있다는 것이다. flag의 마지막 비트는 prev_inuse비트로 바로 뒤의 청크가 할당된 상태인지 해제된 상태인지를 나타낸다. 이 비트가 원래 1이었는데 0이 된다는 것은 뒤의 청크가 할당이 되어있지만 해제 되어있는 것처럼 여긴다는 것이다. 

prev_inuse가 0인 chunk을 free 할때는 이전 청크가 free된 청크라고 생각해서 consolidate 즉 병합하려고 한다. 이전 청크를 찾아갈때는 pre_size를 이용해서 찾아가는데 size를 덮을 수 있는 overwrite이면 당연히 prev_size 도 overwrite가 가능하다. 이를 이용해서 fake청크와 병합되게 한후 malloc하면 fake chunk로 할당이 된당(fake chunk의 size는 small bin 크기여야하고 fake chunk의 fd bk는 fakechunk의 주소를 넣으면 unlink assertion 을 우회할수 있다) 





#### 3.unsorted bin attack 


libc메모리릭할때 요긴하게 쓰인다

small/large bin 을 free하고 전후로 해제했던 다른 bin 이 없으면 fd/bk가 main_arena랑 연결이 됨. 해제한 메모리에 접근이 가능할때 libc릭으로 요긴하게 쓰인다.

그리고 임의의 주소를 free할 수 있을 때 특정주소에 main_arena의 주소를 쓸 수 있다. 이거는 자주 쓰이진 않지만 언제 쓰일지 모름으로 염두에 항상 두자






이 세가지가 가장 많이 나오고 자주나오진 않지만 핵심이 될 수 있는 기법은

top_chunk를 덮어버리는 기법들이다 


#### 1.house of force

 

top chunk를 덮어서 엄청 크게 만든다음 다른 메모리 영역을 건드린다.


#### 2.house of orange


top_chunk free를 이용한 최초의 문제가 hitcon2016의 house of orange 문제였기 때문에 house of orange 기법이라 칭하겠다


분명히 heap 문제인 것 같은데 free하는 부분이 없다거나 약간 모자른데 오버플로우가 난다?하면 이거다. 게다가 size가 덮어진다?하면 100프로 이거다.

처음에 할당했던 heap영역이 다 차게 되면 top_chunk를 free해버리고 mmap을 다시함. 아무튼 중요한건 top_chunk가 ㄹfree된다는것. top_chunk의 사이즈를 조절해서 fastbin attack으로 쓸 수도 있고 unsorted bin attack으로 릭이나 overwrite로 쓸 수도 있다, 이때 top_chunk가 무너지면서 abort를 출력할때 IO_FILE을 사용하는걸 이용해서 취약점 트리거에 이용하거나 int_free를 공격벡터로 사용하기도 한다.


#### 3.마지막으로 main_arena를 전체적으로 건드릴수 있을 때 top chunk와 unsorted bin 포인터를 덮어버릴 수도 있다.





만약에??


1. unlink 함수를 직접 구현해 놨다? -> 백퍼센트 unlink 취약점이다. unlink는 최신 glibc에 막혀있다.

막혀있기 전에는 unlink 할떄 bk->fd->bk=obj 또는 fd->bk->fd=obj를 확인하지 않았다.

그래서 obj의 fd bk overwrite만 가능하면 unlink취약점을 어렵지 않게 트리거 할 수 있다.


2. 당장 생각이 안나니 추후 추가




**이래도 취약점이 안보이면?**


_만약에 heap바이너리가 너무 크다 하면 왠만하면 uaf가 숨어있다. 잘 찾자! c++의 경우 vtable을 유념해서 봐보도록하자_

또는 how2heap의 다른 기법을 뒤져보자

https://github.com/david942j/one_gadget


one_gadget (libc_file)

ulimit


ulimit -s unlimited -> aslr ㄲㅓ짐 


http://err0rless313.tistory.com/entry/ulimit-s-unlimited-ASLR%EC%9D%B4-%EA%BA%BC%EC%A7%80%EB%8A%94-%EC%9D%B4%EC%9C%A0

64bit binary나 , ubuntu 16.04같은 최신 리눅스는 안될 수 있음


ulimit -d 10(ulimit -d 0)


Malloc은 크기에 따라 heap이나 mmap을 이용해서 주소를 할당하는데 Ulimit –d 옵션은 heap에 할당하는 크기의 최댓값을 정의한다

즉 mmap을 이용하면 malloc이 libc영역에 할당됨

ulimit -s unlimited와 같이 libc주소도 고정시켜버리면

malloc주소를 고정시킬 수 있음


ulimit -a해서 보이는 다양한 설정값들을 제한하거나 컨트롤할 수 있음


ulimit -a

core file size          (blocks, -c) 0

data seg size           (kbytes, -d) unlimited

file size               (blocks, -f) unlimited

max locked memory       (kbytes, -l) unlimited

max memory size         (kbytes, -m) unlimited

open files                      (-n) 256

pipe size            (512 bytes, -p) 1

stack size              (kbytes, -s) 8192

cpu time               (seconds, -t) unlimited

max user processes              (-u) 266

virtual memory          (kbytes, -v) unlimited


file size도 local exploit에 가끔 쓰인다.

pwnable.kr의 문제중 하나에 있었던 것 같음

또 core size의 default는 0이고 이 크기 늘려주면 프로그램이 비정상종료될 떄 core파일이 생긴다

디버깅할때 유용함


write by say2

glibc malloc.c에 있는 _int_malloc의 소스를 몇가지 주석을 없애거나 번역하고(번역표시 - ***) 약간의 exploit 관련된 설명이나 코드설명을 달았습니다.

그리고 malloc함수를 분석한 다른 글들을 가져와 관련된 코드에 덧붙여 놓았습니다. (갈색 글씨)

**chunk 나 bin에 대한 malloc에 대한 기본적인 지식을 갖추었다고 생각하고 작성하였습니다.

**마지막에 small bin처리와 top chunk처리에 대해서 좀더 분석하여 추가할 예정입니다.


libc_malloc을 호출하면 몇가지 루틴을 거친후 _int_malloc을 호출합니다.

우리가 아는 기능을 하는게 _int_malloc이 됩니다


static void *

_int_malloc (mstate av, size_t bytes)

{

  INTERNAL_SIZE_T nb;               /* normalized request size */

  unsigned int idx;                 /* associated bin index */

  mbinptr bin;                      /* associated bin */


  mchunkptr victim;                 /* inspected/selected chunk */

  INTERNAL_SIZE_T size;             /* its size */

  int victim_index;                 /* its bin index */


  mchunkptr remainder;              /* remainder from a split */

  unsigned long remainder_size;     /* its size */


  unsigned int block;               /* bit map traverser */

  unsigned int bit;                 /* bit map traverser */

  unsigned int map;                 /* current word of binmap */


  mchunkptr fwd;                    /* misc temp for linking */

  mchunkptr bck;                    /* misc temp for linking */


  const char *errstr = NULL;


  checked_request2size (bytes, nb);


사용가능한 공간이 더이상 없을 경우 sysmalloc을 호출합니다. sysmalloc은 시스템에 메모리를 요구할 수 있는 함수 입니다

sysmalloc에서는 mmap을 호출하는데 이를 통해서 top chunk의 크기를 확장하거나 대체하여 동적 할당 영역을 넓힐 수 있습니다.

참고로 sysmalloc은 기존의 old 영역을 free시키고 새로 mmap하는데 여기서 나오는 free를 이용해서 exploit을 짜는 ctf문제도 있었습니다.


  if (__glibc_unlikely (av == NULL))

    {

      void *p = sysmalloc (nb, av);

      if (p != NULL)

alloc_perturb (p, bytes);

      return p;

    }


  /*

     If the size qualifies as a fastbin, first check corresponding bin.

     This code is safe to execute even if av is not yet initialized, so we

     can try it without checking, which saves some time on this fast path.

   */


제일먼저 요구한 size가 fastbin영역인지 체크합니다. 이는 M_MXFAST라는 변수로 fastbin의 max size가 결정되는데 default는 64byte입니다.

M_MXFAST는 환경 변수로 조작가능하며 정식으로는 mallopt() 함수에 의해서 setting됩니다. 또한 좀더 high level에서는 초반 get_max_fast()에 의해서 M_MXFAST에 따라 정의되는 global_max_fast라는 변수가 있는데 이 변수로 fastbin의 boundary를 조작할 수 있습니다.


  1. 아래 코드 루틴 )
  2. 주어진 크기가 fast bin에 속한다면 (<= 72) fast bin 내의 free chunk를 찾아본다.
    1.  주어진 크기에 맞는 fast bin의 인덱스를 계산한다.
    2.  해당 인덱스의 포인터가 가리키는 chunk를 victim 지역 변수에 저장한다.
    3.  victim이 NULL이 아니라면 fast bin의 해당 인덱스에 victim->fb가 가리키는 chunk를 저장하고 victim의 데이터 영역에 대한 포인터를 반환한다. (종료)

  if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ()))

    {

      idx = fastbin_index (nb);

      mfastbinptr *fb = &fastbin (av, idx);

      mchunkptr pp = *fb;

      do

        {

          victim = pp;

          if (victim == NULL)

            break;

        }

      while ((pp = catomic_compare_and_exchange_val_acq (fb, victim->fd, victim))

             != victim);

      if (victim != 0)

        {

          if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0))

            {

              errstr = "malloc(): memory corruption (fast)";

            errout:

              malloc_printerr (check_action, errstr, chunk2mem (victim), av);

              return NULL;

            }

          check_remalloced_chunk (av, victim, nb);

          void *p = chunk2mem (victim);

          alloc_perturb (p, bytes);

          return p;

        }

    }



***

그다음은 smallbin영역인지 체크합니다.

smallbin은 각각 하나의 사이즈를 유지하기 때문에 bin들 안에서 검색을 하는건 무의미 합니다.

large요청이라면 우리는 unsorted chunk에서 가장 적합한 놈을 찾을 때까지 기다려야 합니다.

그러나 small은 어떤것에도 잘 맞아서 할당 해제가 large보다 빠르죠

***

  1. 주어진 크기가 small bin에 속한다면 (< 512) small bin 내에서 free chunk를 찾아본다.
    1.  주어진 크기에 맞는 small bin의 인덱스를 계산하여 idx 지역 변수에 저장한다.
    2.  해당 인덱스 내에 가장 오래된 chunk를 victim 지역 변수에 저장한다.(LIFO-last in first out)
    3.  victim이 올바른 chunk를 가리킨다면 해당 인덱스 내의 리스트에서 victim을 제거하고, victim 바로 다음에 위치한 chunk의 헤더에 P (PREV_INUSE) 플래그를 설정한 뒤 victim의 데이터 영역에 대한 포인터를 반환한다. (종료) 설명을 단순하게 하기 위해 앞으로 'victim을 반환한다'라는 표현은 P 플래그를 설정하는 것과 데이터 포인터를 반환하는 작업을 뜻하는 것으로 사용할 것이다.
    4.  victim이 올바른 chunk를 가리키지 않는다는 것은 다음의 두 경우 중 하나이다.
    5.   victim이 NULL이면 최초로 malloc() 함수가 호출된 경우이다. 아직 초기화가 제대로 이루어지지 않았으므로 malloc_init_state() 내부 함수를 호출하여 초기화를 수행한다.
    6.   victim이 NULL이 아니고 bin 자신을 가리킨다면 해당 bin은 비어있는 것이다. (초기화 과정에서 각 bin의 리스트는 자기 자신을 가리키도록 설정된다.)

  if (in_smallbin_range (nb))

    {

      idx = smallbin_index (nb);

      bin = bin_at (av, idx);


      if ((victim = last (bin)) != bin)

        {

          if (victim == 0) /* initialization check */

            malloc_consolidate (av);

          else

            {

              bck = victim->bk;

if (__glibc_unlikely (bck->fd != victim))

                {

                  errstr = "malloc(): smallbin double linked list corrupted";

                  goto errout;

                }

              set_inuse_bit_at_offset (victim, nb);

              bin->bk = bck;

              bck->fd = bin;


              if (av != &main_arena)

                victim->size |= NON_MAIN_ARENA;

              check_malloced_chunk (av, victim, nb);

              void *p = chunk2mem (victim);

              alloc_perturb (p, bytes);

              return p;

            }

        }

    }



마지막으로 large요청이 남았죠. large요청은 fasbin을 통합하는것부터 시작합니다.

좀 과도해 보일지라도 fastbin과 관련된 문제들을 피하려면 필요합니다.



  1. 여기까지 왔다면 주어진 크기는 large bin에 속한다. large bin은 바로 찾아보지 않고 다음과 같은 준비 과정을 거친다.
    1.  주어진 크기에 맞는 large bin의 인덱스를 계산하여 idx 지역 변수에 저장한다.
    2.  만약 fast bin을 포함하고 있다면 이들을 모두 병합(consolidate)하여 보다 큰 chunk로 만든다. 이는 큰 메모리 요청을 받은 경우에는 더 이상 작은 크기의 요청이 (최소한 당분간은) 없을 것이라고 가정하기 때문이다. (이로 인해 fast bin으로 인한 fragmentation 문제를 줄일 수 있다.)


  else

    {

      idx = largebin_index (nb);

      if (have_fastchunks (av))

        malloc_consolidate (av);

    }






  1. by http://egloos.zum.com/

-----------------------------------------------

기존에 존재하는 fastbin smallbin을 모두 뒤졌다. 이제는 unsorted bin을 하나씩 뒤지고

꺼낸 unsorted bin을 size에 맞게 bin에 넣는 작업을 하고 없으면 largebin을 뒤지고, 그래도 없으면.. top chunk에서 새로운 메모리를 꺼낼겁니다.


***

최근에 해제되거나 남은 덩어리를 처리하고, 정확히 맞는 경우에만 하나를 취하거나, 작은 요청 인 경우 가장 최근의 정확하지 않은 부분부터 덩어리를 남깁니다. 횡단 된 다른 덩어리를 휴지통에 넣습니다. 이 단계는 청크가 저장소에 배치되는 모든 루틴에서 유일한 위치입니다. 외부 루프는 malloc의 끝까지 우리가 통합해야 할 때까지 깨닫지 못하기 때문에 필요합니다. 그렇게해야하고 다시 시도해야합니다. 이것은 한 번만 발생하며 "작은"요청을 처리하기 위해 메모리를 확장해야하는 경우에만 발생합니다.(구글번역기 좋네요)

***

즉 포인트는 사이즈를 정확하게만 할당한다 이겁니다.

  1. 이제 unsorted bin을 검색하여 일치하는 크기의 free chunk가 있는지 검색한다.
    1.  unsorted bin 내의 가장 오래된 chunk를 victim 지역 변수에 저장한다.
    2.  victim을 unsorted bin의 리스트에서 분리한다. (unsorted bin 내의 chunk들은 오직 한 번만 검사된다.)
    3.  victim의 크기와 주어진 크기가 일치한다면 victim을 반환한다. (종료)
    4.  만약 victim의 unsorted bin 내의 마지막 chunk이고 주어진 크기가 small bin에 속하는 작은 크기이며 victim이 이전 요청을 처리하고 남은 자투리 영역이고 (last_remainder), victim의 크기가 주어진 크기를 처리하고 다른 chunk를 만들만한 여유가 있다면 victim을 분할하여 요청을 처리하고 나머지는 다시 unsorted bin 내에 남겨둔다. (종료) 이는 작은 크기의 연속된 요청이 메모리 상의 연속된 위치에 존재하도록 하여 locality를 높이기 위함이다.
    5.  victim의 크기에 맞는 bin의 리스트의 제일 처음에 삽입한다.
    1. 만약 victim이 large bin에 속한다면 large bin 내의 다른 chunk들과 크기를 비교하여 적절한 위치에 삽입된다.
  1. 이제 large bin에 속하는 경우 해당 리스트를 검사한다. large bin은 (일정 범위 내에서) 크기가 다른 chunk들이 섞여있으므로 현재의 요청을 만족시킬 수 있는 가장 작은 크기의 chunk를 찾아야 한다.
    1.  앞서 계산한 idx에 해당하는 bin의 제일 앞에 있는 chunk를 victim 지역 변수에 저장한다.
    2.  victim의 크기가 주어진 크기보다 큰지 검사하여 그렇지 않다면 탐색을 중지한다. large bin은 앞쪽부터 큰 chunk가 놓이고 뒤로 갈수록 (즉, fd_nextsize 링크를 따라갈수록) 크기가 작아진다. 현재 검사한 victim의 크기는 해당 bin 내에 있는 가장 큰 chunk의 크기이므로 이보다 큰 요청은 해당 bin에서 처리할 수 없다.
    3.  victim을 victim->bk_nextsize로 설정한다. 이제 victim은 해당 bin 내의 가장 작은 크기의 chunk이다.
    4.  victim의 크기가 주어진 크기보다 커질 때까지 victim을 victim->bk_nextsize로 변경한다.
    5.  이제 요청을 처리할 chunk를 찾았다. victim을 리스트에서 분리한다.
    6.  victim의 크기가 요청을 처리하고도 다른 chunk를 구성할 수 있을 정도로 크다면 분할하여 나머지 영역을 chunk로 만들어서 unsorted bin에 추가한다.
    7.  victim을 반환한다. (종료)


  for (;; )

    {

      int iters = 0;

unsorted bin은 FIFO형태의 동작입니다.(first in first out) 아래는 그부분을 보여주는 코드네요

적당한 unsorted_chunk가 나올때까지 계속 뺍니다. 안맞는건 버려버리구요

      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);


small size요청일 경우 unsorted bin에 있는 최근의 bin을 사용합니다. 속도를 신경쓴 부분이죠.

유일하게 size를 정확하게 맞추지 않고 속도를 고려한 부분입니다. 정확한 사이즈의 small chunk가 없을 때만 적용된답니다.


unsorted bin에서 나온 녀석이 요청한 사이즈보다 충분히 클때(remainder chunk(>MINSIZE:청크의 가능한 최소 사이즈)가 나올수 있을정도!) 이녀석을 요청한 size를 할당할 영역으로 쓰기로 합니다. remainder도 싹싹하게 모아두고요


          if (in_smallbin_range (nb) &&

              bck == unsorted_chunks (av) &&

              victim == av->last_remainder &&

              (unsigned long) (size) > (unsigned long) (nb + MINSIZE))

            {

              /* split and reattach remainder */

unsorted bin은 size가 정확하게 들어맞지는 않으니 남는부분을 정리해야겠죠

              remainder_size = size - nb;

              remainder = chunk_at_offset (victim, nb);

              unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder;

              av->last_remainder = remainder;

              remainder->bk = remainder->fd = unsorted_chunks (av);

small_bin이라면 fd_nextsize,bk_nextsize의 영역은 필요없습니다. 이거는 large bin만 가지고 있는 구조체의 값들이죠

              if (!in_smallbin_range (remainder_size))

                {

                  remainder->fd_nextsize = NULL;

                  remainder->bk_nextsize = NULL;

                }


              set_head (victim, nb | PREV_INUSE |

                        (av != &main_arena ? NON_MAIN_ARENA : 0));

              set_head (remainder, remainder_size | PREV_INUSE);

              set_foot (remainder, remainder_size);


              check_malloced_chunk (av, victim, nb);

              void *p = chunk2mem (victim);

              alloc_perturb (p, bytes);

              return p; 

적절한걸 찾았으니까 뒤에 볼필요도 없겠죠? 바로 return 해버립니다.

            }


       size가 안 맞았던 녀석은 바로 unsorted bin에서 제거합니다.

          unsorted_chunks (av)->bk = bck;

          bck->fd = unsorted_chunks (av);


이제 사이즈가 정확한지도 체크해봅니다.

          if (size == nb)

            {

              set_inuse_bit_at_offset (victim, size);

              if (av != &main_arena)

                victim->size |= NON_MAIN_ARENA;

              check_malloced_chunk (av, victim, nb);

              void *p = chunk2mem (victim);

              alloc_perturb (p, bytes);

              return p;

            }


          /* place chunk in bin */

여기까지 왔다는건 unsorted bin에서 뽑아낸 bin의 size가 적절하지 않았다는 거네요

그럼 꺼낸 bin을 처리하는 과정을 거쳐야합니다.

small size인지 large size인지 구분해서 처리합니다.


          if (in_smallbin_range (size))

            {

              victim_index = smallbin_index (size);

              bck = bin_at (av, victim_index);

              fwd = bck->fd;

            }

          else

            {

              victim_index = largebin_index (size);

              bck = bin_at (av, victim_index);

              fwd = bck->fd;


              /* maintain large bins in sorted order */

              if (fwd != bck)

                {

                  /* Or with inuse bit to speed comparisons */

                  size |= PREV_INUSE;

                  /* if smaller than smallest, bypass loop below */

                  assert ((bck->bk->size & NON_MAIN_ARENA) == 0);

                  if ((unsigned long) (size) < (unsigned long) (bck->bk->size))

                    {

                      fwd = bck;

                      bck = bck->bk;


                      victim->fd_nextsize = fwd->fd;

                      victim->bk_nextsize = fwd->fd->bk_nextsize;

                      fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;

                    }

                  else

                    {

                      assert ((fwd->size & NON_MAIN_ARENA) == 0);

                      while ((unsigned long) size < fwd->size)

                        {

                          fwd = fwd->fd_nextsize;

                          assert ((fwd->size & NON_MAIN_ARENA) == 0);

                        }


                      if ((unsigned long) size == (unsigned long) fwd->size)

                        /* Always insert in the second position.  */

                        fwd = fwd->fd;

                      else

                        {

                          victim->fd_nextsize = fwd;

                          victim->bk_nextsize = fwd->bk_nextsize;

                          fwd->bk_nextsize = victim;

                          victim->bk_nextsize->fd_nextsize = victim;

                        }

                      bck = fwd->bk;

                    }

                }

              else

                victim->fd_nextsize = victim->bk_nextsize = victim;

            }


          mark_bin (av, victim_index);

          victim->bk = bck;

          victim->fd = fwd;

          fwd->bk = victim;

          bck->fd = victim;


#define MAX_ITERS       10000

          if (++iters >= MAX_ITERS)

            break;

        }

-------------------------

자 여기 까지가 unsorted bin을 꺼내보는 과정입니다.

여기까지 왔으면 unsorted bin은 비워져 있고 꺼내진 bin들은 자기 size들에 맞게 small이나 large bin으로 들어갔을 겁니다. 아쉽게도 적절한 size를 찾지 못했군요


large size요청이라면 현재 bin들 중에서 요청 size에 적합한 가장 작은 bin을 꺼냅니다. 


  1. 여기까지 왔다면 해당하는 bin 내에서 적당한 chunk를 찾지 못한 것이다. idx 값을 하나 증가시킨 후 더 큰 크기의 bin 내에 free chunk가 있는지 검사한다. (이는 bitmap을 통해 빨리 확인할 수 있다.)
    1.  현재 인덱스에 해당하는 bitmap을 검사하여 free chunk가 있는지 확인한다. 만약 해당 bin이 비어있다면 인덱스를 하나 증가시킨 후 검사를 다시한다. 모든 bitmap을 검사했다면 8번 과정으로 넘어간다.
    2.  bitmap이 설정된 bin이 있다면 해당 bin 내의 (가장 작은 크기의) 가장 오래된 chunk를 victim 지역 변수에 저장한다.
    3.  victim을 리스트에서 분리한다.
    4.  victim의 크기가 요청을 처리하고도 다른 chunk를 구성할 수 있을 정도로 크다면 분할하여 나머지 영역을 chunk로 만들어서 unsorted bin에 추가한다. 나머지 영역의 크기가 small bin에 속한다면 last_remainder 변수가 나머지 영역을 가리키도록 설정한다.
    5.  victim을 반환한다. (종료)



      if (!in_smallbin_range (nb))

        {

          bin = bin_at (av, idx);


          /* skip scan if empty or largest chunk is too small */

          if ((victim = first (bin)) != bin &&

              (unsigned long) (victim->size) >= (unsigned long) (nb))

            {

              victim = victim->bk_nextsize;

              while (((unsigned long) (size = chunksize (victim)) <

                      (unsigned long) (nb)))

                victim = victim->bk_nextsize;


              /* Avoid removing the first entry for a size so that the skip

                 list does not have to be rerouted.  */

              if (victim != last (bin) && victim->size == victim->fd->size)

                victim = victim->fd;


              remainder_size = size - nb;

              unlink (av, victim, bck, fwd);


unlink가 나왔네요 알다싶이 unlink는 중요한 exploit target이죠. largebin이나 smallbin은 fd bk 포인터를 가지고 있어서 unlink가 필요합니다.

쉽게 말하면 bin들은 fwd bck포인터로 연결되어 있는데요(double linked list형태죠) 어떤 bin을 할당하게 되면 연결되어있던 고리가 끊어지겠죠? 이를 다시 연결하는 함수가 unlink입니다.

unlink는 malloc.c의 가장 초반에 나오는데요 꼭 이해하시길 바랍니다.


아래는 remainder를 처리해주는 부분입니다.

              /* Exhaust */

              if (remainder_size < MINSIZE)

                {

                  set_inuse_bit_at_offset (victim, size);

                  if (av != &main_arena)

                    victim->size |= NON_MAIN_ARENA;

                }

              /* Split */

              else

                {

                  remainder = chunk_at_offset (victim, nb);

                  /* We cannot assume the unsorted list is empty and therefore

                     have to perform a complete insert here.  */

                  bck = unsorted_chunks (av);

                  fwd = bck->fd;

 if (__glibc_unlikely (fwd->bk != bck))

                    {

                      errstr = "malloc(): corrupted unsorted chunks";

                      goto errout;

                    }

                  remainder->bk = bck;

                  remainder->fd = fwd;

                  bck->fd = remainder;

                  fwd->bk = remainder;

                  if (!in_smallbin_range (remainder_size))

                    {

                      remainder->fd_nextsize = NULL;

                      remainder->bk_nextsize = NULL;

                    }

                  set_head (victim, nb | PREV_INUSE |

                            (av != &main_arena ? NON_MAIN_ARENA : 0));

                  set_head (remainder, remainder_size | PREV_INUSE);

                  set_foot (remainder, remainder_size);

                }

              check_malloced_chunk (av, victim, nb);

              void *p = chunk2mem (victim);

              alloc_perturb (p, bytes);

              return p;

            }

        }

---------------------------------------

      /*

         Search for a chunk by scanning bins, starting with next largest

         bin. This search is strictly by best-fit; i.e., the smallest

         (with ties going to approximately the least recently used) chunk

         that fits is selected.


         The bitmap avoids needing to check that most blocks are nonempty.

         The particular case of skipping all bins during warm-up phases

         when no chunks have been returned yet is faster than it might look.

       */

 size가 큰쪽부터 request size보다는 큰 최소의 bin을 찾습니다.

여기서 bitmap을 이용합니다. 영여주석을 따로 지우지 않겠습니다. 한번 읽어보세요


      ++idx;

      bin = bin_at (av, idx);

      block = idx2block (idx);

      map = av->binmap[block];

      bit = idx2bit (idx);


      for (;; )

        {

          /* Skip rest of block if there are no more set bits in this block.  */

          if (bit > map || bit == 0)

            {

              do

                {

                  if (++block >= BINMAPSIZE) /* out of bins */

                    goto use_top;

                }

              while ((map = av->binmap[block]) == 0);


              bin = bin_at (av, (block << BINMAPSHIFT));

              bit = 1;

            }


          /* Advance to bin with set bit. There must be one. */

          while ((bit & map) == 0)

            {

              bin = next_bin (bin);

              bit <<= 1;

              assert (bit != 0);

            }


          /* Inspect the bin. It is likely to be non-empty */

          victim = last (bin);


          /*  If a false alarm (empty bin), clear the bit. */

          if (victim == bin)

            {

              av->binmap[block] = map &= ~bit; /* Write through */

              bin = next_bin (bin);

              bit <<= 1;

            }


          else

            {

              size = chunksize (victim);


              /*  We know the first chunk in this bin is big enough to use. */

              assert ((unsigned long) (size) >= (unsigned long) (nb));


              remainder_size = size - nb;


              /* unlink */

              unlink (av, victim, bck, fwd);


              /* Exhaust */

              if (remainder_size < MINSIZE)

                {

                  set_inuse_bit_at_offset (victim, size);

                  if (av != &main_arena)

                    victim->size |= NON_MAIN_ARENA;

                }


              /* Split */

              else

                {

                  remainder = chunk_at_offset (victim, nb);


                  /* We cannot assume the unsorted list is empty and therefore

                     have to perform a complete insert here.  */

                  bck = unsorted_chunks (av);

                  fwd = bck->fd;

 if (__glibc_unlikely (fwd->bk != bck))

                    {

                      errstr = "malloc(): corrupted unsorted chunks 2";

                      goto errout;

                    }

                  remainder->bk = bck;

                  remainder->fd = fwd;

                  bck->fd = remainder;

                  fwd->bk = remainder;


                  /* advertise as last remainder */

                  if (in_smallbin_range (nb))

                    av->last_remainder = remainder;

                  if (!in_smallbin_range (remainder_size))

                    {

                      remainder->fd_nextsize = NULL;

                      remainder->bk_nextsize = NULL;

                    }

                  set_head (victim, nb | PREV_INUSE |

                            (av != &main_arena ? NON_MAIN_ARENA : 0));

                  set_head (remainder, remainder_size | PREV_INUSE);

                  set_foot (remainder, remainder_size);

                }

              check_malloced_chunk (av, victim, nb);

              void *p = chunk2mem (victim);

              alloc_perturb (p, bytes);

              return p;

            }

        }

-------------------------------------

결국 bin에는 쓸만한게 없었습니다.

이제는 bin에서 할당할게 아니라 아예 동적할당영역의 끝부분에서새롭게 할당해 줄겁니다. 


  1. 여기까지 왔다면 bin 내에 모든 chunk가 비어있거나 요청을 만족할 수 없는 상황이다. 이제 top chunk를 사용한다.
    1.  만약 top chunk의 크기가 주어진 요청을 만족하고도 새로운 chunk를 만들 수 있는 크기라면 분할하여 victim을 반환하고 나머지 영역을 top chunk로 설정한다. (종료)
    2.  그렇지 않고 주어진 크기가 small bin 영역에 속한다면 fast bin이 남아있을 것이다. fast bin을 병합한 후 다시 2번 과정으로 돌아가서 할당을 시도한다.
    3.  그렇지 않다면 시스템의 heap 영역을 늘려야 한다. 이는 sYSMALLOc() 함수가 처리하며, 이 함수의 반환값을 반환하고 종료한다.


    use_top:

      /*

         If large enough, split off the chunk bordering the end of memory

         (held in av->top). Note that this is in accord with the best-fit

         search rule.  In effect, av->top is treated as larger (and thus

         less well fitting) than any other available chunk since it can

         be extended to be as large as necessary (up to system

         limitations).


         We require that av->top always exists (i.e., has size >=

         MINSIZE) after initialization, so if it would otherwise be

         exhausted by current request, it is replenished. (The main

         reason for ensuring it exists is that we may need MINSIZE space

         to put in fenceposts in sysmalloc.)

       */


      victim = av->top;

      size = chunksize (victim);


      if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))

        {

          remainder_size = size - nb;

          remainder = chunk_at_offset (victim, nb);

          av->top = remainder;

          set_head (victim, nb | PREV_INUSE |

                    (av != &main_arena ? NON_MAIN_ARENA : 0));

          set_head (remainder, remainder_size | PREV_INUSE);


          check_malloced_chunk (av, victim, nb);

          void *p = chunk2mem (victim);

          alloc_perturb (p, bytes);

          return p;

        }


      /* When we are using atomic ops to free fast chunks we can get

         here for all block sizes.  */

      else if (have_fastchunks (av))

        {

          malloc_consolidate (av);

          /* restore original bin index */

          if (in_smallbin_range (nb))

            idx = smallbin_index (nb);

          else

            idx = largebin_index (nb);

        }


      /*

         Otherwise, relay to handle system-dependent cases

       */

      else

        {

          void *p = sysmalloc (nb, av);

          if (p != NULL)

            alloc_perturb (p, bytes);

          return p;

        }

    }

}


    1.  


'시스템 > 시스템 해킹' 카테고리의 다른 글

ulimit  (0) 2017.02.19
glibc malloc.c의 free함수 분석 약간의 exploit관점(작성중)  (0) 2017.02.09
버퍼오버플로우를 시작하며  (1) 2017.02.05
별건 아니고 malloc.c  (0) 2017.01.30
angr 정리  (0) 2017.01.25

처음 버퍼오버플로우라는 개념을 알았을때는 너무 쉽지만 힘이없어보였다.


처음에 나는 이 버퍼오버플로우가 변수의 값을 조작하는 용도로만 생각했기 때문이다.

지금은 그 힘과 영향력에 대해서 조금씩 알아가고 있는중이다.



버퍼 오버플로(영어: buffer overflow) 또는 버퍼 오버런(buffer overrun)은 메모리를 다루는 데에 오류가 발생하여 잘못된 동작을 하는 프로그램 취약점이다.


버퍼오버 플로우의 정의는 다음과 같다.


처음보는 사람들은 감의 안 올 것이다.


버퍼오버플로우를 공부하기 위해선 메모리 구조부터 공부해야한다.


프로그래밍한 결과가 변수들이 메모리에 어떤 순서로 어떤 방식으로 올라가는지 알아야 한다.


hackerschool.org 라는 사이트에서 버퍼오버플로우의 기본에 대해서 배울 수 있다.


버퍼오버플로우의 기초에 대해서 알았다면


쉘에 대해 알아야 한다.

버퍼오버플로우를 이용하면 자신의 권한보다 높아진 상태의 쉘을 획득할 수 있다.


즉 쉘을 이용하면 권한이 낮은 상태로 시스템에 들어가 자신의 권한을 높일 수 있다.


이렇게 자신의 권한을 루트로 만들면 백도어를 만드는 것으로 마무리를 한다.


이제 자신이 루트가 된 상황에서 외부인이 루트권한을 갖게 하는 파일을 만들 수 있다.

그 파일을 만들어 놓고 다시 시스템에 침입할때 실행 시키면 쉽게 다시 루트권한을 딸수 있는 것이다.


지금까지 버퍼오버플로우와 시스템해킹에 관한 간단한 개요를 작성하였다.

큰그림을 그리는데 도움이 되었으면 한다.

malloc.c

틈틈이 분석해야지

'시스템 > 시스템 해킹' 카테고리의 다른 글

glibc malloc.c의 malloc함수 분석 약간의 exploit관점  (0) 2017.02.09
버퍼오버플로우를 시작하며  (1) 2017.02.05
angr 정리  (0) 2017.01.25
pwndbg.  (0) 2017.01.19
메모리 보호기법 RELRO  (0) 2017.01.10

shellfish 팀에서 만든 파이썬 모듈이다.


https://docs.angr.io


활성화

mkvirtualenv angr


dist-package 추가하는법

vi ~/.bashrc

에서

export PYTHONPATH=$PYTHONPATH:/usr/lib/python2.7/dist-packages:/usr/local/lib/python2.7/dist-packages

한줄 추가


egoist님의 문서

angr.pdf


공식문서

angr.pdf



ex)


import angr

b = angr.Project(“./f158d82c3a24c9de9e560713327b9c7e”)
s = b.factory.blank_state(addr=0x400CBB)
v = s.se.BVS(‘key’, 15*8)
s.memory.store( 0x51410000, v )
s.regs.rdi=0x51410000

initpath = b.factory.path(state=s)
ex = b.surveyors.Explorer( start=initpath, find=(0x400E5C))
ex.run()

print ex.found[0].state.se.any_str( ex.found[0].state.memory.load(0x51410000, 13))


'시스템 > 시스템 해킹' 카테고리의 다른 글

버퍼오버플로우를 시작하며  (1) 2017.02.05
별건 아니고 malloc.c  (0) 2017.01.30
pwndbg.  (0) 2017.01.19
메모리 보호기법 RELRO  (0) 2017.01.10
ascii shellcode 사이트  (0) 2016.12.23

https://github.com/pwndbg/pwndbg/blob/master/FEATURES.md


heap 문제 풀때

특히나 좋은 것 같다

일단 peda에서 갈아탐 ㅎㅎ..



'시스템 > 시스템 해킹' 카테고리의 다른 글

별건 아니고 malloc.c  (0) 2017.01.30
angr 정리  (0) 2017.01.25
메모리 보호기법 RELRO  (0) 2017.01.10
ascii shellcode 사이트  (0) 2016.12.23
arm 문법 블로그, 자료, arm ROP  (0) 2016.12.10

full relro : .ctors, .dtors, .jcr, .dynamic, .got 영역을 read only 상태로 만든다(elf 실행시 got에 libc주소 바인딩)

partitial relro : got영역 writable   



'시스템 > 시스템 해킹' 카테고리의 다른 글

angr 정리  (0) 2017.01.25
pwndbg.  (0) 2017.01.19
ascii shellcode 사이트  (0) 2016.12.23
arm 문법 블로그, 자료, arm ROP  (0) 2016.12.10
(문서)SROP-null0  (0) 2016.12.09

+ Recent posts