-
-
Save hasherezade/c7701821784c436d40d1442c1a623614 to your computer and use it in GitHub Desktop.
Decoder for Rhadamanthys' Strings (stage 2)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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