Skip to content

Instantly share code, notes, and snippets.

@hasherezade
Last active December 14, 2023 04:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hasherezade/c7701821784c436d40d1442c1a623614 to your computer and use it in GitHub Desktop.
Save hasherezade/c7701821784c436d40d1442c1a623614 to your computer and use it in GitHub Desktop.
Decoder for Rhadamanthys' Strings (stage 2)
#!/usr/bin/python3
# for IDA >= 7.5
import idautils
DECODE_CSTR_FUNC = "dec_cstring"
DECODE_WSTR_FUNC = "dec_wstring"
modulebase = idaapi.get_imagebase()
###
def wstr_to_cstr(wstr):
out = bytearray()
for c in wstr:
if c != 0:
out.append(c)
return out
def is_valid_wstr(wstr):
i = 0
for c in wstr:
if i % 2 == 1 and c != 0:
return False
i +=1
return True
def is_printable(cstr):
for c in cstr:
if not (ord(' ') <= c <= ord('~')):
return False
return True
###
def mix_key_round(ctx, size, key3, key2, key_size):
if not size: return
for i in range(size):
pos = key_size % size
key_size += 87
val = ctx[pos]
result = (key2 + ((val >> 5) & 0xFF)) + ctx[(val % size)] + (i * (((val + key3) >> 3) & 0xFF)) + 1
ctx[i] = (ctx[i] + result) & 0xFF
return ctx
def decrypt_data(in_buf, in_size, key_buf, key_size, key2, key3):
out_buf = [0] * in_size
ctx = [key_buf[(i % key_size)] for i in range(in_size)]
for _ in range(4):
ctx = mix_key_round(ctx, in_size, key3, key2, key_size)
for i in range(in_size):
out_buf[i] = (ctx[i] ^ in_buf[i]) & 0xFF
return out_buf
def get_data_len1(data_buf):
for i in range(len(data_buf)):
if data_buf[i] == 0:
return i
return len(data_buf)
def get_data_len2(data_buf):
for i in range(len(data_buf)):
if data_buf[i] == 0 and i > 0 and data_buf[i - 1] == 0:
return (i - 1)
return len(data_buf)
def decrypt_string_custom(in_buf, key_buf, is_wide):
str_decrypt_const = 0x3779E9B
if is_wide:
in_size = get_data_len2(in_buf)
else:
in_size = get_data_len1(in_buf)
s = decrypt_data(in_buf, in_size, key_buf, 16, str_decrypt_const, 0)
if (is_wide and not is_valid_wstr(s)) or (not is_wide and not is_printable(s)):
s = decrypt_data(in_buf, in_size + 1, key_buf, 16, str_decrypt_const, 0)
return wstr_to_cstr(s)
###
def decrypt_string(in_buf, key_buf, is_wide):
return decrypt_string_custom(in_buf, key_buf, is_wide)
###
def get_reg_value(func_start, inst, reg_id):
if reg_id == None:
return None
while inst != idaapi.BADADDR and inst != func_start:
inst = idc.prev_head(inst)
mnem = idc.print_insn_mnem(inst)
if reg_id == 0 and mnem in ["call"]: # reg EAX can be modified by a call
return None
if get_operand_type(inst, 0) == o_reg and reg_id == idc.get_operand_value(inst, 0):
if mnem in ["mov"]:
if get_operand_type(inst, 1) == o_imm:
return idc.get_operand_value(inst, 1)
if get_operand_type(inst, 1) == o_reg:
reg_id2 = idc.get_operand_value(inst, 1)
return get_reg_value(func_start, inst, reg_id2) # try to get recursively
return None # moving unknown value
if mnem in ["lea"]:
return None # cannot resolve this
return None
def get_pushed_value(func_start, inst):
frame = idc.get_func_attr(inst, FUNCATTR_FRAME)
value = None
while inst != idaapi.BADADDR and inst != func_start:
inst = idc.prev_head(inst)
mnem = idc.print_insn_mnem(inst)
op_type = get_operand_type(inst, 0)
if mnem in ["push"]:
if op_type == o_imm:
return idc.get_operand_value(inst, 0) #simple push <value>
elif op_type == o_reg:
reg_id = idc.get_operand_value(inst, 0) #push <register>
return get_reg_value(func_start, inst, reg_id)
else:
break # unrecognized param is pushed
return value
def set_string_as_comment(str_va, myStr):
if myStr is None:
return False
ida_bytes.create_data(str_va, idaapi.FF_DWORD, 4, idaapi.BADADDR)
idc.set_name(str_va, "enc_" + myStr, ida_name.SN_NOCHECK | ida_name.SN_FORCE)
myStr = "\"" + myStr + "\""
idc.set_cmt(str_va, myStr, True)
idc.set_cmt(str_va, myStr, False)
return True
def list_function_params(func_va, is_wide):
functionName = idc.get_func_name(modulebase + func_va)
total_count = 0
for xref in idautils.XrefsTo(modulebase + func_va):
func = idaapi.get_func(xref.frm)
if not func:
continue
frame = idc.get_func_attr(xref.frm, FUNCATTR_FRAME)
func_start = get_func_attr(xref.frm, idc.FUNCATTR_START)
inst = xref.frm
arg0 = get_pushed_value(func_start, inst)
#print(("# %x") % (xref.frm))
if (arg0 is None):
print(("### Cannot list params for: %x, %s") % (inst, functionName))
continue
str_ptr = arg0
total_count += 1
if str_ptr:
enc_key = ida_bytes.get_bytes(str_ptr, 16)
enc_buf = ida_bytes.get_bytes(str_ptr + 16, 1000)
decrypted = decrypt_string(enc_buf, enc_key, is_wide)
cmt = None
try:
cmt = (decrypted.decode("utf-8") )
except:
cmt = str(decrypted)
if str_ptr and cmt:
set_string_as_comment(str_ptr, cmt)
print("%x,%s" % (str_ptr - modulebase, "\"" + cmt + "\""))
print(("#---\n#Total count %d\n" % (total_count)))
#---
def get_function_rva_by_name(func_name_str):
#Get number of segments
seg=idaapi.getnseg(0)
if not seg:
return None
#Get list of functions in that segment
funcs=idautils.Functions(seg.start_ea, seg.end_ea)
for funcaddress in funcs:
f_name = idc.get_func_name(funcaddress)
if f_name == func_name_str:
return funcaddress - modulebase
return None
#---
def print_all_params():
md5 = idaapi.retrieve_input_file_md5()
md5_str = md5.hex()
print("#sample: \"%s\"\n" % md5_str)
func_cstr_rva = get_function_rva_by_name(DECODE_CSTR_FUNC)
if func_cstr_rva is not None:
print(("### %s: %x") % (DECODE_CSTR_FUNC, func_cstr_rva))
list_function_params(func_cstr_rva, False)
func_wstr_rva = get_function_rva_by_name(DECODE_WSTR_FUNC)
if func_wstr_rva is not None:
print(("### %s: %x") % (DECODE_WSTR_FUNC, func_wstr_rva))
list_function_params(func_wstr_rva, True)
print_all_params()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment