diff --git a/Oceanlotus/OL_OSX_decryptor.py b/Oceanlotus/OL_OSX_decryptor.py new file mode 100644 index 0000000..fd4f99b --- /dev/null +++ b/Oceanlotus/OL_OSX_decryptor.py @@ -0,0 +1,247 @@ +# String deobfuscation for Oceanlotus OSX backdoor + +from __future__ import print_function + +import idaapi +import idc +import idautils +import ida_kernwin +import ida_funcs + +HELPER_COPY = ["qmemcpy", "memcpy", "strcpy"] +FUNC_COPY = ["_memcpy", "_strcpy"] + +from Crypto.Cipher import AES +from base64 import b64decode +import string +import codecs + +def search_binary(binary_string): + for i in range(idaapi.get_segm_qty()): + segm = idaapi.getnseg(i) + current_ea = segm.startEA + while True: + current_ea = idaapi.find_binary(current_ea + 1, segm.endEA, binary_string, 16, idaapi.SEARCH_DOWN) + if current_ea == idaapi.BADADDR: + break + return current_ea + return 0 + +def is_number(n): + return isinstance(n, int) or isinstance(n, long) + +def display_num(n): + return hex(n) if is_number(n) else n + +def add_comment(cfunc, s, ea): + idc.MakeComm(ea, s) + tl = idaapi.treeloc_t() + tl.ea = ea + tl.itp = idaapi.ITP_SEMI + cfunc.set_user_cmt(tl, s) + cfunc.save_user_cmts() + +# Generic function arguments extraction methods +def get_var(block, var_expr, stop_ea): + class ExtractVar(idaapi.ctree_visitor_t): + def __init__(self, var_expr, stop_ea): + idaapi.ctree_visitor_t.__init__(self, idaapi.CV_FAST) + self.var_expr = var_expr + self.ret_expr = None + self.stop_ea = stop_ea + + def visit_expr(self, i): + if i.op == idaapi.cot_asg: + if i.x.op == idaapi.cot_var: + if i.x.v.idx == self.var_expr.v.idx: + self.ret_expr = i.y + elif i.x.op == idaapi.cot_ptr: + if i.x.x.op == idaapi.cot_var: + if i.x.x.v.idx == self.var_expr.v.idx: + self.ret_expr = i.y + elif i.x.x.op == idaapi.cot_cast: + if i.x.x.x.op == idaapi.cot_var: + if i.x.x.x.v.idx == self.var_expr.v.idx: + self.ret_expr = i.y + elif i.op == idaapi.cot_call: + if i.x.helper in HELPER_COPY or idc.Name(i.x.obj_ea) in FUNC_COPY: + if i.a[0].op == idaapi.cot_var: + if i.a[0].v.idx == self.var_expr.v.idx: + self.ret_expr = i.a[1] + elif i.a[0].op == idaapi.cot_cast or i.a[0].op == idaapi.cot_ref: + if i.a[0].x.op == idaapi.cot_var: + if i.a[0].x.v.idx == self.var_expr.v.idx: + self.ret_expr = i.a[1] + elif i.a[0].x.op == idaapi.cot_ref: + if i.a[0].x.x.op == idaapi.cot_var: + if i.a[0].x.x.v.idx == self.var_expr.v.idx: + self.ret_expr = i.a[1] + if i.ea == self.stop_ea: + return 1 + return 0 + + x = ExtractVar(var_expr, stop_ea) + x.apply_to(block, None) + return x.ret_expr + +def get_args(cfunc, xref_addr, var_prop): + class ExtractArgs(idaapi.ctree_visitor_t): + def __init__(self, cfunc, xref_addr, var_prop=False): + idaapi.ctree_visitor_t.__init__(self, idaapi.CV_POST) + self.cfunc = cfunc + self.call_addr = xref_addr + self.args = [] + self.var_prop = var_prop + self.blocks = [cfunc.body] + self.cur_block = self.blocks[-1] + + + def handle_expr(self, e): + if e.op == idaapi.cot_num: + return int(e.numval()) + elif e.op == idaapi.cot_var: + v = get_var(self.cur_block, e, self.call_addr) + if self.var_prop and v: + return self.handle_expr(v) + else: + return self.cfunc.get_lvars()[e.v.idx].name + elif e.op == idaapi.cot_obj: + return int(e.obj_ea) + elif e.op == idaapi.cot_cast: + x = self.handle_expr(e.x) + return x if is_number(x) else "(%s)%s" % (e.type, x) + elif e.op == idaapi.cot_ptr: + x = self.handle_expr(e.x) + return x if is_number(x) else "*%s" % x + elif e.op == idaapi.cot_ref: + x = self.handle_expr(e.x) + return x if is_number(x) else "&%s" % x + elif e.op == idaapi.cot_add or e.op == idaapi.cot_sub: + is_add = e.op == idaapi.cot_add + x = self.handle_expr(e.x) + y = self.handle_expr(e.y) + if is_number(x) and is_number(y): + return x + y if is_add else x - y + else: + return "(%s %s %s)" % (x, "+" if is_add else "-", y) + elif e.op == idaapi.cot_call: + args = ', '.join([display_num(self.handle_expr(x)) for x in e.a]) + if e.x.op == idaapi.cot_helper: + return "%s(%s)" % (e.x.helper, args) + else: + return "%s(%s)" % (idc.Name(e.x.obj_ea), args) + elif e.op == idaapi.cot_str: + binary_string = " ".join(["{:02x}".format(ord(x)) for x in codecs.escape_decode(e.string)[0]]) + occurence_ea = search_binary(binary_string) + return occurence_ea if occurence_ea else int(e.ea) + else: + print("Error: cot_%s not handled" % e.opname) + + + def visit_expr(self, i): + if (i.op == idaapi.cot_call and i.ea == self.call_addr): + self.args = [] + for arg in i.a: + self.args.append(display_num(self.handle_expr(arg))) + return 1 + return 0 + + def visit_insn(self, i): + if (i.op == idaapi.cit_block): + self.blocks.append(i) + self.cur_block = self.blocks[-1] + return 0 + + def leave_insn(self, i): + if (i.op == idaapi.cit_block): + self.blocks.pop() + self.cur_block = self.blocks[-1] + return 0 + + x = ExtractArgs(cfunc, xref_addr, var_prop) + x.apply_to(cfunc.body, None) + return x.args + +class extract_args_t(ida_kernwin.action_handler_t): + def __init__(self, callback, var_prop=False): + ida_kernwin.action_handler_t.__init__(self) + self.var_prop = var_prop + self.callback = callback + + def activate(self, ctx): + for pfn_idx in ctx.chooser_selection: + pfn = ida_funcs.getn_func(pfn_idx) + if pfn: + xrefs = [x for x in idautils.CodeRefsTo(pfn.start_ea, 0)] + for xref in list(set(xrefs)): + cfunc = idaapi.decompile(xref) + if cfunc: + xref_args = get_args(cfunc, xref, self.var_prop) + self.callback(xref, cfunc, xref_args) + return 1 + + def update(self, ctx): + if ctx.widget_type == ida_kernwin.BWN_FUNCS: + return ida_kernwin.AST_ENABLE_FOR_WIDGET + else: + return ida_kernwin.AST_DISABLE_FOR_WIDGET + +# Decryption specific methods +def custom_b64decode(encoded_str): + normal_abc = string.uppercase + string.lowercase + string.digits + "+/" + custom_abc = GetManyBytes(CUSTOM_B64_ALPHA, 0x40) + decode_abc = string.maketrans(custom_abc, normal_abc) + try: + decoded = b64decode(encoded_str.translate(decode_abc)) + except: + decoded = '' + return decoded + +def null_pad(s, blocksize): + return s + "\x00" * (blocksize - len(s)) + +def PKCS7_unpad(string): + pad = string[-1] + if ord(pad) > len(string) or not all(pad == x for x in string[len(string) - ord(pad):]): + return string + return string[:len(string) - ord(pad)] + +def convert_args_to_long(xref_args): + try: + args = [long(l, 16) for l in xref_args] + except: + args = [] + return args + +def decrypt_data(xref, cfunc, xref_args): + print("%s: " % hex(int(xref)), end='') + args = convert_args_to_long(xref_args) + if args: + try: + key = idaapi.get_many_bytes(args[2], args[3] if idc.Dword(args[3]) == 0xffffffff else idc.Dword(args[3])) + data = idaapi.get_many_bytes(args[0], args[1] if idc.Dword(args[1]) == 0xffffffff else idc.Dword(args[1])) + except TypeError: + print("Couldn't retrieve the cipher or the key.") + print(xref_args) + else: + key = null_pad(key, 0x20) + if args[4] == 1: + data = custom_b64decode(data) + plain = PKCS7_unpad(AES.new(key, AES.MODE_CBC, "\x00"*16).decrypt(data)) + #add_comment(cfunc, plain, xref) + print(plain) + else: + print("Not all args are numbers") + print(xref_args) + +CUSTOM_B64_ALPHA = "IJKLMNOPABCDEFGHQRSTUVWXghijklmnYZabcdefopqrstuv456789+/wxyz0123" +ACTION_NAME = "extract-decrypt-arguments-var-prop" +ida_kernwin.unregister_action(ACTION_NAME) +if idaapi.init_hexrays_plugin(): + ida_kernwin.register_action(ida_kernwin.action_desc_t(ACTION_NAME, "Extract and decrypt arguments", extract_args_t(decrypt_data, True), None)) +class popup_hooks_t(ida_kernwin.UI_Hooks): + def finish_populating_widget_popup(self, w, popup): + if ida_kernwin.get_widget_type(w) == ida_kernwin.BWN_FUNCS: + ida_kernwin.attach_action_to_popup(w, popup, ACTION_NAME, None) +hooks = popup_hooks_t() +hooks.hook()