物联网中的 ARM 漏洞利用
發(fā)文動(dòng)機(jī)
幾周前我參加某個(gè)會(huì)議的時(shí)候,有個(gè)“物聯(lián)網(wǎng)上的 ARM 漏洞利用課程”的議題我覺(jué)得很多干貨,我也決定自己寫(xiě)一篇,給去不了現(xiàn)場(chǎng)的同學(xué)發(fā)些福利。我打算分為三個(gè)部分來(lái)寫(xiě)。
當(dāng)然我的文章沒(méi)辦法和現(xiàn)場(chǎng)的 course 相比,我還是想為大家做一些微小的工作。
這三個(gè)部分是:
-
第一部分:逆向 ARM 應(yīng)用
-
第二部分:編寫(xiě) ARM shellcode
-
第三部分:ARM 漏洞利用
一、逆向ARM應(yīng)用
-
環(huán)境:
樹(shù)莓派3——我選了這個(gè)又便宜又好配置的環(huán)境,Android 也是個(gè)不錯(cuò)的選擇。
-
硬件:
具體型號(hào):>樹(shù)莓派3、型號(hào)B、ARM-Cortex-A53架構(gòu)
-
軟件
這是些軟件信息,接下來(lái)三部分都會(huì)用到。
操作系統(tǒng)的安裝請(qǐng)看:https://www.raspberrypi.org/documentation/installation/installing-images/linux.md
配置遠(yuǎn)程 ssh 請(qǐng)看:https://www.raspberrypi.org/documentation/remote-access/ssh/
? 編譯器
我們用到的所有 C、C++、匯編代碼都會(huì)用樹(shù)莓派自帶的 GCC 編譯器。
版本如下:
還有一點(diǎn)是 GCC 的匯編指令和其他的編譯器不同,最好先看一下這些指令:http://www.ic.unicamp.br/~celio/mc404-2014/docs/gnu-arm-directives.pdf
? 源碼
本部分我用到的源碼都放在這里了:https://github.com/invictus1306/ARM-episodes/tree/master/Episode1
??編譯選項(xiàng)
這一節(jié)我會(huì)介紹三個(gè)選項(xiàng),并附帶例子。
這是用到的源碼:
??調(diào)試符號(hào)?
-g 選項(xiàng)會(huì)在編譯時(shí)向可執(zhí)行文件中加入調(diào)試信息(符號(hào)表)。
編譯帶 -g 和不帶 -g 選項(xiàng)的兩個(gè) ELF 文件,比較大小:
第二個(gè)文件更大,意味著它被加入了其他的信息。用 readelf 命令的 -S 選項(xiàng)(查看節(jié)頭)查看其調(diào)試信息:
這些調(diào)試信息以 GCC 默認(rèn)的 DWARF 格式存儲(chǔ)。用 objdump 查看:
去除符號(hào)表和重定位信息
選項(xiàng)為 -s。
可以看到 .symtab 有很多本地符號(hào),這些符號(hào)運(yùn)行時(shí)并不需要,可以把它們?nèi)サ簟?/span>
用過(guò) -s 選項(xiàng)后,函數(shù)名之類(lèi)的信息已經(jīng)去掉了,某個(gè)逆向小子的生活又艱難了一步。
在之后的第三部分我會(huì)介紹其他更復(fù)雜的編譯選項(xiàng)。
ARM Hello World
我們以?xún)煞N方式開(kāi)始這個(gè) hello world 的研究:
1. 使用樹(shù)莓派系統(tǒng)調(diào)用
2. 使用 libc 函數(shù)
? 使用樹(shù)莓派的系統(tǒng)調(diào)用
匯編并鏈接此程序:
注意:如果用 gcc,
會(huì)得到錯(cuò)誤:
這是因?yàn)樵闯绦蚶餂](méi)有 main 函數(shù),在另一種實(shí)現(xiàn)里我們會(huì)看到如何使用 gcc 編譯。
執(zhí)行:
接下來(lái)使用 gdb:
可以看到 .text 段中放著我們的代碼。0x10078 處的指令代表將 0x10090 指向的值載入 r1。
? 使用 libc 函數(shù)
這次我們會(huì)使用 printf 函數(shù),這里我們將程序中的 .global _start 改為 ?.global main。
編譯器需要我們指定 global main, .func main, main: 等符號(hào)告訴 libc main 函數(shù)在哪。
匯編器和鏈接器只是 GCC 的一小部分,下面我們會(huì)用到 GCC 其他的特性來(lái)編譯。
可以看到在進(jìn)程的地址空間里加載了 libc 共享庫(kù)(libc-2.19.so)。
0x10428 處調(diào)用了 printf 函數(shù),0x10428 是個(gè) PLT 入口,指向 GOT 中 printf 函數(shù)在運(yùn)行時(shí)的真實(shí)地址。
GCC 編譯時(shí),libc 的函數(shù)并沒(méi)有被編譯到可執(zhí)行文件中,而是通過(guò)動(dòng)態(tài)鏈接到 libc 使之可用。用 ldd 命令查看程序引用的動(dòng)態(tài)庫(kù)。
可用看到 libc 是程序所需要的。多次查看如果 libc 的地址不同,是因?yàn)榇蜷_(kāi)了 ASLR。用 IDA 打開(kāi):
0x10428 處調(diào)用 printf,雙擊并沒(méi)有進(jìn)入 libc。
而是到了 PLT 段,0x102D0 處通過(guò) LDR PC, […] 修改 PC 跳轉(zhuǎn)到了其他地址。
到達(dá) GOT 段,這里存著外部符號(hào)的地址。
下面用 gdb 調(diào)試,斷點(diǎn)下在 0x10428 處。
stepi 繼續(xù)運(yùn)行。
走幾步到達(dá) ld 庫(kù)中的 dl_runtime_resolve 函數(shù)。
ld 是動(dòng)態(tài)鏈接器,這里建立起了 libc 的外部引用環(huán)境。
更多細(xì)節(jié)參考?http://eli.thegreenplace.net/2011/11/03/position-independent-code-pic-in-shared-libraries/。
逆向工程介紹
這部分對(duì)于要分析的程序我不會(huì)提供源碼。
??逆向算法
第一個(gè)例子是輸入一個(gè)字符串,經(jīng)過(guò)算法處理后會(huì)輸出另一個(gè)字符串,我需要使輸出字符串為“Hello”。
下面是源碼(我只會(huì)提供這個(gè)例子的):
編譯:
調(diào)試?yán)斫馑惴?#xff1a;
0x10454 代表 r0=*(pc+92)。查看 pc+92 的內(nèi)容:
是數(shù)據(jù)段的地址,看一下內(nèi)容:
0x20668 是 printf 函數(shù)的參數(shù)。
運(yùn)行到 0x10464,r0 是格式的地址,r1 是輸入字符串的地址。
從源碼中可以看到輸入字符串的長(zhǎng)度為5:
輸入 “ABCDE”:
0x10468 和 0x1046c 處的指令將輸出字符串地址賦給 r5,輸入字符串地址賦給 r1。運(yùn)行至 0x10470:
注釋如下:
運(yùn)行到 0x10480 處查看 r0, r2, r3 的值:
即 *r5 = r2 xor r3。
可以用偽代碼表示:byte1strOut = byte1strInput xor byte2strInput。 比如我們想要生成“Hello”,需要使 r0 為 0x48(H)。
接著看 0x10480,
注意看注釋:
執(zhí)行到 0x1048c,看一下 r0、r3、r4 的值:
即 *(r5+1) = r4 xor r3。偽代碼表示:byte2strOut = byte2strInput xor byte3strInput。
意味著 *(r5+2) = r2 + 0x5。偽代碼表示:byte3outStr = byte1strInput + 0x5。第 4 個(gè)字節(jié):
就是 *(r5+3) = r3 xor r4。偽代碼:byte4strOut = byte2strInput xor byte4strInput。第 5 個(gè)字節(jié):
就是 *(r5+4) = r4 xor r2。偽代碼:byte5strOut = byte4strInput xor byte5strInput。
整個(gè)算法合在一起:
byte1strOut = byte1strInput xor byte2strInput
byte2strOut = byte2strInput xor byte3strInput
byte3strOut = byte2strInput + 0x5
byte4strOut = byte2strInput xor byte4strInput
byte5strOut = byte4strInput xor byte5strInput
將輸出字符替換:
‘H’ = 0x48 = byte1strInput xor byte2strInput
‘e’ = 0x65 = byte2strInput xor byte3strInput
‘l’ = 0x6c = byte1strInput + 0x5
‘l’ = 0x6c = byte2strInput xor byte4strInput
‘o’ = 0x6f = byte4strInput xor byte5strInput
推出應(yīng)輸入字符:
byte1strInput = 0x6c – 0x5 = 0x67 (g)
byte2strInput = 0x48 xor 0x67 = 0x2f (/)
byte3strInput = 0x2f xor 0x65 = 0x4a (J)
byte4strInput = 0x2f xor 0x6c = 0x43 (C)
byte5strInput = 0x43 xor 0x6f = 0x2c (,)
輸入試試:
? 逆向一個(gè)簡(jiǎn)單的加載器
這個(gè)加載器的作用是把指令加載到內(nèi)存里,當(dāng)你打印消息時(shí)執(zhí)行。我們這次要打印“WIN”。程序在這里。
用 IDA 打開(kāi):
在 _start 這兒可以看到 0x10090 處有系統(tǒng)調(diào)用,調(diào)用號(hào)是 0xc0(mmap)。
看下面我的注釋分析:
mmap 后有一塊新內(nèi)存(0x30000)。
0x10098 處的指令 .text:00010098 LDR R1, =code 把某個(gè)變量的地址存入 r1,看一下:
這些看起來(lái)不像 arm 代碼,接著看 0x100A4:
.text:000100A4 LDR R2, [R1,R4]?把 r1+r4 地址額值存入 r2,r1 是 code 變量,r4 表示索引,第一次值為 0。
.text:000100A8 EOR R2, R2, R6?r2 與 r6 異或,r6 的值是 0x123456,第一次 r2 的值是 0x56。異或的值存在 r2,在下條指令?
.text:000100AC STR R2, [R0,R4]?中被寫(xiě)入 mmap 分配的地址 0x30000 處,注意 r0 是 mmap 的返回值。
循環(huán)的作用是解密 code 的所有字節(jié),在 0x100BC 處下斷點(diǎn)查看 0x30000 的值。
這些就是 ARM 指令了,也可以用 idc 腳本解密:
現(xiàn)在來(lái)分析解密的指令:
執(zhí)行過(guò) 0x30004 到 0x30014 的 5 條指令后,棧指針向低地址處移動(dòng)了 8 ,r4 是棧指針,r2 的值是 0x3e,r3 的值是 0x2,r5 的值是 0x96。
接下來(lái)兩條指令(0x30018 和 0x3001c)r2 與 r5 異或的結(jié)果 0xa8 存入 r1,這個(gè)值寫(xiě)入了棧頂,棧指針向高地址移動(dòng)了 1 。此時(shí):
0x30020 處 r2 自減 0x1e,得到:
0x30024 處是一個(gè)循環(huán):
每次循環(huán)都將 r2 和 r5 異或,結(jié)果存入棧頂,sp + 1。0x30030 處可以看到 r3 是循環(huán)索引,每次減1,初始值為 2,所以共循環(huán)兩次。
循環(huán)結(jié)束時(shí)運(yùn)行到 0x30038時(shí), 0x7efff7b0 值為:
還有兩個(gè)字節(jié)在棧頂存著,此時(shí)棧指針為:
0x3003c 處的兩條指令將剩下的兩個(gè)字節(jié)存入棧頂:
執(zhí)行完 0x30040 后0x7efff7b0 值為:
往下看就是 write 的系統(tǒng)調(diào)用了:
write 過(guò)后:
我們想要的是“WIN”,這時(shí)候就要修改 xor 的 key,這樣存入棧頂?shù)牟艜?huì)是正確的 0x57 0x49 0x4e。
來(lái)看 0x30018 處的異或操作 0x30018: eor r1, r2, r5,r2 每次都變,所以 r5 是異或的 key,我們需要修改 r5 使得 r1 = r2 xor r5 = 0x57。
r2 的值是 0x3e,則 r5 應(yīng)該是 0x69。
異或的 key 沒(méi)有變過(guò),這就直接繼續(xù)執(zhí)行就可以了。
? 基本的反調(diào)試技術(shù)
這是本章最后一個(gè)程序了,這次需要理解算法并繞過(guò)一些基本的反調(diào)試,使程序輸出“Good”。在這里下載。
試著用調(diào)試器運(yùn)行:
即使再用 strace/ltrace 命令也是同樣的輸出。
IDA 打開(kāi):
我們從 ldr r2, =aAd 開(kāi)始分析。
aAd 是個(gè)變量:
把 Array 轉(zhuǎn)為 data 更好理解:
0x10988 處的數(shù)組用 var_c 表示,還有另一個(gè)變量 var_10,aAd + 4 的值如下:
即 var_10 變量存著 0x1098C 處的數(shù)組。
看接下來(lái)的指令做了什么:
總結(jié)來(lái)說(shuō)就是有兩個(gè)數(shù)組:
4個(gè)元素的 var_c: ?0x7, 0x2f, 0x2f, 0x24; 3個(gè)元素的 var_10:0x22, 0x41, 0x44。
下面有個(gè) flag 變量,我們來(lái)看 main 函數(shù)中關(guān)于它的流程圖:
flag為1,紅色執(zhí)行,不為1綠色執(zhí)行。
flag為1,r3 為 0 隨后與 3 比較。
flag不為1,r3 為 0 隨后 與 2 比較。
flag 為 1,我們來(lái)到 loc_107F8,最關(guān)鍵的是這句 ADD R3, R3, #0x40,r3 的值是 r3 = *(var_C+var_8),var_C 和 var_8 分別是:
var_C = 4個(gè)元素的數(shù)組
var_8 = 0 (索引,第一次的值)
相加之后 r3 的值為 r3 = 0x7 + 0x40 = 0x47。
可以用個(gè)簡(jiǎn)單的 idc 腳本計(jì)算:
結(jié)果是 Good:
再來(lái)看flag不為1時(shí)的 loc_10864,這里的循環(huán)計(jì)算的是3個(gè)元素的數(shù)組,關(guān)鍵的是 ADD R3, R3, #0x20。
像之前一樣,idc 腳本
結(jié)果是 Bad:
為了使程序輸出“Good”,需要找到 flag 賦值的地方,而且剛剛并沒(méi)有發(fā)現(xiàn)檢測(cè)調(diào)試器的地方, “You want debug me?” 也沒(méi)有出現(xiàn)過(guò)。
查看 flag 的交叉引用:
發(fā)現(xiàn)有個(gè) ptrace_capt 的函數(shù),在 main 函數(shù)前執(zhí)行。可以在 .ctors (或者 .init_array) 段中發(fā)現(xiàn)其提供了一些列函數(shù)用來(lái)在程序開(kāi)始/結(jié)束前執(zhí)行。
來(lái)看 ptrace_capt 函數(shù):
這里有個(gè)檢查:
用調(diào)試器可以輕易繞過(guò),先來(lái)看 loc_10690:
大致如下:
1. 只讀模式打開(kāi) password.raw;
2. 計(jì)算大小
3. 驗(yàn)證大小是否小于 6
如果小于等于6,來(lái)到 loc_10700:
往下看發(fā)現(xiàn)這是個(gè)循環(huán):
調(diào)用 fgetc 函數(shù):
如果 r3 等于 0,則來(lái)到 loc_10750:
var_18 是讀取的字符,var_8 是循環(huán)索引,sub0 的調(diào)用則為 sub0(var_18, var_8, &var_1C);。
看 sub0 函數(shù):
C 代碼:
sub0 返回時(shí)繼續(xù)執(zhí)行,var_1C 保存返回值:
這段用偽碼表示即:
如果 r3 不等于 0,則來(lái)到:
C 代碼表示:
終于找到了 flag 賦值的地方,而我們需要其值為 1。
新建 password.raw 文件,寫(xiě)入:
我用 vim 的設(shè)置刪掉了換行:
運(yùn)行:
現(xiàn)在用 gdb 運(yùn)行去掉反調(diào)試:# gdb ./3b
在 0x10678 處下斷,修改 r3 的值:
繼續(xù)往下分析,我現(xiàn)在要使 var_C=0x997,flag 才能賦值為 1。現(xiàn)在文件里的內(nèi)容是:
要修改第五個(gè)字節(jié)使得 var_C=0x997,就要知道第4次循環(huán)時(shí) var_C 的值。
在 0x10774 下斷點(diǎn),
此時(shí)循環(huán)索引為 3(第4次),var_C 的值為 0x724,現(xiàn)在要改掉第 5 個(gè)字節(jié)的值,我用了 Python 來(lái)計(jì)算:
運(yùn)行:
得到了第五個(gè)字節(jié)的值,修改它:
記得刪掉換行 :set noendofline binary。
運(yùn)行:
“Good” 就被打印了。
本文由看雪翻譯小組 kiyaa 編譯,來(lái)源quequero@andrea sindoni
轉(zhuǎn)載請(qǐng)注明來(lái)自看雪社區(qū)
總結(jié)
以上是生活随笔為你收集整理的物联网中的 ARM 漏洞利用的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: GAN for NLP (论文笔记及解读
- 下一篇: Using Markov Chains