tools/warmcookie/warmcookie_str_decrypt.py (88 lines of code) (raw):
import idautils
import idc
import idaapi
import yara
from Crypto.Cipher import ARC4
class WarmCookie(object):
KEY_OFFSET = 0x4
KEY_SIZE = 0x4
DATA_OFFSET = 0x8
LOOKBACK_COUNT = 0xA
WARMCOOKIE_YARA = """
rule warmcookie_string_decryption {
strings:
$seq_str_decrypt =
{
48894C24??4881EC68010000488B05????????4833C448898424????????48C74424
}
$seq_str_decrypt2 =
{
48895C24??48896C24??48897424??574881EC50010000488B05????????4833C448
}
$seq_str_decrypt3 =
{
554881ec40010000488dac248000000048898dd0000000
}
condition:
any of them
}
"""
RULES = yara.compile(source=WARMCOOKIE_YARA)
@staticmethod
def decrypt_string(encrypted_str: bytes, key: bytes) -> bytes:
return ARC4.new(key).decrypt(encrypted_str)
@staticmethod
def get_xrefs(ea: int) -> list[int]:
return [ref.frm for ref in idautils.XrefsTo(ea)]
@staticmethod
def set_decompiler_comment(address: int, decrypted_string: str) -> None:
if not (cfunc := idaapi.decompile(address)):
print(f"Failed to decompile function at: {hex(address)}")
return
tl = idaapi.treeloc_t()
tl.ea = cfunc.get_eamap()[address][0].ea
tl.itp = idaapi.ITP_SEMI
cfunc.set_user_cmt(tl, decrypted_string)
cfunc.save_user_cmts()
@staticmethod
def get_string_decrypt_funcs() -> list[int]:
result = list()
text_seg = ida_segment.get_segm_by_name(".text")
if not text_seg:
return result
matches = WarmCookie.RULES.match(
data=idaapi.get_bytes(
text_seg.start_ea, text_seg.end_ea - text_seg.start_ea
)
)
for match in matches:
print(f"Matched rule: {match.rule}")
for offset in match.strings:
result.append(text_seg.start_ea + offset[0])
return result
@staticmethod
def get_encrypted_string(
candidate_addrs: list[int],
) -> list[tuple[int, bytes, bytes]]:
encrypted_strings = []
for candidate_addr in candidate_addrs:
lea_ea = candidate_addr
for _ in range(WarmCookie.LOOKBACK_COUNT):
lea_ea = idc.prev_head(lea_ea)
if (
idc.print_insn_mnem(lea_ea) == "lea"
and idc.print_operand(lea_ea, 0) == "rcx"
):
break
arg_ea = idc.get_operand_value(lea_ea, 1)
size = idc.get_wide_dword(arg_ea)
if size < 1000:
key = idc.get_bytes(arg_ea + WarmCookie.KEY_OFFSET, WarmCookie.KEY_SIZE)
data = idc.get_bytes(arg_ea + WarmCookie.DATA_OFFSET, size)
encrypted_strings.append((candidate_addr, key, data))
return encrypted_strings
@staticmethod
def main():
for func in WarmCookie.get_string_decrypt_funcs():
for addr, key, encrypted_str in WarmCookie.get_encrypted_string(
WarmCookie.get_xrefs(func)
):
try:
decrypted_str = WarmCookie.decrypt_string(encrypted_str, key)
null_byte_count = decrypted_str.count(b"\x00")
try:
if null_byte_count > 1:
new_decrypted_str = decrypted_str.decode("utf-16")
else:
new_decrypted_str = decrypted_str.decode("utf-8")
except UnicodeDecodeError as e:
print(f"Decoding error at {hex(addr)}: {e}")
continue
print(hex(addr), new_decrypted_str)
WarmCookie.set_decompiler_comment(addr, new_decrypted_str)
except TypeError:
print(f"Failed to decrypt string at: {hex(addr)}")
if __name__ == "__main__":
WarmCookie.main()