IOT固件仿真与gdbserver远程调试
IOT 固件仿真
想要复现真实的硬件环境,但是又没有真实的硬件设备,我们就需要对固件进行仿真
注意:本文需要固件环境搭建和固件分析作为前置基础,请先参考本站《IOT环境搭建与固件分析》一文
使用 QEMU 仿真
固件仿真以 Cisco 的
RV34X-v1.0.03.29-2022-10-17-13-45-34-PM.img
固件为例
确定仿真架构
按照本站的《IOT环境搭建与固件分析》一文中《未加密固件的分析》章节提取出固件的文件系统后,接下来进行路由器的仿真
由于 RV34X-v1.0.03.29-2022-10-17-13-45-34-PM.img
固件是 32 位小端序 ARM 架构
下载 QEMU 镜像
首先需要下载对应 ARM 架构的 QEMU 内核映像文件,以及磁盘映像文件,下载地址:Index of /~aurel32/qemu
这里我们选择 armhf
,下载图中三个文件:
也可以直接通过 wget
下载:
wget https://people.debian.org/~aurel32/qemu/armhf/debian_wheezy_armhf_standard.qcow2 https://people.debian.org/~aurel32/qemu/armhf/vmlinuz-3.2.0-4-vexpress https://people.debian.org/~aurel32/qemu/armhf/initrd.img-3.2.0-4-vexpress
这三个文件的作用:
文件 | 作用 |
---|---|
debian_wheezy_armhf_standard.qcow2 | 这是一个 QEMU 镜像文件,包含了 Debian Wheezy 操作系统的 ARMHF 架构的根文件系统 |
vmlinuz-3.2.0-4-vexpress | 这是一个压缩的 Linux 内核镜像文件,版本为 3.2.0,通常在引导过程中被解压并加载到内存中 |
initrd.img-3.2.0-4-vexpress | 这是一个初始 RAM 磁盘映像文件,在引导过程中加载到内存中的临时根文件系统,用于在实际根文件系统挂载之前执行一些必要的初始化任务 |
为了方便理解,其实 Kali Linux 本机也存在这样的文件(只不过是 Kali 内核映像),在
/boot
目录下:
启动 QEMU 虚拟机
首先使用如下命令启动 QEMU ARM 虚拟机:(使用系统级的 qemu-system-arm
)
sudo qemu-system-arm \
-M vexpress-a9 \
-kernel ./vmlinuz-3.2.0-4-vexpress \
-initrd ./initrd.img-3.2.0-4-vexpress \
-drive if=sd,file=./debian_wheezy_armhf_standard.qcow2 \
-append "root=/dev/mmcblk0p2" \
-net nic \
-net tap,ifname=tap0,script=no,downscript=no \
-nographic \
-smp 4
当然也可以将这条命令写入 start.sh
文件(看个人喜好),即:
#!/bin/bash
sudo qemu-system-arm \
-M vexpress-a9 \
-kernel ./vmlinuz-3.2.0-4-vexpress \
-initrd ./initrd.img-3.2.0-4-vexpress \
-drive if=sd,file=./debian_wheezy_armhf_standard.qcow2 \
-append "root=/dev/mmcblk0p2" \
-net nic \
-net tap,ifname=tap0,script=no,downscript=no \
-nographic \
-smp 4
然后就可以通过 start.sh
脚本来启动 QEMU 虚拟机,而不是使用命令行:
sudo chmod +x start.sh
./start.sh
启动 QEMU 虚拟机的常用参数及其解释:
参数 | 解释 |
---|---|
-M <machine> | 指定仿真目标机器类型(如:pc 、vexpress-a9 ) |
-kernel <file> | 指定内核映像文件 |
-initrd <file> | 指定初始 RAM 磁盘映像文件 |
-drive <opts> | 高级硬盘映像文件配置选项(如:file=<file>,if=<interface>,media=<media> ) |
-drive file=<file> | 指定硬盘映像文件 |
-append <cmdline> | 向内核传递命令行参数 |
-net nic | 添加一个虚拟网络接口卡(默认为 tap0 ) |
-net user | 使用用户模式网络堆栈 |
-net tap,<opts> | 使用 TAP 接口进行网络配置(如:ifname=<tapN> 、script=<script> 、downscript=<script> ) |
-netdev <opts> | 高级网络配置选项(例如:tap,id=<id>,ifname=<tapN>,script=<script>,downscript=<script> ) |
-nographic | 禁用图形输出,所有输出通过控制台 |
-smp <n> | 指定虚拟机使用的虚拟 CPU 数量 |
-m <size> | 指定虚拟机内存大小(例如:512M 、1G ) |
-cpu <model> | 指定虚拟 CPU 模型(如:qemu64 、host ) |
-hda <file> | 指定第一个虚拟硬盘映像文件 |
-hdb <file> | 指定第二个虚拟硬盘映像文件 |
-hdc <file> | 指定第三个虚拟硬盘映像文件 |
-hdd <file> | 指定第四个虚拟硬盘映像文件 |
-cdrom <file> | 指定 CD-ROM 映像文件 |
-boot <device> | 指定启动设备顺序(例如:a 、c 、d 、n ) |
-device <device> | 添加虚拟设备(例如:virtio-net-device,netdev=<id> ) |
-serial <opts> | 配置虚拟机的串行端口(如:file:<file> 、pty 、tcp:<host>:<port> ) |
-monitor <opts> | 配置 QEMU 监视器(如:file:<file> 、stdio 、tcp:<host>:<port> ) |
-snapshot | 启动虚拟机时不保存更改(快照模式) |
-usb | 启用 USB 支持 |
-device usb-<device> | 添加 USB 设备(如:usb-mouse 、usb-keyboard 、usb-storage ) |
-redir <opts> | 重定向主机到虚拟机的网络端口(如:tcp:<host-port>::<guest-port> ) |
-display <opts> | 配置显示输出(如:sdl 、curses 、gtk 、vnc=<host>:<port> ) |
-spice <opts> | 配置 SPICE 协议支持,用于高性能的远程显示(如:port=<port> ) |
-enable-kvm | 启用 KVM 加速(需要硬件支持) |
-full-screen | 以全屏模式启动虚拟机 |
-pidfile <file> | 将 QEMU 进程 ID 写入指定文件 |
-daemonize | 以守护进程模式运行 QEMU |
-rtc <opts> | 配置 RTC 选项(如:base=utc 、base=localtime 、clock=host ) |
-name <name> | 指定虚拟机名称 |
-uuid <uuid> | 指定虚拟机 UUID |
如果出现如下报错:
qemu-system-arm: Invalid SD card size: 25 GiB
SD card size has to be a power of 2, e.g. 32 GiB.
You can resize disk images with 'qemu-img resize <imagefile> <new-size>'
(note that this will lose data if you make the image smaller than it currently is).
原因是 SD card size
应该为 2
的幂,当前是 25GB
,应该改为 32GB
之类的数值
使用如下命令解决:
sudo qemu-img resize ./debian_wheezy_armhf_standard.qcow2 32G
再次启动 QEMU 虚拟机,需等待几分钟后会出现输入账号密码界面,账号密码都是 root
:
配置虚拟网卡实现通信
此时虽然开启了 QEMU 虚拟机,但没有 ip 地址,因此该 QEMU 虚拟机还无法与 Kali Linux 本机通信:
我们需要将路由器的文件系统上传到 QEMU 虚拟机,因此必须保证能与 QEMU 虚拟机通信
通信的大概原理就是设置一个网桥,然后开一个接口,把这个接口给 QEMU,然后流量的发送都通过这个网桥,画成图的话就是下面这个样子:
我这里的网卡是 eth0
:
在 Kali Linux 中创建一个 net.sh
脚本,并写入如下内容:
#!/bin/sh
sudo brctl addbr br0 # 添加一座名为 br0 的网桥
sudo ifconfig br0 192.168.2.3/24 up # 启用 br0 接口
sudo tunctl -t tap0 -u root # 创建一个只许 root 访问的 tap0 接口
sudo ifconfig tap0 192.168.2.1/24 up # 启用 tap0 接口
sudo brctl addif br0 tap0 # 在虚拟网桥中增加一个 tap0 接口
赋予执行权限并运行该脚本
sudo chmod +x net.sh
./net.sh
如果运行出现报错:
sudo: brctl:找不到命令
说明缺少相关库,安装后再次运行即可:
sudo apt install bridge-utils uml-utilities
如果配置成功,会多出两个虚拟网卡 br0
和 tap0
:(每次重启 Kali Linux 后都需要重新配置一次)
如果以后不需要了,使用如下脚本 unset_net.sh
取消即可:
#!/bin/sh
sudo ifconfig br0 down # 关闭设备
sudo ifconfig tap0 down # 关闭设备
sudo brctl delbr br0 # 删除网桥
sudo tunctl -d tap0 # 删除网卡
接下来在 QEMU 虚拟机中设置 ip 地址,注意与 tap0
在同一网段:(每次重启 QEMU 虚拟机后都需要重新配置一次)
(root@debian-armhf) ifconfig eth0 192.168.2.2/24 up
测试一下 Kali Linux 与 QEMU 虚拟机能否相互 ping 通:
到此为止,说明网络配置成功
上传路由器文件系统
接下来,就是将路由器的文件系统上传到 QEMU 虚拟机,让 QEMU 虚拟机来启动路由器
这里注意:要先把文件系统压缩打包,然后用 scp
命令传到 QEMU 虚拟机中,再将文件系统解压
如果直接用
scp
命令上传文件夹,后续有可能会缺少文件,所以必须先压缩
在 rootfs
文件夹所在的目录下,使用如下命令打包并上传至 QEMU 虚拟机:
tar -czvf RV34X_v1.0.03.29_rootfs.tar.gz rootfs
sudo scp RV34X_v1.0.03.29_rootfs.tar.gz root@192.168.2.2:~/
然后在 QEMU 虚拟机中解压:
(root@debian-armhf) tar -xzvf RV34X_v1.0.03.29_rootfs.tar.gz
得到解压后的文件系统:
启动路由器服务
接下来进行仿真时要先用 chroot
命令创建隔离的文件系统环境,但这会导致无法在隔离的文件系统中访问原本的 /proc
和 /dev
目录,因为它们是特殊的虚拟文件夹(用于提供系统信息和设备的访问)
为了让 QEMU 环境正常运行,需将原本 QEMU 的 /proc
和 /dev
目录挂载到新创建的隔离环境中:
chmod -R 777 rootfs
cd rootfs/
mount --bind /proc proc
mount --bind /dev dev
注意检查一下软连接是否正确:
启动路由器的终端,开启路由器服务:
chroot . /bin/sh
/etc/init.d/boot boot # 创建初始化环境
generate_default_cert # 生成 ssl 证书文件
/etc/init.d/confd start # 启动 confd 服务
/etc/init.d/nginx start # 启动 nginx 服务
如果开启 nginx 服务显示端口占用,重启一下 nginx 服务:
/etc/init.d/nginx restart
至于为什么启动 Cisco RV34X 路由器服务的顺序是这样的,这里不再详细说明
参考文章:
- [原创]从零开始复现 CVE-2023-20073-智能设备-看雪-安全社区|安全招聘|kanxue.com 的《启动服务&&解决报错》一节
- Cisco RV340仿真_cve-2023-20073-CSDN博客 的《启动文件系统》一节
执行 /etc/init.d/boot boot
后:
/ # /etc/init.d/boot boot
mount: mounting debugfs on /sys/kernel/debug failed: No such file or directory
Mounting mnt partitions..mount: mounting /dev/mtdblock9 on /mnt/configcert failed: No such device
mount: mounting /dev/mtdblock10 on /mnt/avcsign failed: No such device
mount: mounting /dev/mtdblock11 on /mnt/webrootdb failed: No such device
mount: mounting /dev/mtdblock12 on /mnt/license failed: No such device
done.
create_meta_data_xml begin
meta_data_gen_state: 0
meta_data_gen_state: 1
create_meta_data_xml end
执行 generate_default_cert
后:
/ # generate_default_cert
touch: /tmp/stats/certstats.tmp: No such file or directory
/usr/bin/certscript: line 1: can't create /tmp/stats/certstats.tmp: nonexistent directory
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to the standard locale ("C").
cp: can't stat '/tmp/stats/certstats.tmp': No such file or directory
Default
执行 /etc/init.d/confd start
后:
/ # /etc/init.d/confd start
TRACE Connected (maapi) to ConfD
attaching to init session...
TRACE MAAPI_ATTACH --> CONFD_OK
TRACE MAAPI_DELETE /avc-meta-data --> CONFD_OK
TRACE MAAPI_LOAD_CONFIG_FILE --> CONFD_OK
TRACE Connected (maapi) to ConfD
attaching to init session...
TRACE MAAPI_ATTACH --> CONFD_OK
TRACE MAAPI_DELETE /device-os-types --> CONFD_OK
TRACE MAAPI_LOAD_CONFIG_FILE --> CONFD_OK
TRACE Connected (maapi) to ConfD
attaching to init session...
TRACE MAAPI_ATTACH --> CONFD_OK
TRACE MAAPI_DELETE /webfilter-meta-data --> CONFD_OK
TRACE MAAPI_LOAD_CONFIG_FILE --> CONFD_OK
0
uci: Entry not found
0
uci: Entry not found
rm: can't remove '/tmp/update.sh': No such file or directory
uci: Entry not found
uci: Entry not found
uci: Parse error (option/list command found before the first section) at line 2492, byte 1
cp: can't stat '/tmp/etc/syslog_config_template': No such file or directory
sed: /tmp/syslog-ng.conf: No such file or directory
Error opening configuration file; filename='/tmp/syslog-ng.conf', error='Success (0)'
SIOCGMIIPHY: No such device
Failed to connect to ubus
Failed to parse json data: unexpected end of data
Failed to connect to ubus
Failed to parse json data: unexpected end of data
Failed to connect to ubus
Failed to parse json data: unexpected end of data
Failed to connect to ubus
Failed to parse json data: unexpected end of data
Failed to connect to ubus
Failed to parse json data: unexpected end of data
Failed to connect to ubus
Failed to parse json data: unexpected end of data
Failed to connect to ubus
Failed to parse json data: unexpected end of data
Failed to connect to ubus
Failed to parse json data: unexpected end of data
Failed to connect to ubus
Failed to parse json data: unexpected end of data
Failed to connect to ubus
Failed to parse json data: unexpected end of data
Failed to connect to ubus
0
json_object_from_file: error reading file /tmp/webcache/dep: No such file or directory
PnP Agent is starting!
执行 /etc/init.d/nginx start
后:
/ # /etc/init.d/nginx start
chown: /var/firmware: No such file or directory
chown: /var/3g-4g-driver: No such file or directory
chown: /var/in_certs: No such file or directory
chown: /var/signature: No such file or directory
chown: /var/language-pack: No such file or directory
chown: /var/configuration: No such file or directory
FAILED: maapi_get_elem(ms, mtid, &val, argv[0]), Error: item does not exist (1): /firewall-basic-settings:firewall/remote-web-management/cert does not exist, in function do_maapi_get, line 1463
touch: /tmp/stats/certstats.tmp: No such file or directory
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to the standard locale ("C").
cp: can't stat '/tmp/stats/certstats.tmp': No such file or directory
FAILED: maapi_get_elem(ms, mtid, &val, argv[0]), Error: item does not exist (1): /ciscosb-restconf:ciscosb-restconf/transport/https/cert does not exist, in function do_maapi_get, line 1463
touch: /tmp/stats/certstats.tmp: No such file or directory
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to the standard locale ("C").
cp: can't stat '/tmp/stats/certstats.tmp': No such file or directory
FAILED: maapi_get_elem(ms, mtid, &val, argv[0]), Error: item does not exist (1): /ciscosb-netconf:ciscosb-netconf/transport/ssh/cert does not exist, in function do_maapi_get, line 1463
touch: /tmp/stats/certstats.tmp: No such file or directory
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
LANGUAGE = (unset),
LC_ALL = (unset),
LANG = "en_US.UTF-8"
are supported and installed on your system.
perl: warning: Falling back to the standard locale ("C").
cp: can't stat '/tmp/stats/certstats.tmp': No such file or directory
/ # [uWSGI] getting INI configuration from /etc/uwsgi/jsonrpc.ini
[uWSGI] getting INI configuration from /etc/uwsgi/blockpage.ini
[uWSGI] getting INI configuration from /etc/uwsgi/upload.ini
*** Starting uWSGI 2.0.15 (32bit) on [Sun Jun 9 16:04:10 2024] ***
compiled with version: 4.8.3 on 17 October 2022 13:32:49
*** Starting uWSGI 2.0.15 (32bit) on [Sun Jun 9 16:04:10 2024] ***
*** Starting uWSGI 2.0.15 (32bit) on [Sun Jun 9 16:04:10 2024] ***
compiled with version: 4.8.3 on 17 October 2022 13:32:49
os: Linux-3.2.0-4-vexpress #1 SMP Debian 3.2.51-1
os: Linux-3.2.0-4-vexpress #1 SMP Debian 3.2.51-1
compiled with version: 4.8.3 on 17 October 2022 13:32:49
nodename: Router
machine: armv7l
nodename: Router
clock source: unix
os: Linux-3.2.0-4-vexpress #1 SMP Debian 3.2.51-1
machine: armv7l
nodename: Router
clock source: unix
machine: armv7l
pcre jit disabled
clock source: unix
detected number of CPU cores: 4
pcre jit disabled
current working directory: /
detected number of CPU cores: 4
pcre jit disabled
detected binary path: /usr/sbin/uwsgi
current working directory: /
detected number of CPU cores: 4
detected binary path: /usr/sbin/uwsgi
setgid() to 33
current working directory: /
detected binary path: /usr/sbin/uwsgi
setgid() to 33
setgid() to 33
setuid() to 33
your processes number limit is 961
your memory page size is 4096 bytes
detected max file descriptor number: 1024
lock engine: pthread robust mutexes
setuid() to 33
your processes number limit is 961
setuid() to 33
your memory page size is 4096 bytes
detected max file descriptor number: 1024
lock engine: pthread robust mutexes
thunder lock: disabled (you can enable it with --thunder-lock)
thunder lock: disabled (you can enable it with --thunder-lock)
your processes number limit is 961
your memory page size is 4096 bytes
detected max file descriptor number: 1024
lock engine: pthread robust mutexes
uwsgi socket 0 bound to TCP address 127.0.0.1:9001 fd 3
thunder lock: disabled (you can enable it with --thunder-lock)
uwsgi socket 0 bound to TCP address 127.0.0.1:9003 fd 3
your server socket listen backlog is limited to 100 connections
your server socket listen backlog is limited to 100 connections
your mercy for graceful operations on workers is 60 seconds
your mercy for graceful operations on workers is 60 seconds
uwsgi socket 0 bound to TCP address 127.0.0.1:9000 fd 3
your server socket listen backlog is limited to 100 connections
your mercy for graceful operations on workers is 60 seconds
mapped 128512 bytes (125 KB) for 1 cores
mapped 128512 bytes (125 KB) for 1 cores
*** Operational MODE: single process ***
*** Operational MODE: single process ***
initialized CGI path: /www/cgi-bin/upload.cgi
*** no app loaded. going in full dynamic mode ***
*** uWSGI is running in multiple interpreter mode ***
spawned uWSGI master process (pid: 4183)
initialized CGI mountpoint: /blocked.php = /www/cgi-bin/blockpage.cgi
spawned uWSGI worker 1 (pid: 4191, cores: 1)
mapped 321280 bytes (313 KB) for 4 cores
*** no app loaded. going in full dynamic mode ***
*** uWSGI is running in multiple interpreter mode ***
spawned uWSGI master process (pid: 4182)
*** Operational MODE: preforking ***
initialized CGI mountpoint: /jsonrpc = /www/cgi-bin/jsonrpc.cgi
*** no app loaded. going in full dynamic mode ***
*** uWSGI is running in multiple interpreter mode ***
spawned uWSGI master process (pid: 4181)
spawned uWSGI worker 1 (pid: 4192, cores: 1)
spawned uWSGI worker 1 (pid: 4193, cores: 1)
spawned uWSGI worker 2 (pid: 4194, cores: 1)
spawned uWSGI worker 3 (pid: 4195, cores: 1)
spawned uWSGI worker 4 (pid: 4196, cores: 1)
然后打开浏览器,访问 QEMU 虚拟机的 ip 地址,验证服务是否开启:(如果使用了代理,请关闭代理,否则可能无法访问)
如果能看到 Cisco Router 的登陆界面,那么到这里为止,我们就仿真成功了
注意:
如果按照以上步骤还是无法访问,请检查浏览器的代理设置是否关闭,或者重启 QEMU 虚拟机后再重新启动一次路由器服务(每次重启 QEMU 虚拟机后都需要重新配置一次 ip 地址)
第一次启动路由器报错会多一些,之后重新启动报错会相对少一点,但我们毕竟是仿真,并不是真正的路由器硬件环境,因此启动过程中有报错是很正常的,只要能将我们需要的服务跑起来即可
使用 GNS3 仿真
GNS3 仅可用于 Cisco 产品的仿真,例如 Cisco 路由器、Cisco ASA 防火墙等
仿真 Cisco 路由器
以 Cisco 的
c7200-adventerprisek9-mz.124-22.T.bin
固件为例进行仿真
使用 GNS3 模拟 Cisco 路由器,需要先下载其路由器对应的固件,这里以 c7200 路由器为例
路由器固件默认存放在 GNS3 的 /images/IOS
目录,具体路径在此查看:
将 c7200-adventerprisek9-mz.124-22.T.bin
固件置于上图的 E:\GNS3\images\IOS
目录下
在 Dynamips 下的 IOS routers 中,新建一个 IOS 路由器模板,选择该固件:
一路下一步,保持默认设置即可
计算并设置 idle
值:
添加完成:
如果后续需要设置路由器的插槽模块和其他配置,点击 Edit 进行修改即可
新建一个 GNS3 项目,拖出一个 c7200 路由器,在路由器上右键选择 start
开启路由器,如果正常开机,右上角会由红色变为绿色:
然后在路由器上右键选择 console
,不出意外的话会弹出 SecureCRT 的终端,检查是否显示路由器设备型号,型号是否正确:
也可以通过 show hardware
命令查看路由器的硬件信息:
到此就说明我们仿真成功了
仿真 Cisco 防火墙
Cisco 防火墙名为 ASA(Adaptive Security Appliance)
这里首先要了解一下 Cisco 防火墙的各种文件类型和作用:
文件类型 | 作用 |
---|---|
asa842-k8.bin | Cisco ASA 固件镜像,包括 ASA 防火墙的操作系统和所有相关组件,可以从中获取 asa842-vmlinuz 和 asa842-initrd.gz |
asa842-vmlinuz | 一个压缩的 Linux 内核镜像文件,包含了所有必要的内核模块和驱动程序,可以启动并管理硬件资源,负责初始化硬件并启动操作系统 |
asa842-initrd.gz | 包含一个临时的根文件系统,供操作系统内核在引导时使用。内核会先加载 initrd ,从中加载必要的驱动程序和文件,直到可以切换到真正的根文件系统 |
asav941-200.qcow2 | QEMU 磁盘镜像,用来虚拟化存储设备的磁盘映像文件,用作虚拟机的硬盘,存储操作系统、配置和数据,适用于新版本 GNS3 的仿真 |
旧版 GNS3
由于一些老版本的 ASA 防火墙固件无法在新版本的 GNS3 下仿真,因此这里记录一下新版 GNS3 与旧版 GNS3 在固件仿真上的区别
以 GNS3 v1.3.10 模拟 ASA842 固件为例
- GNS3 v1.3.10 下载地址:Release Version 1.3.10 · GNS3/gns3-gui
- 固件下载地址:Index of /wp-content/themes/vantage/images/ASA/
与路由器不同,GNS3 要仿真 Cisco 防火墙还需要借助 QEMU
首先打开 CMD,进入 GNS3 安装目录下的 qemu-2.4.0
文件夹,并执行如下命令:
cd C:\Program Files\GNS3\qemu-2.4.0
qemu-img create FLASH 512M
这会在当前目录下生成一个 512 MB 的 FLASH 文件,这个就是我们的 QEMU 磁盘镜像:
防火墙固件默认存放在 GNS3 的 /images/QEMU
目录,将该 FLASH 文件移动到 GNS3 的 /images/QEMU
目录下
如果不配置 FLASH 文件,后面仿真可能会报如下错误:
Reading from flash... Flash read failed ERROR: MIGRATION - Could not get the startup configuration. Configuration has non-ASCII characters and will be ignored. Cryptochecksum (changed): d41d8cd9 8f00b204 e9800998 ecf8427e COREDUMP UPDATE: open message queue fail: No such file or directory/2 INFO: MIGRATION - Saving the startup errors to file 'flash:upgrade_startup_errors_202309110814.log'
下载我们需要的两个文件:asa842-initrd.gz
、asa842-vmlinuz
这两个文件可以直接在网上下载现成的,但也有一些版本是下载不到的,因此也可以考虑自己通过 ASA 固件制作这两个文件
下载 NCC Group 发布的 asatools
:
sudo git clone --recursive https://github.com/nccgroup/asatools /opt/asatools
在 asatools/asafw
目录下的 bin.py
就是用来从 ASA 固件 asa842-k8.bin
中获取 asa842-initrd.gz
和 asa842-vmlinuz
的工具:
python /opt/asatools/asafw/bin.py -f ASA防火墙固件路径(如:asa842-k8.bin) -u
在 ASA 固件 asa842-k8.bin
所在路径下,即可获得这两个文件:
asatools
是一个专门用于进行 Cisco ASA 防火墙固件处理的工具,功能很强大,其他功能可查看原仓库的介绍:nccgroup/asatools: Main repository to pull all NCC Group Cisco ASA-related tool projects.
在 QEMU 下的 QEMU VMs 中,新建一个 ASA 8.4(2)
:
将 RAM 设为 2048 MB
,ASA 防火墙至少需要 2 GB 的内存才能正常工作,否则可能会在启动 ASA 时发生崩溃
同时将 Qemu binary 改为 qemu-system-i386w.exe
一般来说,asa842-k8.bin
这种命名的固件是 32 位,而 asa842-smp-k8.bin
为 64 位
注意:
这一步如果没有更改 Qemu binary 的选项,后面开启 ASA 防火墙的终端时会显示:
- SecureCRT 终端:
"The remote system refused the connection."
- Putty 终端:
"127.0.0.1:2001 (ASA842-1)-Network error: Connection refused!- (inactive) - [Restart in 3s]"
导致无法连接
选择我们获取的两个文件,注意对应即可:
点击编辑,将 HDD 的第一块硬盘 hda
设置为前面创建的 FLASH 文件:
其他的配置均保持默认即可
其中 Kernel command line
默认为:
ide_generic.probe_mask=0x01 ide_core.chs=0.0:980,16,32 auto nousb console=ttyS0,9600 bigphysarea=65536 ide1=noprobe no-hlt -net nic
Options
默认为:
-icount auto -hdachs 980,16,32 -vga none -vnc none
拖出一个 ASA842 防火墙,在防火墙上右键点击 start
,然后右键打开 console
,此时右上角 ASA842 由红色变为绿色:
这就说明我们设置没有问题,等待 ASA 防火墙开机即可
仿真成功:
查看设备信息:
show version
输入 enable
从用户模式进入特权模式,进入系统:
enable # 可以简写为 en
# Password 密码直接回车,为空即可
输入 key 激活:(第二个 key 需要的时间会比较长)
activation-key 0x4a3ec071 0x0d86fbf6 0x7cb1bc48 0x8b48b8b0 0xf317c0b5
activation-key 0xb23bcf4a 0x1c713b4f 0x7d53bcbc 0xc4f8d09c 0x0e24c6b6
将更改写入:
wr
然后重启 ASA 防火墙,就可以看到刚刚的 key 被使用了:
新版 GNS3
注意:
旧版本 GNS3(如 GNS3 v1.3.10 版本)在仿真 ASA 防火墙固件时,通常需要
asa842-initrd.gz
和asa842-vmlinuz
之类的两个文件,其实就是 QEMU 用到的 Linux 内核镜像和 RAM 磁盘映像文件但是,在新版本的 GNS3 中不再推荐使用
asa842-initrd.gz
和asa842-vmlinuz
来进行仿真,而是用asav
版的固件来替代,否则会有如下弹窗:强行添加后,在 GNS3 控制台会报如下警告:
Warning ASA 8 is not supported by GNS3 and Cisco, please use ASAv instead. Depending of your hardware and OS this could not work or you could be limited to one instance. If ASA 8 is not booting their is no GNS3 solution, you must to upgrade to ASAv.
同时也无法开启路由器(开机后自动关机)
以 GNS3 v2.2.47 模拟 ASA941 固件为例
- GNS3 v2.2.47 下载地址:Release Version 2.2.47 · GNS3/gns3-gui
- 固件下载地址:asav941-200.qcow2
使用 qcow2 来导入 ASAv 防火墙固件时,不需要像老版本 GNS3 中那样提取两个固件映像文件
为了确定该 ASA 固件的架构,先在 Kali Linux 下通过 binwalk -Me
解压得到文件系统:
查看 busybox
文件,提示 busybox
被链接到 busybox.nosuid
,查看该文件:
可见该 ASA 固件系统的架构为 x86_64
注意:
使用 GNS3 模拟 ASAv 版本的固件时,不再需要 FLASH 文件
在 QEMU 下的 QEMU VMs 中,新建一个模板,ASAv 固件建议在 GNS3 VM 中运行:
选择 x86_64 架构的 QEMU,并将内存设置为 2048 MB
:(ASA 防火墙至少需要 2 GB 的内存才能正常工作,否则可能会在启动 ASA 时发生崩溃)
选择本地的 asav941-200.qcow2
文件,GNS3 会自动将其上传到 GNS3 VM 中:(如果选择 FLASH 文件会导致 ASA 防火墙开启后无法连接)
设置 ASAv941-200 为安全设备:
我们拖出一个 ASAv941-200,并右键 start
开启,右上角会由红色转变为绿色:
这里显示我们的 GNS3 VM 内存不足,将 GNS3 VM 虚拟机的内存给大一点即可
也可以直接在 GNS3 中设置:
此时我们尝试通过 ssh
连接 GNS3 VM:
选择开启一个 shell,可以在 /opt/gns3/images/QEMU
路径下看到我们上传的文件:
到这里,说明我们的 GNS3 VM 配置没有问题
在 GNS3 中,右键 ASAv941-200 连接上 console
:
发现可以正常开机,仿真成功(刚开机的话会比较慢,需要等待几分钟)
查看设备信息:
show version
可以看到系统镜像文件是:asa941-smp-k8.bin
输入 enable
从用户模式进入特权模式,进入系统:
enable # 可以简写为 en
# Password 密码直接回车,为空即可
查看当前 ASA 防火墙配置:
sh run
查看 ASA 设备上运行的内核进程的详细信息:
show kernel process # 该命令可以提供有关内核进程的名称、进程ID、状态、优先级、CPU使用率等信息
可以看到 lina 进程,lina 是一个包含所有 ASA 功能的 ELF 可执行文件,也是分析研究 ASA 防火墙的关键二进制文件
IOT 远程调试
我们常使用的 Ubuntu、Kali Linux 都是 x86 架构的,安装的 GDB 通常也只支持 x86 架构,而 IOT 固件需要我们进行 ARM、MIPS 等架构下的调试,而且是使用 gdbserver 进行远程调试
安装 gdb-multiarch
顾名思义,可以理解为这是一个支持多架构的 GDB
sudo apt install gdb-multiarch
设置架构的命令为:
# 首先运行 gdb-multiarch
gdb-multiarch
# 设置架构为 ARM
(gdb-multiarch) set architecture arm
# 输出为:The target architecture is set to "arm".
注意:
由于 GDB 不支持多架构,因此在 GDB 中使用
set architecture arm
命令会报错:Undefined item: "arm".
这个
gdb-multiarch
安装的版本通常与本机的 GDB 版本一致,且支持多架构,因此无需自己再去手动编译各种架构下的 GDB,我这里是 GDB v13.2:然而,如果你需要使用其他版本的 GDB,就需要手动编译对应版本的 GDB 源码,同时需要单独编译各种架构下的 GDB,比如
mips-linux-gnu-gdb
、arm-linux-gnueabihf-gdb
等
手动编译调试工具
在研究 IOT 漏洞时,我们往往需要上传 gdbserver 进行远程调试(可以是真机,也可以是仿真环境)
但是,上传到 IOT 设备的 gdbserver 版本需要与我们本地用于远程连接的 GDB 版本一致,如果 gdbserver 与 GDB 版本不一致,容易出现一些非预期的问题
编译好的 gdbserver 在网上都可以搜到,各种架构、各种版本资源都很多,这里就不说了
为了保证 gdbserver 与本地的 GDB 版本一致,当然最好是自己动手编译了
首先查看本地 GDB 版本,我这里以 Kali Linux 自带的 GDB 13.2 为例,在这个网站下载对应版本的 GDB 源码:Index of /gnu/gdb
如果在物理机上运行
arm-linux-gnueabihf
或mips-linux-gnu
等交叉编译工具链的话,建议选择与物理机同一个版本的 GDB 比较好当然也可以下载其他版本的 GDB 源码,然后自己编译 GDB,就可以获得与物理机不同版本的 GDB,不过不管 GDB 版本怎么选,gdbserver 的版本一定要和你连接远程所使用的 GDB 版本保持一致
MIPS 架构
下面以编译 MIPS 架构下的 gdbserver 为例(mipsel
)
首先下载 GDB 源码并解压:
wget https://ftp.gnu.org/gnu/gdb/gdb-13.2.tar.gz
tar zxvf gdb-13.2.tar.gz
cd gdb-13.2/
mkdir gdb13.2_mipsel
注意:由于是编译 MIPS 架构的 gdbserver,因此需要使用交叉编译工具
安装相关依赖:
# mips
sudo apt install linux-libc-dev-mips-cross libc6-mips-cross libc6-dev-mips-cross binutils-mips-linux-gnu gcc-mips-linux-gnu g++-mips-linux-gnu
# mipsel
sudo apt install linux-libc-dev-mipsel-cross libc6-mipsel-cross libc6-dev-mipsel-cross binutils-mipsel-linux-gnu gcc-mipsel-linux-gnu g++-mipsel-linux-gnu
编译 GDB
编译安装:
# 由于 GDB 是本机用来连接远程 IOT 设备的 gdbserver 的,所以不用指定 --host 选项和 CC 以及 CXX 变量的值,configure 会自动检测(即:本机运行不需要交叉编译)
./configure --target="mipsel-linux-gnu" --prefix="/home/wyy/下载/gdb-13.2/gdb13.2_mipsel/gdb/build/"
sudo make -j$(nproc)
sudo make install
执行完成后在 gdb-13.2/gdb13.2_mipsel/gdb/build/bin
路径下会生成一个 mipsel-linux-gnu-gdb
二进制程序,它是 x86 架构,可以直接在本机运行:
编译 gdbserver
编译安装:
# 清理缓存,避免与之前的编译配置冲突,如果清理后还是冲突,请重新解压 GDB 源码
sudo make distclean
# gdbserver 运行在 IOT 设备,因此需要使用交叉编译器
CC="mipsel-linux-gnu-gcc" CXX="mipsel-linux-gnu-g++" ./configure --target="mipsel-linux-gnu" --host="mipsel-linux-gnu" --prefix="/home/wyy/下载/gdb-13.2/gdb13.2_mipsel/gdbserver/build/" LDFLAGS="-static"
sudo make -j$(nproc)
sudo make install
相关参数的解释:
参数 | 解释 |
---|---|
CC | 用于编译 C 代码的编译器 |
CXX | 用于编译 C++ 代码的编译器 |
--build | 运行编译工具链的平台,也就是正在执行编译操作的平台。通常都不指定此参数 |
--host | 可执行程序将运行的平台,如果未指定此参数,则和 --build 相同。如果 --host 和 --build 不同,是交叉编译;否则是普通编译 |
--target | 可执行程序的目标平台,如果未指定此参数,则和 --host 相同。一般来说,程序将要运行在什么平台,--target 就是什么平台 |
LDFLAGS | 设置链接器标志,将 -static 传递给链接器,意味着进行静态链接 |
注意:
关于 GDB 和 gdbserver 的编译和编译选项,详细说明请参考:交叉编译问题记录-嵌入式环境下 GDB 的使用方法-腾讯云开发者社区-腾讯云
这里编译的是
mipsel
(小端序),如果要编译mips
(大端序),将命令中的mipsel
改为mips
即可(不指定参数的情况下,默认都是编译为 32 位程序)
如果编译过程中报错:
configure: WARNING: no enhanced curses library found; disabling TUI
checking for library containing tgetent... no
checking size of unsigned long long... 8
checking size of unsigned long... 4
checking size of unsigned __int128... 0
checking for library containing dlopen... none required
checking whether to use expat... auto
checking for libexpat... no
configure: WARNING: expat is missing or unusable; some features may be unavailable.
checking for libgmp... no
configure: error: GMP is missing or unusable
make[1]: *** [Makefile:12161:configure-gdb] 错误 1
make[1]: 离开目录“/home/wyy/下载/gdb-13.2”
make: *** [Makefile:1006:all] 错误 2
说明缺少 GMP,首先安装一下 GMP:(以 GMP v6.1.2 为例)
sudo apt install m4 # 安装依赖
sudo apt install libgmp-dev # 安装 gmp 库
wget https://gmplib.org/download/gmp/gmp-6.1.2.tar.xz
sudo tar -xf gmp-6.1.2.tar.xz
cd gmp-6.1.2
sudo ./configure --enable-cxx
# --enable-cxx: 配置 GMP 时,默认情况下不启用 C++ 支持,开启后还将安装 gmpxx.h header 以及 libgmpxx.dylib 和 / 或 libgmpxx.a 库
# 默认路径为 /user/local,也可以用 --prefix=/path_to_install 指定安装路径
sudo make -j$(nproc)
sudo make check
sudo make install
如果自己指定了安装路径,添加一下环境变量:
# 如果使用的是 bash 终端则打开 ~/.bashrc
sudo gedit ~/.zshrc
# 在文件最后加入如下两个---之间的内容,记得将路径改为自己的
# 例如安装在 /opt/gmp-6.1.2 下
----------------------------------------------------
export LD_LIBRARY_PATH=/opt/gmp-6.1.2/lib:$LD_LIBRARY_PATH
----------------------------------------------------
# 使环境变量生效
source ~/.zshrc
编写以下程序测试一下 GMP 的安装:
// 这是用 GMP 实现的一个简单的多精度整数加法器,读取两个大整数,并输出它们的和
#include <iostream>
#include <gmpxx.h>
using namespace std;
int main()
{
mpz_t a,b,c;
mpz_init(a);
mpz_init(b);
mpz_init(c);
gmp_scanf("%Zd%Zd",a,b);
mpz_add(c,a,b);
gmp_printf("%Zd\n",c);
mpz_clear(a);
mpz_clear(b);
mpz_clear(c);
return 0;
}
编译运行:
g++ test.cpp -lgmp -lgmpxx -o test
./test
如果程序正常运行,说明 GMP 正常安装
默认的安装路径如下:
/usr/local/include
/usr/local/lib
但是编译 gdbserver 时依然报相同的错误:"configure: error: GMP is missing or unusable"
然后又尝试了网上所说的两种方式来指定 GMP 安装路径,还是报 GMP 找不到的错误:
# 清理缓存,避免与之前的编译配置冲突,如果清理后还是冲突,请重新解压 GDB 源码
sudo make distclean
# 默认安装时,/gmp/include/path 为 /usr/local/include,/gmp/include/path 为 /usr/local/lib
CC="mipsel-linux-gnu-gcc" CXX="mipsel-linux-gnu-g++" ./configure --target="mipsel-linux-gnu" --host="mipsel-linux-gnu" --prefix="/home/wyy/下载/gdb-13.2/gdb13.2_mipsel/gdbserver/build/" LDFLAGS="-static" CFLAGS="-I/gmp/include/path -L/gmp/lib/path" CXXFLAGS="-I/gmp/include/path -L/gmp/lib/path"
# 清理缓存,避免与之前的编译配置冲突,如果清理后还是冲突,请重新解压 GDB 源码
sudo make distclean
CC="mipsel-linux-gnu-gcc" CXX="mipsel-linux-gnu-g++" ./configure --target="mipsel-linux-gnu" --host="mipsel-linux-gnu" --prefix="/home/wyy/下载/gdb-13.2/gdb13.2_mipsel/gdbserver/build/" --with-gmp="/usr/local" LDFLAGS="-static"
后来无意间发现在原本就有的 gdb-13.2/gdbserver/
路径下已经生成了 mipsel
架构的 gdbserver 二进制文件:
再后来,找到一个网站说明了这个问题:GDB。编译gdbserver出现错误 - NXP / IMX6ULL_PRO - 嵌入式开发问答社区
好家伙,我直呼好家伙,自作多情了属于是。。。
直接无视 "configure: error: GMP is missing or unusable"
即可,直接重来一遍:
# 清理缓存,避免与之前的编译配置冲突,如果清理后还是冲突,请重新解压 GDB 源码
sudo make distclean
CC="mipsel-linux-gnu-gcc" CXX="mipsel-linux-gnu-g++" ./configure --target="mipsel-linux-gnu" --host="mipsel-linux-gnu" --prefix="/home/wyy/下载/gdb-13.2/gdb13.2_mipsel/gdbserver/build/" LDFLAGS="-static"
sudo make -j$(nproc)
sudo make install
不知道为什么,我有一天再次执行
configure
编译 MIPS 架构 gdbserver 的时候又不报"configure: error: GMP is missing or unusable"
这个错误了,我也不知道这期间我做了些什么,也许是安装 GMP 后需要重启?反正很离奇。。。
不过编译过程中还是会有其他的报错,不过无关紧要,无视即可,只要执行完后
gdb-13.2/gdb13.2_mipsel/gdbserver/build/
路径下存在bin
文件夹即可
执行完成后在 gdb-13.2/gdb13.2_mipsel/gdbserver/build/bin
路径下会生成一个 gdbserver
二进制程序,它是 MIPS 架构(mipsel
),须在 MIPS 架构的 IOT 设备上运行:
ARM 架构
下面以编译 ARM 架构下的 gdbserver 为例(arm
)
首先下载 GDB 源码并解压:
wget https://ftp.gnu.org/gnu/gdb/gdb-13.2.tar.gz
tar zxvf gdb-13.2.tar.gz
cd gdb-13.2/
mkdir gdb13.2_arm
注意:由于是编译 ARM 架构的 gdbserver,因此需要使用交叉编译工具
关于如何配置 ARM 交叉编译环境的具体说明,详见本站的《多架构与交叉编译》一文 ,这里不再详细说明
编译 GDB
编译安装:
# 由于 GDB 是本机用来连接远程 IOT 设备的 gdbserver 的,所以不用指定 --host 选项和 CC 以及 CXX 变量的值,configure 会自动检测(即:本机运行不需要交叉编译)
./configure --target="arm-linux-gnueabihf" --prefix="/home/wyy/下载/gdb-13.2/gdb13.2_arm/gdb/build/"
sudo make -j$(nproc)
sudo make install
执行完成后在 gdb-13.2/gdb13.2_arm/gdb/build/bin
路径下会生成一个 arm-linux-gnueabihf-gdb
二进制程序,它是 x86 架构,可以直接在本机运行:
编译 gdbserver
编译安装:
# 清理缓存,避免与之前的编译配置冲突,如果清理后还是冲突,请重新解压 GDB 源码
sudo make distclean
# gdbserver 运行在 IOT 设备,因此需要使用交叉编译器
CC="arm-linux-gnueabihf-gcc" CXX="arm-linux-gnueabihf-g++" ./configure --target="arm-linux-gnueabihf" --host="arm-linux-gnueabihf" --prefix="/home/wyy/下载/gdb-13.2/gdb13.2_arm/gdbserver/build" LDFLAGS="-static"
sudo make -j$(nproc)
sudo make install
如果是旧版本的 GDB 源码,可能需要先进入 gdbserver 目录下,路径通常为
gdb-xxx/gdb/gdbserver
,然后再执行上述configure
命令
如果编译过程中报错:
configure: error: *** A compiler with support for C++11 language features is required.
说明安装的 GCC 交叉编译工具版本太低,不支持 C++11,重新安装更新的版本即可
注意:
编译过程中可能还是会有报错,无视即可,只要执行完后
gdb-13.2/gdb13.2_arm/gdbserver/build/
路径下存在bin
文件夹即可
执行完成后在 gdb-13.2/gdb13.2_arm/gdbserver/build/bin
路径下会生成一个 gdbserver
二进制程序,它是 ARM 架构,须在 ARM 架构的 IOT 设备上运行:
上传 gdbserver 并远程连接
因为这里主要是做个示例,我们自己交叉编译一个 ARM 架构的二进制程序:
#include <stdio.h>
int main() {
int a, b, sum;
printf("put a:");
scanf("%d", &a);
printf("put b:");
scanf("%d", &b);
sum = a + b;
printf("a + b = %d", sum);
return 0;
}
交叉编译:
arm-linux-gnueabihf-gcc test.c -o gdbserver_test -static
关于如何配置 ARM 交叉编译环境的具体说明,详见本站的《多架构与交叉编译》一文 ,这里不再详细说明
我们将前面编译好的 ARM 架构下的 gdbserver 和交叉编译的程序 gdbserver_test 上传到路由器的文件系统内
以本文的《使用 QEMU 仿真》一节中仿真的 Cisco 路由器 RV34X-v1.0.03.29-2022-10-17-13-45-34-PM.img
固件为例 ,这里不再进行仿真的教程
sudo scp -r gdbserver gdbserver_test root@192.168.2.2:~/rootfs
然后我们 SSH 连接上 QEMU 虚拟机,并给执行权限:
ssh root@192.168.2.2
cd rootfs
chmod +x gdbserver
chmod +x gdbserver_test
用 gdbserver 启动被调试程序,格式为:gdbserver路径 IP地址:端口号 被调试程序
# 端口号范围:0 ~ 65535,尽量避免端口冲突
./gdbserver 127.0.0.1:6666 ./gdbserver_test
# 也可以写成如下形式:
./gdbserver :6666 ./gdbserver_test
./gdbserver 192.168.2.2:6666 ./gdbserver_test
然后在宿主机使用 GDB 连接远程端口,这里介绍两种方式:
- 第一种方式是使用我们前面自己编译的
arm-linux-gnueabihf-gdb
(这样一定能保证 GDB 与 gdbserver 版本是一致的):
./arm-linux-gnueabihf-gdb
(arm-linux-gnueabihf-gdb) target remote 192.168.2.2:6666
QEMU 虚拟机也显示远程调试被连接:
然后下断点尝试调试 gdbserver_test 程序:
(arm-linux-gnueabihf-gdb) b main
(arm-linux-gnueabihf-gdb) c
注意:
由于待调试的程序实际已在 QEMU 虚拟机上运行了(因为我们使用了 gdbserver 来启动被调试程序),所以宿主机的 GDB 要使用
'c'
指令,不能使用'r'
指令如果输入
'r'
指令,会看到提示 remote 模式下不支持'r'
指令:
可以看到程序正常断在 main()
函数,已经可以正常调试了:
- 另一种方法当然就是使用
gdb-multiarch
了(要不然我们安装它干嘛嘿嘿嘿)
# 运行 gdb-multiarch
gdb-multiarch
# 设置架构
(gdb-multiarch) set architecture arm
# 远程连接
(gdb-multiarch) target remote 192.168.2.2:6666
# 调试
(gdb-multiarch) b main
(gdb-multiarch) c
可以看到,其实 gdb-multiarch
操作起来与 arm-linux-gnueabihf-gdb
几乎是一样的
只是,gdb-multiarch
需要单独指定架构,因为它支持多架构,而 arm-linux-gnueabihf-gdb
是我们自己编译的专门用于 ARM 架构的 GDB
除此之外,还有个区别就是:
gdb-multiarch
的版本与本机自带的 GDB 版本一致,而arm-linux-gnueabihf-gdb
的版本取决于我们编译的时候用的是哪个版本的 GDB 源码,因此arm-linux-gnueabihf-gdb
的灵活性更高,可以根据我们的需求选择 GDB 版本