破解 D-Link DIR3060 固件加密-侦查篇
2021-03-24 12:47:00 Author: paper.seebug.org(查看原文) 阅读量:184 收藏

译者:知道创宇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二进制文件,敬请关注!

参考


Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1523/


文章来源: https://paper.seebug.org/1523/
如有侵权请联系:admin#unsafe.sh