Skip to content

Instantly share code, notes, and snippets.

@sooshie
Last active June 6, 2018 06:11
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save sooshie/0bfd49ad4bfece043c51 to your computer and use it in GitHub Desktop.
Save sooshie/0bfd49ad4bfece043c51 to your computer and use it in GitHub Desktop.
Ponmocup decryption w/Unicorn
#https://r3v3rs3r.wordpress.com/2015/12/12/unicorn-vs-malware/
from unicorn import *
from unicorn.x86_const import *
from capstone import *
from capstone.x86 import *
import pefile
import struct
import string
import sys
stacksize = 0x10000
verbose = True
# -------------------------------------------------------------------------------------------
def char_size(s):
# data to small
if len(s) < 4:
return 0
b = struct.unpack_from('<BBBB', s, 0)
# first byte not printable
if b[0] == 0:
return 0
# X0X0
if chr(b[0]) in string.printable and b[1] == 0 and chr(b[2]) in string.printable and b[3] == 0:
return 2
# XX00
if chr(b[0]) in string.printable and chr(b[1]) in string.printable:
return 1
return 0
# -------------------------------------------------------------------------------------------
def get_string(data, offset):
s = str(data[ len(data)-offset : ])
size = len(s)
cs = char_size(s)
if cs == 0:
return '',0
if cs == 1:
f = '<B'
else:
f = '<H'
r = ''
i = 0
while True:
b = struct.unpack_from(f, s, i)[0]
# 0 byte, end string
if b == 0:
break
# invalid char, discard all
if chr(b) not in string.printable:
return '',0
r += chr(b)
i += cs
return r, cs
# -------------------------------------------------------------------------------------------
def str_to_int(s):
try:
if s.startswith('0x'):
return int(s, 16)
if s.isdigit:
return int(s)
except ValueError:
print "Error, invalid integer string"
exit(-1)
# -------------------------------------------------------------------------------------------
def str_type(t):
if t == 1:
return 'ASCII'
elif t == 2:
return 'UTF16'
else:
raise Exception("Invalid string type")
# -------------------------------------------------------------------------------------------
def format_disasembly(ins):
opbytes = ''.join(('%02X'%(b)) for b in ins.bytes)
instr = '%s' % (ins.mnemonic)
if len(ins.op_str) > 0:
instr += ' %s' % (ins.op_str)
return '%08X %s %s' % (ins.address, opbytes.ljust(20, ' '), instr)
# -------------------------------------------------------------------------------------------
def code_analyzer(pe, virtualaddress, max_instructions=128):
# get the raw offset from the virtualaddress
a_off = pe.get_offset_from_rva(virtualaddress - pe.OPTIONAL_HEADER.ImageBase)
# init disassembler lib
caps = Cs(CS_ARCH_X86, CS_MODE_32)
caps.detail = True
# init vars
code_len = 0
stack_offsets = []
jmpfound = False
# disassemble code and analyze the instructions
for ins in caps.disasm(pe.__data__[a_off:], virtualaddress, max_instructions):
# increase code_len with current instruction size
code_len += ins.size
if verbose:
print format_disasembly(ins)
# process operands
if ins.operands:
for ops in ins.operands:
# memory access operands
if ops.type == X86_OP_MEM:
# ebp base register and disp value not 0
if ops.value.mem.base == X86_REG_EBP and ops.value.mem.disp != 0:
disp = abs(ops.value.mem.disp)
# add new disp value
if disp not in stack_offsets:
stack_offsets.append(disp)
# process groups
if ins.groups:
# jump types
if ins.group(CS_GRP_JUMP):
# JMP backwards
if ins.id == X86_INS_JMP and int(ins.op_str, 16) < ins.address:
jmpfound = True
break
# return types
elif ins.group(CS_GRP_RET):
break
# false if max instructions reached
if not jmpfound:
print "End decryption loop not found"
return 0,[]
# paranoid mode
if len(stack_offsets) == 0:
print "No stack offsets found"
return 0,[]
# ...
for offset in stack_offsets:
if offset > stacksize:
print "Stack offset 0x%08x is larger then the stacksize 0x%08x" %(offset, stacksize)
return 0,[]
# return code length and stackoffsets sorted descending
return code_len, sorted(stack_offsets, reverse=True)
# -------------------------------------------------------------------------------------------
def decrypt_string(filename, address):
# get virtualaddress
virtualaddress = str_to_int(address)
# get some needed PE info from target file
pe = pefile.PE(filename)
imagebase = pe.OPTIONAL_HEADER.ImageBase
imagesize = pe.OPTIONAL_HEADER.SizeOfImage
stackaddress = imagebase + imagesize
# run the code analyzer to locate and analyse the
# decryption loop
code_len, stack_offsets = code_analyzer(pe, virtualaddress)
if not code_len:
print "Failed to locate the decryption loop"
exit(-1)
try:
# Initialize emulator
emu = Uc(UC_ARCH_X86, UC_MODE_32)
# map memory at the imagebase and copy each section
# data to it's virtualaddress
emu.mem_map(imagebase, imagesize + stacksize)
for section in pe.sections:
emu.mem_write(imagebase + section.VirtualAddress, section.get_data())
# initialize stack registers ebp and esp
emu.reg_write(UC_X86_REG_ESP, stackaddress + stacksize)
emu.reg_write(UC_X86_REG_EBP, stackaddress + stacksize)
# start emulator
emu.emu_start(virtualaddress, virtualaddress + code_len)
# use the largest stack_offset value to define the min.
# ammount of stack data to read
ebp_addr = stackaddress + stacksize - stack_offsets[0]
# read stack memory, largest stack_offset as size
data = emu.mem_read(ebp_addr, stack_offsets[0])
# locate and print strings
print "offset type length content"
print 32 * "="
for offset in stack_offsets:
s, t = get_string(data, offset)
if s:
print "%06x %s %s %s" % (offset, str_type(t), str(len(s)).rjust(6,' '), s)
except UcError as e:
print "Error, %s" % (e)
return
# -------------------------------------------------------------------------------------------
if __name__ == '__main__':
decrypt_string(sys.argv[1], sys.argv[2])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment