Last active
June 12, 2020 06:03
-
-
Save 0xcpu/705a7f50a81e1b7b1f9b0c599bfd92f8 to your computer and use it in GitHub Desktop.
IDAPython 3 Snake ransomware string decryption
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
# IDAPython 3 | |
import ida_funcs | |
import idautils | |
import ida_ua | |
import ida_xref | |
import ida_bytes | |
import heapq | |
from collections import defaultdict | |
from ctypes import c_ubyte | |
def snake_str_decrypt(): | |
# find parent function by number of xrefs / out-degree count | |
xrefs_cnt = defaultdict(list) | |
func = ida_funcs.get_next_func(0) | |
while func: | |
# search near calls / p type references | |
xrefs = [xref for xref in idautils.XrefsTo(func.start_ea) if xref.type == ida_xref.fl_CN] | |
if len(xrefs) == 1: | |
src_node = ida_funcs.get_func(xrefs[0].frm) | |
if src_node: | |
xrefs_cnt[src_node.start_ea].append(xrefs[0].to) | |
func = ida_funcs.get_next_func(func.end_ea) | |
heap = [] | |
for k, v in xrefs_cnt.items(): | |
heapq.heappush(heap, (len(v), k)) | |
# get first n functions having the highest out degree count | |
nfuncs = heapq.nlargest(10, heap) | |
# Iterate over every child node and pattern match | |
for ea in nfuncs: | |
for func_ea in xrefs_cnt[ea[1]]: | |
func = ida_funcs.get_func(func_ea) | |
if func is None: | |
print("Failed obtaining function") | |
continue | |
print("Function @ {:08x}".format(func_ea)) | |
curr_ea = func.start_ea | |
# length of the blob to decrypt, it's offset and key offset | |
length, offsets = None, [None, None] | |
# True when decryption key blob was found | |
matched = False | |
while curr_ea < func.end_ea: | |
insn = idautils.DecodeInstruction(curr_ea) | |
if insn is None: | |
print("Failed decoding instruction at {:08x}".format(curr_ea)) | |
break | |
""" | |
General pattern | |
.text:00567560 074 8D 05 E3 54 62 00 lea eax, byte_6254E3 | |
.text:00567566 074 89 44 24 04 mov [esp+74h+var_70], eax | |
.text:0056756A 074 C7 44 24 08 14 00 00 00 mov [esp+74h+var_6C], 14h | |
.text:00567572 074 E8 19 34 ED FF call runtime_stringtoslicebyte | |
""" | |
if insn.get_canon_mnem() == "lea" and insn.Op1.type == ida_ua.o_reg: | |
insn2 = idautils.DecodeInstruction(insn.ea + insn.size) | |
insn3 = idautils.DecodeInstruction(insn2.ea + insn2.size) | |
insn4 = idautils.DecodeInstruction(insn3.ea + insn3.size) | |
insn5 = idautils.DecodeInstruction(insn4.ea + insn4.size) | |
if any(t is None for t in (insn2, insn3, insn4, insn5)): | |
print("Failed decoding following instructions") | |
break | |
if insn2.get_canon_mnem() == "mov" and insn2.Op2.type == ida_ua.o_reg: | |
if insn3.get_canon_mnem() == "mov" and insn3.Op2.type == ida_ua.o_imm \ | |
and insn4.get_canon_mnem() == "call" and insn5.get_canon_mnem() != "ud2": | |
if matched: | |
if length == insn3.Op2.value: | |
offsets[1] = insn.Op2.addr | |
print("Blob1 @ {:08x}".format(offsets[0])) | |
print("Blob2 @ {:08x}".format(offsets[1])) | |
# decrypt | |
blob1 = ida_bytes.get_bytes(offsets[0], length) | |
blob2 = ida_bytes.get_bytes(offsets[1], length) | |
dec_blob = [c_ubyte(b1 + 2 * i).value ^ b2 for b1, b2, i in zip(blob1, blob2, range(len(blob1)))] | |
print("".join(map(chr, dec_blob))) | |
ida_bytes.set_cmt(offsets[0], "".join(map(chr, dec_blob)), False) | |
#ida_bytes.patch_bytes(offsets[0], bytes(dec_blob)) | |
matched = False | |
else: | |
matched = True | |
length = insn3.Op2.value | |
offsets[0] = insn.Op2.addr | |
curr_ea += insn.size | |
if __name__ == "__main__": | |
snake_str_decrypt() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment