4

I'm trying to figure out a way to automatically decrypt certain strings in a binary called by a function several times.

The function takes three arguments and is a simple xor decryption. However it uses a different key for each unique string it wants to decrypt.

char* decrypt(char* string_to_decrypt, uint string_len, char xor_byte)

What I'm attempting to do is get a list of the xrefsTo this function (This is as far as I got) and read in the current string it wants to decrypt in the binary, and grab the xor key and length, then patch the binary to display the plaintext strings.

The function is called like this

.text:0040168A push    83h
.text:0040168F push    9
.text:00401691 mov     eax, esi
.text:00401693 mov     edx, offset unk_406D58
.text:00401698 call    decrypt_string

Where EDX, always holds the address of the encrypted string, and the two pushes are the key and length (9 length, 0x83 key)

The mov eax, esi isn't always present. Is there a way to read the disassembly and get this function and then just dynamically decrypt all the strings in the database?

For completeness this is where I started.

import idaapi
import idc

ea = here()
print hex(ea)

xrefs = CodeRefsTo(ea,0)
    for xref in xrefs:
    print "Ref Addr: {}".format(hex(xref))

# for all xrefs, get string offset, length and key,
# decrypt that string, and patch or rename it to display decrypted string)

Thank you.

EDIT: w-s response got me in the right direction. Here is what I came up with. It's rough, I know :)

import idaapi
import idc
import idautils

ea = here()
xrefs = CodeRefsTo(ea,0)

data = []
decrypted = []

for xref in xrefs:
    current_x = xref
    d = {}
    d['addr'] = hex(current_x)
    n_pushes = 0
    n_movedx = 0

    for i in xrange(6):
        if n_pushes == 2 and n_movedx == 1:
            break

        current_x = idc.PrevHead(current_x)
        instr = idautils.DecodeInstruction(current_x)
        if instr.itype == idaapi.NN_push:

            if n_pushes < 1:
                d['len'] = int(GetOperandValue(current_x, 0))

            if n_pushes == 1:
                d['key'] = int( hex(GetOperandValue(current_x, 0)), 16)

            n_pushes += 1

        if instr.itype == idaapi.NN_mov:
            if GetOpnd(current_x, 0) == 'edx':
                d['string_offset'] = GetOperandValue(current_x, 1)

    data.append(d)
......

2 Answers 2

4

When I did something similar I used idc.PrevHead(ea)to iterate back from the call site, so it was something similar to this:

# Not tested, even not runned even once
# expect errors, use on your own risk 
# probably parsing and analysing disassembly is not best way to do it

import idc
import idautils


ea = idc.ScreenEA()

xrefs = CodeRefsTo(ea,0)
for xref in xrefs:
    current = xref
    numOfPushes = 0
    numOfEdx = 0
    edxes = []
    pushes = []
    for i in range(10): # taking 10 instructions back
        if numOfEdx == 1 and numOfPushes == 2:
            break
        current = idc.PrevHead(current)
        s = idc.GetDisasm(current)
        if s.find("mov edx") != -1: #copy-paste exact string here, tabulation expected
            edxes.append(s)
            numOfEdx += 1
        if s.find("push")!= -1: 
            pushes.append(s)
            numOfPushes += 1
    if len(edxes) > 0:
        print "%08x : EDX instruction " % xref, edxes[0]
    else:
        print "%08x : EDX mov not found" % xref
    if len(pushes) >= 2:
        print "%08x : Push[0] " % xref, pushes[0]
        print "%08x : Push[1] " % xref, pushes[1]
    else:
        print "%08x : can not find appropriate push instructions" %  xref

When you have your instructions, all the rest should be obvious. Good luck.

1
  • Thanks, your answer got me to where I needed to go. I'll append my solution to my question.
    – gandolf
    Commented Nov 17, 2015 at 1:44
2

You can use Sark to do most of the work. It should be something similar to this:

import sark

def get_edx_values_at_calls_to(function):
    for xref in function.xrefs_to:
        if not xref.type.is_call:
            continue

        # start at the function call (`xref.frm`) and go back to the beginning of
        # the basic-block (`sark.CodeBlock(xref.frm).startEA`).
        for line in sark.lines(start=sark.CodeBlock(xref.frm).startEA,
                               end=xref.frm, 
                               reverse=True):
            if line.insn.mnem == 'mov' and line.insn.operands[0].reg == 'edx':
                yield line.insn.operands[1].value

decrypt_function = sark.Function()
for value in get_edx_values_at_calls_to(decrypt_function):
    pass  # decryption code
1
  • Thanks for the response. I bookmarked your project to check it out at a later time, but felt that since I'm still fairly new to the IDAPython API I would go with a native solution.
    – gandolf
    Commented Nov 17, 2015 at 1:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.