当前位置: 首页 > 技术干货 > 用Radare2模拟shellcode运行

用Radare2模拟shellcode运行

发表于:2023-05-06 15:27 作者: 大鱼 阅读数(5568人)

当我们在编写汇编时,可能有的时候你需要看看编译器中到底发生了什么。如果你正在排除shellcode出现的问题,你那么更需要耐心地、慎重地运行指令。

本文将探讨如何在x86_64的Ubuntu系统上模拟32位ARM shellcode。由于大多数笔记本电脑和工作站还没有运行ARM,我们这里需要一种其他方法在系统上执行非原生的指令。另外,原始的shellcode二进制文件并不是可执行文件格式,并不能被大多数工具所运行,所以我们需要一种其他的方法来执行这些文件。

在这里我们使用的是Radare2, Radare2是一个控制台驱动的框架,集成了一套简便易用的二进制分析工具。你可以把这些工具编写成脚本,或者使用交互式的命令行界面。要在Ubuntu上设置这个,我们只需要几个简单的命令。

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使用的一种从硬件中抽象出来的指令,可以在不考虑底层硬件的情况下,来 "执行" 机器指令。这对于在仿真环境中执行非本地的汇编指令是非常理想的。

为了使用ESIL来执行我们的shellcode,我们需要做以下工作:

  1. 加载我们的shellcode二进制文件

  2. 配置radare2,使其知道如何正确解释我们的shellcode二进制文件

  3. 初始化ESIL

  4. 根据需要设置寄存器

  5. 通过我们的汇编指令来验证其功能

用 ESIL 执行ARM shellcode

  1. 加载我们的shellcode二进制文件

当我们对shellcode二进制文件运行 "file"命令时,我们看到Linux不能确定其文件格式。同样地,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
  1. 配置radare2,使其知道如何正确运行我们的shellcode二进制文件

接下来我们要指定哪些指令是ARM,哪些是THUMB。我发现要做到这一点,就需要定义指令类型改变的函数。在这个特定的shellcode中,它会在ARM和THUMB指令之间进行切换。

[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)
│           0x00000000     01308fe2       add r3, pc, 1
└           0x00000004     13ff2fe1       bx r3

在这个radare2命令的片段中,我正在分析一个地址为0的函数。在这里并不存在一个真正的函数,但是我们这样做是为了让我们的"函数"可以指定为ARM或者THUMB。"pdf "命令只是打印了函数的反汇编指令,这其中包含了add和bx指令。

<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
│           0x00000008     78460c30       andlo r4, ip, r8, ror r6
│           0x0000000c     c0460190       andls r4, r1, r0, asr 13   ; arg2
│       ┌─< 0x00000010     491a921a       bne 0xfe48693c
│       │   0x00000014     0b2701df       svcle 0x1270b
│       │   0x00000018     2f62696e       cdpvs p2, 6, c6, c9, c15, 1
└       │   0x0000001c     2f736800       rsbeq r7, r8, pc, lsr 6
[0x00000008]> afB 16</p>

从地址8开始的下一组指令是THUMB指令。"s 8 " 指令会在文件中寻找8个字节,并跳转到一个我们希望到达的地方,然后定义下一个 "函数"。用 "af "创建函数后,当我们试图用 "pdf "显示它时,它看起来有点古怪。这是因为工具仍然会将这些指令解释为ARM。

我们可以通过设置比特数为16来指定这个 "函数 " 是THUMB。也就是将asm.bits设置为16,不过只针对这个函数生效。在一个正常的ARM二进制文件中,radare2会尝试自动进行这种区分,但是由于我们只有这一串shellcode指令,我们仍然需要进行手动区分。

注意我们可以删掉前两条指令并使用全THUMB的shellcode。如果我们这样做,我们就可以在打开文件时设置 "e asm.bits=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二进制文件了。

  1. 初始化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[?]                   continue until ^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 debugger for the given expression or current instruction
| aex [hex]               evaluate opcode expression

在这里,我们首先需要用 "aei "命令来初始化ESIL。之后,我们需要初始化一个堆栈。Radare2会自动选择一个堆栈的位置,不过这也可以使用"aeim "命令参数来指定地址 。

[0x00000008]> aei
[0x00000008]> aeim
  1. 根据需要设置寄存器

由于我们的shellcode指令是从0开始的,我们需要用 "aepc 0 "命令将我们的程序计数器(PC)设置为0。如果我们想在偏移量0以外的位置开始执行,我们可以用 "aepc <address>"来设置起始地址。

[0x00000008]> aepc 0

我们的shellcode中的一条指令("subs r1, r1, r1")会将r1设置为0.由于这个寄存器默认已经为0,让我们将它设置为0xffff,这样我们就可以看到当我们在shellcode中步进时所发生的变化。要做到这一点,我们需要使用 "aer "命令。

[0x00000008]> aer r1 = 0xffff
  1. 通过我们的设置来验证其功能

好了,现在我们已经设置好了。我们可以切换到可视化模式,进入到调试器面板。在可视模式下有多个选项(面板),所以我们需要敲两次 "p "来进入正确的面板。如果你想在任何时候退出可视化模式,只需按下escape键。你也可以按"?"来查看可用的命令列表。

[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二进制文件或固件镜像)加载非本地汇编文件,并一步一步地执行指令。