过去的几个月我一直在周游以指导人们如何对嵌入式设备进行漏洞利用,单单幻灯片已经不足以承载足够的信息,所以我将所有的都写下来以便与知识的消化。接下来的内容是 第一部分,介绍了一些嵌入式设备端的软件。
我决定首先介绍软件 因为很多漏洞都发生在软件端,从二进制可执行程序到驱动程序。第二部分会介绍硬件层,教授 JTAG 是如何工作的,如何利用修改硬件绕过密码保护或提取可入侵目标设备的机密信息。
目录
1 使用Binwalk提取固件
2 学习目标设备的汇编
3 GPL
4 漏洞利用
5 DVRFv0.3 socket_bof 解决方案
6 引用参考
1 使用Binwalk提取固件
当你能够得到你有的嵌入式设备的二进制固件的时候,或许你想看看里面有什么。幸运的是有个开源的工具 Binwalk,可分析目标的二进制文件中的 Magicbytes,看这里。
为了更加直观的表现,这里使用Binwalk提取DVRFv0.3
通过 binwalk -e filename提取二进制文件的内容:
binwalk_dvrf3.png
binwalk 显示已知的结构以及在二进制文件中的偏移量
Offset_binwalk_arrow.png
在vim中使用 xxd 显示TRX和gzip偏移量,确实和Binwalk提供的相匹配(使用vim打开DVRFv0.3,在命令模式下输入%!xxd)
gzip_trx_offset_arrows.png
Binwalk交叉参考出TRX结构
trx_magic_bytes_git.png
Binwalk交叉参考出gzip结构的
gzip_github.png
根据Binwalk发现的内容,使用vim和xxd交叉参考出SquashFS结构的偏移量
squashfs_dvrf_arrows.png
binwalk 交叉参考出SquashFS结构
2学习目标设备的汇编
如果对目标设备使用的汇编不熟悉,可以使用C语言和反汇编器快速学习。我认为,下面的内容是学习一门新汇编最先要去看的部分。
参数传递
函数的进入和返回
堆栈的使用
函数的调用
条件分支
参数传递
有个简单的C语言程序, 传递2个int 参数给另一个函数并且返回二者之和。
当你编译完成C程序后你想对生成的可执行文件进行反汇编
Note : 使用自己熟悉的反汇编工具, 这边使用的是Radare2
虽然我们可以看到图形话的视图,不过也可以按g 然后按 a 查看函数 pass_args_to_me
为了理解了当传递的参数数量多于参数寄存器数量的情况。比如在MIPS中寄存器参数使用$a0 - $a3 ,所以修改上面的代码,增加传递的参数使得参数数量大于4
将编译后的可执行文件使用radare2进行反汇编,查看生成的汇编
可以看到当参数个数大于4时,多余的参数会被压入栈中
函数的进入 调用 返回
需要注意的是:在 MIPS和ARM处理器中的返回地址寄存器。当一个跳转链接指令在MIPS中执行的时候,返回地址寄存器的地址是当前指令指针+8 bytes,8bytes的偏移是因为pipeling,因为PC+4 会在跳转发生前执行。让我们编译一个在返回main函数前调用2个或更多函数的程序。
记住 发生函数调用(JAL)会把 $PC+8 保存到$ra寄存器中,但是如果被调用函数还会调用其他函数时,$ra寄存器会被覆盖,调用者的地址会丢失。为了防止这种情况,返回地址首先被保存到函数入口的栈上.所以我们可以看到所有的函数会将返回地址保存到栈上,除了 函数call_two,因为call_two()没有调用其他函数。
只是对函数入口进行分析我们就可以判断这个函数是否调用了其他函数。当试图找内存堆栈溢出漏洞的时候这个技巧很有用
条件分支
分析一个新架构的时候,最重要的事情之一是处理器怎么处理条件分支。像之前一样我们使用c和radare2进行分析。
下面的程序会传入一个命令行参数,类型为int, 并判断是否小于5
查看编译器会产生怎样的汇编来满足条件
可以看到当比较结果为小于的时候,使用了 slti。 学习一种新的汇编语言的时候,由于大量的比较运算符和类型,条件判断会花费大部分的时间,参考c语言中的表达, 确保你分析了所有生成条件分支的方法。例如 :在MIPS架构中,有时既可以使用有符号立即数,又可以使用无符号立即数,这可能会被滥用。
现在你看了上面一些例子,你掌握那些技巧后, 就可以在只有编译器和反汇编器的情况下学习任何处理器的架构和汇编。不这样的话,那就只能不幸的用更艰苦的方式学习,查看处理器的开发手册,甚至涉及自己的汇编器,模拟器,反汇编器。
3GPL
如果你审计的设备使用了开源软件,那么软件应该是遵循GPL授权的。那么如果开发者使用代码并编译的话,源码必须公开,不公开就违反了GPL协议。
很多路由器和小型设备使用Linxu(或者FressRTOS), Busybox,和其他授权GPL协议开源软件,所以开始反汇编之前可以在Google搜索一小段供应商的或者产品的源代码。下面是一些我搜索到的示例源代码库.
4漏洞利用
这一部分假定读者有利用内存漏洞的基础知识。如果没有,可以查看底部的 SmashtheStack, SmashtheStack是我开始学习x86漏洞利用的地方。
如果你在审计的是MIPS架构的嵌入式Linux设备, 那么很有可能在分析目标二进制文件的时候看到的是一下内容
如你所见,栈区和堆区被标记为可执行,所以不必担心NX(Not execute), 虽然栈上是可执行的,但是为了让代码执行,ROP(return-oriented Programming)也是必要的。你也会发现 ASLR在大部分设备上都不会生效,所以不必要寻找首先泄露漏洞的信息
译者注 :NX bit 是 一些CPU内存管路单元的特性,允许特定内存页可执行或不可执行。more of NX
模拟
一旦使用Binwalk提取固件,为了分析崩溃你会想模拟运行二进制文件。我个人使用的静态编译版本的QEMU,可以使用 chroot,在提取的固件环境中加载程序。于是漏洞利用者可以使用与目标设备同一套的libc库,改变得仅仅只是libc的地址。而有时使用QEMU
模拟一阵套系统也是必要的,因为主机也会不支持二进制文件使用的IO操作而导致崩溃。
如果你使用的是基于debian的Linux发行版, 你可以通过 apt-get安装QEMU
sudo apt-get install qemu-user-static qemu-system-*
安装完成QEMU,需要将qemu的可执行文件拷贝到提取的固件的根目录处。例如我们在你DVRFv0.3中使用MIPS little Endian的模拟器。
cp `which qemu-mipsel-static` ./
这边我们使用可以被攻破的二进制文件
/pwnable/lntro/stack_bof_01 ,并为之写一段利用代码。然后将payload作为程序参数 ,看看会发生什么。
二进制的源代码:
我们有一个简单的栈溢出漏洞了,目的是执行 dat_shell函数。但我们分析ELF文件的时候可以看到如下:
Entry point address: 0x00400630
因为Payload中不能有NULL字符,于是得依靠部分覆盖来执行,因为是小端格式,我们可以覆盖最低的3个字节,最高位置NULL, 在大端机不适用。
为了演示模拟环境的功能, 我会编写payload并展示怎么找到模拟环境中加载库的地址。
gdb远程调试附加进程
可以看到 CP置为了 A8gA ,可以算出偏移量为 204, 即$RA在208字节上,在这边我们只会覆盖4字节中的3字节。
再次尝试,使$RA寄存器为 0x42424242
我们想要跳过修改$gp的指令,它会导致程序崩溃, 我以我们跳转到 0x0040095c
我们也可以打断点来确认是否跳转到了函数正确的偏移地址
所以构建ROP 链的时候你所要做的就是替换libc的地址(可以通过 cat /proc/[pid]/maps获得).你需要的是libc的基址。如果构造的ROP链在QEMU中可以正常运行那么99%可以在真实设备上运行。
5DVRFv0.3 socket_bof 解决方案
当设计DVRF项目的实验的时候,我想纳入我所见过的大部分的常见漏洞类型。最常见的是栈溢出漏洞,如果不熟悉汇编的话会有点挑战。
下面的漏洞利用代码花了大概8个小时,因为自己人在学MIPS汇编 ,这段代码是在QEMU上完成编写的。
因为栈上可执行,且库文件没有地址偏移,所以我们可以对ROP链进行硬编码,但是ROP的本质是将$SP的值想想一个可以调用的寄存器。我认为硬编码栈地址不够可靠,我更加喜欢使用偏移量来代替,下面是socket_bof的内存映射,
地址0x2ab3e000是 libc可执行块的基址, 当测试实际设备的时候,在QEMU上编写的exploit代码中,这是唯一需要更改的地方。
整个ROP 链 都是使用Radare2的 /R功能来完成的,比如,我想要查找 mmove t9, a1 来作为 ROP 的最后一小部分,我们可以按照如下方式来寻找:
Note : 一开始我是准备自己编写shellcode的,不过了解到了一个项目 Bowcaster已经给出了。所以这边展示流程,我根据下面的C语言代码来进行模块化.
如果我们查看 Bowcaster Reverse_TCP 的shellcode,会发现上面的C代码和Bowcaster的Shellocde是一致的
首先设置 Socket ( syscall Value 4183)
连接socket (syscall value 4170)
调用dup2 (syscall value 4063)
执行 sh (syscall value 4011)
我们可以通过radare2 反汇编C语言程序来验证syscall。
我们可以看到 C中调用socket()函数就是 syscall 4183,其他的系统调用号也可以这样来进行查看。
注意shellcode在QEMU的用户模式下不是100%成功的, 表现在于你会看到一个TCP的反向连接,但是不会弹出shell而是一段错误信息。而这段Shellocde在实际设备上能够正常运行。
更加简单的方式去分析运行中的是使用qira(QEMU Interactive Runtime Analyse),下面的图片展示了Qira是如何在不需要断点的情况下分析二进制文件
qira_shellcode.jpg
基于web的Qira 输出显示了所有的指令和系统调用
所以为了编写漏洞利用代码而重新造轮子是没有必要的, 而设计自己的shellcode 和shellcode的编码器则是进行漏洞利用的很好的锻炼方式。当决定自己设计之前确保使用过所有可用的工具。如果已经存在的shellcode 正好适用目标设备,那么拿来用没有什么错,但是确认要对网上找来的shellcode进行代码审计。
6引用参考
审核编辑:汤梓红
评论
查看更多