8 Apr 2019

格式化字符串漏洞

格式化字符串漏洞

11

  • 格式化字符串

    • 无格式字符

      一般的字符,可以直接复制到输出流。

    • 格式化占位符
      %[parameter][flags][field width][.precision][length]type
      
      • Parameter

        可以忽略或者n$

        使得参数可以输出多次,使用多个格式说明符,以不同的顺序输出。 如果任意一个占位符使用了parameter,则其他所有占位符必须也使用parameter

        printf("%2$d %2$#x; %1$d %1$#x",16,17) 
        //"17 0x11; 16 0x10"
        
      • flags

        字符 描述
        + 总是表示有符号数值的 + 或 - 号,缺省情况是忽略正数的符号。仅适用于数值类型。
        空格 使得有符号数的输出如果没有正负号或者输出0个字符,则前缀1个空格。如果空格与’+’同时出现,则空格说明符被忽略。
        - 左对齐。缺省情况是右对齐。
        # 对于’g‘与’G‘,不删除尾部0以表示精度。对于’f’, ‘F’, ‘e’, ‘E’, ‘g’, ‘G’, 总是输出小数点。对于’o’, ‘x’, ‘X’, 在非0数值前分别输出前缀0, 0x, and 0X表示数制。
        0 如果width选项前缀以0,则在左侧用0填充直至达到宽度要求。例如printf("%2d", 3)输出” 3“,而printf("%02d", 3)输出”03“。如果0-均出现,则0被忽略,即左对齐依然用空格填充。
      • width

        输出最小宽度,若实际位数多于width,则按照实际输出,若小于,则补空格或0。

      • Precision

        指明输出的最大长度。

        对于d、i、u、x、o的整型数值,是指最小数字位数,不足的位要在左侧补0,如果超过也不截断,缺省值为1。

        对于a,A,e,E,f,F的浮点数值,是指小数点右边显示的数字位数,必要时四舍五入或补0;缺省值为6。

        对于g,G的浮点数值,是指有效数字的最大位数;缺省值为6。

        对于s的字符串类型,是指输出的字节的上限,超出限制的其它字符将被截断。如果域宽为*,则由对应的函数参数的值为当前域宽。如果仅给出了小数点,则域宽为0。

      • Length

        指出浮点型参数或整型参数的长度

        字符 描述
        hh 对于整数类型,printf期待一个从char提升的int尺寸的整型参数。
        h 对于整数类型,printf期待一个从short提升的int尺寸的整型参数。
        l 对于整数类型,printf期待一个long尺寸的整型参数。对于浮点类型,printf期待一个double尺寸的整型参数。对于字符串s类型,printf期待一个wchar_t指针参数。对于字符c类型,printf期待一个wint_t型的参数。
        ll 对于整数类型,printf期待一个long long尺寸的整型参数。Microsoft也可以使用I64
        L 对于浮点类型,printf期待一个long double尺寸的整型参数。
        z 对于整数类型,printf期待一个size_t尺寸的整型参数。
        j 对于整数类型,printf期待一个intmax_t尺寸的整型参数。
        t 对于整数类型,printf期待一个ptrdiff_t尺寸的整型参数。
      • Type

        字符 描述
        d, i 有符号十进制数值int。’%d‘与’%i‘对于输出是同义;但对于scanf()输入二者不同,其中%i在输入值有前缀0x或0时,分别表示16进制或8进制的值。如果指定了精度,则输出的数字不足时在左侧补0。默认精度为1。精度为0且值为0,则输出为空。
        u 十进制unsigned int。如果指定了精度,则输出的数字不足时在左侧补0。默认精度为1。精度为0且值为0,则输出为空。
        f, F double型输出10进制定点表示。’f‘与’F‘差异是表示无穷与NaN时,’f‘输出’inf’, ‘infinity‘与’nan‘;’F‘输出’INF’, ‘INFINITY‘与’NAN‘。小数点后的数字位数等于精度,最后一位数字四舍五入。精度默认为6。如果精度为0且没有#标记,则不出现小数点。小数点左侧至少一位数字。
        e, E double值,输出形式为10进制的([-]d.ddd e[+/-]ddd). E版本使用的指数符号为E(而不是e)。指数部分至少包含2位数字,如果值为0,则指数部分为00。Windows系统,指数部分至少为3位数字,例如1.5e002,也可用Microsoft版的运行时函数_set_output_format 修改。小数点前存在1位数字。小数点后的数字位数等于精度。精度默认为6。如果精度为0且没有#标记,则不出现小数点。
        g, G double型数值,精度定义为全部有效数字位数。当指数部分在闭区间[-4,5] 内,输出为定点形式;否则输出为指数浮点形式。’g‘使用小写字母,’G‘使用大写字母。小数点右侧的尾数0不被显示;显示小数点仅当输出的小数部分不为0。
        x, X 16进制unsigned int。’x‘使用小写字母;’X‘使用大写字母。如果指定了精度,则输出的数字不足时在左侧补0。默认精度为1。精度为0且值为0,则输出为空。
        o 8进制unsigned int。如果指定了精度,则输出的数字不足时在左侧补0。默认精度为1。精度为0且值为0,则输出为空。
        s 如果没有用l标志,输出null结尾字符串直到精度规定的上限;如果没有指定精度,则输出所有字节。如果用了l标志,则对应函数参数指向wchar_t型的数组,输出时把每个宽字符转化为多字节字符,相当于调用wcrtomb函数。
        c 如果没有用l标志,把int参数转为unsigned char型输出;如果用了l标志,把wint_t参数转为包含两个元素的wchart_t数组,其中第一个元素包含要输出的字符,第二个元素为null宽字符。
        p void *
        a, A double型的16进制表示,”[−]0xh.hhhh p±d”。其中指数部分为10进制表示的形式。例如:1025.010输出为0x1.004000p+10。’a‘使用小写字母,’A‘使用大写字母。(C++11流使用hexfloat输出16进制浮点数)
        n 不输出字符,但是把已经成功输出的字符个数写入对应的整型指针参数所指的变量。
        % %‘字面值,不接受任何flags, width, precision or length。
    • 转义序列
      转义字符 意义 ASCII码值(十进制)
      \a 响铃(BEL) 007
      \b 退格(BS) ,将当前位置移到前一列 008
      \f 换页(FF),将当前位置移到下页开头 012
      \n 换行(LF) ,将当前位置移到下一行开头 010
      \r 回车(CR) ,将当前位置移到本行开头 013
      \t 水平制表(HT) (跳到下一个TAB位置) 009
      \v 垂直制表(VT) 011
      \ 代表一个反斜线字符’’' 092
      ' 代表一个单引号(撇号)字符 039
      " 代表一个双引号字符 034
      \? 代表一个问号 063
      \0 空字符(NUL) 000
      \ddd 1到3位八进制数所代表的任意字符 三位八进制
      \xhh 1到2位十六进制所代表的任意字符 十六进制
  • 格式化字符串函数

    • 输入
      • scanf
    • 输出
      函数 基本介绍
      printf 输出到 stdout
      fprintf 输出到指定 FILE 流
      vprintf 根据参数列表格式化输出到 stdout
      vfprintf 根据参数列表格式化输出到指定 FILE 流
      sprintf 输出到字符串
      snprintf 输出指定字节数到字符串
      vsprintf 根据参数列表格式化输出到字符串
      vsnprintf 根据参数列表格式化输出指定字节到字符串
      setproctitle 设置 argv
      syslog 输出日志
  • 格式化字符串漏洞原理

    printf

    #include <stdio.h>
    int main(void)
    {
            printf("%d%d%d%d%s",1,2,3,4,"a");
            return 0;
    }
    
    mov     dword ptr [esp+14h], offset aA ; "a"
    mov     dword ptr [esp+10h], 4
    mov     dword ptr [esp+0Ch], 3
    mov     dword ptr [esp+8], 2
    mov     dword ptr [esp+4], 1
    mov     dword ptr [esp], offset aDDDDS ; "%d%d%d%d%s"
    call    _printf
    

    调用printf时栈的布局

    1

    在进入 printf 之后,函数首先获取第一个参数(”%d%d%d%d%s”),一个一个读取其字符会遇到两种情况

    1. 当前字符不是 %,直接输出到相应标准输出
    2. 当前字符是 %, 继续读取下一个字符
      • 如果没有字符,报错
      • 如果下一个字符是 %, 输出 %
      • 否则根据相应的字符,获取相应的参数,对其进行解析并输出

    如果参数数量大于给出的数据数量,printf会按照参数规定的格式逐个输出栈上的数据

    #include <stdio.h>
    int main(void)
    {
            printf("%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",1,2,3);
            return 0;
    }
    
  • 格式化字符串漏洞利用

    • 程序崩溃

      对于%s,如果提供了一个不可访问的地址,程序就会崩溃

      栈上不可能每一格值都对应合法的地址,总会有可以使程序崩溃的值,所以给printf传入若干个%s就可以使程序崩溃

    • 泄露栈内存
      • 获取某个变量的值

        俺寻思这就根据想要的数据传入type就完事了,想搞到指定数据就用Parameter

      • 获取栈内存地址

        %p,然后算一波,可以得到栈基址

      • 获取某个变量对应地址的内存

        %s可以获取变量所对应地址的内容,但是有零截断

    • 泄露任意地址内存

      libc泄露

    • 覆盖任意地址内存
      1. 确定要覆盖的变量地址

      2. 确定相对偏移

        找到格式化字符串在栈上的位置,计算相对于printf格式化字符串参数的偏移,然后得出格式化字符串中的要覆盖的参数的地址相当于格式化字符串的第几个参数

        2

      3. 进行覆盖

        #payload
        ...[overwrite addr]....%[overwrite offset]$n
        


Tags:
0 comments



本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议CC BY-NC-ND 4.0)进行许可。

This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License (CC BY-NC-ND 4.0).