sigthief.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. #!/usr/bin/env python3
  2. # LICENSE: BSD-3
  3. # Copyright: Josh Pitts @midnite_runr
  4. import sys
  5. import struct
  6. import shutil
  7. import io
  8. from optparse import OptionParser
  9. def gather_file_info_win(binary):
  10. """
  11. Borrowed from BDF...
  12. I could just skip to certLOC... *shrug*
  13. """
  14. flItms = {}
  15. binary = open(binary, 'rb')
  16. binary.seek(int('3C', 16))
  17. flItms['buffer'] = 0
  18. flItms['JMPtoCodeAddress'] = 0
  19. flItms['dis_frm_pehdrs_sectble'] = 248
  20. flItms['pe_header_location'] = struct.unpack('<i', binary.read(4))[0]
  21. # Start of COFF
  22. flItms['COFF_Start'] = flItms['pe_header_location'] + 4
  23. binary.seek(flItms['COFF_Start'])
  24. flItms['MachineType'] = struct.unpack('<H', binary.read(2))[0]
  25. binary.seek(flItms['COFF_Start'] + 2, 0)
  26. flItms['NumberOfSections'] = struct.unpack('<H', binary.read(2))[0]
  27. flItms['TimeDateStamp'] = struct.unpack('<I', binary.read(4))[0]
  28. binary.seek(flItms['COFF_Start'] + 16, 0)
  29. flItms['SizeOfOptionalHeader'] = struct.unpack('<H', binary.read(2))[0]
  30. flItms['Characteristics'] = struct.unpack('<H', binary.read(2))[0]
  31. #End of COFF
  32. flItms['OptionalHeader_start'] = flItms['COFF_Start'] + 20
  33. #if flItms['SizeOfOptionalHeader']:
  34. #Begin Standard Fields section of Optional Header
  35. binary.seek(flItms['OptionalHeader_start'])
  36. flItms['Magic'] = struct.unpack('<H', binary.read(2))[0]
  37. flItms['MajorLinkerVersion'] = struct.unpack("!B", binary.read(1))[0]
  38. flItms['MinorLinkerVersion'] = struct.unpack("!B", binary.read(1))[0]
  39. flItms['SizeOfCode'] = struct.unpack("<I", binary.read(4))[0]
  40. flItms['SizeOfInitializedData'] = struct.unpack("<I", binary.read(4))[0]
  41. flItms['SizeOfUninitializedData'] = struct.unpack("<I",
  42. binary.read(4))[0]
  43. flItms['AddressOfEntryPoint'] = struct.unpack('<I', binary.read(4))[0]
  44. flItms['PatchLocation'] = flItms['AddressOfEntryPoint']
  45. flItms['BaseOfCode'] = struct.unpack('<I', binary.read(4))[0]
  46. if flItms['Magic'] != 0x20B:
  47. flItms['BaseOfData'] = struct.unpack('<I', binary.read(4))[0]
  48. # End Standard Fields section of Optional Header
  49. # Begin Windows-Specific Fields of Optional Header
  50. if flItms['Magic'] == 0x20B:
  51. flItms['ImageBase'] = struct.unpack('<Q', binary.read(8))[0]
  52. else:
  53. flItms['ImageBase'] = struct.unpack('<I', binary.read(4))[0]
  54. flItms['SectionAlignment'] = struct.unpack('<I', binary.read(4))[0]
  55. flItms['FileAlignment'] = struct.unpack('<I', binary.read(4))[0]
  56. flItms['MajorOperatingSystemVersion'] = struct.unpack('<H',
  57. binary.read(2))[0]
  58. flItms['MinorOperatingSystemVersion'] = struct.unpack('<H',
  59. binary.read(2))[0]
  60. flItms['MajorImageVersion'] = struct.unpack('<H', binary.read(2))[0]
  61. flItms['MinorImageVersion'] = struct.unpack('<H', binary.read(2))[0]
  62. flItms['MajorSubsystemVersion'] = struct.unpack('<H', binary.read(2))[0]
  63. flItms['MinorSubsystemVersion'] = struct.unpack('<H', binary.read(2))[0]
  64. flItms['Win32VersionValue'] = struct.unpack('<I', binary.read(4))[0]
  65. flItms['SizeOfImageLoc'] = binary.tell()
  66. flItms['SizeOfImage'] = struct.unpack('<I', binary.read(4))[0]
  67. flItms['SizeOfHeaders'] = struct.unpack('<I', binary.read(4))[0]
  68. flItms['CheckSum'] = struct.unpack('<I', binary.read(4))[0]
  69. flItms['Subsystem'] = struct.unpack('<H', binary.read(2))[0]
  70. flItms['DllCharacteristics'] = struct.unpack('<H', binary.read(2))[0]
  71. if flItms['Magic'] == 0x20B:
  72. flItms['SizeOfStackReserve'] = struct.unpack('<Q', binary.read(8))[0]
  73. flItms['SizeOfStackCommit'] = struct.unpack('<Q', binary.read(8))[0]
  74. flItms['SizeOfHeapReserve'] = struct.unpack('<Q', binary.read(8))[0]
  75. flItms['SizeOfHeapCommit'] = struct.unpack('<Q', binary.read(8))[0]
  76. else:
  77. flItms['SizeOfStackReserve'] = struct.unpack('<I', binary.read(4))[0]
  78. flItms['SizeOfStackCommit'] = struct.unpack('<I', binary.read(4))[0]
  79. flItms['SizeOfHeapReserve'] = struct.unpack('<I', binary.read(4))[0]
  80. flItms['SizeOfHeapCommit'] = struct.unpack('<I', binary.read(4))[0]
  81. flItms['LoaderFlags'] = struct.unpack('<I', binary.read(4))[0] # zero
  82. flItms['NumberofRvaAndSizes'] = struct.unpack('<I', binary.read(4))[0]
  83. # End Windows-Specific Fields of Optional Header
  84. # Begin Data Directories of Optional Header
  85. flItms['ExportTableRVA'] = struct.unpack('<I', binary.read(4))[0]
  86. flItms['ExportTableSize'] = struct.unpack('<I', binary.read(4))[0]
  87. flItms['ImportTableLOCInPEOptHdrs'] = binary.tell()
  88. #ImportTable SIZE|LOC
  89. flItms['ImportTableRVA'] = struct.unpack('<I', binary.read(4))[0]
  90. flItms['ImportTableSize'] = struct.unpack('<I', binary.read(4))[0]
  91. flItms['ResourceTable'] = struct.unpack('<Q', binary.read(8))[0]
  92. flItms['ExceptionTable'] = struct.unpack('<Q', binary.read(8))[0]
  93. flItms['CertTableLOC'] = binary.tell()
  94. flItms['CertLOC'] = struct.unpack("<I", binary.read(4))[0]
  95. flItms['CertSize'] = struct.unpack("<I", binary.read(4))[0]
  96. binary.close()
  97. return flItms
  98. def copyCert(exe):
  99. flItms = gather_file_info_win(exe)
  100. if flItms['CertLOC'] == 0 or flItms['CertSize'] == 0:
  101. # not signed
  102. print("Input file Not signed!")
  103. sys.exit(-1)
  104. with open(exe, 'rb') as f:
  105. f.seek(flItms['CertLOC'], 0)
  106. cert = f.read(flItms['CertSize'])
  107. return cert
  108. def writeCert(cert, exe, output):
  109. flItms = gather_file_info_win(exe)
  110. if not output:
  111. output = output = str(exe) + "_signed"
  112. shutil.copy2(exe, output)
  113. print("Output file: {0}".format(output))
  114. with open(exe, 'rb') as g:
  115. with open(output, 'wb') as f:
  116. f.write(g.read())
  117. f.seek(0)
  118. f.seek(flItms['CertTableLOC'], 0)
  119. f.write(struct.pack("<I", len(open(exe, 'rb').read())))
  120. f.write(struct.pack("<I", len(cert)))
  121. f.seek(0, io.SEEK_END)
  122. f.write(cert)
  123. print("Signature appended. \nFIN.")
  124. def outputCert(exe, output):
  125. cert = copyCert(exe)
  126. if not output:
  127. output = str(exe) + "_sig"
  128. print("Output file: {0}".format(output))
  129. open(output, 'wb').write(cert)
  130. print("Signature ripped. \nFIN.")
  131. def check_sig(exe):
  132. flItms = gather_file_info_win(exe)
  133. if flItms['CertLOC'] == 0 or flItms['CertSize'] == 0:
  134. # not signed
  135. print("Inputfile Not signed!")
  136. else:
  137. print("Inputfile is signed!")
  138. def truncate(exe, output):
  139. flItms = gather_file_info_win(exe)
  140. if flItms['CertLOC'] == 0 or flItms['CertSize'] == 0:
  141. # not signed
  142. print("Inputfile Not signed!")
  143. sys.exit(-1)
  144. else:
  145. print( "Inputfile is signed!")
  146. if not output:
  147. output = str(exe) + "_nosig"
  148. print("Output file: {0}".format(output))
  149. shutil.copy2(exe, output)
  150. with open(output, "r+b") as binary:
  151. print('Overwriting certificate table pointer and truncating binary')
  152. binary.seek(-flItms['CertSize'], io.SEEK_END)
  153. binary.truncate()
  154. binary.seek(flItms['CertTableLOC'], 0)
  155. binary.write(b"\x00\x00\x00\x00\x00\x00\x00\x00")
  156. print("Signature removed. \nFIN.")
  157. def signfile(exe, sigfile, output):
  158. flItms = gather_file_info_win(exe)
  159. cert = open(sigfile, 'rb').read()
  160. if not output:
  161. output = output = str(exe) + "_signed"
  162. shutil.copy2(exe, output)
  163. print("Output file: {0}".format(output))
  164. with open(exe, 'rb') as g:
  165. with open(output, 'wb') as f:
  166. f.write(g.read())
  167. f.seek(0)
  168. f.seek(flItms['CertTableLOC'], 0)
  169. f.write(struct.pack("<I", len(open(exe, 'rb').read())))
  170. f.write(struct.pack("<I", len(cert)))
  171. f.seek(0, io.SEEK_END)
  172. f.write(cert)
  173. print("Signature appended. \nFIN.")
  174. if __name__ == "__main__":
  175. usage = 'usage: %prog [options]'
  176. print("\n\n!! New Version available now for Dev Tier Sponsors! Sponsor here: https://github.com/sponsors/secretsquirrel\n\n")
  177. parser = OptionParser()
  178. parser.add_option("-i", "--file", dest="inputfile",
  179. help="input file", metavar="FILE")
  180. parser.add_option('-r', '--rip', dest='ripsig', action='store_true',
  181. help='rip signature off inputfile')
  182. parser.add_option('-a', '--add', dest='addsig', action='store_true',
  183. help='add signautre to targetfile')
  184. parser.add_option('-o', '--output', dest='outputfile',
  185. help='output file')
  186. parser.add_option('-s', '--sig', dest='sigfile',
  187. help='binary signature from disk')
  188. parser.add_option('-t', '--target', dest='targetfile',
  189. help='file to append signature to')
  190. parser.add_option('-c', '--checksig', dest='checksig', action='store_true',
  191. help='file to check if signed; does not verify signature')
  192. parser.add_option('-T', '--truncate', dest="truncate", action='store_true',
  193. help='truncate signature (i.e. remove sig)')
  194. (options, args) = parser.parse_args()
  195. # rip signature
  196. # inputfile and rip to outputfile
  197. if options.inputfile and options.ripsig:
  198. print("Ripping signature to file!")
  199. outputCert(options.inputfile, options.outputfile)
  200. sys.exit()
  201. # copy from one to another
  202. # inputfile and rip to targetfile to outputfile
  203. if options.inputfile and options.targetfile:
  204. cert = copyCert(options.inputfile)
  205. writeCert(cert, options.targetfile, options.outputfile)
  206. sys.exit()
  207. # check signature
  208. # inputfile
  209. if options.inputfile and options.checksig:
  210. check_sig(options.inputfile)
  211. sys.exit()
  212. # add sig to target file
  213. if options.targetfile and options.sigfile:
  214. signfile(options.targetfile, options.sigfile, options.outputfile)
  215. sys.exit()
  216. # truncate
  217. if options.inputfile and options.truncate:
  218. truncate(options.inputfile, options.outputfile)
  219. sys.exit()
  220. parser.print_help()
  221. parser.error("You must do something!")