tools/latrodectus/latro_str_decrypt.py (90 lines of code) (raw):

import idautils import idc import idaapi import os import yara from nightmare import cast from nightmare.malware.latrodectus import crypto latro_yara = """ rule latrodectus_string_decryption { strings: $seq_str_decrypt = { 48 89 54 24 ?? 48 89 4C 24 ?? 48 83 EC ?? 33 C9 E8 ?? ?? ?? ?? 48 8B 44 24 ?? 8B 00 } condition: 1 of them } """ RULES = yara.compile(source=latro_yara) def get_xrefs(ea: int) -> list[int]: return [ref.frm for ref in idautils.XrefsTo(ea)] def set_decompiler_comment(address: int, decrypted_string: str) -> None: try: seg = idaapi.getseg(address) if seg and seg.name == ".pdata": print(f"Skipping comment in .pdata section at: {hex(address)}") return cfunc = idaapi.decompile(address) if cfunc is None: print(f"Failed to decompile function at: {hex(address)}") return eamap = cfunc.get_eamap() decomp_addr = eamap[address][0].ea tl = idaapi.treeloc_t() tl.ea = decomp_addr commentFlag = False for itp in range(idaapi.ITP_SEMI, idaapi.ITP_COLON): tl.itp = itp cfunc.set_user_cmt(tl, decrypted_string) cfunc.save_user_cmts() cfunc.__str__() if not cfunc.has_orphan_cmts(): commentFlag = True cfunc.save_user_cmts() break cfunc.del_orphan_cmts() if not commentFlag: print( f"Failed to put in decompiler comment at: {hex(int.from_bytes(address, 'big'))}" ) except Exception as e: print( f"Failed to put in decompiler comment at: {hex(int.from_bytes(address, 'big'))}" ) def get_string_decrypt_funcs(imagebase: int) -> list[int]: result = list() text_seg = ida_segment.get_segm_by_name(".text") if text_seg: text_start = text_seg.start_ea for ea in idautils.Segments(): matches = RULES.match(data=idaapi.get_bytes(ea, get_segm_end(ea) - ea)) for match in matches: print(f"Matched rule: {match.rule}") for offset in match.strings: result.append(text_start + offset[0]) return result def get_encrypted_string(xrefs: list) -> list[bytes]: encrypted_strings = [] for xref in xrefs: push_ea = idc.prev_head(xref) arg_ea = idc.get_operand_value(push_ea, 1) if idc.is_loaded(arg_ea): byte_list = [] i = 0 while True: byte1 = idc.get_wide_byte(arg_ea + i) byte2 = idc.get_wide_byte(arg_ea + i + 1) if byte1 == 0 and byte2 == 0: break byte_list.append(byte1) byte_list.append(byte2) i += 2 encrypted_strings.append((xref, bytes(byte_list))) return encrypted_strings imagebase = ida_nalt.get_imagebase() xrefs = get_xrefs(get_string_decrypt_funcs(imagebase)[0]) addrs_encrypted_strings = get_encrypted_string(xrefs) for addr, encrypted_str in addrs_encrypted_strings: decrypted_str = crypto.decrypt_string(encrypted_str) null_byte_count = decrypted_str.count(b"\x00") for encoding in ["utf-16", "utf-8"] if null_byte_count > 1 else ["utf-8"]: try: new_decrypted_str = decrypted_str.decode(encoding) print(hex(addr), new_decrypted_str) break except UnicodeDecodeError: continue else: print(f"Unable to decode as {', '.join(encoding)}") set_decompiler_comment(addr, new_decrypted_str)