最近拿到一个ROM
2020-01-11 16:57:20 Author: bbs.pediy.com(查看原文) 阅读量:381 收藏

当学习主动安全认证专家(OSCE)认证时,我花费了大量的时间用于研究如何编写自定义shellcode。本文是我计划要发布的一系列博文里的第一篇,这个系列详细介绍我在准备认证过程中所学习到的技术。本文的重点是描述如何找到EIP/RIP以及找到后做什么。OSCE专注于32位系统,作为继续学习的一部分,我会研究并记录下在64位系统上的方法。这超出了OSCE的需求,但这是我继续学习过程的一部分。

在本文中,我不会介绍到所有的可能找到EIPRIP的方法。我会介绍一些我熟悉并已经研究过的方法。如果你想找到更多的方法,我鼓励你开始你自己的研究并开始做自己的学习总结。在这里你看到的是我的学习总结,我希望它会在某些方面对你有帮助。我知道这些主题多年来已经被多次提到,也没有新的东西,但重点不是提出新的东西,而是在学习总结中提升自己,并且还可以帮助到刚开始学习的人。

从使用x86汇编(32位)指令定位EIP的方法开始。有两种方法可以用,根据你可能会遇到的情况和限制,两种方法可能都有用。两种方法都可以找到EIP。我要介绍的第一种方法比第二种方法小一个字节。两种方法都可以达到完全相同的目的,存储EIP的值到EAX寄存器中。两种方法都没有空(0x00)字节。之所以使用一种而不是另一种,是因为您可能会遇到大小或字符限制。

方法1:使用FPU指令

该方法最先由Aaron Adams在漏洞开发(Vulnerability Development)邮件列表中提出.他使用的这种方法利用x87浮点单元(FPU)寄存器来获取EIP值。以下的基础汇编代码将EIP的值存储到EAX寄存器中。

[SECTION .text]

BITS 32

global _start

_start:
    fldz
    fnstenv [esp-0x0C]
    pop eax
    add al, 0x07

执行fldz指令来激活FPU寄存器。该指令将常数值+0.0压入FPU寄存器栈(ST(0))。在这个过程中,FPU寄存器被初始化,当前的EIP值被存储到FPU指令指针偏移(FIP)寄存器中。图 1描述了FPU寄存器的结构。该表拷贝自《Intel(r) 64 和 IA-32 架构软件开发者手册,卷1:基础架构PDF》.

图片描述
图1:内存中保护模式x87 FPU状态图片(32位格式)

下一条指令存储FPU寄存器在特殊的地址。因此使用fnstenv指令将EIP的值存储在便宜0x0C(12)的FIP寄存器中。ESP-0x0C的目标是固定的,所以FIP的值会存储在当前ESP地址处。接下来,该值会从栈中弹出,并存储到EAX寄存器中。由于在将EIP存储到FPU的FIP寄存器和弹到EAX的这段时间已经执行了几个字节的指令,所以需要调整该值来表示当前的EIP的值。用AL寄存器加0x7(7)完成此操作。AL寄存器被用来避免使用空字节。

测试

要测试可以使用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 节标记为可执行。你可以使用调试器来实现或者使用如LordPE这样的工具。如果你不标记 .data 节为可执行,你会在执行你的shellcode时发生访问违例。我需要在PE文件中搜索call eax指令,并设置断点,第一个调用到EAX的会是shellcode。如果一切顺利,EAX会在执行add al,7指令时指向EIP,如图 2

图片描述
图 2:EAX指向EIP

** 方法1的替代——使用减法

感谢来自@TheColonial的建议,对EAX寄存器使用减法而不是add al指令可以再一些情况下避免错误,我添加了该替代方法。他正确的指出的问题,是通过对AL进行加法运算,如果加法导致了进位,EAX就会指向错误的地址。例如,如果AL寄存器包含大于或等于0xF9的任何数值,加上0x07后,进位的1会被丢弃掉。例如,如果EAX0x001234F9,我们把0x07加到AL寄存器(0xF9)后,结果会是0x00123400而不是我们需要的0x00123500

为了避免这个问题,并且避免空字节,可以让EAX减去一个负值。基本的数学运算,减去一个负值等于加上它(的绝对值)。简单却有效。正确的代码如下,还避开了空字节,并且只比之前大了1字节:

[SECTION .text]

BITS 32

global _start

_start:
    fldz
    fnstenv [esp-0x0C]
    pop eax
    sub eax, -0x07

图 9中我们可以看出建议的代码正常运行并且会比原来的代码更加可靠。

图片描述
图 9 EAX指向EIP

方法2:使用跳转和调用

第二种方法仅比第一种方法长一个字节,也完成了将EIP的值存储到EAX中的目标。我从 Phrack issue 62,Phile 7标题为History and Advances in Windows Shellcode中学到的这种方法。这篇文章由SK Chong所写。为了获得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,一个call指令调用了label1。当call被执行时,返回地址,即下一条指令的地址,被压入栈中。执行,随后跳转到getEIP,会弹出返回地址到EAX。不要小题大作(No muss, no fuss.)。

测试

你可以使用测试方法 1的测试方法来测试方法2.当执行完,结果看起来如图 3

图片描述
图 3:EAX指向EIP

查看是否32位架构中运行的方法在64位世界中是否运行是一个好的开始。如果只需做些微修改,为什么还要重新造轮子?

方法1:使用FPU指令

尝试1

使用FPU指令可以运行,因为指令在64位下仍然生效。首先,找出未对32位模式运行下的指令进行修改或微小修改发生的情况。要做到这点,我使用x64dbg并使其执行notepad.exe,运行直到命中EntryPoing。使用Assemble指令,我手动替换了开始的一些指令位32-bit的指令。这样做的结果如图 4。这里有一些需要注意。首先,67:出现在fnstenv指令前。根据Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 2 (2A, 2B, 2C & 2D): Instruction Set Reference, A-Z 章节2.1.1,67h前缀是地址大小覆写。这一定会出现的,因为我之前使用的是ESP寄存器而不是RSP。第二,我不能使用POP EAX指令,我需要使用POP RAX
图片描述
图 4:使用FPU寻找RIP,尝试1

运行该序列会部分成功(见图5)。当加法执行时,RAX指向RIP之前的指令。这是必须使用67h地址大小前缀的原因。使用0x08替代0x07就能很容易的使之平衡。继续,自己尝试下,看看它是否有效。
图片描述
图 5:使用FPU指令寻找RIP,尝试1的结果

编写汇编

既然已经证明了使用FPU寄存器来获取RIP的值时可行的。我的下一步是看看是否能够写一些汇编代码,可以在调试器中手工输入命令可以使之平衡。经过一些调整后,这是我想出的代码:

SECTION .text]

BITS 64

global _start

_start:
    fldz
    fnstenv [rsp-0x0C]
    pop rax
    add rax, 0x07

当使用Nasm汇编时,上面指令中的代码结果如图 6。为了测试那些指令,我对用x64dbg打开的64位进程的第一条指令做二进制粘贴。如你所看到的,在add rax,0x07指令前有了新的48h前缀。根据Intel® 64 and IA-32 Architectures Software Developer’s Manual, Combined Volumes: 1, 2A, 2B, 2C, 2D, 3A, 3B, 3C, 3D and 4 章节3.7.2.1,这是一个REX前缀,允许指令寻址8位寄存器。汇编器可能够以这种方式丢到空字节。而且,由于当使用RSP而不是ESP时,对于fnstenv指令,地址大小前缀是非必须的,所以,RAX加上0x07字节的结果是RAX包含了RIP的地址。
图片描述
图 6:使用FPU'指令寻找RIP,终结

方法2:使用跳转和调用

在开始测试该方法前,我想做些微小的修改。我想有必要将EAX替换成RAX。这种跳转和调用和手工干扰到调试器中有一些不同,所以我选择先开始编写一些汇编代码。下面的代码就是我选择用来最开始尝试的:

[SECTION .text]

BITS 64

global _start

_start:
    jmp label2
    label1:
        jmp getEIP
    label2:
        call label1
    getEIP:
        pop rax

执行了使用汇编的代码覆盖的notepad的入口点的结果并可以在图 7中看到。这种方法依旧没有空字节,长度上仅仅10个字节。
图片描述
图 7:使用跳转和调用寻找RIP,终结

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

在64位架构上获取了两中方法后,我想看看是否有更好的方式。当搜索时,我想到了一篇由 Booze | Allen | Hamilton发表的文章,详细的描述了由Rapid7 在Metasploit中实现的方法,该方法使用LEA指令来获取RIP。经过一番尝试,在使用Nasm命令行时,我想出了下面的代码,可以复现Metasploit使用的方法:

[SECTION .text]

BITS 64

default rel

global _start

_start:
    lea rax, [_start]

当使用Nasm并使用一下标记汇编时,结果代码没有空字节,并仅仅7字节长。-O0标记告诉Nasm不要执行优化。如果你没有使用该标记,操作码会包含空值。自己尝试,并看看是如何功能做的:

nasm -O0 method3.asm -o method3.bin

代码和结果可以在图 8中看到。
图片描述
图 8:使用加载有效地址(LEA)指令寻找RIP,终结

我已经详细描述了几种根据目标架构寻找EIPRAX的方法。如果希望执行其他与shellcode交互的操作,动态获取这些值是非常重要的。由于ASLR无法动态查找EIP/RIP,所以解码,复制或者其他修改你的代码的操作是不可能的。在之后的文章,我会继续以此为基础。本系列的最后一篇文章将会编写一个完全自定义的shellcode,使用Intel x64汇编中的scatch编写。

感谢您花费时间阅读我的博客!我希望你能够从中学习并可能启发你自己学习更多。

原文链接:https://blog.xenoscr.net/Finding-EIP/
编译:看雪翻译小组 sudozhange
校对:看雪翻译小组 lipss

[2020元旦礼物]《看雪论坛精华17》发布!(补齐之前所有遗漏版本)!


文章来源: https://bbs.pediy.com/thread-257009.htm
如有侵权请联系:admin#unsafe.sh