Skip to content

Instantly share code, notes, and snippets.

@hasherezade
Last active December 14, 2023 04:45
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/51cb827b101cd31ef3061543d001b190 to your computer and use it in GitHub Desktop.
Save hasherezade/51cb827b101cd31ef3061543d001b190 to your computer and use it in GitHub Desktop.
Decoder for Rhadamanthys' Strings (stage 3)
#!/usr/bin/python3
# for IDA >= 7.5
import idautils
DECODE_CSTR_FUNC = "dec_cstring"
DECODE_WSTR_FUNC = "dec_wstring"
modulebase = idaapi.get_imagebase()
###
def RC4_crypt(key, data):
S = list(range(256))
j = 0
for i in list(range(256)):
j = (j + S[i] + (key[i % len(key)])) % 256
S[i], S[j] = S[j], S[i]
j = 0
y = 0
prev_char = None
res = None
out = bytearray()
for char in data:
j = (j + 1) % 256
y = (y + S[j]) % 256
S[j], S[y] = S[y], S[j]
res = (char ^ S[(S[j] + S[y]) % 256]) & 0xFF
out.append(res)
return (out)
###
def wstr_to_cstr(wstr):
out = bytearray()
for c in wstr:
if c != 0:
out.append(c)
return out
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 get_printable_len(cstr):
strlen = 0
for c in cstr:
if not (c == ord('\n') or (ord(' ') <= c <= ord('~'))):
return strlen
strlen += 1
return strlen
def decrypt_string(in_buf, key_buf, is_wide):
data_len = get_data_len2(in_buf)
in_buf = in_buf[0:data_len]
out_str = RC4_crypt(key_buf, in_buf)
if is_wide:
out_str = wstr_to_cstr(out_str)
out_len = get_printable_len(out_str)
return out_str[0:out_len]
###
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= 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