译者:知道创宇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
[...]
这里有很多有用的东西,我只是删除了“[…]”所指示的行,最值得注意的是以下几点:
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汇编语言有点生疏的人来说,这里是简短的入门。
大多数人可能都很熟悉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
。$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
二进制文件,敬请关注!
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1523/