Linux pwn入門教程之環境配置

  • 2019 年 10 月 25 日
  • 筆記

作者:Sp4ce 轉自 | i春秋社區 前言

作為一個畢業一年多的辣雞CTF選手,一直苦於pwn題目的入門難,入了門更難的問題。本來網上關於pwn的資料就比較零散,而且經常會碰到師傅們堪比解題過程略的writeup和沒有注釋,存在大量硬編碼偏移的腳本,還有練習題目難找,調試環境難搭建,GDB沒有IDA好操作等等問題。作為一個老萌新(霧),決定依據Atum師傅在i春秋上的pwn入門課程中的技術分類,結合近幾年賽事中出現的一些題目和文章整理出一份自己心目中相對完整的Linux pwn教程。

本系列教程僅針對i386/amd64下的Linux pwn常見的pwn手法,如棧,堆,整數溢出,格式化字元串,條件競爭等進行介紹。為了方便和我一樣的萌新們進行學習,所有環境都會封裝在docker鏡像當中,並提供調試用的教學程式,來自歷年賽事的原題和帶有注釋的python腳本。教程歡迎各位師傅吐槽,若對題目和腳本的使用有不妥之處,會在當事師傅回饋之後致歉並應要求進行處理。

docker容器的使用與簡單操作

在搭建環境之前我們需要準備一個裝有docker的64位Linux系統,內核版本高於3.10(可以通過uname -r查看),可以運行在實體機或者是虛擬機中。關於docker的安裝與啟動此處不再贅述,讀者可以根據自己的Linux發行版本自行搜索。此處提供兩個鏈接,供Ubuntu和Kali使用者參考:

Kali:《kali Rolling安裝docker》http://www.cnblogs.com/Roachs/p/6308896.html Ubuntu:《Ubuntu 16.04安裝Docker》http://blog.csdn.net/qq_27818541/article/details/73647797

在成功安裝了docker並驗證其可用性後,我們就可以訂製自己的實驗用容器了。這部分內容可以在各個地方找到教程,且與pwn的學習不相關,此處不再贅述。為了方便實驗,我把實驗環境打包成了幾個容器快照,可以直接導入成鏡像使用。

以ubuntu.17.04.amd64為例,導入的命令為

cat ubuntu.17.04.amd64 | docker import – ubuntu/17.04.amd64

導入成功後使用命令docker images會看到鏡像倉庫中出現了一個新的鏡像。

運行docker run -it -p 23946:23946 ubuntu/17.04.amd64 /bin/bash

就可以以這個鏡像創建一個容器,開啟一個shell,並且將IDA調試伺服器監聽的23946埠轉發到本地的23946埠。

通過命令docker container ls -a 我們發現容器列表裡多了一個剛剛創建的容器,並且被賦予了一個隨機的名字,在我的實驗中它是nostalgic_raman。

我們可以通過命令

docker container rename nostalgic_raman ubuntu.17.04.amd64

把這個容器重命名為ubuntu.17.04.amd64或者其他你認為合適的名字。

使用

docker exec -it ubuntu.17.04.amd64 /bin/bash

我們可以打開目標容器的一個新的bash shell。這使得我們在後續的調試中可以在容器中啟動IDA調試伺服器並用socat部署pwn題目。

此外,可以使用docker container cp命令在docker容器內外雙向傳輸文件等等。需要注意的是,對容器的各種操作需要在容器運行時進行,若容器尚未運行(運行docker container ls未顯示對應容器),需使用命令docker start運行對應容器。此外,若同時運行多個容器,為了避免埠衝突,在啟動容器時,可以將命令docker run -it -p 23946:23946 ubuntu/17.04.amd64 /bin/bash 中的第一個埠號23946改為其他數字。

IDA的簡單使用及遠程調試配置

成功搭建了docker環境之後,我們接下來熟悉一下IDA和IDA的遠程調試環境搭建。首先我們在IDA所在的文件夾的dbgsrv文件夾下找到需要的調試伺服器linux_server(32位)和linux_serverx64(64位)並複製到kali中。

然後使用命令

dockercontainercplinux_server ubuntu.17.04.i386:/root/linux_server

將linux_server複製到32位容器中的/root目錄下。此時我們登錄容器可以看到linux_server,運行該server會提示正在監聽23946埠。

接著我們打開32位的ida,載入一個後面會用於演示堆漏洞的程式heapTest_x86,在左側的Functions window中找到main函數,隨便挑一行程式碼按F2下一個斷點。然後通過Debugger->Process options…打開選項窗口設置遠程調試選項。

在彈出的選項窗口中配置Hostname為kali的ip地址,Port為容器映射到kali中的埠。

填好後點擊OK,按快捷鍵F9運行程式。若連接正常可能提示Input file is missing:xxxxx,一路OK就行,IDA會將被調試的文件複製到伺服器所在目錄下,然後彙編程式碼所在窗口背景會變成淺藍色並且窗口布局發生變化。若IDA僵死一段時間後跳出Warning窗口,則需要檢查IDA所在機器與kali是否能ping通,容器對應埠是否映射,參數是否填錯等問題。

調試器連接成功後我們就可以使用各種快捷鍵對目標程式進行調試,常用的快捷鍵有 下斷點/取消斷點 F2,運行程式F9,單步跨過函數F8,單步進入函數F7,運行到選中位置F4等等。在調試模式下主要使用到的窗口有彙編窗口 IDA View-EIP,暫存器窗口General registers,棧窗口Stack view,記憶體窗口Hex View,系統日誌窗口Output window等。

切回到kali,我們會看到隨著程式運行,運行調試伺服器的shell窗口會顯示出新的內容

當IDA中的程式執行完

call ___isoc99_scanf

或者類似的等待輸入的指令後會陷入阻塞狀態,F4,F7,F8,F9等和運行相關的快捷鍵都不生效。此時我們可以在shell中輸入內容,IDA中的程式即可恢復執行。

使用pwntools和IDA調試程式

在上一節中我們嘗試了使用IDA配置遠程調試,但是在調試中我們可能會有一些特殊的需求,比如自動化完成一些操作或者向程式傳遞一些包含不可見字元的地址,如x50x83x04x08(0x08048350)。這個時候我們就需要使用腳本來完成此類操作。我們選用的是著名的python庫pwntools。 pwntools庫可以使用pip進行安裝,其官方文檔地址為http://docs.pwntools.com/en/stable/ 。在本節中我們將使用pwntools和IDA配合調試程式。

首先我們在kali中安裝pwntools,安裝完成後輸入python進入python環境,使用from pwn import * 導入pwntools庫。

使用docker exec在32位的容器中新開一個bash shell,跳轉到heapTest_x86所在目錄/root,查看容器的IP地址,然後執行命令

socattcp-listen:10001,reuseaddr,fork EXEC:./heapTest_x86,pty,raw,echo=0

將heapTest_x86的IO轉發到10001埠上。

我們可以看到我的容器中的IP地址是172.17.0.2。回到python中,使用io = remote("172.17.0.2", 10001)打開與heapTest_x86的連接。

這個時候我們返回到IDA中設置斷點。需要注意的是此時heapTest_x86已經開始運行,我們的目標是附加到其運行的進程上,所以我們需要把斷點設置在call ___isoc99_scanf等等待輸入的指令運行順序之後,否則由於電腦的運行速度,我們的斷點將會因為已經目標指令已經執行完而失效,達不到斷下來的效果。

選擇Debugger->Attach to process…,附加到./heapTest_x86的進程上。

此時EIP將指向vdso中的pop ebp指令上。

這幾行指令實際上是執行完sys_read後的指令,此處我們不需要關心它,直接按F9,選中標誌會消失。

回到python窗口,我們使用pwntools的recv/send函數族來與運行中的heapTest_x86進行交互。首先輸入io.recv(),我們發現原先會在shell窗口出現的菜單被讀出到python窗口裡了。

同樣的,我們通過io.send()也可以向這個進程傳遞輸入。我們使用io.send('1')告訴這個進程我們要選擇選項1。這個時候我們切換到IDA窗口,發現IDA還是處於掛起狀態,這是為什麼呢?

回想一下我們通過shell與這個進程交互的時候,輸入選項後需要按回車鍵以「告訴」這個進程我們的輸入結束了。那麼在這裡我們同樣需要再發送一個回車,所以我們再執行io.send('n'),切換到IDA窗口就會發現EIP停在了熟悉的程式領空。這時候我們再使用IDA的快捷鍵就可以進行調試,隨心所欲地觀察進程的記憶體,棧,暫存器等的狀態了。當然,我們也可以直接使用io.sendline(),就可以直接在輸入的結尾自動加上'n'了。

在上圖的狀態中,我們在python中再次輸入io.recv(),發現並沒有讀取到輸出,並且python處於阻塞狀態。這是因為程式此時沒有輸出可讀取。我們在IDA中按F8到call mallocChunk一行,此時按F7進入函數,在函數中運行到call _fflush的下一行,就會發現python的阻塞狀態解除了。

當我們希望結束調試時,應該使用io.close()關閉掉這個io。否則下一次試圖attach時會發現有兩個./heapTest_x86進程。在IDA中按Ctrl+F2即可退出調試模式。