pwn入門之棧溢出練習


本文原創作者:W1ngs,本文屬i春秋原創獎勵計划,未經許可禁止轉載!
前言:最近在入門pwn的棧溢出,做了一下jarvisoj里的一些ctf pwn題,感覺質量都很不錯,難度循序漸進,把自己做題的思路和心得記錄了一下,希望能給入門pwn的朋友帶來點幫助和啟發,大牛輕噴

題目鏈接:https://www.jarvisoj.com/challenges
1、level0(64位)
代碼
<ignore_js_op><ignore_js_op> 
也就是輸出Hello World以后接收一個輸出,仔細看會發現一個system函數
<ignore_js_op> 
那么思路就很清晰,在read函數接收輸入的時候直接覆蓋返回地址為system函數即可

在IDA中可以看到,buf距離EBP為0x80,但是這個是64位的程序,一個EBP占8bytes,那么payload:

[AppleScript]  純文本查看 復制代碼
?
1
payload = 'a' * 0 x 80 + 'a' * 8 ( ebp的地址 ) + p 32 ( system函數的地址 )

 

  • p32也就是pack32,把地址的hex值轉化為32小端地址存儲的方法進行發送


腳本如下:
<ignore_js_op> 
運行腳本得到shell
<ignore_js_op> 

2、level1

0x00 代碼
    main函數
<ignore_js_op> 
   vulnerable_function函數
<ignore_js_op> 
可以看到buf的偏移是0x88,那么需要的填充:'a'*0x88+'a'*4(EBP)+返回地址
0x01 第一種解法

使用pwntools自帶的工具checksec查看程序的保護機制,發現是NX disabled,可以直接往棧上寫匯編語句來執行來達到溢出的目的


基本思路:這里printf給出了buf的首地址,所以可以填充返回地址為接收到的buf的首地址,用asm模塊向上面寫數據,這個也是pwntools自帶的模塊
使用方法:

[AppleScript]  純文本查看 復制代碼
?
1
asm ( shellcraft.sh ( ) )


運行以后可以看到,打印出來的地址的值就是buf的首地址,我們用pwntools接收
<ignore_js_op> 

  • 這里接收到的buf_addr一定要進行int16進制的轉換( int(buf,16) ),才能用p32封包進行發送




最后的腳本:
<ignore_js_op> 
E:/%E6%9C%89%E9%81%93/qq987FE2B65EC068C281254890044944CD/f76f4bc855a34842a1163a96af15d769/clipboard.png

<ignore_js_op> 

0x02 第二種解法
    這種方法算是一個通法,也就是使用dynelf的方法來動態的泄露system函數的地址,這里先記住這個方法,后面還會說到

dynelf使用的條件:棧溢出、有輸入輸出函數(gets、puts、read、write)、無PIE保護


這里在第一輸入點進行棧溢出,leak出system函數的地址並返回主函數
<ignore_js_op> 
這里的leak函數計算出libc的基地址,並查找libc庫來泄露system函數的地址
調用read函數向bss可寫段寫入/bin/sh字符串,接着調用system拿到shell
<ignore_js_op> 

  • 這里使用gdb的vmmap的方法找到可寫段,使用后面的數據段不容易出錯


<ignore_js_op> 

3、level2
main函數
<ignore_js_op> 
   vulnerable_function函數
<ignore_js_op> 
存在棧溢出,而且可以直接調用system函數,搜索字符串還可以發現有/bin/sh字符串
<ignore_js_op> 
思路:通過溢出到返回地址修改為system函數的地址,參數為/bin/sh字符串
此時棧的分布情況:
<ignore_js_op> 


最后的腳本:
<ignore_js_op> 
payload = 'a' *0x88 + 'a'*4 + p32(system_addr) + p32(1) + p32(bin_addr)

  • p32(1)為無效的填充的返回地址,這里我們調用完system函數就行了,不用管這個地址
  • 這里使用elf的search方法來搜索字符串,next方法取到第一個匹配到的字符串



這里的ELF方法可以打印輸出基本的plt、got表中函數的地址,就不需要我們手動進行查找了,具體的可以看下面的文章
https://blog.csdn.net/weixin_41400278/article/details/78819950


   這里也可以通過read函數來向可寫段寫入/bin/sh字符串,思路就是先調用read函數寫入字符,之后返回主函數,再向棧內寫入system函數,這是在沒有/bin/sh字符串的時候使用。在這里就顯得多此一舉了
<ignore_js_op> 

4、level3
代碼:
<ignore_js_op> 
<ignore_js_op> 

這里有read、write函數,但是沒有system函數,這題還給了libc庫,所以這題就是使用比較標准的got表泄露system函數的方法來getshell
<ignore_js_op> 
思路:使用write函數將got表中write地址作為參數,調用plt表中的write函數,得到基地址后加上system函數在程序中的偏移,就得到了system函數的實際地址,進行調用即可
(1)、使用ELF函數獲取plt、got表的地址
<ignore_js_op> 
(2)、構造payload
<ignore_js_op> 


因為這里的plt_write的地址是將jmp語句傳入棧中,后面還傳入了三個參數,所以為了平衡棧需要pop三次(使用ROPgadget查找,rop方法的一種)
https://blog.csdn.net/weixin_40850881/article/details/80216764


在qira中看到的情況是這樣的
<ignore_js_op> 
(3)、減去write函數在libc庫中的偏移得到基地址
<ignore_js_op> 


write函數的偏移地址的查找:
先使用ldd命令找到libc庫的名字
<ignore_js_op> 


然后用readelf方法找到函數的相對偏移位置
<ignore_js_op> 
(4)、構造system函數並調用
payload1: payload = "A" * 0x88 + "A" * 4 + p32(plt_write) + p32(main_addr) + p32(1) + p32(elf.got["write"]) + p32(4)


也就是先調用write(1,write函數地址,4),第一個參數文件描述符等於1表示輸出,4代表輸出的長度,中間的就是輸出的內容,調用完成后返回主函數,得到基地址后為泄露system函數的地址做准備


先leak出write函數的libc地址,從而計算出libc的基地址,sendline之后返回主函數
<ignore_js_op> 

<ignore_js_op> 
leak出system函數的地址
<ignore_js_op> 


運行腳本的結果:
<ignore_js_op> 
在input輸入后調用system函數
payload2 = "A" * 0x88 + "A" * 4 + p32(system_addr) + p32(1) + p32(binsh_addr)


找到字符串/bin/sh在libc庫中的位置(注意這里是偏移地址),可以在IDA中直接找到,也可以用上面說的search方法
<ignore_js_op> 
<ignore_js_op> 


根據libc的基地址,得到system函數的地址,/bin/sh字符串的地址,sendline出payload以后就會執行system函數
<ignore_js_op> 

  • 這里最后用sleep函數暫停一下


運行腳本getshell
<ignore_js_op> 


0x03 level4
代碼
<ignore_js_op> 
<ignore_js_op> 

思路:這題和上面的level3幾乎一樣,但是這道題沒有給出libc庫,所以這里使用的是pwntools工具包里的DynELF,利用DynELF工具來泄漏system函數的地址,再往bss段寫入"/bin/sh"字符串,接着調用system函數
具體可以看下這里:
https://blog.csdn.net/guiguzi5512407/article/details/52752909

dynelf : 用於遠程符號泄漏,需要提供leak方法,簡單來說就是將原來需要輸出的got表的地址處替換成了需要尋找的system函數的地址
<ignore_js_op> 
按照前面的思路,正常的寫法是先leak出write函數的地址
<ignore_js_op> 
得到的write地址:0xf76da900
<ignore_js_op> 


使用leak方法泄露出system函數的地址,腳本會從github上下載相應的libc庫
<ignore_js_op> 
system函數的地址:0xf75a2e70


<ignore_js_op> 
調用read函數向bss段填充/bin/sh字符串,這里為了不影響程序的正常執行
<ignore_js_op> 
   這里為了平衡棧(不影響程序的正常執行)需要pop三個參數,再傳入read的三個參數:0,/bin/sh字符串寫入的地址,8(/bin/sh的長度,末尾為\x00
   接着直接調用system函數,傳入的參數為/bin/sh字符串的地址,發送payload之后,sendline內容為"/bin/sh"的字符串


運行腳本拿到shell
<ignore_js_op> 

0x04 結束語
    所有的腳本下載:https://github.com/H4lo/jarvisoj_pwn
  這幾道題雖然算是ROP的入門題,但是正常的解法也就是這些方法,再掌握一些進階的ROP技巧,比如繞過canary保護之類的,再加上多刷點題,那么CTF比賽中的棧溢出幾乎就沒有問題了。
  總的來說就是多刷題,在刷題中學習思路和技巧,但是最基本的pwn知識要掌握,比如棧的結構,基本的程序運行機制和流程,最后再推薦幾個學習和刷題的網站:

http://www.whaledu.com
http://pwnable.kr

大家有任何問題可以提問,更多文章可到i春秋論壇閱讀喲~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM