如何编写shellcode查找EIP & RIP - 嘶吼 RoarTalk – 回归最本质的信息安全,互联网安全新媒体,4hou.com

如何编写shellcode查找EIP & RIP

h1apwn 技术 2020-01-23 10:52:05
收藏

导语:我在学习OSCE认证时,花费了大量时间研究如何编写自定义shellcode。这是我打算发布的一系列博客文章中的第一篇,其中将详细介绍我在此过程中学到的一些技术。

Shellcoding:查找EIP / RIP

我在学习OSCE认证时,花费了大量时间研究如何编写自定义shellcode。这是我打算发布的一系列博客文章中的第一篇,其中将详细介绍我在此过程中学到的一些技术。这篇博客文章的重点将是描述如何找到EIP / RIP,以及找到它可以做什么。OSCE将精力集中在32位系统上,作为我继续学习的一部分,我将研究和记录适用于64位系统的方法。

在这篇博文中,我不会介绍找到EIP和RIP的所有可能方法。我将介绍一些我熟悉并研究过的内容。如果你有兴趣寻找更多的方法,我鼓励你进行研究并踏上自己的学习之旅。你在这里看到的是我自己的学习总结,我希望它能以某种方式对你有所帮助。这些主题多年来已经涵盖了许多主题,而且并不是什么新鲜事物,重点不是提出新的东西而是在我自己的学习过程中提供帮助,也许还可以帮助其他人开始自己的旅程。

查找EIP:32位汇编方法

我将从使用x86汇编(32位)指令找到EIP的方法开始。有两种方法,这两种方法都可能有用,具体取决于你可能遇到的情况和限制。两种方法将实现寻找EIP的相同目标。我将详细介绍的第一种方法比第二种方法小一个字节。两种方法都可以达到完全相同的目的,即将EIP的值存储在EAX寄存器中。两种方法都没有NULL(0x00)字节。之所以使用前一种而不是另一种,是因为你可能会遇到大小或字符限制。

方法1:使用FPU指令

此方法由Aaron Adams首次在漏洞开发邮件中进行了说明。他使用的方法利用x87浮点单元(FPU)寄存器来获取EIP的值。以下基本汇编代码会将EIP的值存储在EAX寄存器中。

 [SECTION .text]
 
 BITS 32
 
 global _start
 
 _start:
     fldz
     fnstenv [esp-0x0C]
     pop eax
     add al, 0x07

为了激活FPU寄存器,执行fldz指令。该指令将常数值+0.0压入FPU寄存器堆栈(ST(0))。在此过程中,将初始化FPU寄存器,并将EIP的当前值存储在FPU指令指针偏移量(FIP)寄存器中。图1详细说明了FPU寄存器的结构。该表摘自《英特尔64和IA-32架构软件开发人员手册,第1卷:基本架构 PDF》。

figure1.png

图1:内存中的保护模式x87 FPU状态图像(32位格式)

下一条指令将FPU寄存器存储在指定地址。由于使用fnstenv指令将EIP值以0x0C(12)的偏移量存储在FIP寄存器中。指定了ESP-0x0C的目标,以便将FIP值存储在当前ESP地址中。接下来,该值从堆栈中弹出并存储在EAX寄存器中。由于在将EIP存储到FPU的FIP寄存器与将其弹出到EAX的时间之间已经执行了几个字节的指令,因此有必要调整EIP值以表示FPU的当前值。为此,将0x07(7)添加到AL寄存器,AL寄存器用于避免使用NULL字节。

测试

要对此进行测试,可以使用nasm汇编代码:

 nasm method1.asm -o method1.asm

然后将字节插入c代码中:

 char code[] = "\xD9\xEE\xD9\x74\x24\xF4\x58\x04\x07";
 
 int main(int argc, char **argv)
 {
    int (*func)();
    func = (int (*)()) code;
    (int)(*func)();
 }

用MingW编译代码:

 i686-w64-mingw32-gcc-win32 method1.c -o method1.exe -fno-stack-protector -no-pie -m32

在你喜欢的调试器中运行生成的PE文件,并查看其工作方式。你将需要将.data节标记为可执行文件。你可以使用degger或诸如LordPE之类的工具来执行此操作。如果未将.data节标记为可执行文件,则当执行到达你的Shellcode时,将出现访问冲突。我建议在PE文件中搜索调用eax指令并设置断点,对EAX的第一个调用将是对shellcode的调用。如果一切顺利,EAX将在执行add al,7指令时指向EIP ,请参见图2。

figure2.png

图2:EAX指向EIP

方法1替代-使用减法

由于使用减法从EAX寄存器在某些情况下会失败,我加入此替代方法。问题在于,通过添加到AL寄存器中,如果加的字节将导致进位,则EAX可能指向错误的位置。例如,如果AL寄存器包含大于或等于0xF9的任何内容,则向其添加0x07会导致有1位被丢弃。例如,如果EAX包含0x001234F9,而我们添加0x07到AL寄存器(0xF9),结果将是0x00123400,而不是需要的0x00123500。

为了克服此问题并避免使用空字节,可以从EAX减去负值。基本算术,加上负值会得出加法。更正后的代码在下面,并且避免NULL字节,并且仅大一个字节:

 [SECTION .text]
 
 BITS 32
 
 global _start
 
 _start:
     fldz
     fnstenv [esp-0x0C]
     pop eax
     sub eax, -0x07

在图9中,我们可以看到代码确实有效,并且比原始代码更可靠。

figure9.png

图9:EAX指向EIP

方法2:使用跳转和调用

第二个方法仅比第一个方法长一个字节,并达到了将EIP值存储在EAX寄存器中的目的。为了获得EIP,此方法使用了一系列跳转和调用,这些跳转和调用导致EIP的值存储在EAX中。SK Chong文章的示例中,他使用db条目对这些跳转和调用进行硬编码。我要介绍的版本可以通过nasm轻松阅读和修改。

 [SECTION .text]
 
 BITS 32
 
 global _start
 
 _start:
     jmp label2
     label1:
         jmp getEIP
     label2:
         call label1
     getEIP:
         pop eax

上面的代码中发生的是,执行跳至label2,这是一个调用label1的调用指令。执行调用时,返回地址(即下一条指令的地址)被压入堆栈。执行然后跳到getEIP,它将返回地址弹出到EAX中。

测试

你可以按照测试方法1的相同方法来测试方法2。执行完成后,结果应类似于图3。

figure3.png

图3:EAX指向EIP

查找RIP:64位汇编方法

一个很好的起点是查看在32位体系结构中工作的方法是否在64位环境中工作。如果只需要做些微修改,为什么还要重新设计轮子呢?

方法1:使用FPU指令

尝试

由于FPU指令在x64 Assembly下仍然有效,因此可以使用FPU指令。首先,找出在32位模式下运行时未对指令进行任何修改或进行最小改动会发生什么情况。为此,我启动了x64dbg并执行notepad.exe并运行它,直到我点击EntryPoint。使用Assemble命令,我手动卡住了32位指令来代替前几条指令。这样做的结果可以在图4中看到。这里有几件事要注意。首先是出现在fnstenv指令前面的67 :。根据英特尔®64和IA-32体系结构软件开发人员手册第2卷(2A,2B,2C和2D):指令集参考,AZ部分2.1.1,67h前缀是地址大小的覆盖。由于我使用的是ESP寄存器而不是RSP,因此必须这么做。其次,我无法使用POP EAX指令,不得不使用POP RAX。

figure4.png

图4:使用FPU指令查找RIP

运行此序列会部分成功。(请参见图5)RAX指向执行加法时RIP之前的指令。这是因为必须使用67h地址大小前缀。只需添加0x08而不是0x07。继续,看看它是否有效。

figure5.png

图5:使用FPU指令查找RIP

编写代码

现已证明,可以使用FPU寄存器检索RIP的值。对我而言,下一步是查看是否可以编写一些汇编代码来复制在调试器中手动输入命令所产生的结果。经过一些调整后,这是我的代码:

 [SECTION .text]
 
 BITS 64
 
 global _start
 
 _start:
     fldz
     fnstenv [rsp-0x0C]
     pop rax
     add rax, 0x07

以上代码产生的指令如图6所示。为了测试这些指令,我对使用x64dbg打开的64位进程的第一条指令进行了粘贴。在add rax,0x07指令上现在有了一个新的48h前缀。根据Intel®64 和IA-32体系结构软件开发人员手册,组合卷:1、2A,2B,2C,2D,3A,3B,3C,3D和4节3.7.2.1,REX前缀允许寻址8位寄存器的指令。汇编程序可以通过这种方式丢弃NULL字节。另外,由于fnstenv不需要地址大小前缀当与RSP而不是ESP一起使用时,向RAX添加0x07字节应导致RAX包含RIP地址。

figure6.png

图6:使用FPU指令查找RIP

方法2:使用跳转和调用

在开始测试此方法之前,我希望它可以进行最少的修改。希望至少有必要将EAX更改为RAX。这些类型的跳转和调用要放入调试器中要困难一些,所以我选择首先编写一些汇编代码。以下代码是我选择用于第一个attmpet的代码:

 [SECTION .text]
 
 BITS 64
 
 global _start
 
 _start:
     jmp label2
     label1:
         jmp getEIP
     label2:
         call label1
     getEIP:
         pop rax

如图7所示,在写好的代码的记事本的入口点上执行粘贴并运行,结果是成功的。此方法仍然是NULL字节,并且长度仅为10个字节。

figure7.png

图7:使用跳转和调用查找RIP

方法3:加载RIP的有效地址

在使前两种方法与64位体系结构一起使用之后,我想看看是否还有更好的方法。在搜索时,我遇到了Booze发表的一篇文章,汉密尔顿详细介绍了Rapid7在Metasploit中实现的一种方法,该方法使用LEA指令获取RIP。经过一番尝试,并使用Nasm命令行,我想到了以下代码,该代码将复制Metasploit所使用的方法:

 [SECTION .text]
 
 BITS 64
 
 default rel
 
 global _start
 
 _start:
     lea rax, [_start]

当使用以下flag与Nasm配合使用时,代码将只有7个字节长。该-O0标志告诉NASM不进行优化。如果不使用此标志,则最终代码将包含NULL值。

 nasm -O0 method3.asm -o method3.bin

代码和结果可见于图8。

figure8.png

图8:使用payload 地址(LEA)指令查找RIP

结论

现在,我详细介绍了根据目标体系结构查找EIP或RAX的几种方法。如果希望执行其他与Shellcode交互的操作,则动态获取这些值非常重要。由于ASLR无法动态查找EIP / RIP,因此无法解码,复制或以其他方式修改你的代码。在以后的文章中,我将继续以此为基础。本系列的最后一篇文章可以编写一个完全自定义的shellcode,该代码是从Intel x64 Assembly中的scatch编写的。

本文翻译自:https://blackcloud.me/SLAE32-1/如若转载,请注明原文地址:
  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论