日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

物联网中的 ARM 漏洞利用

發布時間:2025/3/15 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 物联网中的 ARM 漏洞利用 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

發文動機


幾周前我參加某個會議的時候,有個“物聯網上的 ARM 漏洞利用課程”的議題我覺得很多干貨,我也決定自己寫一篇,給去不了現場的同學發些福利。我打算分為三個部分來寫。


當然我的文章沒辦法和現場的 course 相比,我還是想為大家做一些微小的工作。


這三個部分是:


  • 第一部分:逆向 ARM 應用

  • 第二部分:編寫 ARM shellcode

  • 第三部分:ARM 漏洞利用




一、逆向ARM應用


  • 環境

    樹莓派3——我選了這個又便宜又好配置的環境,Android 也是個不錯的選擇。

  • 硬件

    具體型號:>樹莓派3、型號B、ARM-Cortex-A53架構


  • 軟件

    這是些軟件信息,接下來三部分都會用到。


操作系統的安裝請看:https://www.raspberrypi.org/documentation/installation/installing-images/linux.md


配置遠程 ssh 請看:https://www.raspberrypi.org/documentation/remote-access/ssh/


? 編譯器


我們用到的所有 C、C++、匯編代碼都會用樹莓派自帶的 GCC 編譯器。


版本如下:



還有一點是 GCC 的匯編指令和其他的編譯器不同,最好先看一下這些指令:http://www.ic.unicamp.br/~celio/mc404-2014/docs/gnu-arm-directives.pdf


? 源碼


本部分我用到的源碼都放在這里了:https://github.com/invictus1306/ARM-episodes/tree/master/Episode1


??編譯選項


這一節我會介紹三個選項,并附帶例子。


這是用到的源碼:



??調試符號?


-g 選項會在編譯時向可執行文件中加入調試信息(符號表)。

編譯帶 -g 和不帶 -g 選項的兩個 ELF 文件,比較大小:



第二個文件更大,意味著它被加入了其他的信息。用 readelf 命令的 -S 選項(查看節頭)查看其調試信息:



這些調試信息以 GCC 默認的 DWARF 格式存儲。用 objdump 查看:



去除符號表和重定位信息

選項為 -s。



可以看到 .symtab 有很多本地符號,這些符號運行時并不需要,可以把它們去掉。



用過 -s 選項后,函數名之類的信息已經去掉了,某個逆向小子的生活又艱難了一步。


在之后的第三部分我會介紹其他更復雜的編譯選項。




ARM Hello World



我們以兩種方式開始這個 hello world 的研究:


1. 使用樹莓派系統調用

2. 使用 libc 函數


? 使用樹莓派的系統調用



匯編并鏈接此程序:



注意:如果用 gcc,



會得到錯誤:


這是因為源程序里沒有 main 函數,在另一種實現里我們會看到如何使用 gcc 編譯。


執行:


接下來使用 gdb:


可以看到 .text 段中放著我們的代碼。0x10078 處的指令代表將 0x10090 指向的值載入 r1。


? 使用 libc 函數


這次我們會使用 printf 函數,這里我們將程序中的 .global _start 改為 ?.global main。



編譯器需要我們指定 global main, .func main, main: 等符號告訴 libc main 函數在哪。



匯編器和鏈接器只是 GCC 的一小部分,下面我們會用到 GCC 其他的特性來編譯。


可以看到在進程的地址空間里加載了 libc 共享庫(libc-2.19.so)。



0x10428 處調用了 printf 函數,0x10428 是個 PLT 入口,指向 GOT 中 printf 函數在運行時的真實地址。


GCC 編譯時,libc 的函數并沒有被編譯到可執行文件中,而是通過動態鏈接到 libc 使之可用。用 ldd 命令查看程序引用的動態庫。



可用看到 libc 是程序所需要的。多次查看如果 libc 的地址不同,是因為打開了 ASLR。用 IDA 打開:



0x10428 處調用 printf,雙擊并沒有進入 libc。



而是到了 PLT 段,0x102D0 處通過 LDR PC, […] 修改 PC 跳轉到了其他地址。



到達 GOT 段,這里存著外部符號的地址。


下面用 gdb 調試,斷點下在 0x10428 處。


stepi 繼續運行。



走幾步到達 ld 庫中的 dl_runtime_resolve 函數。



ld 是動態鏈接器,這里建立起了 libc 的外部引用環境。

更多細節參考?http://eli.thegreenplace.net/2011/11/03/position-independent-code-pic-in-shared-libraries/。


逆向工程介紹

這部分對于要分析的程序我不會提供源碼。


??逆向算法


第一個例子是輸入一個字符串,經過算法處理后會輸出另一個字符串,我需要使輸出字符串為“Hello”。



下面是源碼(我只會提供這個例子的):



編譯:



調試理解算法:



0x10454 代表 r0=*(pc+92)。查看 pc+92 的內容:



是數據段的地址,看一下內容:



0x20668 是 printf 函數的參數。
運行到 0x10464,r0 是格式的地址,r1 是輸入字符串的地址。



從源碼中可以看到輸入字符串的長度為5:



輸入 “ABCDE”:



0x10468 和 0x1046c 處的指令將輸出字符串地址賦給 r5,輸入字符串地址賦給 r1。運行至 0x10470:



注釋如下:



運行到 0x10480 處查看 r0, r2, r3 的值:



即 *r5 = r2 xor r3。

可以用偽代碼表示:byte1strOut = byte1strInput xor byte2strInput。 比如我們想要生成“Hello”,需要使 r0 為 0x48(H)。

接著看 0x10480,



注意看注釋:



執行到 0x1048c,看一下 r0、r3、r4 的值:



即 *(r5+1) = r4 xor r3。偽代碼表示:byte2strOut = byte2strInput xor byte3strInput。



意味著 *(r5+2) = r2 + 0x5。偽代碼表示:byte3outStr = byte1strInput + 0x5。第 4 個字節:



就是 *(r5+3) = r3 xor r4。偽代碼:byte4strOut = byte2strInput xor byte4strInput。第 5 個字節:



就是 *(r5+4) = r4 xor r2。偽代碼:byte5strOut = byte4strInput xor byte5strInput。


整個算法合在一起:

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


推出應輸入字符:

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 (,)


輸入試試:



? 逆向一個簡單的加載器


這個加載器的作用是把指令加載到內存里,當你打印消息時執行。我們這次要打印“WIN”。程序在這里。



用 IDA 打開:



在 _start 這兒可以看到 0x10090 處有系統調用,調用號是 0xc0(mmap)。


看下面我的注釋分析:



mmap 后有一塊新內存(0x30000)。



0x10098 處的指令 .text:00010098 LDR R1, =code 把某個變量的地址存入 r1,看一下:



這些看起來不像 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]?中被寫入 mmap 分配的地址 0x30000 處,注意 r0 是 mmap 的返回值。

循環的作用是解密 code 的所有字節,在 0x100BC 處下斷點查看 0x30000 的值。


這些就是 ARM 指令了,也可以用 idc 腳本解密:

現在來分析解密的指令:


執行過 0x30004 到 0x30014 的 5 條指令后,棧指針向低地址處移動了 8 ,r4 是棧指針,r2 的值是 0x3e,r3 的值是 0x2,r5 的值是 0x96。



接下來兩條指令(0x30018 和 0x3001c)r2 與 r5 異或的結果 0xa8 存入 r1,這個值寫入了棧頂,棧指針向高地址移動了 1 。此時:



0x30020 處 r2 自減 0x1e,得到:



0x30024 處是一個循環:



每次循環都將 r2 和 r5 異或,結果存入棧頂,sp + 1。0x30030 處可以看到 r3 是循環索引,每次減1,初始值為 2,所以共循環兩次。

循環結束時運行到 0x30038時, 0x7efff7b0 值為:



還有兩個字節在棧頂存著,此時棧指針為:



0x3003c 處的兩條指令將剩下的兩個字節存入棧頂:



執行完 0x30040 后0x7efff7b0 值為:



往下看就是 write 的系統調用了:



write 過后:


我們想要的是“WIN”,這時候就要修改 xor 的 key,這樣存入棧頂的才會是正確的 0x57 0x49 0x4e。


來看 0x30018 處的異或操作 0x30018: eor r1, r2, r5,r2 每次都變,所以 r5 是異或的 key,我們需要修改 r5 使得 r1 = r2 xor r5 = 0x57。


r2 的值是 0x3e,則 r5 應該是 0x69。


異或的 key 沒有變過,這就直接繼續執行就可以了。


? 基本的反調試技術


這是本章最后一個程序了,這次需要理解算法并繞過一些基本的反調試,使程序輸出“Good”。在這里下載。


試著用調試器運行:


即使再用 strace/ltrace 命令也是同樣的輸出。
IDA 打開:


我們從 ldr r2, =aAd 開始分析。
aAd 是個變量:


把 Array 轉為 data 更好理解:


0x10988 處的數組用 var_c 表示,還有另一個變量 var_10,aAd + 4 的值如下:


即 var_10 變量存著 0x1098C 處的數組。

看接下來的指令做了什么:


總結來說就是有兩個數組:


4個元素的 var_c: ?0x7, 0x2f, 0x2f, 0x24; 3個元素的 var_10:0x22, 0x41, 0x44。


下面有個 flag 變量,我們來看 main 函數中關于它的流程圖:


flag為1,紅色執行,不為1綠色執行。

flag為1,r3 為 0 隨后與 3 比較。
flag不為1,r3 為 0 隨后 與 2 比較。



flag 為 1,我們來到 loc_107F8,最關鍵的是這句 ADD R3, R3, #0x40,r3 的值是 r3 = *(var_C+var_8),var_C 和 var_8 分別是:
var_C = 4個元素的數組
var_8 = 0 (索引,第一次的值)
相加之后 r3 的值為 r3 = 0x7 + 0x40 = 0x47。


可以用個簡單的 idc 腳本計算:


結果是 Good:


再來看flag不為1時的 loc_10864,這里的循環計算的是3個元素的數組,關鍵的是 ADD R3, R3, #0x20。


像之前一樣,idc 腳本


結果是 Bad:


為了使程序輸出“Good”,需要找到 flag 賦值的地方,而且剛剛并沒有發現檢測調試器的地方, “You want debug me?” 也沒有出現過。


查看 flag 的交叉引用:


發現有個 ptrace_capt 的函數,在 main 函數前執行。可以在 .ctors (或者 .init_array) 段中發現其提供了一些列函數用來在程序開始/結束前執行。


來看 ptrace_capt 函數:


這里有個檢查:


用調試器可以輕易繞過,先來看 loc_10690:


大致如下:

1. 只讀模式打開 password.raw;


2. 計算大小


3. 驗證大小是否小于 6


如果小于等于6,來到 loc_10700:


往下看發現這是個循環:


調用 fgetc 函數:


如果 r3 等于 0,則來到 loc_10750:


var_18 是讀取的字符,var_8 是循環索引,sub0 的調用則為 sub0(var_18, var_8, &var_1C);。


看 sub0 函數:


C 代碼:


sub0 返回時繼續執行,var_1C 保存返回值:


這段用偽碼表示即:


如果 r3 不等于 0,則來到:


C 代碼表示:


終于找到了 flag 賦值的地方,而我們需要其值為 1。
新建 password.raw 文件,寫入:


我用 vim 的設置刪掉了換行:


運行:


現在用 gdb 運行去掉反調試:# gdb ./3b


在 0x10678 處下斷,修改 r3 的值:


繼續往下分析,我現在要使 var_C=0x997,flag 才能賦值為 1?,F在文件里的內容是:


要修改第五個字節使得 var_C=0x997,就要知道第4次循環時 var_C 的值。


在 0x10774 下斷點,


此時循環索引為 3(第4次),var_C 的值為 0x724,現在要改掉第 5 個字節的值,我用了 Python 來計算:


運行:


得到了第五個字節的值,修改它:


記得刪掉換行 :set noendofline binary。


運行:


“Good” 就被打印了。





本文由看雪翻譯小組 kiyaa 編譯,來源quequero@andrea sindoni

轉載請注明來自看雪社區


總結

以上是生活随笔為你收集整理的物联网中的 ARM 漏洞利用的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。