精华内容
下载资源
问答
  • Python格式化字符串f-string概览

    万次阅读 多人点赞 2018-07-15 01:11:22
    f-string,亦称为格式化字符串常量(formatted string literals),是Python3.6新引入的一种字符串格式化方法,该方法源于PEP 498 – Literal String Interpolation,主要目的是使格式化字符串的操作更加简便。...

    简介

    f-string,亦称为格式化字符串常量(formatted string literals),是Python3.6新引入的一种字符串格式化方法,该方法源于PEP 498 – Literal String Interpolation,主要目的是使格式化字符串的操作更加简便。f-string在形式上是以 fF 修饰符引领的字符串(f'xxx'F'xxx'),以大括号 {} 标明被替换的字段;f-string在本质上并不是字符串常量,而是一个在运行时运算求值的表达式:

    While other string literals always have a constant value, formatted strings are really expressions evaluated at run time.
    (与具有恒定值的其它字符串常量不同,格式化字符串实际上是运行时运算求值的表达式。)
    —— Python Documentation

    f-string在功能方面不逊于传统的%-formatting语句str.format()函数,同时性能又优于二者,且使用起来也更加简洁明了,因此对于Python3.6及以后的版本,推荐使用f-string进行字符串格式化。

    用法

    此部分内容主要参考以下资料:

    简单使用

    f-string用大括号 {} 表示被替换字段,其中直接填入替换内容:

    >>> name = 'Eric'
    >>> f'Hello, my name is {name}'
    'Hello, my name is Eric'
    
    >>> number = 7
    >>> f'My lucky number is {number}'
    'My lucky number is 7'
    
    >>> price = 19.99
    >>> f'The price of this book is {price}'
    'The price of this book is 19.99'
    

    表达式求值与函数调用

    f-string的大括号 {} 可以填入表达式或调用函数,Python会求出其结果并填入返回的字符串内:

    >>> f'A total number of {24 * 8 + 4}'
    'A total number of 196'
    
    >>> f'Complex number {(2 + 2j) / (2 - 3j)}'
    'Complex number (-0.15384615384615388+0.7692307692307692j)'
    
    >>> name = 'ERIC'
    >>> f'My name is {name.lower()}'
    'My name is eric'
    
    >>> import math
    >>> f'The answer is {math.log(math.pi)}'
    'The answer is 1.1447298858494002'
    

    引号、大括号与反斜杠

    f-string大括号内所用的引号不能和大括号外的引号定界符冲突,可根据情况灵活切换 '"

    >>> f'I am {"Eric"}'
    'I am Eric'
    >>> f'I am {'Eric'}'
      File "<stdin>", line 1
        f'I am {'Eric'}'
                    ^
    SyntaxError: invalid syntax
    

    '" 不足以满足要求,还可以使用 '''"""

    >>> f"He said {"I'm Eric"}"
      File "<stdin>", line 1
        f"He said {"I'm Eric"}"
                    ^
    SyntaxError: invalid syntax
    
    >>> f'He said {"I'm Eric"}'
      File "<stdin>", line 1
        f'He said {"I'm Eric"}'
                      ^
    SyntaxError: invalid syntax
    
    >>> f"""He said {"I'm Eric"}"""
    "He said I'm Eric"
    >>> f'''He said {"I'm Eric"}'''
    "He said I'm Eric"
    

    大括号外的引号还可以使用 \ 转义,但大括号内不能使用 \ 转义:

    >>> f'''He\'ll say {"I'm Eric"}'''
    "He'll say I'm Eric"
    >>> f'''He'll say {"I\'m Eric"}'''
      File "<stdin>", line 1
    SyntaxError: f-string expression part cannot include a backslash
    

    f-string大括号外如果需要显示大括号,则应输入连续两个大括号 {{}}

    >>> f'5 {"{stars}"}'
    '5 {stars}'
    >>> f'{{5}} {"stars"}'
    '{5} stars'
    

    上面提到,f-string大括号内不能使用 \ 转义,事实上不仅如此,f-string大括号内根本就不允许出现 \。如果确实需要 \,则应首先将包含 \ 的内容用一个变量表示,再在f-string大括号内填入变量名:

    >>> f"newline: {ord('\n')}"
      File "<stdin>", line 1
    SyntaxError: f-string expression part cannot include a backslash
    
    >>> newline = ord('\n')
    >>> f'newline: {newline}'
    'newline: 10'
    

    多行f-string

    f-string还可用于多行字符串:

    >>> name = 'Eric'
    >>> age = 27
    >>> f"Hello!" \
    ... f"I'm {name}." \
    ... f"I'm {age}."
    "Hello!I'm Eric.I'm 27."
    >>> f"""Hello!
    ...     I'm {name}.
    ...     I'm {age}."""
    "Hello!\n    I'm Eric.\n    I'm 27."
    

    自定义格式:对齐、宽度、符号、补零、精度、进制等

    f-string采用 {content:format} 设置字符串格式,其中 content 是替换并填入字符串的内容,可以是变量、表达式或函数等,format 是格式描述符。采用默认格式时不必指定 {:format},如上面例子所示只写 {content} 即可。

    关于格式描述符的详细语法及含义可查阅Python官方文档,这里按使用时的先后顺序简要介绍常用格式描述符的含义与作用:

    对齐相关格式描述符

    格式描述符 含义与作用
    < 左对齐(字符串默认对齐方式)
    > 右对齐(数值默认对齐方式)
    ^ 居中

    数字符号相关格式描述符

    格式描述符 含义与作用
    + 负数前加负号(-),正数前加正号(+
    - 负数前加负号(-),正数前不加任何符号(默认)
    (空格) 负数前加负号(-),正数前加一个空格

    注:仅适用于数值类型。

    数字显示方式相关格式描述符

    格式描述符 含义与作用
    # 切换数字显示方式

    注1:仅适用于数值类型。
    注2:# 对不同数值类型的作用效果不同,详见下表:

    数值类型 不加#(默认) # 区别
    二进制整数 '1111011' '0b1111011' 开头是否显示 0b
    八进制整数 '173' '0o173' 开头是否显示 0o
    十进制整数 '123' '123' 无区别
    十六进制整数(小写字母) '7b' '0x7b' 开头是否显示 0x
    十六进制整数(大写字母) '7B' '0X7B' 开头是否显示 0X

    宽度与精度相关格式描述符

    格式描述符 含义与作用
    width 整数 width 指定宽度
    0width 整数 width 指定宽度,开头的 0 指定高位用 0 补足宽度
    width.precision 整数 width 指定宽度,整数 precision 指定显示精度

    注1:0width 不可用于复数类型和非数值类型,width.precision 不可用于整数类型。
    注2:width.precision 用于不同格式类型的浮点数、复数时的含义也不同:用于 fFeE%precision 指定的是小数点后的位数,用于 gGprecision 指定的是有效数字位数(小数点前位数+小数点后位数)。
    注3:width.precision 除浮点数、复数外还可用于字符串,此时 precision 含义是只使用字符串中前 precision 位字符。

    示例:

    >>> a = 123.456
    >>> f'a is {a:8.2f}'
    'a is   123.46'
    >>> f'a is {a:08.2f}'
    'a is 00123.46'
    >>> f'a is {a:8.2e}'
    'a is 1.23e+02'
    >>> f'a is {a:8.2%}'
    'a is 12345.60%'
    >>> f'a is {a:8.2g}'
    'a is  1.2e+02'
    
    >>> s = 'hello'
    >>> f's is {s:8s}'
    's is hello   '
    >>> f's is {s:8.3s}'
    's is hel     '
    

    千位分隔符相关格式描述符

    格式描述符 含义与作用
    , 使用,作为千位分隔符
    _ 使用_作为千位分隔符

    注1:若不指定 ,_,则f-string不使用任何千位分隔符,此为默认设置。
    注2:, 仅适用于浮点数、复数与十进制整数:对于浮点数和复数,, 只分隔小数点前的数位。
    注3:_ 适用于浮点数、复数与二、八、十、十六进制整数:对于浮点数和复数,_ 只分隔小数点前的数位;对于二、八、十六进制整数,固定从低位到高位每隔四位插入一个 _(十进制整数是每隔三位插入一个 _)。

    示例:

    >>> a = 1234567890.098765
    >>> f'a is {a:f}'
    'a is 1234567890.098765'
    >>> f'a is {a:,f}'
    'a is 1,234,567,890.098765'
    >>> f'a is {a:_f}'
    'a is 1_234_567_890.098765'
    
    >>> b = 1234567890
    >>> f'b is {b:_b}'
    'b is 100_1001_1001_0110_0000_0010_1101_0010'
    >>> f'b is {b:_o}'
    'b is 111_4540_1322'
    >>> f'b is {b:_d}'
    'b is 1_234_567_890'
    >>> f'b is {b:_x}'
    'b is 4996_02d2'
    

    格式类型相关格式描述符

    基本格式类型

    格式描述符 含义与作用 适用变量类型
    s 普通字符串格式 字符串
    b 二进制整数格式 整数
    c 字符格式,按unicode编码将整数转换为对应字符 整数
    d 十进制整数格式 整数
    o 八进制整数格式 整数
    x 十六进制整数格式(小写字母) 整数
    X 十六进制整数格式(大写字母) 整数
    e 科学计数格式,以 e 表示 ×10^ 浮点数、复数、整数(自动转换为浮点数)
    E e 等价,但以 E 表示 ×10^ 浮点数、复数、整数(自动转换为浮点数)
    f 定点数格式,默认精度(precision)是6 浮点数、复数、整数(自动转换为浮点数)
    F f 等价,但将 naninf 换成 NANINF 浮点数、复数、整数(自动转换为浮点数)
    g 通用格式,小数用 f,大数用 e 浮点数、复数、整数(自动转换为浮点数)
    G G 等价,但小数用 F,大数用 E 浮点数、复数、整数(自动转换为浮点数)
    % 百分比格式,数字自动乘上100后按 f 格式排版,并加 % 后缀 浮点数、整数(自动转换为浮点数)

    常用的特殊格式类型:标准库 datetime 给定的用于排版时间信息的格式类型,适用于 datedatetimetime 对象

    格式描述符 含义 显示样例
    %a 星期几(缩写) 'Sun'
    %A 星期几(全名) 'Sunday'
    %w 星期几(数字,0 是周日,6 是周六) '0'
    %u 星期几(数字,1 是周一,7 是周日) '7'
    %d 日(数字,以 0 补足两位) '07'
    %b 月(缩写) 'Aug'
    %B 月(全名) 'August'
    %m 月(数字,以 0 补足两位) '08'
    %y 年(后两位数字,以 0 补足两位) '14'
    %Y 年(完整数字,不补零) '2014'
    %H 小时(24小时制,以 0 补足两位) '23'
    %I 小时(12小时制,以 0 补足两位) '11'
    %p 上午/下午 'PM'
    %M 分钟(以 0 补足两位) '23'
    %S 秒钟(以 0 补足两位) '56'
    %f 微秒(以 0 补足六位) '553777'
    %z UTC偏移量(格式是 ±HHMM[SS],未指定时区则返回空字符串) '+1030'
    %Z 时区名(未指定时区则返回空字符串) 'EST'
    %j 一年中的第几天(以 0 补足三位) '195'
    %U 一年中的第几周(以全年首个周日后的星期为第0周,以 0 补足两位) '27'
    %w 一年中的第几周(以全年首个周一后的星期为第0周,以 0 补足两位) '28'
    %V 一年中的第几周(以全年首个包含1月4日的星期为第1周,以 0 补足两位) '28'

    综合示例

    >>> a = 1234
    >>> f'a is {a:^#10X}'      # 居中,宽度10位,十六进制整数(大写字母),显示0X前缀
    'a is   0X4D2   '
    
    >>> b = 1234.5678
    >>> f'b is {b:<+10.2f}'    # 左对齐,宽度10位,显示正号(+),定点数格式,2位小数
    'b is +1234.57  '
    
    >>> c = 12345678
    >>> f'c is {c:015,d}'      # 高位补零,宽度15位,十进制整数,使用,作为千分分割位
    'c is 000,012,345,678'
    
    >>> d = 0.5 + 2.5j
    >>> f'd is {d:30.3e}'      # 宽度30位,科学计数法,3位小数
    'd is           5.000e-01+2.500e+00j'
    
    >>> import datetime
    >>> e = datetime.datetime.today()
    >>> f'the time is {e:%Y-%m-%d (%a) %H:%M:%S}'   # datetime时间格式
    'the time is 2018-07-14 (Sat) 20:46:02'
    

    lambda表达式

    f-string大括号内也可填入lambda表达式,但lambda表达式的 : 会被f-string误认为是表达式与格式描述符之间的分隔符,为避免歧义,需要将lambda表达式置于括号 () 内:

    >>> f'result is {lambda x: x ** 2 + 1 (2)}'
      File "<fstring>", line 1
        (lambda x)
                 ^
    SyntaxError: unexpected EOF while parsing
    
    >>> f'result is {(lambda x: x ** 2 + 1) (2)}'
    'result is 5'
    >>> f'result is {(lambda x: x ** 2 + 1) (2):<+7.2f}'
    'result is +5.00  '
    
    展开全文
  • 三、格式化字符串漏洞 原文:Exploiting Format String Vulnerabilities 作者:scut@team-teso.net 译者:飞龙 日期:2001.9.1 版本:v1.2 格式化字符串漏洞的通常分类是“通道问题”。如果二类不同的...

    三、格式化字符串漏洞

    原文:Exploiting Format String Vulnerabilities

    作者:scut@team-teso.net

    译者:飞龙

    日期:2001.9.1

    版本:v1.2

    格式化字符串漏洞的通常分类是“通道问题”。如果二类不同的信息通道混合为一个,并且特殊的转义字符或序列用于分辨当前哪个通道是激活的,这一类型的漏洞就可能出现。多数情况下,通道之一是数据通道,它不会解析,只会复制,而另一个通道是控制通道。

    虽然对于其本身来说并不是件坏事,如果攻击者能够提供用于某个通道的输入,它可能很快成为严重的安全问题。通常存在错误的转义,或者反转义的途径,或者忽视了某个层面,就像格式化字符串漏洞中那样。所以我们总结一下:通道问题本身没有任何漏洞,但是它们使得 bug 可以利用。

    为了展示它背后的普遍问题,这里是一个常见通道问题的列表:

    场景 数据通道 控制通道 安全问题
    电话系统 声音或数据 控制音调 线路控制
    PPP 协议 传输数据 PPP 命令 流量放大
    栈数据 返回地址 返回地址控制
    Malloc 缓冲区 Malloc 数据 管理信息 内存写入
    格式化字符串 输出字符串 格式化参数 格式化函数控制

    回到特定的格式化字符串漏洞,有两种典型的场景,其中产生了格式化字符串漏洞。

    第一类(Linux rpc.statd 和 IRIX telnetd 中)。漏洞存在于syslog的第二个参数中。格式化字符串部分是用户提供。

    char tmpbuf[512];
    
    snprintf (tmpbuf, sizeof (tmpbuf), "foo: %s", user); 
    tmpbuf[sizeof (tmpbuf) - 1] = ’\0’; 
    syslog (LOG_NOTICE, tmpbuf);

    第二类(wu-ftpd 和 Qualcomm Popper QPOP 2.53 中)。部分由用户提供的字符串简介传给了格式化函数。

    int Error (char *fmt, ...);
    
    ... 
    int someotherfunc (char *user) { 
        ...
        Error (user); 
        ...
    } 
    ...

    虽然第一类漏洞能够由自动化工具安全监测(例如 pscan 或 TESOgcc),只有工具被告知函数Error用作格式化函数,第二类漏洞才能检测出来。

    但是,你可以自动化识别源码中的额外格式化函数,以及它们的参数的过程,所以总之,寻找格式化字符串的过程可以完全自动化。你甚至可以归纳出,如果有这样的工具来完成这件事,并且它没有在你的源码中发现格式化字符串漏洞,你的源码就没有这类漏洞。这不同于缓冲区溢出漏洞,其中即使由资深审计者手动审计了源码,还是会错过漏洞,并且没有可靠的方式来自动化找出它们。

    3.1 我们能够控制什么?

    通过提供格式化字符串,我们就能够控制格式化函数的行为。我们现在需要检验我们具体能够控制什么,以及如何使用它来扩展这个对进程的部分控制,来完全控制执行流。

    3.2 使程序崩溃

    使用格式化字符串漏洞的简单攻击,就是使进程崩溃。这对于某些事情是实用的,例如使守护进程崩溃,它会转储核心,并且在核心转储中有一些有用的数据。或者在一些网络攻击中,让一个服务无法响应十分有用,例如 DNS 伪造。

    但是,在使其崩溃中有一些趣味。几乎所有 UNIX 系统中,内核都会检测非法指针访问,并且进程会接收到SIGSEGV信号。通常程序会终止并转储核心。

    通过利用格式化字符串,我们可以轻易触发一些无效指针访问,通过仅仅提供像这样的格式化字符串:

    printf ("%s%s%s%s%s%s%s%s%s%s%s%s");

    由于%s展示某个地址中的内存,这个地址位于栈上,栈上也储存了大量其他数据。我们就有很大机会来从非法地址服务数据,这个地址并没有映射。同时,多数何世华函数的实现提供了%n参数的功能,他可以用于向栈上的地址写入。如果它执行了几次,也一定会产生崩溃。

    3.3 查看进程内存

    如果我们可以查看格式户函数的回复 – 也就是输出字符串 – 我们就可以从中收集有用信息,因为它是我们所控制的行为的输出。而且我们可以使用这个结果,来获得我们的客户端字符串做了什么,以及进程的布局是什么样的概览。

    这对于很多东西都很使用,例如为真正的利用寻找正确的偏移,或者仅仅是重新构造目标进程栈帧。

    3.3.1 查看栈

    我们可以展示栈内存的一些部分,通过像这样使用格式化字符串:

    printf ("%08x.%08x.%08x.%08x.%08x\n");

    这可以工作,因为我们让printf函数来从栈中获取五个参数,并将其展示为 8 位填充的十六进制数值。所以可能的输出是:

    40012980.080628c4.bffff7a4.00000005.08059c04

    这是栈内存的部分转储,从当前的栈底一直到栈顶 – 假设栈向低地址增长。取决于格式化字符传缓冲区的大小,以及输出缓冲区的大小,使用这种技巧,你可以或多或少重构栈内存的一部分。在一些情况下,你甚至可以获取整个栈内存。

    栈的转储提供了关于程序流以及函数局部变量的重要信息,并且可能对于寻找正确偏移以便成功利用有所帮助。

    3.3.2 查看任何地址的内存

    我们也可以查看不同于栈内存的任意地址。为此,我们需要让格式化函数从我们可以提供的某个地址展示内存。这就有两个问题:首先,我们需要找到一个格式化字符串,它将某个地址(传值)用作栈的参数,并且展示其中的内存,并且我们需要提供这个地址,我们在第一种情况中足够幸运,由于%s参数就是干这个的,它展示内存 – 通常是 ASCIIZ 字符串 – 从栈上提供的地址。所以剩下的问题是,如何将这个栈上的地址放到正确的位置上。

    我们的格式化字符串通常位于栈上,所以我们已经距离完全控制这个区域非常近了,格式化字符串就在这里。格式化函数在内部维护一个指针,指向当前格式化参数的栈区域。如果我们能够将这个指针指向一块可控的内存区域,我们就能向%s参数提供一个地址。为了修改栈指针,我们可以仅仅使用假的参数,它会通过打印垃圾来挖掘栈区。

    这里我们假设我们能够完全控制整个字符串。我们稍后会看到,部分控制,字符串过滤,空字节包含的地址,以及类似的问题都会存在,无论何时利用字符串格式化漏洞。

    printf ("AAA0AAA1_%08x.%08x.%08x.%08x.%08x");

    %08x 参数使格式化函数内部的栈指针向栈顶方向增加。将这个参数增加之后,栈指针就指向了我们的内存:格式化字符串本身。格式化函数总是维护最低的栈帧,所以如果我们的缓冲区完全在栈上,它一定会在当前栈指针的上面。如果我们正确选择了%08x的数值,我们就能够展示任意地址的内存,通过向我们的字符串附加%s。在我们的例子中,地址是非法的,它是AAA0。让我们将其换成真实的地址。

    例如:

    address = 0x08480110 
    // address (encoded as 32 bit le string): "\x10\x01\x48\x08"
    printf ("\x10\x01\x48\x08_%08x.%08x.%08x.%08x.%08x|%s|");

    就会转储0x08480110的内存,直到到达了空字符。通过动态增加内存地址,我们可以查看整个进程空间。甚至可以创建远程进程的核心转储,就像映像那样,以及从中重新构建二进制。寻找利用不成功的原因也是很有用的。

    如果我们不能通过使用 4 字节的 POP 来达到精确的格式化字符串的边界,我们需要填充格式化字符串,通过前置一个、两个或三个垃圾字符。这就好比缓冲区溢出利用中的对齐。

    我们不能够按位移动栈指针,反之我们移动格式化字符串本身,以便到达栈指针的四字节边界,并且我们可以使用多个四字节 POP 来到达它。

    3.4 任意内存覆盖

    漏洞利用的圣杯就是控制进程的指令指针。在多数情况下,指令指针(通常命名为 IP,或者 PC)是一个 CPU 中的寄存器,并不能直接修改,因为只有机器指令可以修改它。但是如果我们能够改动机器指令,我们就已经控制了它。所以我们不能直接控制进程。通常,进程比起当前的攻击者拥有更多的权限。

    反之,我们需要寻找修改指令指针的指令,并且影响这些指令修改它的方式。这听起来很复杂,但是多数情况下这非常简单,因为有些指令从内存获取指令指针,并且跳到那里。所以在多数情况下,控制了这部分内存,其中储存了指令指针,就控制了指令指针本身。这就是多数缓冲区溢出的工作方式。

    在两阶段的过程中,首先要覆盖保存的指令指针,之后程序会指令一个合法的指令,它将控制流转移到攻击者提供的地址中。

    我们会检测一些不同的方式,使用格式化字符串漏洞来完成它。

    3.4.1 利用 - 类似于常见的缓冲区溢出

    格式化字符串漏洞有时提供了一个在缓冲区长度周围的方式,并且和常见的缓冲区溢出的利用方式相似。这是出现在 QPOP 2.53 和 bftpd 中的代码:

    char outbuf[512]; 
    char buffer[512];
    
    sprintf (buffer, "ERR Wrong command: %400s", user); 
    sprintf (outbuf, buffer);

    这种例子通常深藏在真实的代码中,并且不会那么明显,就像上面的例子那样。通过提供一个特殊的格式化字符串,我们就能够绕过%400s的限制:

    "%497d\x3c\xd3\xff\xbf<nops><shellcode>"

    任何东西都和常见的缓冲区溢出类似,只是开头 – %497d – 不同。在常见的缓冲区溢出中,我们覆盖了函数帧在栈上的返回地址。在拥有该帧的函数返回值,它会返回到我们提供的地址。地址指向<nop>中的某个地方。有一些不错的文章,描述了这一利用方式,并且如果这个例子对于你来说还不够清楚,你应该考虑首先阅读一篇入门文章,就像 [5] 那样。

    它创建了长度为 497 的字符串。再加上错误信息(ERR Wrong command:),它超出了outbuf缓冲区四个字节。虽然user字符串只允许为 400 字节,我们可以通过不当使用格式化字符串参数来突破这个长度。由于第二个sprintf不检查其长度,它可以用于突破output的边界。现在我们写入一个返回地址0xbfffd33c,并且使用已知的旧办法来利用它,就像我们在任何缓冲区溢出中所做的那样。虽然任何允许拉伸的格式化参数都这样,例如%50d%50f或者%50s,我们还是应该选择一个不会提领指令或者可能导致除零错误的参数。这就排除了%50f%50s。我们只剩下了整数输出参数:%u%d%x

    GNU C 库包含一个 Bug,如果你使用 n 大于 1000 的%nd参数,它会导致崩溃。这是一种判断远程 GNU C 库的方式。如果你使用%.nd,它正产工作,除非你用了很大的值。有关这个长度的深入讨论,请见门户网站的文章 [3]。

    3.4.2 利用 - 只通过格式化字符串

    如果我们不能使用刚刚提到的简单的利用方式,我们仍旧可以利用这个过程。由此,我们可以扩展我们极其有限的控制 – 控制格式化函数的能力 – 到真实的执行流控制,它会执行我们的原始机器码。看看这段代码,它在 wu-ftpd 2.6.0 中发现。

    char buffer[512];
    
    snprintf (buffer, sizeof (buffer), user); 
    buffer[sizeof (buffer) - 1] = ’\0’;

    在上面的代码中,我们不能通过插入某些“拉伸”格式参数来扩大缓冲去,因为程序使用了安全的snprintf函数来确保我们不能突破buffer。最开始它像是,我们不能做很多有用的事情,除了使程序崩溃,并且窥探到一些内存。

    让我们回忆提到过的格式化参数。%n参数将已经打印的字节数,写入到我们所选的变量中。通过将整数指针放置到栈上作为参数,变量地址被提供给格式化函数。

    int i;
    
    printf ("foobar%n\n", (int *) &i); 
    printf ("i = %d\n", i);

    它会打印i = 6。使用我们在上面使用的相同方法来打印任何地址的内存,我们可以写入任意地址:

    "AAA0_%08x.%08x.%08x.%08x.%08x.%n"

    使用%08x参数,我们使格式化函数的内部栈指针增加了四个字节。我们这样做,知道这个指针指向了我们格式化字符串的开头(AAA0)。这可以工作,因为我们的格式化字符串通常位于栈上,在我们的格式化函数栈帧的顶部。%n向地址0x30414141写入,它由字符串AAA0表示。通常这会使程序崩溃,由于地址没有映射。但是如果我们提供了一个正确映射并且可写的地址,这可以工作,并且我们在在该地址覆盖了四个字节:

    "\xc0\xc8\xff\xbf_%08x.%08x.%08x.%08x.%08x.%n"

    上面的格式化字符串会将0xbfffc8c0的四个字节覆盖为一个小型整数。我们已经完成了目标之一,我们可以写入任意地址。但是我们不能控制我们刚才缩写的竖直 – 但是这也会改变的。

    我们所写的竖直 – 由格式化函数写入的字符储量 – 取决于格式化字符串。因为我们控制了格式化字符串,我们至少可以影响这个数量,通过写入或多或少的字节:

    int a; 
    printf ("%10u%n", 7350, &a); 
    /* a == 10 */
    
    int a; 
    printf ("%150u%n", 7350, &a); 
    /* a == 150 */

    通过使用伪造的参数%nu,我们就能控制由%n写入的数量,至少一位。但是对于写入较大数量来说 – 例如地址 – 这还不足够,所以我们需要找到一种方式来写入任意数据。

    x86 架构上的整数以四个字节储存,小端序,最低字节在内存的开始。所以例如0x0000014c的数值在内存中为\x4c\x01\x00\x00。对于格式化函数中的数量,我们可以控制最低字节,也就是内存中首先储存的字节,通过使用伪造的%nu参数来修改它。

    例如:

    unsigned char foo[4];
    
    printf ("%64u%n", 7350, (int *) foo);

    printf函数返回时,foo[0]包含\x40,它等于 64,我们使用这个数值来增加计数器。

    但是对于一个地址,我们需要完全控制四个字节。如果我们不能一次写入四个字节,我们可以尝试在一行中,写入四次,一次写入一个字节。在多数 CISC 架构中,能够写入未对齐的任意地址。这可以用于写入内存的第二个低字节,其中储存了地址,就像:

    unsigned char canary[5]; 
    unsigned char foo[4];
    
    memset (foo, ’\x00’, sizeof (foo)); 
    /* 0 * before */ strcpy (canary, "AAAA");
    
    /* 1 */ printf ("%16u%n", 7350, (int *) &foo[0]); 
    /* 2 */ printf ("%32u%n", 7350, (int *) &foo[1]); 
    /* 3 */ printf ("%64u%n", 7350, (int *) &foo[2]); 
    /* 4 */ printf ("%128u%n", 7350, (int *) &foo[3]);
    
    /* 5 * after */ printf ("%02x%02x%02x%02x\n", foo[0], foo[1], foo[2], foo[3]); 
    printf ("canary: %02x%02x%02x%02x\n", canary[0], canary[1], canary[2], canary[3]);

    返回了输出10204080canary: 00000041。我们将我们所指向的整数的低地址字节覆盖了四次。通过每次增加指针,低地址字节在我们想要写入的内存中移动,并允许我们储存完全任意的数据。

    你可以在图一的第一行看到,所有八个字节都没有被我们的覆盖代码访问。从第二行开始,我们执行了四次覆盖,每一步都向右提升一个字节。最后一行展示了最终的预期状态:我们覆盖了foo数组的所有四个字节,但是这样做的时候,我们破坏了canary的三个字节。我们包含了canary数组,只是为了看到我们覆盖了不想覆盖的内存。

    图一:四阶段的地址覆盖

    虽然这个方式看起来复杂,它也可以用于覆盖任意地址的任意数据。为了解释,我们现在为止只对每个格式化字符串使用了一次写入,但是他可以在一个格式化字符串内执行多次写入。

    strcpy (canary, "AAAA"); 
    printf ("%16u%n%16u%n%32u%n%64u%n", 1, (int *) &foo[0], 1, (int *) &foo[1], 1, (int *) &foo[2], 1, (int *) &foo[3]);
    
    printf ("%02x%02x%02x%02x\n", foo[0], foo[1], foo[2], foo[3]); 
    printf ("canary: %02x%02x%02x%02x\n", canary[0], canary[1], canary[2], canary[3]);

    我们使用参数1作为%u填充的伪造参数。同样,填充发生了改变,因为字符数量在我们写入32的时候已经是16了。所以我们只需要添加16个字符,而不是32个,来获取我们想要的结果。

    这是个特殊案例,其中所有字节在写入过程中递增。但是通过一个微小的修改,我们也可以写入80 40 20 10。由于我们写入整数并且顺序是小端的,在写入过程中只有最低地址字节是重要的。通过使用0x80 0x140 0x220 0x310的计数器,我们就可以构造预期的字符串。计算写入字符预期数量的计数器的代码是这个:

    write_byte += 0x100; 
    already_written %= 0x100; 
    padding = (write_byte - already_written) % 0x100; 
    if (padding < 10) padding += 0x100;

    其中write_byte是我们想要创建的字节,already_written是当前写入数量,由格式化函数维护,padding是我们已经使计数器增加的字节数,例如:

    write_byte = 0x7f; 
    already_written = 30;
    
    write_byte += 0x100; /* write_byte is 0x17f now */ 
    already_written %= 0x100; /* already_written is 30 */
    
    /* afterwards padding is 97 (= 0x61) */ 
    padding = (write_byte - already_written) % 0x100; 
    if (padding < 10) padding += 0x100;

    现在格式化字符串%97u会增加%n计数器,使最低地址字节等于write_byte。最后检查了填充是否低于 10,这非常需要注意。一个简单的整数输出,例如%u最多可以生成十个字符的字符串,取决于所输出的整数值。如果所需长度大于我们指定的填充,假如我们想要使用%2u输出1000,我们的值就会丢弃,以便不会丢失任何有意义的输出。通过确保我们的填充永远大于 10,我们可以使already_written的数值永远保持精确,它是格式化函数维护的计数器,由于我们总是使用格式化参数中的长度选项,写入大量的输出,就像我们指定的那样。

    这取决于格式化函数所运行的操作系统的默认字长,我们假设这里是基于 ILP32 的架构。

    在实践过程中,为了利用这种漏洞,唯一剩下的事情就是将参数以正确的顺序放到栈上,并且使用栈的 POP 序列来增加栈指针。它看起来像:

              A
    <stackpop><dummy-addr-pair * 4><write-code>

    译者注:我更推荐把dummy-addr-pair放在stackpop前面,这样偏移数量更小,stackpop长度更短,而且stackpop的长度就不会影响偏移,也不需要对齐。

    • stackpop:栈的 POP 序列,它会弹出参数,增加栈指针。一旦开始处理stackpop,格式化函数的内部栈指针就会指向dummy-addr-pair字符串。

    • dummy-addr-pair:四对伪造整数值,和要写入的地址。每一对中,地址逐个递增,伪造的整数可以是不含空字符任何东西。

    • write-code:格式化字符串实际写入内存的部分,通过使用%{n}u%n偶对,其中{n}大于 10。第一个部分用于增加或溢出格式化函数内部字节写入计数器的最低地址字节,%n用于将这一数值写入dummy-addr-pair部分中的地址。

    write-code需要修改来匹配由stackpop写入的字节数,因为当格式化函数解析write-code的时候,stackpop已经向输出写入了一些字符 – 格式化函数的计数器已经不是从零开始了,并且这个应该考虑到。

    我们所写入的地址叫做返回地址位置,简写为retloc,我们使用格式化字符串在此处创建的地址叫做返回地址,简写为retaddr

    展开全文
  • PHP-格式化字符串打印var_export()

    万次阅读 2020-11-10 17:31:06
    var_export() 函数用于输出或返回一个变量,以字符串形式表示。 var_export() 函数返回关于传递给该函数的变量的结构信息,它和 var_dump() 类似,不同的是其返回的是一个合法的 PHP代码。 PHP 版本要求: PHP 4 >...

    var_export() 函数用于输出或返回一个变量,以字符串形式表示。
    var_export() 函数返回关于传递给该函数的变量的结构信息,它和 var_dump() 类似,不同的是其返回的是一个合法的 PHP代码。
    PHP 版本要求: PHP 4 >= 4.2.0, PHP 5, PHP 7

    语法

    mixed var_export ( mixed $expression [, bool $return ] )
    

    参数说明:

    • $expression: 你要输出的变量。
    • $return: 可选,如果设置为TRUE,该函数不会执行输出结果,而且将输出结果返回给一个变量。

    返回值
    $return 设置为 true 时才有返回值,返回变量的结构信息。

    实例

    $a = array (1, 2, array ("a", "b", "c"));
    var_export ($a);
    

    在这里插入图片描述

    展开全文
  • 二、格式化函数格式化函数是一类特殊的 ANSI C 函数,接受可变数量的参数,其中的一个就是所谓的格式化字符串。当函数求解格式化字符串时,它会访问向函数提供的额外参数。它是一个转换函数,用于将原始的 C 数据...

    二、格式化函数

    原文:Exploiting Format String Vulnerabilities

    作者:scut@team-teso.net

    译者:飞龙

    日期:2001.9.1

    版本:v1.2

    格式化函数是一类特殊的 ANSI C 函数,接受可变数量的参数,其中的一个就是所谓的格式化字符串。当函数求解格式化字符串时,它会访问向函数提供的额外参数。它是一个转换函数,用于将原始的 C 数据类型表示为人类可读的字符串形式。它们在几乎任何 C 程序中都会使用,来输出信息、打印错误信息或处理字符串。

    这一章中,我们会涵盖格式化函数使用中的典型漏洞,正确用法,它们的一些参数,以及格式化字符串漏洞的一般概念。

    2.1 格式化字符串

    如果攻击者能够向 ANSI C 格式化函数提供字符串,无论部分还是全部,就出现了格式化字符串漏洞。由此,格式化函数的行为会改变,并且攻击者就可能控制目标应用。

    在下面的例子中,字符串user由攻击者提供 – 他可以控制整个 ASCIIZ 字符串,例如通过使用命令行参数。

    错误用法:

    int func (char *user) { 
        printf (user); 
    }

    正确用法:

    int func (char *user) { 
        printf ("%s", user); 
    }

    2.2 格式化函数系列

    ANSI C 规范中定义了大量格式化函数。有一些基本的格式化函数,复杂的函数基于它们,它们中的一些并不是标准的一部分,但是广泛可用。

    实际成员为:

    • fprintf – 打印到FILE

    • printf – 打印到stdout

    • sprintf – 打印到字符串

    • snprintf – 打印到字符串,带有长度检查

    • vfprintf – 从va_arg结构打印到FILE

    • vprintf – 从va_arg结构打印到stdout

    • vsprintf – 从va_arg结构打印到字符串

    • vsnprintf – 从va_arg结构打印到字符串,带有长度检查

    近亲:

    • setproctitle – 设置argv[]

    • syslog – 输出到syslog设施

    • 其它类似err*, verr*, warn*, vwarn*的函数

    2.3 格式化函数的用法

    为了理解这个漏洞在 C 语言代码的哪里,我们必须检验格式化函数的目的。

    功能

    • 用于将简单的 C 数据类型转换为字符串表示

    • 允许指定表示的格式

    • 处理产生的字符串(输出到stderrstdoutsyslog…)

    格式化函数工作原理

    • 格式化字符串控制了函数的行为

    • 它指定了需要打印的参数类型

    • 直接(传值)或间接(传址)保存二者

    调用函数

    需要知道它向栈中压入了多少参数,因为它当格式化函数返回时需要清栈。

    2.4 格式化字符串具体是什么?

    格式化字符串是一个 ASCIIZ 字符串,包含文本和格式化参数。

    例如:

    printf ("The magic number is: %d\n", 1911); 

    要打印的文本是The magic number is:,后面是格式化参数%d,它在输出中会被参数1911代替。所以输出是这个样子:he magic number is: 1911

    一些格式化参数:

    参数 输出 传递方式
    %d 十进制(int 传值
    %u 无符号十进制(unsigned int 传值
    %x 十六进制(unsigned int 传值
    %s 字符串((const) char* 传址
    %n 目前为止写入的字节数(int * 传址

    \字符用于转义特殊字符。它会被 C 编译器在编译使其替换,将转义序列替换为二进制中的适当字符。格式化函数并不会识别这些特殊的序列。实际上,它们并不对格式化字符串做任何事情,但是有时会产生混淆,就像它们被编译器求值一样。

    例如:

    printf ("The magic number is: \x25d\n", 23);

    上面的代码可以工作,因为\x25在编译时期替换为%,虽然0x25(37)是百分号字符的 ASCII 值。

    2.5 栈和它在格式化字符串中的作用

    格式化函数的行为由格式化字符串控制。函数接受栈上的一些参数,它们由格式化字符串请求。

    printf ("Number %d has no address, number %d has: %08x\n", i, a, &a);

    printf来看,栈的样子是:

       栈顶
    +--------+
    |  ...   |
    |   &a   |
    |   a    |
    |   i    |
    |   A    |
    |  ...   |
    +--------+
       栈底

    其中:

    符号 含义
    A 格式化字符串的地址
    i 变量i的值
    a 变量a的值
    &a 变量a的地址

    格式化字符串现在解析了格式化字符串A,一次读取一个字符。如果它不是%,字符会复制到输出中。否则,%后面的字符规定了要求值的参数类型。字符串%%拥有特殊函数,用于打印转义字符%本身。其它每个参数都和数据相关,位于栈上。

    展开全文
  • Python格式化字符串(格式化输出)

    千次阅读 2019-06-23 17:59:56
    上面程序中的 print 函数包含以下三个部分,第一部分是格式化字符串(相当于字符串模板),该格式化字符串中包含一个“%s”占位符,它会被第三部分的变量或表达式的值代替;第二部分固定使用“%”作为分隔符...
  • 格式化字符串溢出笔记

    千次阅读 2020-03-24 15:04:34
    格式化字符串溢出 printf是不定参数的,不会检查输入参数个数的函数。 其中的%n可以用来实现任意地址的读写。 %n把前面字符长度写入某个内存地址。 当printf在输出格式化字符串的时候,会维护一个内部指针,当...
  • python中的格式化字符

    万次阅读 多人点赞 2016-09-17 19:41:25
    python中的格式化字符在python中我们会遇到一个问题,问题是如何输出格式化的字符串。我们经常会输出类似'亲爱的xxx你好!你xx月的话费是xx,余额是xx'之类的字符串,而xxx的内容都是根据变量变化的,所以,需要一种...
  • python格式化字符串和转义字符

    千次阅读 2017-03-10 16:53:37
    Python格式化字符串的替代符以及含义  符 号  说 明  %c  格式化字符及其ASCII码  %s  格式化字符串  %d  格式化整数  %u  格式化无符号整型  %o  格式化无...
  • 菜鸟PWN手进阶之格式化字符

    千次阅读 2021-01-05 21:12:00
    菜鸟PWN手进阶之格式化字符
  • 创建格式化字符

    千次阅读 2018-09-12 10:44:04
    输出格式化数字可以使用printf()和format()方法 PrintStream类的format()...String类的静态方法format()能用来创建可复用的格式化字符串,而不仅仅是用于一次打印输出。 如下所示: 结果如下所示: 浮点型...
  • Python:格式化字符

    千次阅读 2019-04-26 17:33:10
    "%[-][+][0][m][.n]格式化字符"%exp 格式字符 说明 格式字符 说明 %s 字符串(采用str()显示) %r 字符串 %c 单个字符 %o 八进制数 %d或者%i 十进制数 %e 指数(基底为e...
  • 格式化字符串溢出

    千次阅读 2018-07-29 12:05:12
    漏洞原理:  printf是c语言中少有的支持可变... 格式化字符串漏洞的产生根源主要源于对用户输入未进行过滤,这些输入数据都作为数据传递给某些执行格式化操作的函数,如printf,sprintf,vprintf,vprintf。恶...
  • 深入解析sprintf格式化字符串漏洞

    千次阅读 多人点赞 2018-11-05 19:33:43
    深入解析sprintf格式化字符串漏洞 0x00 前言 从相遇到相识 从相识到相知 ......... 不过你真的懂ta吗 这次故事的主角是PHP中的格式化函数sprintf 具体详见:http://bey0nd.xyz/2018/11/05/1/ 0x01 sprintf...
  • 格式化字符串漏洞

    千次阅读 2018-05-14 11:02:22
    格式化字符串,也是一种比较常见的漏洞类型。会触发该漏洞的函数很有限。主要就是printf还有sprintf,fprintf等等c库中print家族的函数。 我们先来看看printf的函数声明 int printf(const char* format,…) 这...
  • PHP格式化字符

    千次阅读 2016-11-13 17:24:53
    我们需要用到PHP的格式化字符串函数sprintf() 函数说明:sprintf(格式, 要转化的字符串) 返回:格式化好的字符串 例子: $str = '99.9'; $result = sprintf('%01.2f', $str); echo $result;//结果显示99.90 ...
  • 什么叫数字的格式化呢?比如说我们在填写年/月/日的时候,希望填写成2016/01/01而不希望出现2016/1/1,也就是说,日和月的两个数字要按我们想要的两位来显示,而不是只显示一...一、格式化字符串 我们先定义以下几个变
  • Python编程思想(5):格式化字符

    千次阅读 2020-05-21 08:28:57
    《Python编程思想》总目录 目录 1. 字符串格式化基础 2. 模板字符串 3. 字符串的format方法 ...所谓格式化字符串,其实就是在字符串中包含一些占位符,然后用另外一些字符串来替换这些占位符。本文将介绍Py
  • Python使用字典格式化字符

    千次阅读 2019-08-13 17:22:04
    前面章节介绍过,在格式化字符串时,如果要格式化的字符串模板中包含多个变量,后面就需要按顺序给出多个变量,这种方式对于字符串模板中包含少量变量的情形是合适的,但如果字符串模板中包含大量变量,这种按顺序...
  • Python 字符串-格式化字符

    千次阅读 2019-04-03 14:42:04
    1.使用%操作符:'%[-][+][0][m][.n]格式化字符'%exp - :可选参数,用于指定左对齐,正数前无符号,负数前加负号。 +:可选参数,用于指定右对齐,正数前加正号,负数前加负号。 0:可选参数,表示右对齐,正数前...
  • 3.1.3 格式化字符串的语法

    千次阅读 2015-07-04 12:18:24
    字符串str.format()和Formatter类都是使用相同的格式化字符串。格式化字符使用大括号{}来包含替换的字段,任何不在大括号里的字符都是直接输出而不作转换。因此,想要输出大括号,就需要使用特别的方式,使用双大...
  • Google Gson 格式化字符串输出

    千次阅读 2017-01-05 20:47:06
    Google Gson 格式化字符串输出
  • 前面记录过一篇关于js格式化字符串方法博文:JavaScript格式化字符串函数封装 近日做java项目时也有类似的字符串动态格式化的需求,因此,对照js方法的思路,实现一下java版的。主要是用String.java的replaceAll...
  • C++格式化字符数组

    2013-10-30 09:42:32
    有时需要格式化的字符数组。 比如循环加载规律命名的多个文件。 代码如下。 char str[32]; sprintf(str, "格式化字符数组%d", 1);
  • python3 格式化字符串 f-string 介绍

    千次阅读 2020-06-13 11:27:37
    f-string,亦称为格式化字符串常量(formatted string literals),是Python3.6新引入的一种字符串格式化方法,该方法源于PEP 498 – Literal String Interpolation,主要目的是使格式化字符串的操作更加简便。...
  • Android中格式化字符

    2016-09-02 19:15:55
    在Android中想要格式化字符串,可以通过getString(int resId, Object... formatArgs)方法完成,它是在抽象类Context中实现的final方法,因此在Activity等类中只能使用,不能被重写。其中参数resId是在strings.xml中...
  • linux下格式化字符sprintf

    千次阅读 2014-08-04 17:30:50
    linux下格式化字符sprintf。 记的初始化目标字符串
  • js格式化字符

    千次阅读 2016-01-27 16:16:37
    格式化字符串,有一种方法是这样定义一个函数: function format(string,data){ return string } 使用方法如下:format(string, data),感觉不是很习惯; 我想使用的方式是:string.format(data1, data2...); 如 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 52,166
精华内容 20,866
关键字:

格式化字符