Guest User

modified oledump.py

a guest
Dec 23rd, 2014
2,426
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. #!/usr/bin/env python
  2. # Rodel Mendrez: added -m option to automatically dump the macro code when a macro stream is found in the document
  3. __description__ = 'Process command'
  4. __author__ = 'Didier Stevens'
  5. __version__ = '0.0.1'
  6. __date__ = '2014/08/26'
  7.  
  8. """
  9.  
  10. Source code put in public domain by Didier Stevens, no Copyright
  11. https://DidierStevens.com
  12. Use at your own risk
  13.  
  14. # http://www.wordarticles.com/Articles/Formats/StreamCompression.php
  15.  
  16. History:
  17. 2014/08/21: start
  18. 2014/08/22: added ZIP support
  19. 2014/08/23: added stdin support
  20. 2014/08/25: added options extract and info
  21. 2014/08/26: bugfix pipe
  22.  
  23. Todo:
  24. """
  25.  
  26. import optparse
  27. import OleFileIO_PL
  28. import sys
  29. import math
  30. import os
  31. import zipfile
  32. import cStringIO
  33.  
  34. dumplinelength = 16
  35. MALWARE_PASSWORD = 'infected'
  36.  
  37. #Convert 2 Bytes If Python 3
  38. def C2BIP3(string):
  39. if sys.version_info[0] > 2:
  40. return bytes([ord(x) for x in string])
  41. else:
  42. return string
  43.  
  44. # CIC: Call If Callable
  45. def CIC(expression):
  46. if callable(expression):
  47. return expression()
  48. else:
  49. return expression
  50.  
  51. # IFF: IF Function
  52. def IFF(expression, valueTrue, valueFalse):
  53. if expression:
  54. return CIC(valueTrue)
  55. else:
  56. return CIC(valueFalse)
  57.  
  58. def File2String(filename):
  59. try:
  60. f = open(filename, 'rb')
  61. except:
  62. return None
  63. try:
  64. return f.read()
  65. except:
  66. return None
  67. finally:
  68. f.close()
  69.  
  70. class cDumpStream():
  71. def __init__(self):
  72. self.text = ''
  73.  
  74. def Addline(self, line):
  75. if line != '':
  76. self.text += line + '\n'
  77.  
  78. def Content(self):
  79. return self.text
  80.  
  81. def HexDump(data):
  82. oDumpStream = cDumpStream()
  83. hexDump = ''
  84. for i, b in enumerate(data):
  85. if i % dumplinelength == 0 and hexDump != '':
  86. oDumpStream.Addline(hexDump)
  87. hexDump = ''
  88. hexDump += IFF(hexDump == '', '', ' ') + '%02X' % ord(b)
  89. oDumpStream.Addline(hexDump)
  90. return oDumpStream.Content()
  91.  
  92. def CombineHexAscii(hexDump, asciiDump):
  93. if hexDump == '':
  94. return ''
  95. return hexDump + ' ' + (' ' * (3 * (16 - len(asciiDump)))) + asciiDump
  96.  
  97. def HexAsciiDump(data):
  98. oDumpStream = cDumpStream()
  99. hexDump = ''
  100. asciiDump = ''
  101. for i, b in enumerate(data):
  102. if i % dumplinelength == 0:
  103. if hexDump != '':
  104. oDumpStream.Addline(CombineHexAscii(hexDump, asciiDump))
  105. hexDump = '%08X:' % i
  106. asciiDump = ''
  107. hexDump+= ' %02X' % ord(b)
  108. asciiDump += IFF(ord(b) >= 32 and ord(b), b, '.')
  109. oDumpStream.Addline(CombineHexAscii(hexDump, asciiDump))
  110. return oDumpStream.Content()
  111.  
  112. #Fix for http://bugs.python.org/issue11395
  113. def StdoutWriteChunked(data):
  114. while data != '':
  115. sys.stdout.write(data[0:10000])
  116. try:
  117. sys.stdout.flush()
  118. except IOError:
  119. return
  120. data = data[10000:]
  121.  
  122. def PrintableName(fname):
  123. return repr('/'.join(fname))
  124.  
  125. def ParseTokenSequence(data):
  126. flags = ord(data[0])
  127. data = data[1:]
  128. result = []
  129. for mask in [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80]:
  130. if len(data) > 0:
  131. if flags & mask:
  132. result.append(data[0:2])
  133. data = data[2:]
  134. else:
  135. result.append(data[0])
  136. data = data[1:]
  137. return result, data
  138.  
  139. def OffsetBits(data):
  140. numberOfBits = int(math.ceil(math.log(len(data), 2)))
  141. if numberOfBits < 4:
  142. numberOfBits = 4
  143. elif numberOfBits > 12:
  144. numberOfBits = 12
  145. return numberOfBits
  146.  
  147. def Bin(number):
  148. result = bin(number)[2:]
  149. while len(result) < 16:
  150. result = '0' + result
  151. return result
  152.  
  153. def DecompressChunk(compressedChunk):
  154. header = ord(compressedChunk[0]) + ord(compressedChunk[1]) * 0x100
  155. size = (header & 0x0FFF) + 3
  156. flagCompressed = header & 0x8000
  157. data = compressedChunk[2:2 + size - 2]
  158.  
  159. if flagCompressed == 0:
  160. return data, compressedChunk[size:]
  161.  
  162. decompressedChunk = ''
  163. while len(data) != 0:
  164. tokens, data = ParseTokenSequence(data)
  165. for token in tokens:
  166. if len(token) == 1:
  167. decompressedChunk += token
  168. else:
  169. numberOfOffsetBits = OffsetBits(decompressedChunk)
  170. copyToken = ord(token[0]) + ord(token[1]) * 0x100
  171. offset = 1 + (copyToken >> (16 - numberOfOffsetBits))
  172. length = 3 + (((copyToken << numberOfOffsetBits) & 0xFFFF) >> numberOfOffsetBits)
  173. copy = decompressedChunk[-offset:]
  174. copy = copy[0:length]
  175. lengthCopy = len(copy)
  176. while length > lengthCopy: #a#
  177. if length - lengthCopy >= lengthCopy:
  178. copy += copy[0:lengthCopy]
  179. length -= lengthCopy
  180. else:
  181. copy += copy[0:length - lengthCopy]
  182. length -= length - lengthCopy
  183. decompressedChunk += copy
  184. return decompressedChunk, compressedChunk[size:]
  185.  
  186. def Decompress(compressedData):
  187. if compressedData[0] != chr(1):
  188. return None
  189. remainder = compressedData[1:]
  190. decompressed = ''
  191. while len(remainder) != 0:
  192. decompressedChunk, remainder = DecompressChunk(remainder)
  193. decompressed += decompressedChunk
  194. return decompressed
  195.  
  196. def SearchAndDecompress(data):
  197. position = data.find('\x00Attribut')
  198. if position == -1:
  199. compressedData = data
  200. else:
  201. compressedData = data[position - 3:]
  202. result = Decompress(compressedData)
  203. if result == None:
  204. return 'Error: unable to decompress'
  205. else:
  206. return result
  207.  
  208. def IsZIPFile(filename):
  209. return filename.lower().endswith('.zip') and File2String(filename)[0:2] == 'PK'
  210.  
  211. def ReadWORD(data):
  212. if len(data) < 2:
  213. return None, None
  214. return ord(data[0]) + ord(data[1]) *0x100, data[2:]
  215.  
  216. def ReadDWORD(data):
  217. if len(data) < 4:
  218. return None, None
  219. return ord(data[0]) + ord(data[1]) *0x100 + ord(data[2]) *0x10000 + ord(data[3]) *0x1000000, data[4:]
  220.  
  221. def ReadNullTerminatedString(data):
  222. position = data.find('\x00')
  223. if position == -1:
  224. return None, None
  225. return data[:position], data[position + 1:]
  226.  
  227. def ExtractOle10Native(data):
  228. size, data = ReadDWORD(data)
  229. if size == None:
  230. return []
  231. dummy, data = ReadWORD(data)
  232. if dummy == None:
  233. return []
  234. filename, data = ReadNullTerminatedString(data)
  235. if filename == None:
  236. return []
  237. pathname, data = ReadNullTerminatedString(data)
  238. if pathname == None:
  239. return []
  240. dummy, data = ReadDWORD(data)
  241. if dummy == None:
  242. return []
  243. dummy, data = ReadDWORD(data)
  244. if dummy == None:
  245. return []
  246. temppathname, data = ReadNullTerminatedString(data)
  247. if temppathname == None:
  248. return []
  249. sizeEmbedded, data = ReadDWORD(data)
  250. if sizeEmbedded == None:
  251. return []
  252. if len(data) < sizeEmbedded:
  253. return []
  254.  
  255. return [filename, pathname, temppathname, data[:sizeEmbedded]]
  256.  
  257. def Extract(data):
  258. result = ExtractOle10Native(data)
  259. if result == []:
  260. return 'Error: extraction failed'
  261. return result[3]
  262.  
  263. def Info(data):
  264. result = ExtractOle10Native(data)
  265. if result == []:
  266. return 'Error: extraction failed'
  267. return 'String 1: %s\nString 2: %s\nString 3: %s\nSize embedded file: %d\n' % (result[0], result[1], result[2], len(result[3]))
  268.  
  269. def IfWIN32SetBinary(io):
  270. if sys.platform == 'win32':
  271. import msvcrt
  272. msvcrt.setmode(io.fileno(), os.O_BINARY)
  273.  
  274. def OLEDump(filename, options):
  275. if options.raw:
  276. print SearchAndDecompress(File2String(filename))
  277. return
  278.  
  279. if filename == '':
  280. IfWIN32SetBinary(sys.stdin)
  281. ole = OleFileIO_PL.OleFileIO(cStringIO.StringIO(sys.stdin.read()))
  282. elif IsZIPFile(filename):
  283. oZipfile = zipfile.ZipFile(filename, 'r')
  284. oZipContent = oZipfile.open(oZipfile.infolist()[0], 'r', C2BIP3(MALWARE_PASSWORD))
  285. ole = OleFileIO_PL.OleFileIO(cStringIO.StringIO(oZipContent.read()))
  286. oZipContent.close()
  287. else:
  288. if OleFileIO_PL.isOleFile(filename) is not True:
  289. print >>sys.stderr, 'Error - %s is not a valid OLE file.' % infile
  290. sys.exit(1)
  291. ole = OleFileIO_PL.OleFileIO(filename)
  292.  
  293.  
  294. if options.macrodump:
  295. for fname in ole.listdir():
  296. stream = ole.openstream(fname).read()
  297. if '\x00Attribut' in stream:
  298. print PrintableName(fname)
  299. StdoutWriteChunked(SearchAndDecompress(stream))
  300. #print('%2d: %s %6d %s' % (counter, IFF('\x00Attribut' in stream, 'M', ' '), len(stream), PrintableName(fname)))
  301. return
  302.  
  303. if options.select == '':
  304. counter = 1
  305. for fname in ole.listdir():
  306. stream = ole.openstream(fname).read()
  307. print('%2d: %s %6d %s' % (counter, IFF('\x00Attribut' in stream, 'M', ' '), len(stream), PrintableName(fname)))
  308. counter += 1
  309. else:
  310. if options.dump:
  311. DumpFunction = lambda x:x
  312. IfWIN32SetBinary(sys.stdout)
  313. elif options.hexdump:
  314. DumpFunction = HexDump
  315. elif options.vbadecompress:
  316. DumpFunction = SearchAndDecompress
  317. elif options.extract:
  318. DumpFunction = Extract
  319. IfWIN32SetBinary(sys.stdout)
  320. elif options.info:
  321. DumpFunction = Info
  322. else:
  323. DumpFunction = HexAsciiDump
  324. counter = 1
  325. for fname in ole.listdir():
  326. if counter == int(options.select):
  327. StdoutWriteChunked(DumpFunction(ole.openstream(fname).read()))
  328. break
  329. counter += 1
  330. ole.close()
  331.  
  332. def Main():
  333. oParser = optparse.OptionParser(usage='usage: %prog [options] [file]\n' + __description__, version='%prog ' + __version__)
  334. oParser.add_option('-s', '--select', default='', help='select item nr for dumping')
  335. oParser.add_option('-d', '--dump', action='store_true', default=False, help='perform dump')
  336. oParser.add_option('-x', '--hexdump', action='store_true', default=False, help='perform hex dump')
  337. oParser.add_option('-a', '--asciidump', action='store_true', default=False, help='perform ascii dump')
  338. oParser.add_option('-v', '--vbadecompress', action='store_true', default=False, help='VBA decompression')
  339. oParser.add_option('-r', '--raw', action='store_true', default=False, help='raw file, attempt VBA decompression')
  340. oParser.add_option('-e', '--extract', action='store_true', default=False, help='extract OLE embedded file')
  341. oParser.add_option('-i', '--info', action='store_true', default=False, help='print extra info for selected item')
  342. oParser.add_option('-m', '--macrodump', action='store_true', default=False, help='attempt to dump macro script')
  343. (options, args) = oParser.parse_args()
  344.  
  345. if len(args) > 1:
  346. oParser.print_help()
  347. print('')
  348. print(' Source code put in the public domain by Didier Stevens, no Copyright')
  349. print(' Use at your own risk')
  350. print(' https://DidierStevens.com')
  351. return
  352. elif len(args) == 0:
  353. OLEDump('', options)
  354. else:
  355. OLEDump(args[0], options)
  356.  
  357. if __name__ == '__main__':
  358. Main()
RAW Paste Data