譯者:知道創宇404實驗室翻譯組
原文鏈接:https://0x434b.dev/breaking-the-d-link-dir3060-firmware-encryption-recon-part-1/
前言
最近,我們發現了一些無法解壓的D-Link路由器的固件樣本。通過分析類似的更舊、更便宜的設備(DIR882),我們可以找到一種破解固件加密的方法,以防止篡改和靜態分析。本系列文章重點介紹了編寫自定義解密例程的結果和必要步驟,該例程也可用于其他模型,后續會對此進行更多介紹。
問題
此處可下載最新版D-Link 3060固件(截至撰寫本文時),我將研究于19年10月22日發行的v1.02B03版本,初步分析結果如下:
> md5sum DIR-3060_RevA_Firmware111B01.bin
86e3f7baebf4178920c767611ec2ba50 DIR3060A1_FW102B03.bin
> file DIR-3060_RevA_Firmware111B01.bin
DIR3060A1_FW102B03.bin: data
> binwalk DIR-3060_RevA_Firmware111B01.bin
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
> hd -n 128 DIR-3060_RevA_Firmware111B01.bin
00000000 53 48 52 53 01 13 1f 9e 01 13 1f a0 67 c6 69 73 |SHRS........g.is|
00000010 51 ff 4a ec 29 cd ba ab f2 fb e3 46 2e 97 e7 b1 |Q.J.)......F....|
00000020 56 90 b9 16 f8 0c 77 b8 bf 13 17 46 7b e3 c5 9c |V.....w....F{...|
00000030 39 b5 59 6b 75 8d b8 b0 a3 1d 28 84 33 13 65 04 |9.Yku.....(.3.e.|
00000040 61 de 2d 56 6f 38 d7 eb 43 9d d9 10 eb 38 20 88 |a.-Vo8..C....8 .|
00000050 1f 21 0e 41 88 ff ee aa 85 46 0e ee d7 f6 23 04 |.!.A.....F....#.|
00000060 fa 29 db 31 9c 5f 55 68 12 2e 32 c3 14 5c 0a 53 |.).1._Uh..2..\.S|
00000070 ed 18 24 d0 a6 59 c0 de 1c f3 8b 67 1d e6 31 36 |..$..Y.....g..16|
00000080
從文件命令中得到的某種形式的二進制數據文件并不是很有用,最初的偵察選擇是:binwalk也無法識別固件鏡像中的文件部分,前128個字節的十六進制轉儲顯示了從偏移量0x0開始的看似隨機的數據。這些都是加密圖像的指標,通過熵值分析法可以確認:
> binwalk -E DIR-3060_RevA_Firmware111B01.bin
DECIMAL HEXADECIMAL ENTROPY
--------------------------------------------------------------------------------
0 0x0 Rising entropy edge (0.978280)

曲線沒有任何下降,我們無法提取有關目標的任何信息...
嘗試
我們購買了比200美元的D-Link DIR 3060更便宜但又極其類似的D-Link DIR 882,旨在找到至少一個部署相同加密方案的替代產品。
附帶說明:即使我們無法找到類似的加密方案,不同的固件標頭也可能提供一些提示,進而說明其“保護”固件的流程。
當我們偶然發現DIR 882時,我們檢查了2020年2月20日發行的固件v1.30B10,它顯示出與DIR3060相同的特征,包括接近1的常量熵。大家可能會注意到開始的“ SHRS”處是相同的4字節序列,這個我們稍后再討論。
> md5sum DIR_882_FW120B06.BIN
89a80526d68842531fe29170cbd596c3 DIR_882_FW120B06.BIN
> file DIR_882_FW120B06.BIN
DIR_882_FW120B06.BIN: data
> binwalk DIR_882_FW120B06.BIN
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
> hd -n 128 DIR_882_FW120B06.BIN
00000000 53 48 52 53 00 d1 d9 a6 00 d1 d9 b0 67 c6 69 73 |SHRS........g.is|
00000010 51 ff 4a ec 29 cd ba ab f2 fb e3 46 fd a7 4d 06 |Q.J.)......F..M.|
00000020 a4 66 e6 ad bf c4 9d 13 f3 f7 d1 12 98 6b 2a 35 |.f...........k*5|
00000030 1d 0e 90 85 b7 83 f7 4d 3a 2a 25 5a b8 13 0c fb |.......M:*%Z....|
00000040 2a 17 7a b2 99 04 60 66 eb c2 58 98 82 74 08 e3 |*.z...`f..X..t..|
00000050 54 1e e2 51 44 42 e8 d6 8e 46 6e 2c 16 57 d3 0b |T..QDB...Fn,.W..|
00000060 07 d7 7c 9e 11 ec 72 1d fb 87 a2 5b 18 ec 53 82 |..|...r....[..S.|
00000070 85 b9 84 39 b6 b4 dd 85 de f0 28 3d 36 0e be aa |...9......(=6...|
00000080

截止2020年初,該固件仍使用相同的加密方案。
解決方案
一旦獲得DIR882,我們就可以在設備上輸入一個串行控制臺,并在文件系統中尋找處理固件更新的加密/解密的線索和對象。(UART控制臺不在本文的討論范圍之內,因為它除了連接4條電纜外,沒有涉及“硬件黑客”活動)很快就能找出相關信息:
> file imgdecrypt
imgdecrypt: ELF 32-bit LSB executable, MIPS, MIPS32 rel2 version 1 (SYSV), dynamically linked, interpreter /lib/ld-, stripped
> md5sum imgdecrypt
a5474af860606f035e4b84bd31fc17a1 imgdecrypt
> base64 < imgdecrypt
將輸出復制到本地計算機并將base64轉換回二進制文件后,開始仔細分析:
二進制偵察
上文可見,我們正處理用于MIPS的32位ELF二進制文件,該二進制文件是動態鏈接(按預期方式)和剝離的,讓我們看看strings能做些什么:
> strings -n 10 imgdecrypt | uniq
/lib/ld-uClibc.so.0
[...]
SHA512_Init
SHA512_Update
SHA512_Final
RSA_verify
AES_set_encrypt_key
AES_cbc_encrypt
AES_set_decrypt_key
PEM_write_RSAPublicKey
OPENSSL_add_all_algorithms_noconf
PEM_read_RSAPublicKey
PEM_read_RSAPrivateKey
RSA_generate_key
EVP_aes_256_cbc
PEM_write_RSAPrivateKey
decrypt_firmare
encrypt_firmare
[...]
libcrypto.so.1.0.0
[...]
no image matic found
check SHA512 post failed
check SHA512 before failed %d %d
check SHA512 vendor failed
static const char *pubkey_n = "%s";
static const char *pubkey_e = "%s";
Read RSA private key failed, maybe the key password is incorrect
/etc_ro/public.pem
%s <sourceFile>
/tmp/.firmware.orig
0123456789ABCDEF
%s sourceFile destFile
[...]
這里有很多有用的東西,我只是刪除了“[…]”所指示的行,最值得注意的是以下幾點:
- 使用uClibc和libcrypto
- 計算/檢查SHA512 hash摘要
- 使用AES_CBC模式進行加密/解密
- 進行RSA證書檢查,并將證書路徑固定到/etc_ro/public.pem
- RSA私鑰受密碼保護
- /tmp/.firmware.orig可能暗示了事物被臨時解密到的位置
- imgdecrypt二進制文件的一般用法
小結
1.D-Link可能會在多個設備上重復使用相同的加密方案
2.設備基于MIPS32架構
3.可訪問DIR 882上的UART串行控制臺
4.鏈接到uClibc和libcrypto
可能使用AES,RSA和SHA512例程
5.二進制似乎同時負責加密和解密
6.有公共證書
7.imgdecrypt的用法似乎是./imgdecrypt myInFile
8.使用/ tmp /路徑存儲結果?
接下來,我們將深入研究imgdecrypt二進制文件,以了解如何控制固件更新!對于那些對MIPS32匯編語言有點生疏的人來說,這里是簡短的入門。
MIPS32拆卸的初級讀本
大多數人可能都很熟悉x86/x86_64反匯編,所以這里涉及MIPS如何運行以及它與x86有何不同的一般規則。接下來我將探討最常見的O32:
寄存器
在MIPS32中,可使用32個寄存器,O32對其定義如下:
+---------+-----------+------------------------------------------------+
| Name | Number | Usage |
+----------------------------------------------------------------------+
| $zero | $0 | Is always 0, writes to it are discarded. |
+----------------------------------------------------------------------+
| $at | $1 | Assembler temporary register (pseudo instr.) |
+----------------------------------------------------------------------+
| $v0─$v1 | $2─$3 | Function returns/expression evaluation |
+----------------------------------------------------------------------+
| $a0─$a3 | $4─$7 | Function arguments, remaining are in stack |
+----------------------------------------------------------------------+
| $t0─$t7 | $8─$15 | Temporary registers |
+----------------------------------------------------------------------+
| $s0─$s7 | $16─$23 | Saved temporary registers |
+----------------------------------------------------------------------+
| $t8─$t9 | $24─$25 | Temporary registers |
+----------------------------------------------------------------------+
| $k0─$k1 | $26─$27 | Reserved for kernel |
+----------------------------------------------------------------------+
| $gp | $28 | Global pointer |
+----------------------------------------------------------------------+
| $sp | $29 | Stack pointer |
+----------------------------------------------------------------------+
| $fp | $30 | Frame pointer |
+----------------------------------------------------------------------+
| $ra | $31 | Return address |
+---------+-----------+------------------------------------------------+
最重要的是:
- 前四個函數參數移入
$a0 - $a3,其余參數置于堆棧頂部; - 函數返回被放置,
$v0并最終在$v1第二個返回值存在時被放置; $ra通過跳轉和鏈接(JAL)或跳轉和鏈接寄存器(JALR)執行功能調用時,返回地址存儲在寄存器中;$sX寄存器在過程調用之間保留(子例程可以使用它們,但必須在返回之前將其還原);$gp指向靜態數據段中64k內存塊的中間;$sp指向堆棧的最后一個位置;- 葉子與非葉子子例程之間的區別:
- 葉子:請勿調用任何其他子例程,也不要使用堆棧上的內存空間。沒有建立堆棧框架,因此不需要更改
$sp。 - 帶數據的葉:與葉相同,但它們需要堆棧空間,例如:用于局部變量,將推動堆棧框架,可省略不需要的堆棧框架部分。
- 非葉程序:將調用其他子例程,很可能具有完整的堆棧框架。
- 在有PIC的Linux上,
$t9應該包含被調用函數的地址。
+ +-------------------+ +-+
| | | |
| +-------------------+ |
| | | | Previous
| +-------------------+ +-> Stack
| | | | Frame
| +-------------------+ |
| | | |
| +-------------------+ +-+
| | local data x─1 | +-+
| +-------------------+ |
| | | |
| +-------------------+ |
| | local data 0 | |
| +-------------------+ |
| | empty | |
Stack | +-------------------+ |
Growth | | return value | |
Direction | +-------------------+ |
| | saved reg k─1 | |
| +-------------------+ | Current
| | | +-> Stack
| +-------------------+ | Frame
| | saved reg 0 | |
| +-------------------+ |
| | arg n─1 | |
| +-------------------+ |
| | | |
| +-------------------+ |
| | arg 4 | |
| +-------------------+ |
| | arg 3 | |
| +-------------------+ |
| | arg 2 | |
| +-------------------+ |
| | arg 1 | |
| +-------------------+ |
| | arg 0 | |
v +-------------------+ +-+
|
|
v
常用操作
熟悉其他匯編語言的人將很快上手,以下為可快速入門本系列第2部分的精選內容:
+------------------+----------------------------------------------------+-------------------------+----------------------------------------------------------+
| Mnemonic | Full name | Syntax | Operation |
+------------------+----------------------------------------------------+-------------------------+----------------------------------------------------------+
| ADD | Add (with overflow) | add $a, $b, $c | $a = $b + $c |
+---+--------------+----------------------------------------------------+-------------------------+----------------------------------------------------------+
| ADDI | Add immediate (with overflow) | addi $a, $b, imm | $a = $b + imm |
+--------------+----------------------------------------------------+-------------------------+----------------------------------------------------------+
| ADDIU | Add immediate unsigned (no overflow) | addiu $a, $b, imm | see ADDI |
+--------------+----------------------------------------------------+-------------------------+----------------------------------------------------------+
| ADDU | Add unsigned (no overflow) | addu $a, $b, $c | see ADD |
+---+--------------+----------------------------------------------------+-------------------------+----------------------------------------------------------+
| AND* | Bitwise and | and $a, $b, $c | $a = $b & $c |
+------------------+----------------------------------------------------+-------------------------+----------------------------------------------------------+
| B** | Branch to offset unconditionally | b offset | goto offset |
+---+--------------+----------------------------------------------------+-------------------------+----------------------------------------------------------+
| BEQ | Branch on equal | beq $a, $b, offset | if $a == $t goto offset |
+---+----------+----------------------------------------------------+-------------------------+----------------------------------------------------------+
| BEQZ | Branch on equal to zero | beqz $a, offset | if $a == 0 goto offset |
+---+----------+----------------------------------------------------+-------------------------+----------------------------------------------------------+
| BGEZ | Branch on greater than or equal to zero | bgez $a, offset | if $a >= 0 goto offset |
+---+----------+----------------------------------------------------+-------------------------+----------------------------------------------------------+
| BGEZAL | Branch on greater than or equal to zero and link | bgezal $a, offset | if $a >= 0: $ra = PC+8 and goto offset |
+---+----------+----------------------------------------------------+-------------------------+----------------------------------------------------------+
| BAL | Branch and link | bal offset | $ra=PC+8 and goto offset |
+--------------+----------------------------------------------------+-------------------------+----------------------------------------------------------+
| BNE | Branch on not equal | bne $a, $b, offset | if $a != $b: goto offset |
+---+--------------+----------------------------------------------------+-------------------------+----------------------------------------------------------+
| DIV(U) | Divide (unsigned) | div $a, $b | $LO = $s/$t, $HI = $s%$t (LO/HI are special registers) |
+------------------+----------------------------------------------------+-------------------------+----------------------------------------------------------+
| J** | Jump | j target | PC=target |
+---+--------------+----------------------------------------------------+-------------------------+----------------------------------------------------------+
| JR | Jump register | jr target | PC=$register |
+--------------+----------------------------------------------------+-------------------------+----------------------------------------------------------+
| JALR | Jump and link register | jalr target | $ra=PC+8, PC=$register |
+---+--------------+----------------------------------------------------+-------------------------+----------------------------------------------------------+
| L(B/W) | Load (byte/word) | l(b/w) $a, offset($b) | $a = memory[$b + offset] |
+---+--------------+----------------------------------------------------+-------------------------+----------------------------------------------------------+
| LWL | Load word left | lwl $a, offset(base) | |
+--------------+----------------------------------------------------+-------------------------+----------------------------------------------------------+
| LWR | Load word right | lwr $a, offset(base) | |
+---+--------------+----------------------------------------------------+-------------------------+----------------------------------------------------------+
| OR* | Bitewise or | or $a, $b, $c | $a = $b|$c |
+------------------+----------------------------------------------------+-------------------------+----------------------------------------------------------+
| S(B/W) | Store (byte/word) | s(w/b) $a, offset($b) | memory[$b + offset] = $a |
+------------------+----------------------------------------------------+-------------------------+----------------------------------------------------------+
| SLL** | Shift left logical | sll $a, $b, h | $a = $b << h |
+------------------+----------------------------------------------------+-------------------------+----------------------------------------------------------+
| SRL** | Shift right logical | srl $a, $b, h | $a = $b >> h |
+------------------+----------------------------------------------------+-------------------------+----------------------------------------------------------+
| SYSCALL | System call | syscall | PC+=4 |
+------------------+----------------------------------------------------+-------------------------+----------------------------------------------------------+
| XOR* | Bitwise exclusive or | xor $a, $b, $c | $a = $b^$c |
+------------------+----------------------------------------------------+-------------------------+----------------------------------------------------------+
注意:
1.未明確聲明PC更改的人可以假定在執行時PC + = 4;
2.標有星號的標記至少具有一個即時版本;
3.帶有雙星號的標記還有許多其他變體;
4.ADD變體只能將SUB(U)作為對應物;
5.DIV變體有一個MULT(U)對應物;
6.j和b指令之間的一般區別是分支使用PC相對位移,而跳轉使用絕對地址,當考慮使用PIC時,這一點非常重要。
最初的偵查階段就到這里了,上面的MIPS32匯編表只是所有可用指令的超集,即使不熟悉MIPS組裝,以上表格也足以應對第二部分的內容學習了!
本系列的第二部分,我們將深入研究IDA中的imgdecrypt二進制文件,敬請關注!
參考
- MIPS? Architecture for Programmers Volume II-A: The MIPS32? Instruction Set Manual
- MIPS32? Instruction Set Quick Reference
- MIPS Instruction Reference
- MIPS Architecture and Assembly Language Overview
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1523/
暫無評論