作者:k0shl 转载请注明出处:http://whereisk0shl.top
漏洞说明
软件下载:
请准备glibc-2.2.0环境
PoC:
https://github.com/fjserna/CVE-2015-7547
测试环境:
kali2.0
或 ubuntu 15.04
glibc-2.2.0
使用gcc编译client.c,方便使用gdb调试,运行poc.py,触发崩溃。
漏洞复现
此漏洞是由于linux在进行DNS解析时,调用了glibc中的getaddrinfo函数,此函数在进行处理时,涉及到libnss_dns.so.2和libresolv.so.2两个glibc中的动态链接库问题,其中,关键问题出现在libresolv.so.2中,下面我将从漏洞复现,回溯分析来还原一个完整的漏洞触发流程,进行详细分析。
首先,PoC给出的client,只是复现一个DNS请求的过程,通过wget等方法一样可以实现,但这里,为了能够对动态链接库进行调试,我们需要用gcc编译client,同时将rpath设置为我们编译的可调式的glibc库。
这个过程网上已经有一些分析了,不再做赘述,下面直接来到漏洞触发位置,gdb查看一下崩溃信息。
gdb-peda$ run
Starting program: /root/Desktop/CVE-2015-7547-master/CVE-2015-7547-master/gclient
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0xbfffeb70 ('B' <repeats 200 times>...)
EBX: 0xb7e01000 --> 0x14ed4
ECX: 0xbfffeb70 ('B' <repeats 200 times>...)
EDX: 0x42424242 ('BBBB')
ESI: 0xb7fd6340 --> 0x5
EDI: 0x42424242 ('BBBB')
EBP: 0xbfffd9e8 --> 0x0
ESP: 0xbfffd740 --> 0x1cb55
EIP: 0xb7df1969 (<__GI___libc_res_nquery+377>: movzx ecx,BYTE PTR [edi+0x3])
EFLAGS: 0x10206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0xb7df195b <__GI___libc_res_nquery+363>:
je 0xb7df1e67 <__GI___libc_res_nquery+1655>
0xb7df1961 <__GI___libc_res_nquery+369>: test edi,edi
0xb7df1963 <__GI___libc_res_nquery+371>:
je 0xb7df1e67 <__GI___libc_res_nquery+1655>
=> 0xb7df1969 <__GI___libc_res_nquery+377>: movzx ecx,BYTE PTR [edi+0x3]
0xb7df196d <__GI___libc_res_nquery+381>: and ecx,0xf
0xb7df1970 <__GI___libc_res_nquery+384>:
je 0xb7df19b0 <__GI___libc_res_nquery+448>
0xb7df1972 <__GI___libc_res_nquery+386>: test BYTE PTR [edx+0x3],0xf
0xb7df1976 <__GI___libc_res_nquery+390>:
jne 0xb7df1ba8 <__GI___libc_res_nquery+952>
[------------------------------------stack-------------------------------------]
0000| 0xbfffd740 --> 0x1cb55
0004| 0xbfffd744 --> 0x100
0008| 0xbfffd748 --> 0x0
0012| 0xbfffd74c --> 0x6f6f6603
0016| 0xbfffd750 --> 0x72616203
0020| 0xbfffd754 --> 0x6f6f6706
0024| 0xbfffd758 --> 0x3656c67
0028| 0xbfffd75c --> 0x6d6f63 ('com')
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
__GI___libc_res_nquery (statp=0xb7fd6340 <_res@GLIBC_2.0>,
name=0x8048653 "foo.bar.google.com", class=0x1, type=0xf371,
answer=0xbfffe340 "\324d", 'B' <repeats 198 times>..., anslen=0x800,
answerp=0xbfffeb6c, answerp2=0xbfffeb70, nanswerp2=0xbfffeb74,
resplen2=0xbfffeb78, answerp2_malloced=0xbfffeb7c) at res_query.c:264
264 res_query.c: No such file or directory.
可以看到,此时程序执行到movzx ecx,BYTE PTR [edi+0x3]位置时崩溃,我们可以通过寄存器页面看到edi此时的值是42424242,正是我们通过PoC向请求DNS解析回复的畸形字符串,那么此时应该是一个指针读取操作,由于地址不可读,导致程序崩溃。
接下来,我们需要查看一下堆栈信息。
gdb-peda$ bt
#0 __GI___libc_res_nquery (statp=0xb7fd6340 <_res@GLIBC_2.0>,
name=0x8048653 "foo.bar.google.com", class=0x1, type=0xf371,
answer=0xbfffe340 "\324d", 'B' <repeats 198 times>..., anslen=0x800,
answerp=0xbfffeb6c, answerp2=0xbfffeb70, nanswerp2=0xbfffeb74,
resplen2=0xbfffeb78, answerp2_malloced=0xbfffeb7c) at res_query.c:264
#1 0xb7df1fa1 in __libc_res_nquerydomain (
statp=statp@entry=0xb7fd6340 <_res@GLIBC_2.0>,
name=name@entry=0x8048653 "foo.bar.google.com", domain=0x0, class=0x1,
type=0xf371, answer=0xbfffe340 "\324d", 'B' <repeats 198 times>...,
anslen=0x800, answerp=0xbfffeb6c, answerp2=0xbfffeb70,
nanswerp2=0xbfffeb74, resplen2=0xbfffeb78, answerp2_malloced=0xbfffeb7c)
at res_query.c:594
#2 0xb7df24cb in __GI___libc_res_nsearch (statp=0xb7fd6340 <_res@GLIBC_2.0>,
name=0x8048653 "foo.bar.google.com", class=0x1, type=0xf371,
answer=0xbfffe340 "\324d", 'B' <repeats 198 times>..., anslen=0x800,
answerp=0xbfffeb6c, answerp2=0xbfffeb70, nanswerp2=0xbfffeb74,
resplen2=0xbfffeb78, answerp2_malloced=0xbfffeb7c) at res_query.c:381
#3 0xb7e06590 in _nss_dns_gethostbyname4_r (
name=0x8048653 "foo.bar.google.com", pat=0x42424242,
buffer=0x42424242 <error: Cannot access memory at address 0x42424242>,
buflen=0x42424242, errnop=0x42424242, herrnop=0x42424242, ttlp=0x42424242)
at nss_dns/dns-host.c:315
#4 0x42424242 in ?? ()
#5 0x42424242 in ?? ()
#6 0x42424242 in ?? ()
#7 0x42424242 in ?? ()
#8 0x42424242 in ?? ()
#9 0x42424242 in ?? ()
#10 0x42424242 in ?? ()
#11 0x42424242 in ?? ()
#12 0x42424242 in ?? ()
……
#40 0xb7e44242 in ?? () from /usr/local/glibc220/libc.so.6
Backtrace stopped: previous frame inner to this frame (corrupt stack?)
很显然#4以后的内容已经被畸形字符串冲垮,其实严格意义上来说从#1往后的内容都已经被冲垮了,比如#3位置,那么,我们就通过这个堆栈回溯我们可以初步进行一些分析,比如漏洞触发流程。
首先getaddrinfo的调用,在client客户端中,在getaddrinfo内层某个函数内部,或者直接在此函数中,会调用_nss_dns_gethostbyname4_函数,接着会调用res_nsearch,然后是res_nquerydomain,最后到达res_nquery某处导致了程序崩溃。
漏洞分析
在回溯过程中,我们需要着重观察的是,究竟是何时栈中被畸形字符串覆盖,又是在何处,导致畸形字符串的读取。
首先我们就从#3位置,也就是离崩溃现场已知最远端入手,进行分析。根据bt回溯的信息,我们可以看到nss_dns_gethostbyname4_r是nss_dns/dns-host.c中的函数,这个.c文件对应的动态链接库是libnss_dns.so.2,那么我们需要在加载动态链接库后对这个函数下断点,我们使用gdb中的catch load libnss_dns.so.2对动态链接库加载进行跟踪。
gdb-peda$ catch load libnss_dns.so.2
Catchpoint 1 (load)
gdb-peda$ run
Starting program: /root/Desktop/CVE-2015-7547-master/CVE-2015-7547-master/gclient
[----------------------------------registers-----------------------------------]
EAX: 0xbfffe98c --> 0xbfffeb50 ("libnss_dns.so.2")
EBX: 0xb7fff000 --> 0x22f0c
ECX: 0x4
EDX: 0x9 ('\t')
ESI: 0x0
EDI: 0x4
EBP: 0xbfffe868 --> 0xbfffe9c8 --> 0xbfffeb88 --> 0xbfffebb8 --> 0xbffff0e8 --> 0xbffff218 --> 0xbffff268 --> 0x0
ESP: 0xbfffe800 --> 0x804bff0 --> 0xb7e04000 --> 0x464c457f
EIP: 0xb7fef15a (<dl_open_worker+970>: nop)
EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0xb7fef153 <dl_open_worker+963>: test eax,eax
0xb7fef155 <dl_open_worker+965>: je 0xb7fef15b <dl_open_worker+971>
0xb7fef157 <dl_open_worker+967>: mov eax,DWORD PTR [ebp+0x8]
=> 0xb7fef15a <dl_open_worker+970>: nop
0xb7fef15b <dl_open_worker+971>: mov eax,DWORD PTR [ebp+0x8]
0xb7fef15e <dl_open_worker+974>: sub esp,0xc
0xb7fef161 <dl_open_worker+977>: mov ecx,DWORD PTR [eax+0x1c]
0xb7fef164 <dl_open_worker+980>: mov edx,DWORD PTR [eax+0x18]
[------------------------------------stack-------------------------------------]
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Catchpoint 1
Inferior loaded /lib/i386-linux-gnu/libnss_dns.so.2
/lib/i386-linux-gnu/libresolv.so.2
0xb7fef15a in dl_open_worker (a=0xbfffe98c) at dl-open.c:572
572 dl-open.c: No such file or directory.
程序中断后,说明动态链接库已经被加载,这时,我们就可以给_nss_dns_gethostbyname4_r下断点了。
gdb-peda$ delete
gdb-peda$ b _nss_dns_gethostbyname4_r
Breakpoint 2 at 0xb7e064d0: file nss_dns/dns-host.c, line 284.
gdb-peda$ run
Starting program: /root/Desktop/CVE-2015-7547-master/CVE-2015-7547-master/gclient
[----------------------------------registers-----------------------------------]
EAX: 0xbffff0c4 --> 0x0
EBX: 0xb7fd3000 --> 0x19cd64
ECX: 0xbfffeb27 --> 0x0
EDX: 0xb7e064d0 (<_nss_dns_gethostbyname4_r>: push ebp)
ESI: 0xb7e064d0 (<_nss_dns_gethostbyname4_r>: push ebp)
EDI: 0x420
EBP: 0xbffff0e8 --> 0xbffff218 --> 0xbffff268 --> 0x0
ESP: 0xbfffebac --> 0xb7efddbc (<gaih_inet+3495>: add esp,0x20)
EIP: 0xb7e064d0 (<_nss_dns_gethostbyname4_r>: push ebp)
EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0xb7e064c8 <_nss_dns_gethostbyname_r+136>: pop ebx
0xb7e064c9 <_nss_dns_gethostbyname_r+137>: ret
0xb7e064ca: lea esi,[esi+0x0]
=> 0xb7e064d0 <_nss_dns_gethostbyname4_r>: push ebp
0xb7e064d1 <_nss_dns_gethostbyname4_r+1>: mov ebp,esp
0xb7e064d3 <_nss_dns_gethostbyname4_r+3>: push edi
0xb7e064d4 <_nss_dns_gethostbyname4_r+4>: push esi
0xb7e064d5 <_nss_dns_gethostbyname4_r+5>: push ebx
[------------------------------------stack-------------------------------------]
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 2, _nss_dns_gethostbyname4_r (name=0x8048653 "foo.bar.google.com",
pat=0xbffff0c8, buffer=0xbfffebd0 "\377\002", buflen=0x420,
errnop=0xbffff0c4, herrnop=0xbffff0b0, ttlp=0x0) at nss_dns/dns-host.c:284
284 nss_dns/dns-host.c: No such file or directory.
顺利在入口处断了下来,这时我们继续按c,进行continue操作发现直接到达漏洞现场,这个过程就不展示了,可以在跟踪调试时进行,这说明进入此函数是漏洞触发前唯一一次调用到_nss_dns_gethostbyname4_r函数的位置,我们通过bt来观察一下。
gdb-peda$ bt
#0 _nss_dns_gethostbyname4_r (name=0x8048653 "foo.bar.google.com",
pat=0xbffff0c8, buffer=0xbfffebd0 "\377\002", buflen=0x420,
errnop=0xbffff0c4, herrnop=0xbffff0b0, ttlp=0x0) at nss_dns/dns-host.c:284
#1 0xb7efddbc in gaih_inet (name=<optimized out>,
name@entry=0x8048653 "foo.bar.google.com", service=<optimized out>,
req=0xbffff23c, pai=0xbffff1fc, naddrs=0xbffff1c4)
at ../sysdeps/posix/getaddrinfo.c:862
#2 0xb7f0023e in __GI_getaddrinfo (name=<optimized out>,
service=0x8048650 "22", hints=0xbffff23c, pai=0xbffff234)
at ../sysdeps/posix/getaddrinfo.c:2417
#3 0x08048588 in main ()
#4 0xb7e4d5cb in __libc_start_main (main=0x804853b <main>, argc=0x1,
argv=0xbffff314, init=0x80485d0 <__libc_csu_init>,
fini=0x8048630 <__libc_csu_fini>, rtld_fini=0xb7feb210 <_dl_fini>,
stack_end=0xbffff30c) at libc-start.c:289
#5 0x08048461 in _start ()
整个过程调用非常清晰,#3位置在主函数里,紧接着#2调用了我们的漏洞函数getaddrinfo,调用后某个位置我们调用了nss_dns_gethostbyname4_r函数,在到达此函数时,我们在poc端进行观察,发现poc并没有发送畸形字符串,在此函数入口,我们通过参数观察,也没有看到有畸形字符串加载进来。
这一点说明在getaddrinfo函数到nss_dns_gethostbyname之间没有涉及到畸形字符串获取,也就是说和漏洞无关,那么我们可以跳过这段调试,直接从_nss_gethostbyname4_r入手继续寻找。
接下来,我们通过最开始的bt堆栈调用,对后面几个函数进行分析,如果想在之后的调用位置下断点,需要继续对libresolv.so.2的加载进行跟踪,那么接下来,为了能够快速定位,我们就利用最开始回溯堆栈调用给予的信息,对#0,#1,#2三处下断点,首先利用catch load libresolv.so.2对动态链接库下断点,中断后,我们首先来到第一个#2位置。
gdb-peda$ b __libc_res_nsearch
Breakpoint 4 at 0xb7df5240: file res_query.c, line 342.
gdb-peda$ run
Starting program: /opt/gclient
[----------------------------------registers-----------------------------------]
EAX: 0xffffffb8
EBX: 0xb7e0d000 --> 0x5ec8
ECX: 0xbfffe200 --> 0x0
EDX: 0x0
ESI: 0xb7e35940 (0xb7e35940)
EDI: 0x8048653 ("foo.bar.google.com")
EBP: 0xbfffea68 --> 0xbfffefa8 --> 0xbffff0d8 --> 0xbffff128 --> 0x0
ESP: 0xbfffe1cc --> 0xb7e09590 (<_nss_dns_gethostbyname4_r+192>: add esp,0x30)
EIP: 0xb7df5240 (<__GI___libc_res_nsearch>: push ebp)
EFLAGS: 0x292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0xb7df5238 <__GI___res_hostalias+440>: ret
0xb7df5239 <__GI___res_hostalias+441>:
call 0xb7dfca50 <__stack_chk_fail_local>
0xb7df523e: xchg ax,ax
=> 0xb7df5240 <__GI___libc_res_nsearch>: push ebp
0xb7df5241 <__GI___libc_res_nsearch+1>: push edi
0xb7df5242 <__GI___libc_res_nsearch+2>: push esi
0xb7df5243 <__GI___libc_res_nsearch+3>: push ebx
0xb7df5244 <__GI___libc_res_nsearch+4>:
call 0xb7df06e0 <__x86.get_pc_thunk.bx>
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 4, __GI___libc_res_nsearch (statp=0xb7fd6340 <_res@GLIBC_2.0>,
name=0x8048653 "foo.bar.google.com", class=0x1, type=0xf371,
answer=0xbfffe200 "", anslen=0x800, answerp=0xbfffea2c,
answerp2=0xbfffea30, nanswerp2=0xbfffea34, resplen2=0xbfffea38,
answerp2_malloced=0xbfffea3c) at res_query.c:342
342 res_query.c: No such file or directory.
可以看到,此函数调用时,还是我们程序对应的地址内容,那么接下来,到达#1位置。
gdb-peda$ b __libc_res_nquerydomain
Breakpoint 5 at 0xb7df4eb0: file res_query.c, line 563.
gdb-peda$ run
Starting program: /opt/gclient
[----------------------------------registers-----------------------------------]
EAX: 0xb7fd6340 --> 0x5
EBX: 0xb7e04000 --> 0x14ed4
ECX: 0xbfffea2c --> 0xbfffe200 --> 0x0
EDX: 0x8048653 ("foo.bar.google.com")
ESI: 0x3
EDI: 0xb7fd6340 --> 0x5
EBP: 0xbfffea30 --> 0x0
ESP: 0xbfffdd2c --> 0xb7df54cb (<__GI___libc_res_nsearch+651>: add esp,0x30)
EIP: 0xb7df4eb0 (<__libc_res_nquerydomain>: push ebp)
EFLAGS: 0x292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0xb7df4ea4 <__GI___libc_res_nquery+1716>: push eax
0xb7df4ea5 <__GI___libc_res_nquery+1717>:
call 0xb7df0680 <__assert_fail@plt>
0xb7df4eaa: lea esi,[esi+0x0]
=> 0xb7df4eb0 <__libc_res_nquerydomain>: push ebp
0xb7df4eb1 <__libc_res_nquerydomain+1>: push edi
0xb7df4eb2 <__libc_res_nquerydomain+2>: mov edi,eax
0xb7df4eb4 <__libc_res_nquerydomain+4>: push esi
0xb7df4eb5 <__libc_res_nquerydomain+5>: push ebx
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 5, __libc_res_nquerydomain (
statp=statp@entry=0xb7fd6340 <_res@GLIBC_2.0>,
name=name@entry=0x8048653 "foo.bar.google.com", domain=0x0, class=0x1,
type=0xf371, answer=0xbfffe200 "", anslen=0x800, answerp=0xbfffea2c,
answerp2=0xbfffea30, nanswerp2=0xbfffea34, resplen2=0xbfffea38,
answerp2_malloced=0xbfffea3c) at res_query.c:563
563 res_query.c: No such file or directory.
可以看到,此时还是正常,接下来来到#0位置。
gdb-peda$ b __libc_res_nquery
Breakpoint 6 at 0xb7df47f0: file res_query.c, line 124.
gdb-peda$ run
Starting program: /opt/gclient
[----------------------------------registers-----------------------------------]
EAX: 0x11
EBX: 0xb7e04000 --> 0x14ed4
ECX: 0x13
EDX: 0x5
ESI: 0x8048653 ("foo.bar.google.com")
EDI: 0xb7fd6340 --> 0x5
EBP: 0x0
ESP: 0xbfffd8ac --> 0xb7df4fa1 (<__libc_res_nquerydomain+241>: add esp,0x30)
EIP: 0xb7df47f0 (<__GI___libc_res_nquery>: push ebp)
EFLAGS: 0x292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0xb7df47eb: xchg ax,ax
0xb7df47ed: xchg ax,ax
0xb7df47ef: nop
=> 0xb7df47f0 <__GI___libc_res_nquery>: push ebp
0xb7df47f1 <__GI___libc_res_nquery+1>: mov edx,0x220
0xb7df47f6 <__GI___libc_res_nquery+6>: mov ebp,esp
0xb7df47f8 <__GI___libc_res_nquery+8>: push edi
0xb7df47f9 <__GI___libc_res_nquery+9>: push esi
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 6, __GI___libc_res_nquery (statp=0xb7fd6340 <_res@GLIBC_2.0>,
name=0x8048653 "foo.bar.google.com", class=0x1, type=0xf371,
answer=0xbfffe200 "", anslen=0x800, answerp=0xbfffea2c,
answerp2=0xbfffea30, nanswerp2=0xbfffea34, resplen2=0xbfffea38,
answerp2_malloced=0xbfffea3c) at res_query.c:124
124 res_query.c: No such file or directory.
可以看到此时依然正常,这说明漏洞位置就出现在libc_res_nquery函数中,那么我们接下来,在对此函数进行跟踪分析之前,我们来通过源码来总结一下之前的调用过程。
_nss_dns_gethostbyname4_r (const char *name, struct gaih_addrtuple **pat,
char *buffer, size_t buflen, int *errnop,
int *herrnop, int32_t *ttlp)
{
……
//省略过程
……
host_buffer.buf = orig_host_buffer = (querybuf *) alloca (2048);//开辟2048空间,重要!
u_char *ans2p = NULL;
int nans2p = 0;
int resplen2 = 0;
int ans2p_malloced = 0;
int olderr = errno;
enum nss_status status;
//调用__libc_res_nsearch
int n = __libc_res_nsearch (&_res, name, C_IN, T_UNSPEC,
host_buffer.buf->buf, 2048, &host_buffer.ptr,
&ans2p, &nans2p, &resplen2, &ans2p_malloced);
可以看到这里为host_buffer作为querybuf开辟了2048字节的缓冲区,这也是后面漏洞在res_nquery形成的关键点。我将几次函数调用写在一起,省略了部分过程(毕竟不重要),这里我们还观察一下libc_res_nsearch调用的第五个参数,也就是2048空间对应的地址位置,接下来。
int
__libc_res_nsearch(res_state statp,
const char *name, /* domain name */
int class, int type, /* class and type of query */
u_char *answer, /* buffer to put answer */
int anslen, /* size of answer */
u_char **answerp,
u_char **answerp2,
int *nanswerp2,
int *resplen2,
int *answerp2_malloced)
{
……
省略过程
……
//调用_libc_res_nquerydomain
ret = __libc_res_nquerydomain(statp, name, NULL, class, type,
answer, anslen, answerp,
answerp2, nanswerp2, resplen2,
answerp2_malloced);
还记得刚才我们提到的第五个参数吗,就是现在的*answerp,紧接着继续调用到最后的处理函数。
static int
__libc_res_nquerydomain(res_state statp,
const char *name,
const char *domain,
int class, int type, /* class and type of query */
u_char *answer, /* buffer to put answer */
int anslen, /* size of answer */
u_char **answerp,
u_char **answerp2,
int *nanswerp2,
int *resplen2,
int *answerp2_malloced)
{
……
省略过程
……
//调用libc_res_nquery
return (__libc_res_nquery(statp, longname, class, type, answer,
anslen, answerp, answerp2, nanswerp2,
resplen2, answerp2_malloced));
}
还是answer变量值得关注,接下来的分析中会提到这一点,这个answer函数对应的位置就是已经分配的2048空间,而在函数进行read操作时,并没有对DNS返回的字符串畸形检查,而直接拷贝字符串了到数组空间了!
那么进入到res_nquery之后,我们需要对这个函数进行单步跟踪分析,因为一直到这个函数前,PoC端都没有反应,可见此时还是在本机进行了一些读取操作,后面查询操作时,才涉及到和DNS交互。单步跟踪,在某函数位置发现了问题。
gdb-peda$ run
Starting program: /root/Desktop/CVE-2015-7547-master/CVE-2015-7547-master/gclient
[----------------------------------registers-----------------------------------]
EAX: 0x804c728 --> 0x35000002
EBX: 0xb7e01000 --> 0x14ed4
ECX: 0x0
EDX: 0xb7fd6340 --> 0x5
ESI: 0x0
EDI: 0xb7fd6514 --> 0xffffffff
EBP: 0xb7fd6340 --> 0x5
ESP: 0xbfffd5d0 --> 0xbfffd764 --> 0x1006d
EIP: 0xb7df3702 (<__libc_res_nsend+354>: mov eax,DWORD PTR [esp+0x158])
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0xb7df36f6 <__libc_res_nsend+342>: mov esi,DWORD PTR [esp+0x1c]
0xb7df36fa <__libc_res_nsend+346>: test esi,esi
0xb7df36fc <__libc_res_nsend+348>:
jne 0xb7df4145 <__libc_res_nsend+2981>
=> 0xb7df3702 <__libc_res_nsend+354>: mov eax,DWORD PTR [esp+0x158]
0xb7df3709 <__libc_res_nsend+361>: mov esi,DWORD PTR [ebp+0x0]
0xb7df370c <__libc_res_nsend+364>: mov DWORD PTR [esp+0x9c],0x0
0xb7df3717 <__libc_res_nsend+375>: mov DWORD PTR [esp+0x74],eax
0xb7df371b <__libc_res_nsend+379>: mov eax,DWORD PTR [esp+0x4]
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 2, __libc_res_nsend (statp=0xb7fd6340 <_res@GLIBC_2.0>,
buf=0xbfffd740 "\362 \001", buflen=0x24, buf2=0xbfffd764 "m",
buflen2=0x24, ans=0xbfffe340 "", anssiz=0x800, ansp=0xbfffeb6c,
ansp2=0xbfffeb70, nansp2=0xbfffeb74, resplen2=0xbfffeb78,
ansp2_malloced=0xbfffeb7c) at res_send.c:564
564 res_send.c: No such file or directory.
我们进入到一处res_nsend函数,在进入前一切还正常,我们直接通过finish来执行到函数返回位置。
gdb-peda$ finish
Run till exit from #0 __libc_res_nsend (statp=0xb7fd6340 <_res@GLIBC_2.0>,
buf=0xbfffd740 "\362 \001", buflen=0x24, buf2=0xbfffd764 "m",
buflen2=0x24, ans=0xbfffe340 "", anssiz=0x800, ansp=0xbfffeb6c,
ansp2=0xbfffeb70, nansp2=0xbfffeb74, resplen2=0xbfffeb78,
ansp2_malloced=0xbfffeb7c) at res_send.c:564
[----------------------------------registers-----------------------------------]
EAX: 0xbcc
EBX: 0xb7e01000 --> 0x14ed4
ECX: 0x1
EDX: 0xffffffff
ESI: 0xb7fd6340 --> 0x5
EDI: 0xbfffe340 --> 0x4242006d ('m')
EBP: 0xbfffd9e8 --> 0x0
ESP: 0xbfffd710 --> 0xb7fd6340 --> 0x5
EIP: 0xb7df191b (<__GI___libc_res_nquery+299>: mov DWORD PTR [ebp-0x30],eax)
EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0xb7df1912 <__GI___libc_res_nquery+290>: push DWORD PTR [ebp-0x30]
0xb7df1915 <__GI___libc_res_nquery+293>: push esi
0xb7df1916 <__GI___libc_res_nquery+294>:
call 0xb7df35a0 <__libc_res_nsend>
=> 0xb7df191b <__GI___libc_res_nquery+299>: mov DWORD PTR [ebp-0x30],eax
0xb7df191e <__GI___libc_res_nquery+302>: mov eax,DWORD PTR [ebp-0x40]
0xb7df1921 <__GI___libc_res_nquery+305>: add esp,0x30
0xb7df1924 <__GI___libc_res_nquery+308>: test eax,eax
0xb7df1926 <__GI___libc_res_nquery+310>:
jne 0xb7df1b50 <__GI___libc_res_nquery+864>
[------------------------------------stack-------------------------------------]
0000| 0xbfffd710 --> 0xb7fd6340 --> 0x5
0004| 0xbfffd714 --> 0xbfffd740 --> 0x120f2
0008| 0xbfffd718 --> 0x24 ('$')
0012| 0xbfffd71c --> 0xbfffd764 --> 0x1006d
0016| 0xbfffd720 --> 0x24 ('$')
0020| 0xbfffd724 --> 0xbfffe340 --> 0x4242006d ('m')
0024| 0xbfffd728 --> 0x10000
0028| 0xbfffd72c --> 0xbfffeb6c ('B' <repeats 200 times>...)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0xb7df191b in __GI___libc_res_nquery (statp=0xb7fd6340 <_res@GLIBC_2.0>,
name=0x8048653 "foo.bar.google.com", class=0x1, type=0xf371,
answer=0xbfffe340 "m", anslen=0x800, answerp=0xbfffeb6c,
answerp2=0xbfffeb70, nanswerp2=0xbfffeb74, resplen2=0xbfffeb78,
answerp2_malloced=0xbfffeb7c) at res_query.c:227
227 res_query.c: No such file or directory.
在代码区,我们可以看到现在所处的位置是0xb7df191b的位置,而在这个位置上面的地址,执行了call __libc_res_nsend函数,当函数返回后,我们发现在栈中bfffeb6c的位置,出现了我们的畸形字符串B,而PoC端此时也执行了发送操作。我们来看一下bfffeb6c此时的值。
0xbfffeb9c: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0xbfffeba4: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0xbfffebac: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0xbfffebb4: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0xbfffebbc: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0xbfffebc4: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0xbfffebcc: 0x42 0x42 0x42 0x42
已经覆盖了大量的42424242,那么我们可以定位出现问题的地方在__libc_res_nsend中。在res_query.c中,我们可以看到res_nquery函数对res_nsend的调用。而且也只有这一处调用了res_nsend。
int
__libc_res_nquery(res_state statp,
const char *name, /* domain name */
int class, int type, /* class and type of query */
u_char *answer, /* buffer to put answer */
int anslen, /* size of answer buffer */
u_char **answerp, /* if buffer needs to be enlarged */
u_char **answerp2,
int *nanswerp2,
int *resplen2,
int *answerp2_malloced)
{
HEADER *hp = (HEADER *) answer;
HEADER *hp2;
int n, use_malloc = 0;
u_int oflags = statp->_flags;
……
省略过程
……
assert (answerp == NULL || (void *) *answerp == (void *) answer);
//漏洞触发函数
n = __libc_res_nsend(statp, query1, nquery1, query2, nquery2, answer,
anslen, answerp, answerp2, nanswerp2, resplen2,
answerp2_malloced);
if (use_malloc)
free (buf);
接下来,我们要着重关注一下libc_res_nsend函数,首先我们跟踪调试时发现程序会进入一处if语句判断,进入send_vc和send_dg函数,在send_vc函数中发现了socket和connect连接语句,在连接语句执行结束时,poc端提示connect 127.0.0.1,也就是执行了连接操作。
gdb-peda$ n
[----------------------------------registers-----------------------------------]
EAX: 0x3
EBX: 0xb7e01000 --> 0x14ed4
ECX: 0xbfffd2e0 --> 0x2
EDX: 0xb7e01000 --> 0x14ed4
ESI: 0xbfffeb78 --> 0x0
EDI: 0xb7fd6514 --> 0xffffffff
EBP: 0xb7fd6340 --> 0x5
ESP: 0xbfffd2e0 --> 0x2
EIP: 0xb7df2b64 (<send_vc+244>: add esp,0x10)
EFLAGS: 0x203 (CARRY parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0xb7df2b5b <send_vc+235>: movzx eax,WORD PTR [eax]
0xb7df2b5e <send_vc+238>: push eax
0xb7df2b5f <send_vc+239>: call 0xb7ded620 <socket@plt>
=> 0xb7df2b64 <send_vc+244>: add esp,0x10
0xb7df2b67 <send_vc+247>: test eax,eax
0xb7df2b69 <send_vc+249>: mov DWORD PTR [ebp+0x1c4],eax
0xb7df2b6f <send_vc+255>: js 0xb7df312a <send_vc+1722>
0xb7df2b75 <send_vc+261>: mov edi,DWORD PTR [esp+0x48]
[------------------------------------stack-------------------------------------]
0000| 0xbfffd2e0 --> 0x2
0004| 0xbfffd2e4 --> 0x1
0008| 0xbfffd2e8 --> 0x0
0012| 0xbfffd2ec --> 0xb7e433e8 --> 0x72647800 ('')
0016| 0xbfffd2f0 --> 0xb7fd8900 --> 0xb7e36000 --> 0x464c457f
0020| 0xbfffd2f4 --> 0xbfffeb74 --> 0x0
0024| 0xbfffd2f8 --> 0xbfffeb6c --> 0x804c748 --> 0x8083ab32
0028| 0xbfffd2fc --> 0xbfffd728 --> 0x10000
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
725 in res_send.c
Breakpoint 2, send_vc (statp=0xb7fd6340 <_res@GLIBC_2.0>,
buf=0xbfffd740 "B6\001", buflen=0x24, buf2=0xbfffd764 "\t\374\001",
buflen2=0x24, ansp=0xbfffd65c, anssizp=0xbfffd728, terrno=0xbfffd668,
ns=0x0, anscp=0xbfffeb6c, ansp2=0xbfffeb70, anssizp2=0xbfffeb74,
resplen2=0xbfffeb78, ansp2_malloced=0xbfffeb7c) at res_send.c:669
669 res_send.c: No such file or directory.
连接后,我们继续单步跟进,poc端收到了tcp的请求,同时,glibc接收到了畸形字符串,通过read函数读取,我们可以来观察一下读取前后的情况,在此之前,我们通过bt观察一下某个之前提到的重点变量,就是保存了2048缓冲区的重点变量。
gdb-peda$ bt
#0 send_vc (statp=0xb7fd6340 <_res@GLIBC_2.0>, buf=0xbfffd740 "\274\206\001",
buflen=0x24, buf2=0xbfffd764 "\264\316\001", buflen2=0x24,
ansp=0xbfffd65c, anssizp=0xbfffd728, terrno=0xbfffd668, ns=0x0,
anscp=0xbfffeb6c, ansp2=0xbfffeb70, anssizp2=0xbfffeb74,
resplen2=0xbfffeb78, ansp2_malloced=0xbfffeb7c) at res_send.c:669
#1 0xb7df3c4e in __libc_res_nsend (statp=0xb7fd6340 <_res@GLIBC_2.0>,
buf=0xbfffd740 "\274\206\001", buflen=0x24,
buf2=0xbfffd764 "\264\316\001", buflen2=0x24, ans=0xbfffe340 "",
anssiz=0x10000, ansp=0xbfffeb6c, ansp2=0xbfffeb70, nansp2=0xbfffeb74,
resplen2=0xbfffeb78, ansp2_malloced=0xbfffeb7c) at res_send.c:554
#2 0xb7df191b in __GI___libc_res_nquery (statp=0xb7fd6340 <_res@GLIBC_2.0>,
name=0x8048653 "foo.bar.google.com", class=0x1, type=0xf371,
answer=0xbfffe340 "", anslen=0x800, answerp=0xbfffeb6c,
answerp2=0xbfffeb70, nanswerp2=0xbfffeb74, resplen2=0xbfffeb78,
answerp2_malloced=0xbfffeb7c) at res_query.c:227
#3 0xb7df1fa1 in __libc_res_nquerydomain (
statp=statp@entry=0xb7fd6340 <_res@GLIBC_2.0>,
name=name@entry=0x8048653 "foo.bar.google.com", domain=0x0, class=0x1,
type=0xf371, answer=0xbfffe340 "", anslen=0x800, answerp=0xbfffeb6c,
answerp2=0xbfffeb70, nanswerp2=0xbfffeb74, resplen2=0xbfffeb78,
answerp2_malloced=0xbfffeb7c) at res_query.c:594
#4 0xb7df24cb in __GI___libc_res_nsearch (statp=0xb7fd6340 <_res@GLIBC_2.0>,
name=0x8048653 "foo.bar.google.com", class=0x1, type=0xf371,
answer=0xbfffe340 "", anslen=0x800, answerp=0xbfffeb6c,
answerp2=0xbfffeb70, nanswerp2=0xbfffeb74, resplen2=0xbfffeb78,
answerp2_malloced=0xbfffeb7c) at res_query.c:381
这里我们要好好分析一下,首先是#4处的answer,地址是0xbfffe340,之前我们提到过,这里时开辟的2048长度地址的缓冲区,后面的anslen=0x800也是长度,2048,接下来在#3中,answer地址没有变化继续传递,接下来在res_nquery中,依然没有变化,最后到达关键函数send_vc的时候,我们可以看到ansp=0xbfffd65c,这个地址非常有意思,首先在函数入口处,我们可以看一下这个地址的中存放的值。
gdb-peda$ x/10x 0xbfffd65c
0xbfffd65c: 0xbfffe340 0xbfffd764 0xbfffd770 0x0000006e
0xbfffd66c: 0x000009e8 0x56cd507c 0x1d20b5f8 0x00000003
0xbfffd67c: 0x00010001 0xbfffd740
还是0xbfffe340,那么这个地址很有可能是地址指针的指针,也就是类似于**ansp这样的形式!接下来这个值是如何传递的呢,我们可以分析一下。请注意我单行的注释。
static int
send_vc(res_state statp,
const u_char *buf, int buflen, const u_char *buf2, int buflen2,
u_char **ansp, int *anssizp,//ansp是2048缓冲区对应地址
int *terrno, int ns, u_char **anscp, u_char **ansp2, int *anssizp2,
int *resplen2, int *ansp2_malloced)
{
const HEADER *hp = (HEADER *) buf;
const HEADER *hp2 = (HEADER *) buf2;
u_char *ans = *ansp;//对应地址的传递
int orig_anssizp = *anssizp;
// XXX REMOVE
// int anssiz = *anssizp;
HEADER *anhp = (HEADER *) ans;
……
……
if (statp->_vcsock < 0 || (statp->_flags & RES_F_VC) == 0) {
if (statp->_vcsock >= 0)
__res_iclose(statp, false);
//这里建立socket连接
statp->_vcsock = socket(nsap->sin6_family, SOCK_STREAM, 0);
if (statp->_vcsock < 0) {
*terrno = errno;
Perror(statp, stderr, "socket(vc)", errno);
return (-1);
}
__set_errno (0);
//connect操作,客户端会提示connect 127.0.0.1
if (connect(statp->_vcsock, (struct sockaddr *)nsap,
nsap->sin6_family == AF_INET
? sizeof (struct sockaddr_in)
: sizeof (struct sockaddr_in6)) < 0) {
*terrno = errno;
Aerror(statp, stderr, "connect/vc", errno,
(struct sockaddr *) nsap);
__res_iclose(statp, false);
return (0);
}
statp->_flags |= RES_F_VC;
}
/*发送部分,无关紧要
* Send length & message
*/
……
/*接收部分
* Receive length & response
*/
int recvresp1 = 0;
int recvresp2 = buf2 == NULL;
uint16_t rlen16;
read_len:
cp = (u_char *)&rlen16;
len = sizeof(rlen16);
while ((n = TEMP_FAILURE_RETRY (read(statp->_vcsock, cp,
(int)len))) > 0) {
cp += n;
if ((len -= n) <= 0)
break;
}
if (n <= 0) {
*terrno = errno;
Perror(statp, stderr, "read failed", errno);
__res_iclose(statp, false);
/*
* A long running process might get its TCP
* connection reset if the remote server was
* restarted. Requery the server instead of
* trying a new one. When there is only one
* server, this means that a query might work
* instead of failing. We only allow one reset
* per query to prevent looping.
*/
if (*terrno == ECONNRESET && !connreset) {
connreset = 1;
goto same_ns;
}
return (0);
}
int rlen = ntohs (rlen16);
int *thisanssizp;
u_char **thisansp;
int *thisresplenp;
if ((recvresp1 | recvresp2) == 0 || buf2 == NULL) {
……//第一次收到,无关紧要,第二次收到将进入下面的else部分
} else {
if (*anssizp != MAXPACKET) {
……
} else {
/* The first reply did not fit into the
user-provided buffer. Maybe the second
answer will. */
*anssizp2 = orig_anssizp;
*ansp2 = *ansp;
}
thisanssizp = anssizp2;
thisansp = ansp2;
//此时ansp2会赋值给thisansp,而此时thisansp的值是ansp
thisresplenp = resplen2;
}
……
//此时cp的地址是bfffe340,也就是2048字节缓冲区
cp = *thisansp;
接着read参数会读取这个接收到的参数,第二次接收到时,是长度为超长的字符串,而此时,没有对这个字符串长度进行任何判断!
while (len != 0 && (n = read(statp->_vcsock, (char *)cp, (int)len)) > 0){
cp += n;
len -= n;
}
看到这里,我们基本可以分析出来为什么PoC要发送两次,而在第二次中,加上了2300个'B',也就是说在第二次接收时,2048缓冲区对应的变量会赋值给即将接收字符串的缓冲区,而此时,没有对这个缓冲区要接收内容的长度进行处理,从而导致了超长串覆盖,函数返回后,某个地址被覆盖导致dns请求崩溃。
接下来我们可以看一下read前后,缓冲区的变化。
gdb-peda$ n
[----------------------------------registers-----------------------------------]
EAX: 0x8fe
EBX: 0xb7e01000 --> 0x14ed4
ECX: 0xbfffd65c --> 0xbfffe340 --> 0x0
EDX: 0x10000
ESI: 0xbfffe340 --> 0x0
EDI: 0xbfffeb70 --> 0xbfffe340 --> 0x0
EBP: 0xb7fd6340 --> 0x5
ESP: 0xbfffd2f0 --> 0xb7fd8900 --> 0xb7e36000 --> 0x464c457f
EIP: 0xb7df2eba (<send_vc+1098>: mov edi,DWORD PTR [edi])
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0xb7df2eab <send_vc+1083>: mov WORD PTR [esp+0x5e],ax
0xb7df2eb0 <send_vc+1088>: cmp ax,0xb
0xb7df2eb4 <send_vc+1092>: jbe 0xb7df2fbd <send_vc+1357>
=> 0xb7df2eba <send_vc+1098>: mov edi,DWORD PTR [edi]
0xb7df2ebc <send_vc+1100>: jmp 0xb7df2ed6 <send_vc+1126>
0xb7df2ebe <send_vc+1102>: xchg ax,ax
0xb7df2ec0 <send_vc+1104>: movzx edx,WORD PTR [esp+0x5e]
0xb7df2ec5 <send_vc+1109>: add edi,eax
[------------------------------------stack-------------------------------------]
0000| 0xbfffd2f0 --> 0xb7fd8900 --> 0xb7e36000 --> 0x464c457f
0004| 0xbfffd2f4 --> 0xbfffeb74 --> 0x10000
0008| 0xbfffd2f8 --> 0xbfffeb6c --> 0x804c748 --> 0x80818bf5
0012| 0xbfffd2fc --> 0xbfffd728 --> 0x10000
0016| 0xbfffd300 --> 0xbfffeb70 --> 0xbfffe340 --> 0x0
0020| 0xbfffd304 --> 0x0
0024| 0xbfffd308 --> 0xbfffeb74 --> 0x10000
0028| 0xbfffd30c --> 0x1
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
883 in res_send.c
gdb-peda$ n
[----------------------------------registers-----------------------------------]
EAX: 0x8fe
EBX: 0xb7e01000 --> 0x14ed4
ECX: 0xbfffe340 --> 0x4242bb5e
EDX: 0x8fe
ESI: 0xbfffe340 --> 0x4242bb5e
EDI: 0xbfffe340 --> 0x4242bb5e
EBP: 0xb7fd6340 --> 0x5
ESP: 0xbfffd2f0 --> 0xb7fd8900 --> 0xb7e36000 --> 0x464c457f
EIP: 0xb7df2ec0 (<send_vc+1104>: movzx edx,WORD PTR [esp+0x5e])
EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0xb7df2eba <send_vc+1098>: mov edi,DWORD PTR [edi]
0xb7df2ebc <send_vc+1100>: jmp 0xb7df2ed6 <send_vc+1126>
0xb7df2ebe <send_vc+1102>: xchg ax,ax
=> 0xb7df2ec0 <send_vc+1104>: movzx edx,WORD PTR [esp+0x5e]
0xb7df2ec5 <send_vc+1109>: add edi,eax
0xb7df2ec7 <send_vc+1111>: sub edx,eax
0xb7df2ec9 <send_vc+1113>: movzx eax,dx
0xb7df2ecc <send_vc+1116>: test ax,ax
[------------------------------------stack-------------------------------------]
0000| 0xbfffd2f0 --> 0xb7fd8900 --> 0xb7e36000 --> 0x464c457f
0004| 0xbfffd2f4 --> 0xbfffeb74 ('B' <repeats 200 times>...)
0008| 0xbfffd2f8 --> 0xbfffeb6c ('B' <repeats 200 times>...)
0012| 0xbfffd2fc --> 0xbfffd728 --> 0x10000
0016| 0xbfffd300 --> 0xbfffeb70 ('B' <repeats 200 times>...)
0020| 0xbfffd304 --> 0x0
0024| 0xbfffd308 --> 0xbfffeb74 ('B' <repeats 200 times>...)
0028| 0xbfffd30c --> 0x1
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
886 in res_send.c