将 PUSH+RET 调用自动解码为 JMP

逆向工程 艾达 拆卸 二元分析 蟒蛇
2021-06-27 01:52:35

在逆转我遇到过的反攻击技巧的一段恶意软件时,这实际上在这里得到了很好的描述

所以我的问题是:将装配修补的整个PUSH+RET存根JMP(即C3- > FF25XXXX)或EA是否有原后立即一个备用字节RET这些说明是否足以消除错误的反汇编?

如果不是,还有什么应该解决这个问题?

如果是,是否有人使用现成的 IDC/IDAPython 示例来自动化这项工作?

看起来像这里展示的解决方案(幻灯片 13),但不幸的是,没有提供来源。

UPD

经过一番研究,回答我自己的问题:修补原始可执行文件是不可行的,因为一个命令C3只能用一个字节命令(90又名 nop)覆盖,但不能用最少需要的FF25. 即使无条件跳转也将花费超过一个字节,因此内联可能性也不适用。

消除这种技术的工作方式似乎是这样的:

  1. 完全重新组装二进制文件(参见 ExtremeCoders 的回答中的 DynamoRIO 方式)。
  2. 找出某种 IDC 脚本,该脚本将仅修改 IDA 中的表示而不涉及二进制文件。可以在此处找到可能的起点
3个回答

我写了一个类似的脚本,同时解决了一个crackme。

有问题的二进制文件被混淆了

call imm32
jmp imm32

指示

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

jmp imm32

指示

mov    edx, imm32
xor    edx, imm32
jmp    edx

为了去混淆,我使用DynamoRIO编写了一个指令跟踪器,它将所有执行的基本块记录在跟踪文件中。

现在通过解析跟踪文件和使用正则表达式的模式匹配,可以对混淆的指令进行反混淆处理。

由于跳转和调用目的地需要动态计算,我使用 FASM 即时组装指令。

这是脚本。它可能没有直接用处,但你会得到一个想法。

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()

您可以轻松制作一个 ida 脚本,该脚本将添加从 ret 指令到推送中的地址的交叉引用。您只需要查找具有助记符 push 和下一条指令 RET 的前一条指令的模式。添加交叉引用将确保您的函数的流程是正确的。

如果有帮助,我可以编写一个脚本来添加这种类型的交叉引用吗?

这是我使用 SDK 的 IDA 插件版本的链接。

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

假设 JMP => PUSH; RET 是唯一应用的混淆技术,您可以尝试编写一个执行以下操作的 IDAPython 脚本:-

  • 对于 IDA 发现的每个函数
  • 遍历每条指令。遇到“PUSH;RET”指令时,提取目标对应的字节。修补字节以将其更改为相对 JMP(您需要考虑当前指令地址)。将字节重新组合为指令。
  • 要修补实际的二进制文件,请使用“编辑”菜单中的“将修补程序应用到二进制文件”。