Skip to content
Permalink
Branch: master
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
executable file 247 lines (194 sloc) 9.66 KB
#!/usr/bin/env python
from __future__ import print_function
import logging
import idaapi
from Crypto.Hash import SHA
from crccheck.crc import Crc32Bzip2
# https://github.com/tildedennis/malware/blob/master/formbook/formbook_decryption.py
from formbook_decryption import FormBookDecryption
# https://github.com/fireeye/flare-ida/tree/master/python/flare
from flare.shellcode_hash_search import DbStore, ShellcodeHashSearcher, SearchParams, HashType
# https://github.com/ThisIsSecurity/sinkhole/tree/master/malware_hash
from malware_hash.malware_hash import MalwareHash
SQLITE_DB_PATH = r'formbook.db'
# Byte swapping the array of five 32-bits integer as used by formbook
# which doesn't implement SHA-1 correctly
def sha1_revert(digest):
tuples = struct.unpack("<IIIII", digest)
output_hash = ""
for item in tuples:
output_hash += struct.pack(">I", item)
return output_hash
def formbook_compute_sha1(input_buffer):
sha1 = SHA.new()
sha1.update(input_buffer)
return sha1_revert(sha1.digest())
def formbook_hash_function(hash_string):
return Crc32Bzip2.calc(bytearray(hash_string.lower(), encoding='utf-8'))
def formbook_string_byte_substraction(input_string, sub):
return "".join([chr((ord(c) - 1 - sub) & 0xFF) for c in input_string])
def formbook_decrypt_hashes(fb_decrypt, key, encrypted_hashes_array):
mh = MalwareHash(formbook_hash_function)
db = DbStore(SQLITE_DB_PATH)
dec_hash_array = []
for i in range(0, len(encrypted_hashes_array), 4):
enc_hash = encrypted_hashes_array[i:i+4]
dec_hash = fb_decrypt.decrypt_func2(enc_hash, key)
dec_hash_array.append(struct.unpack("<I", dec_hash)[0])
print('Encrypted string hash decryption:')
f = open('func_index_hashes.txt', 'w')
for index, dec_hash in enumerate(dec_hash_array):
# Request the sqlite db from shellcode_hashes
symbol_object_list = db.getSymbolByHash(dec_hash)
hash_string = ','.join(set([obj.symbolName for obj in symbol_object_list])) if symbol_object_list else None
# Request the json files for strings commonly used by malware
if not hash_string:
hash_string_list = mh.get_strings_by_hash(dec_hash)
hash_string = ','.join(set([hs.lower() for hs in hash_string_list])) if hash_string_list else None
hash_string = hash_string if hash_string else 'unknown'
line = '{:d} 0x{:x} {:s}'.format(index, dec_hash, hash_string)
print(line)
print(line, file=f)
f.close()
db.close()
def formbook_decrypt_strings(fb_decrypt, key, encrypted_strings):
print('Encrypted string decryption:')
offset = 0
i = 0
f = open('decrypted_strings.txt', 'w')
while offset < len(encrypted_strings):
str_len = ord(encrypted_strings[offset])
offset += 1
dec_str = fb_decrypt.decrypt_func2(encrypted_strings[offset:offset+str_len], key)
dec_str = dec_str[:-1] # remove '\0' character
line = '{:d} {:s}'.format(i, dec_str)
print(line)
print(line, file=f)
offset += str_len
i += 1
f.close()
def formbook_decrypt_hashes_and_strings():
fb_decrypt = FormBookDecryption()
# PE base address: 0x00400000
encbuf1_addr = 0x0041AF4C # fake prologue
encbuf2_addr = 0x0041B160 # fake prologue
encbuf3_addr = 0x0041AAA4 # fake prologue
encbuf4_addr = 0x00419A83 # fake prologue
# decrypt_func1 input length buffer is unknown
# but is always >= output length
encbuf1_s0 = idaapi.get_bytes(encbuf1_addr, 0x139 * 2)
encbuf2_s0 = idaapi.get_bytes(encbuf2_addr, 0x35C * 2)
encbuf3_s0 = idaapi.get_bytes(encbuf3_addr, 0x14 * 2)
encbuf4_s0 = idaapi.get_bytes(encbuf4_addr, 0x9d3 * 2)
encbuf1_s1 = fb_decrypt.decrypt_func1(encbuf1_s0, 0x139)
encbuf2_s1 = fb_decrypt.decrypt_func1(encbuf2_s0, 0x35c)
encbuf3_s1 = fb_decrypt.decrypt_func1(encbuf3_s0, 0x14)
encrypted_strings = fb_decrypt.decrypt_func1(encbuf4_s0, 0x9d3)
rc4_key_one = formbook_compute_sha1(encbuf1_s1)
rc4_key_two = formbook_compute_sha1(encbuf3_s1)
encbuf2_s2 = fb_decrypt.decrypt_func2(encbuf2_s1, rc4_key_one)
encrypted_hashes_array = fb_decrypt.decrypt_func2(encbuf2_s2, rc4_key_two)
rc4_key_pre_final = formbook_compute_sha1(encrypted_hashes_array)
rc4_key_final = fb_decrypt.decrypt_func2(encbuf3_s1, rc4_key_pre_final)
formbook_decrypt_hashes(fb_decrypt, rc4_key_final, encrypted_hashes_array)
formbook_decrypt_strings(fb_decrypt, rc4_key_final, encrypted_strings)
def formbook_decrypt_image_name():
fb_decrypt = FormBookDecryption()
encbuf5_addr = 0x41AACE # fake prologue
encbuf5_s0 = idaapi.get_bytes(encbuf5_addr, 0x222 * 2)
encbuf5_s1 = fb_decrypt.decrypt_func1(encbuf5_s0, 0x222)
buff_dbg = '\x00\x00\x01\x01\x00\x00\x01\x00\x01\x00\x01\x00\x00\x00\x00\x00'
rc4_key = formbook_compute_sha1(buff_dbg)
print('Encrypted image name decryption:')
j = 0
enc_str_len = 13 # each encrypted string is 13 bytes long
f = open('decrypted_image_name.txt', 'w')
for i in range(0, len(encbuf5_s1), enc_str_len):
enc_img_name = encbuf5_s1[i:i+enc_str_len]
if j < 2: # the 2 first strings are obfuscated with an additional routine
enc_img_name = formbook_string_byte_substraction(enc_img_name, j)
dec_image_name = str(fb_decrypt.decrypt_func2(enc_img_name, rc4_key))
line = '{:d} {:s}'.format(j, dec_image_name)
print(line)
print(line, file=f)
j += 1
f.close()
def formbook_decrypt_c2c_uri():
fb_decrypt = FormBookDecryption()
encbuf6_addr = 0x0041AAA4 # fake prologue
encbuf7_addr = 0x0041AE45 # fake prologue
encbuf8_addr = 0x0041AF4C # fake prologue
encbuf9_addr = 0x00419A83 # fake prologue
encbuf7_s0 = idaapi.get_bytes(encbuf7_addr, 0x14 * 2)
encbuf6_s0 = idaapi.get_bytes(encbuf6_addr, 0x14 * 2)
encbuf8_s0 = idaapi.get_bytes(encbuf8_addr, 0x139 * 2)
encbuf9_s0 = idaapi.get_bytes(encbuf9_addr, 0x9d3 * 2)
rc4_key_two = fb_decrypt.decrypt_func1(encbuf6_s0, 0x14)
rc4_key_one = fb_decrypt.decrypt_func1(encbuf7_s0, 0x14)
encbuf8_s1 = fb_decrypt.decrypt_func1(encbuf8_s0, 0x139)
encbuf9_s1 = fb_decrypt.decrypt_func1(encbuf9_s0, 0x9d3)
rc4_key = formbook_compute_sha1(encbuf9_s1)
encbuf8_s2 = fb_decrypt.decrypt_func2(encbuf8_s1, rc4_key)
encrypted_c2c_uri = encbuf8_s2[220:220+44] # 44 is the length of the C&C URI
encrypted_c2c_uri = fb_decrypt.decrypt_func2(encrypted_c2c_uri, rc4_key_two)
c2c_uri = fb_decrypt.decrypt_func2(encrypted_c2c_uri, rc4_key_one)
print('C&C URI: {:s}'.format(c2c_uri))
def formbook_search_plain_hashes():
db = DbStore(SQLITE_DB_PATH)
sql_get_hash_type_by_name='select hash_type, hash_size, hash_name, hash_code from hash_types h where h.hash_name =?'
cur = db.conn.execute(sql_get_hash_type_by_name, ('bzip2_crc32',))
bzip2_crc32_type = HashType(*cur.fetchone())
params = SearchParams()
params.searchPushArgs = True
params.hashTypes = [bzip2_crc32_type]
searcher = ShellcodeHashSearcher(db, params)
# Modify ShellcodeHashSearcher logger to fit our needs
fmt = logging.Formatter('%(message)s')
sh = logging.StreamHandler()
sh.setLevel(logging.INFO)
sh.setFormatter(fmt)
fh = logging.FileHandler('plain_hashes.txt', mode='w')
fh.setLevel(logging.INFO)
fh.setFormatter(fmt)
for handler in searcher.logger.handlers:
searcher.logger.removeHandler(handler)
searcher.logger.addHandler(sh)
searcher.logger.addHandler(fh)
searcher.logger.propagate = False # Don't pass events logged to this logger to higher level loggers
print('Searching plain function hashes:')
searcher.processAllSegments()
sh.close()
fh.close()
db.close()
def callback_on_patched_bytes(ea, fpos, org_val, patch_val):
return 1 # simply notify the caller of visit_patched_bytes that
# one byte from the requested range has been patched
def formbook_patch_encrypted_bytecode():
text_segm = ida_segment.get_segm_by_name('.text')
if not text_segm:
return idaapi.BADADDR
seg_start = text_segm.startEA
seg_end = text_segm.endEA
fb_decrypt = FormBookDecryption()
rc4_key = "faddefad156c45629c95d5f429363b0653ad5c1d".decode('hex') # same as rc4_final from formbook_decrypt_hashes_and_strings()
for i in [(0x40, 0x48), (0x41, 0x49), (0x42, 0x4a), (0x43, 0x4b), (0x44, 0x4c)]:
egg_pattern = ''.join('{:02x} '.format(x) for x in [i[0], 0x90, 0x90, 0x90, i[1]])
encrypted_start = idaapi.find_binary(seg_start, seg_end, egg_pattern, 16, idaapi.SEARCH_DOWN)
if encrypted_start != idaapi.BADADDR:
encrypted_end = idaapi.find_binary(encrypted_start + 5, seg_end, "90 90 90 90", 16, idaapi.SEARCH_DOWN)
if encrypted_end != idaapi.BADADDR:
encrypted_start += 5
patch_length = encrypted_end - encrypted_start
if idaapi.visit_patched_bytes(encrypted_start, encrypted_end, callback_on_patched_bytes) == 0:
encrypted_buff = idaapi.get_bytes(encrypted_start, patch_length)
decrypted_buff = fb_decrypt.decrypt_func2(encrypted_buff, rc4_key)
print('Patching encrypted bytecode at 0x{:x} ({:d} bytes)'.format(encrypted_start, patch_length))
idaapi.patch_many_bytes(encrypted_start, decrypted_buff)
else:
print('Encrypted bytecode at 0x{:x} ({:d} bytes) is already patched'.format(encrypted_start, patch_length))
if __name__ == '__main__':
formbook_decrypt_hashes_and_strings()
formbook_decrypt_image_name()
formbook_decrypt_c2c_uri()
formbook_patch_encrypted_bytecode()
formbook_search_plain_hashes()
You can’t perform that action at this time.