4

While reversing a piece of malware I've faced with anti-disasm trick, that actually is well described here.

So my question is: would the assembly patching of whole the PUSH+RET stubs to JMP(i.e. C3-> FF25XXXX) or an EA if there is one spare byte immediately after the original RET. Would these instructions resolve be enough for the wrong disassembly elimination?

If no, what else should resolve the issue?

If yes, has anybody the ready-made IDC/IDAPython sample to automate this work?

Seems like solution illustrated here(slide 13) but, unfortunately, no source provided.

UPD

Answer for my own question after a bit of research: patching of original executable not feasible because one command C3 can be overwritten with with only one byte command(90 a.k.a. nop for instance), but not with the minimum needed FF25. Even unconditional jump will cost more than one byte so the inline possibility isn't applicable also.

The working ways to eliminate this technique are seems to look like:

  1. Reassemble binary completely(see DynamoRIO-way in ExtremeCoders' answer).
  2. Figure out with some sort of IDC script, which will modify only representation in IDA without touching binary. Possible starting points can be found here.
2
  • PUSH+RET is a JMP not a CALL. You'd need an FF25 sequence instead, and to find another four bytes to store the address, or an EA if there is one spare byte immediately after the original RET. Sep 20, 2015 at 15:20
  • @peterferrie thanks for notice. I'll update the post with your tips, taken into consideration.
    – see ya
    Sep 20, 2015 at 16:11

3 Answers 3

5

I wrote a similar sort of script, while solving a crackme.

The binary in question obfuscated

call imm32
jmp imm32

instructions to

push   imm32
xor    dword ptr [esp], imm32
mov    edx, imm32
xor    edx, imm32
jmp    edx

and

jmp imm32

instructions to

mov    edx, imm32
xor    edx, imm32
jmp    edx

In order to deobfuscate, I wrote a instruction tracer using DynamoRIO, which logged all basic blocks executed, in a trace file.

Now by parsing the trace file, and pattern matching using regex it is possible to deobfuscate the obfuscated instructions.

Since the jump and call destinations need to be calculated dynamically, I used FASM to assemble instructions on the fly.

Here is the script. It may not be directly useful, but you will get an idea.

from capstone import *
from capstone.x86 import *
import re
import binascii
import subprocess
import os
import mmap

'''
pattern1
-------------------------------------------------------
68 ?? ?? ?? ??          push   imm32
81 34 24 ?? ?? ?? ??    xor    dword ptr [esp], imm32
ba ?? ?? ?? ??          mov    edx, imm32
81 f2 ?? ?? ?? ??       xor    edx, imm32
ff e2                   jmp    edx

Will be rewritten to

call imm32
jmp imm32


pattern2 (only if pattern1 does not match)
-------------------------------------------------------
ba ?? ?? ?? ??          mov    edx, imm32
81 f2 ?? ?? ?? ??       xor    edx, imm32
ff e2                   jmp    edx

Will be written to

jmp imm32

'''

pat1 = re.compile(r'68.{8}813424.{8}ba.{8}81f2.{8}ffe2')
pat2 = re.compile(r'ba.{8}81f2.{8}ffe2')

md = Cs(CS_ARCH_X86, CS_MODE_32)
md.detail = True

handle = open('code_section_p.bin', 'r+b')
mm = mmap.mmap(handle.fileno(), 0, access = mmap.ACCESS_WRITE)


def assemble(asm):
    f = open('temp.asm', 'w')
    f.write(asm)
    f.close()
    devnull = open(os.devnull, 'w')
    subprocess.call(['fasm.exe', 'temp.asm'], stdout=devnull, stderr=devnull)
    return open('temp.bin', 'rb').read()    


def pat1_rewrite(buf, off_start, va_start, numBytes): 
    gen = md.disasm(buf[off_start: off_start + numBytes], va_start)

    oper1 = gen.next().operands   # push   imm32
    oper2 = gen.next().operands   # xor    dword ptr [esp], imm32
    oper3 = gen.next().operands   # mov    edx, imm32
    oper4 = gen.next().operands   # xor    edx, imm32

    jmp_target = oper1[0].value.imm ^ oper2[1].value.imm
    call_target = oper3[1].value.imm ^ oper4[1].value.imm

    return 'use32\n' \
           'org {}\n'\
           'call {}\n'\
           'jmp {}'.format(va_start, call_target, jmp_target)



def pat2_rewrite(buf, off_start, va_start, numBytes): 
    gen = md.disasm(buf[off_start: off_start + numBytes], va_start)

    oper1 = gen.next().operands   # mov    edx, imm32
    oper2 = gen.next().operands   # xor    edx, imm32

    jmp_target = oper1[1].value.imm ^ oper2[1].value.imm

    return 'use32\n' \
           'org {}\n'\
           'jmp {}'.format(va_start, jmp_target)    



def analyze_bb(buf, off_start, va_start, bbsize):
    hexstr = binascii.hexlify(buf[off_start: off_start + bbsize])

    mtch = pat1.search(hexstr)
    if mtch is not None:
        start = off_start + mtch.start() / 2
        numBytes = bbsize - mtch.start() / 2
        asm = pat1_rewrite(buf, start, va_start + mtch.start() / 2, numBytes)
        mm.seek(start)
        assembled = assemble(asm)
        mm.write(assembled)
        mm.write('\xCC' * (numBytes - len(assembled)))


    else:
        mtch = pat2.search(hexstr)
        if mtch is not None:
            start = off_start + mtch.start() / 2
            numBytes = bbsize - mtch.start() / 2            
            asm = pat2_rewrite(buf, start, va_start + mtch.start() / 2, numBytes)
            mm.seek(start)
            assembled = assemble(asm)
            mm.write(assembled)
            mm.write('\xCC' * (numBytes - len(assembled)))


def main():
    addrStartEnd_list = open('trace.txt', 'r').readlines()
    buf = open('code_section.bin', 'rb').read()

    for addrStartEnd in addrStartEnd_list:
        va_start, bbsize = map(lambda x: int(x, 16), addrStartEnd.split())
        off_start = va_start - 0x30001000

        analyze_bb(buf, off_start, va_start, bbsize) 
        handle.close()



if __name__ == '__main__':
    main()
1

You can easily make an ida script that will add a cross-reference from the ret instruction to the address that is in the push. You simply need to look for the pattern that has the previous instruction with a mnemonic that is push and the next instruction that is a RET. Adding a cross reference will ensure that the flow for your function is correct.

If it would help, I could put together a script that will add this type of cross reference?

Here is a link to my version of a IDA Pluging using the SDK.

http://milhous.no-ip.com:15980/blog/2015/09/20/ida-call-jump/

1
  • Can't really run your script for tests because of segment_t not defined error. Tried many header insertions/code modifications with no luck. Can you please update answer with details of how this should work? IDA 6.6
    – see ya
    Sep 22, 2015 at 9:57
1

Assuming that JMP => PUSH; RET is the only obfuscation technique applied, you could try writing an IDAPython script that does the following :-

  • For each function that has been discovered by IDA
  • Traverse each instruction. Upon encountering a "PUSH; RET" instructions, extract the bytes corresponding to the target. Patch the bytes to change it to a relative JMP(you will need to take the current instruction address into account for that). Reassemble the bytes as instructions.
  • In order to patch the actual binary, use "Apply patches to binary" from the EDIT menu.
1
  • as soon as I'm rather new to IPAPython there is one question: can the wildcards be set for this operation? Assume situation, when there is no direct PUSH and strictly then RET. How about PUSH + wildcard + random basic block + wildcard + RET?
    – see ya
    Sep 21, 2015 at 5:24

Your Answer

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

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