Add files via upload
This commit is contained in:
247
Oceanlotus/OL_OSX_decryptor.py
Normal file
247
Oceanlotus/OL_OSX_decryptor.py
Normal file
@@ -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()
|
||||
Reference in New Issue
Block a user