全部版块 我的主页
论坛 经管考试 九区 经管在职研
1195 0
2025-12-03

在编写汇编代码的过程中,有时我们需要深入观察编译器内部的执行细节。尤其是在调试 shellcode 时,更需要细致、谨慎地逐条运行指令,以便准确捕捉问题所在。

本文将介绍如何在基于 x86_64 架构的 Ubuntu 系统上模拟执行 32 位 ARM 架构的 shellcode。由于大多数开发设备仍采用 x86_64 架构,并不原生支持 ARM 指令集,因此我们必须借助仿真手段来运行非本机架构的代码。此外,原始的 shellcode 通常仅为裸二进制数据,不具备标准可执行文件格式(如 ELF),无法被常规工具直接加载运行,这就要求我们采用特殊的执行方式。

为此,我们将使用 Radare2 —— 一个功能强大的命令行驱动逆向分析框架,集成了多种用于二进制分析的实用工具。它既支持脚本化操作,也提供交互式命令行界面,便于灵活操控。在 Ubuntu 系统中安装和配置 Radare2 只需执行几个简单的命令即可完成。

mkdir?~/github
cd?~/github
git?clone?https://github.com/radareorg/radare2.git
cd?radare2
sys/install.sh

若你已安装 Radare2,请务必确认当前版本为最新。该项目持续维护并频繁更新,旧版本可能存在影响实验结果的缺陷。特别是 2022 年 6 月之前的某些版本存在关键性错误,可能导致本次仿真任务无法顺利完成。

cd?~/github/radare2
git?pull
sys/install.sh
r2?-V

要获取本文所用的 shellcode 二进制文件,可在 bash 终端中执行以下命令进行复制:

nemo@hammerhead:~$?echo?-n?-e?'\x01\x30\x8f\xe2\x13\xff\x2f\xe1\x78\x46\x0c\x30\xc0\x46\x01\x90\x49\x1a\x92\x1a\x0b\x27\x01\xdf\x2f\x62\x69\x6e\x2f\x73\x68\x00'?>?shellcode-696.bin

nemo@hammerhead:~$?md5sum?shellcode-696.bin?
42ba1c77446594cac3508b940926575d??shellcode-696.bin

ESIL 简介

ESIL(可评估字符串中间语言)是 Radare2 中实现的一种抽象指令表示方法,能够脱离具体硬件平台来“执行”机器码指令。这种机制特别适用于跨架构仿真场景,使得在 x86_64 主机上运行 ARM 指令成为可能。

利用 ESIL 执行 ARM shellcode 需完成以下几个步骤:

  1. 加载目标 shellcode 二进制文件
  2. 配置 Radare2 以正确解析该二进制内容
  3. 初始化 ESIL 仿真环境
  4. 根据需要设置相关寄存器状态
  5. 逐步执行并验证汇编指令的行为

使用 ESIL 执行 ARM shellcode

1. 加载 shellcode 二进制文件

当我们对 shellcode 文件执行 file 命令时,系统通常无法识别其格式。同样,Radare2 初始也无法判断其类型。

nemo@hammerhead:~/labs/shellcode/asm$?file?shellcode-696.bin
shellcode-696.bin:?data

由于该文件仅包含原始二进制代码,无任何文件头信息,因此需要手动告知 Radare2 如何解析。我们需调整部分分析参数,以确保能正确识别 ARM 架构指令:

nemo@hammerhead:~/labs/shellcode/asm$?r2?shellcode-696.bin
[0x00000000]>?e?anal.arch?=?arm
[0x00000000]>?e?asm.arch?=?arm
[0x00000000]>?e?asm.bits?=?32
[0x00000000]>?e?anal.armthumb=true

2. 配置 Radare2 解析规则

接下来的关键是明确哪些部分属于 ARM 指令,哪些属于 THUMB 指令。实践中发现,通过定义函数边界可以有效控制指令解码模式。本例中的 shellcode 会在 ARM 与 THUMB 状态之间切换。

在以下命令片段中,我们对地址 0 处创建一个函数,尽管此处并非真正的函数起点,但此举可帮助 Radare2 正确识别指令集类型。pdf 命令用于打印该函数的反汇编结果,其中包含 addbx 指令。

[0x00000000]>?af
[0x00000000]>?pdf
┌8:?fcn.00000000();
│?rg:0(vars?0,?args?0)
│?bp:0(vars?0,?args?0)
│?sp:0(vars?0,?args?0)
│0x0000000001308fe2???????add?r3,?pc,1
└0x0000000413ff2fe1???????bx?r3
<p>[0x00000000]>?s?8
[0x00000008]>?af
[0x00000008]>?pdf
┌24:?fcn.00000008(int32_t?arg1,int32_t?arg2);
│;?arg?int32_t?arg1?@?r0
│;?arg?int32_t?arg2?@?r1
│0x0000000878460c30???????andlo?r4,?ip,?r8,?ror?r6
│0x0000000c??????c0460190???????andls?r4,?r1,?r0,?asr?13;?arg2
│┌─<0x00000010491a921a???????bne?0xfe48693c
││0x000000140b2701df???????svcle?0x1270b
││0x000000182f62696e???????cdpvs?p2,6,?c6,?c9,?c15,1
└│0x0000001c2f736800???????rsbeq?r7,?r8,?pc,?lsr?6
[0x00000008]>?afB?16</p>

从地址 8 开始的后续指令属于 THUMB 模式。s 8 命令将位置跳转至第 8 字节处,随后定义新的函数区域。使用 af 创建函数后,若直接调用 pdf 查看,显示结果可能异常,因为默认仍按 ARM 模式解析。

为解决此问题,可通过设置 asm.bits=16 来指定该函数使用 THUMB 指令集。这一设置仅作用于当前函数范围。在完整的 ARM 可执行文件中,Radare2 通常能自动识别模式切换,但在纯 shellcode 场景下,必须手动干预。

注意:我们可以选择移除前两条 ARM 指令,改用全 THUMB 编写的 shellcode。此时,在打开文件时设置 e asm.bits=16 即可全局启用 16 位模式,无需再单独定义函数。当然,保留两种模式混合的情况有助于展示更复杂的控制流程。

Radare2 还提供了便捷命令 izz,可用于快速提取二进制文件中嵌入的所有字符串信息。

>?izz
[Strings]
nth?paddr??????vaddr??????len?size?section?type??string
―――――――――――――――――――――――――――――――――――――――――――――――――――――――
0???0x00000008?0x00000008?4???5????????????ascii?xF\f0
1???0x00000018?0x00000018?7???8????????????ascii?/bin/sh</p>

至此,shellcode 已被正确加载并解析。

3. 初始化 ESIL 仿真环境

如前所述,Radare2 支持通过在命令后添加 ? 来查看其子命令列表。ae? 可列出所有与 ESIL 及仿真相关的操作命令。

[0x00000000]>?ae?
Usage:?ae[idesr?][arg]??ESIL?code?emulation
|?ae?[expr]????????????????evaluate?ESIL?expression
|?ae???????????????????????show?this?help
|?ae???????????????????????show?ESIL?help
|?aea[f][count]???????????analyse?n?esil?instructions?accesses?(regs,?mem..)
|?aeA[f][count]???????????analyse?n?bytes?for?their?esil?accesses?(regs,?mem..)
|?aeb?([addr])?????????????emulate?block?in?current?or?given?address
|?aeC[arg0?arg1..]@?addr??appcall?in?esil
|?aec[?]continueuntil^C
|?aef?[addr]???????????????emulate?function
|?aefa?[addr]??????????????emulate?function?to?find?out?args?in?given?or?current?offset
|?aeg?[expr]???????????????esil?data?flow?graph
|?aegf?[expr][register]???esil?data?flow?graph?filter
|?aei[?]???????????????????initialize?ESIL?VM?state?(aei-?to?deinitialize)
|?aek[?][query]???????????perform?sdb?query?on?ESIL.info
|?aeL??????????????????????list?ESIL?plugins
|?aep[?][addr]????????????manage?esil?pin?hooks?(see?“e?cmd.esil.pin”)
|?aepc?[addr]??????????????change?esil?PC?to?this?address
|?aer[?][..]??????????????handle?ESIL?registers?like?“ar”or“dr”?does
|?aes[?]???????????????????perform?emulated?debugger?step
|?aets[?]??????????????????esil?Trace?session
|?aev?[esil]????????visual?esil?debuggerfor?the?given?expression?or?current?instruction
|?aex?[hex]????????????????evaluate?opcode?expression

首先,使用 aei 命令初始化 ESIL 引擎。接着需建立堆栈空间。Radare2 会自动分配默认堆栈地址,也可通过 aeim 命令手动指定特定内存位置作为堆栈基址。

[0x00000008]>?aei
[0x00000008]>?aeim

4. 设置寄存器状态

由于 shellcode 从地址 0 开始执行,必须将程序计数器(PC)设置为 0。可通过 aepc 0 命令完成。若希望从其他偏移地址开始执行,则可使用 aepc [address] 指定起始位置。

[0x00000008]>?aepc?0

在我们的shellcode中,有一条指令("subs r1, r1, r1")会将寄存器r1的值减去自身并存回r1,从而使其归零。由于该寄存器初始状态通常已是0,为了更清晰地观察执行过程中的变化,我们可以先将其设置为0xffff。实现这一操作需要使用“aer”命令来修改寄存器状态。

[0x00000008]>?aer?r1?=?0xffff

1. 验证配置是否生效

完成上述设置后,接下来可以切换至可视化调试界面。进入调试器的视觉模式后,系统提供了多个面板视图,因此需连续按两次“p”键以切换到正确的显示面板。若需退出当前模式,直接按下Esc键即可。此外,随时可按下“?”查看当前环境下支持的所有操作指令。

[0x00000008]>?V
(hit?“p”?twice?to?get?to?the?debugger?panel)

进入面板后,可在顶部区域看到一组当前寄存器的状态信息,其布局如下所示:

值得注意的是,在视觉模式下同样可以执行常规r2命令。例如,若要打印位于偏移地址0x18处的字符串内容,只需输入相应的命令即可实现。

#?Hit?“:”?while?in?visual?mode.

>?ps?@0x18
/bin/sh
>?#?Hit?enter?on?a?blank?line?to?return?to?visual?mode.

通过按“s”键,我们可以逐条执行汇编指令。在逐步执行过程中,顶部的寄存器值和堆栈数据会实时更新(堆栈起始地址如第一张图所示为0x00178000)。同时,程序计数器(PC)所指向的下一条待执行指令会在汇编代码区被高亮标识出来(如第一幅图中地址0x00000010所示)。

从上图可见,此时r1寄存器的值为0x0000ffff。紧接着即将执行的指令是“subs r1, r1, r1”,该指令将r1与自身相减,并将结果写回r1,最终使其值变为0。

再次按下“s”键,进入下一条指令的执行阶段。

此时已准备就绪,即将通过“svc 1”指令触发系统调用。当前我们正在执行的是execve系统调用,因此需要确保r7寄存器中存放了对应的系统调用号0xb。而r0寄存器作为第一个参数,应包含指向目标二进制文件路径的指针。当前r0的值为0x18,我们可通过以下命令验证其所指向的具体内容:

#?Hit?“:”?while?at?the?“svc?1”?instruction?in?visual?mode.

>?ps?@r0
/bin/sh
>

由于本次调用并未向"/bin/sh"传递额外参数,也未设置环境变量,因此r1和r2寄存器均被置为0。

需要注意的是,由于当前并非运行在真实的ARM架构系统上,因此无法真正完成守护进程调用(即“svc 1”指令的功能)。这一点在测试复杂shellcode时尤为重要,必须加以注意。

总结

无论是调试自行编写的shellcode,还是分析静态提取的机器码片段,有时都必须深入观察每条指令的实际行为。Radare2提供了一种强大的能力:可以从非标准格式文件(如裸shellcode二进制或固件镜像)中加载汇编代码,并对其进行逐指令的动态执行分析,帮助我们准确理解其运行机制。

二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

相关推荐
栏目导航
热门文章
推荐文章

说点什么

分享

扫码加好友,拉您进群
各岗位、行业、专业交流群