破解 D-Link DIR3060 固件加密-侦查篇

译者:知道创宇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)

init_Entropy

曲线没有任何下降,我们无法提取有关目标的任何信息...

尝试

我们购买了比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

dir882_entropy

截止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.jb指令之间的一般区别是分支使用PC相对位移,而跳转使用绝对地址,当考虑使用PIC时,这一点非常重要。

最初的侦查阶段就到这里了,上面的MIPS32汇编表只是所有可用指令的超集,即使不熟悉MIPS组装,以上表格也足以应对第二部分的内容学习了!

本系列的第二部分,我们将深入研究IDA中的imgdecrypt二进制文件,敬请关注!

参考

以上是 破解 D-Link DIR3060 固件加密-侦查篇 的全部内容, 来源链接: utcz.com/p/199891.html

回到顶部