格式化字符串漏洞学习汇总笔记

久仰”系统实战篇”,最近终于拜读了这本神作,在格式化串漏洞部分令我受益良多

前言

格式化字符串的格式如下:

%[parameter][flags][field width][.precision][length]type

知道漏洞怎么利用的第一步当然是知道漏洞是怎么发生的:

  • 使用格式化字符串的函数有:
1
2
3
4
5
6
int printf(const char *format, ...);                       
int fprintf(FILE *stream, const char *format, ...);
int dprintf(int fd, const char *format, ...);
int sprintf(char *str, const char *format, ...);
int snprintf(char *str, size_t size, const char *format, ...);
......
  • 格式化符:

    • 整数:

      %d,%i,%u,%o,%x
      
    • 浮点数:

      %e,%f,%g,%a
      
    • 字符:

      %c
      
    • 特殊字符:

      %s: 视为指向字符串的指针,以字符串的形式输出参数
      %n: 视为指向整数的指针,于参数前输出的字符数量将存于所指向的地址
      
    • 小技巧:

      1. 使用%p获取对应栈内存  
      2. 使用%s获取变量所对应的地址内容(有00截断)
      3. %order$x(s)来直接读取对应参数
      

利用所需满足的条件:

  • 可控制参数,并可以将输出字符写入任意区域
  • 宽度格式字符允许用任意长度填充输出,因此可以修改单字节

一些有意思的地址:

  • 保存的返回地址
  • 全局偏移表GOT
  • 析构函数表
  • C函数库钩子(malloc_hook,realloc_hook,free_hook)
  • atexit结构
  • 函数指针 : c++ vtables,回调函数

利用姿势

  • 改写保存的返回地址
  • 改写其他函数指针
  • 改写指向异常处理的指针,然后引起异常
  • 改写GOT条目
  • 改写atexit处理程序
  • 改写DTORS区段的条目
  • 用非i空数据改写空值终止符,将其转化为堆/栈溢出
  • 改写特殊数据,如(uid或gid)
  • 修改字符串中包含的命令反映所选命令
  • 泄露内存
    • 栈内存:
      • 某变量对应内存
      • 某个变量对应地址的内存
    • 泄露任意地址内存:
      • 利用GOT表得到libc基地址

如果程序开启了NX保护,可以先利用%n类格式符,将shellcode写至内存地址;
也可以先写一个小的shellcode来寻找一大片可以写入的shellcode然后利用

辅助利用模块(pwntools)

pwntools的pwnlib.fmtstr模块提供了一些格式化字符串漏洞的相关利用工具:

1
class pwnlib.fmtstr.FmtStr(execute_fmt, offset=None, padlen=0, n umbwritten=0)

各参数意义如下:

  • execute_fmt (function):与漏洞进程进行交互的函数
  • offset (int):所控制的第一个格式化程序的偏移量
  • padlen (int):在 paylod 之前添加的 pad 的大小
  • numbwritten (int):已经写入的字节数

还有自动生成payload的fmtstr_payload

1
pwnlib.fmtstr.fmtstr_payload(offset, writes, numbwritten=0, writ e_size='byte')

其中各参数意义如下:

  • offset (int):所控制的第一个格式化程序的偏移量
  • writes (dict):格式为 {addr: value, addr2: value2},用于往 addr 里写入 value的值
  • numbwritten(int): 已经由 printf 函数写入的字节数
  • write_size (str):必须是 byte,short 或 int。告诉你是要逐 byte 写,逐 short 写还是逐 int 写(hhn,hn或n)

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!