尝试对 x64 代码进行反混淆。您可以略读/跳过大部分内容,因为它的存在主要是为了证明我已经尝试并用尽了所有途径。
燃烧目的
在 64 位 Windows 游戏的 [内存转储] 中大规模自动化反混淆。
目前使用的方法
PCRE 驱动的字节替换
像 Variant 1 & 2 这样的简单混淆效果很好
原始(变体 1):
48 8D 64 24 F8 - lea rsp,[rsp-08] ; Stack -= 8
48 89 2C 24 - mov [rsp],rbp ; Push RBP
48 8D 2D 156A5A00 - lea rbp,[7FF749022784] ; Put JMP target in RSP
48 87 2C 24 - xchg [rsp],rbp ; Pop RBP (RBP restored)
48 8D 64 24 08 - lea rsp,[rsp+08] ; Stack += 8 (Balanced)
FF 64 24 F8 - jmp qword ptr [rsp-08] ; JMP (target)
原始(变体 2):
48 89 6c 24 f8 - mov [rsp-0x8], rbp
48 8d 64 24 f8 - lea rsp, [rsp-0x8]
(rest as per Variant 1)
去混淆:
90 90 90 .. .. - (Variant 1: NOP * 9, Variant 2: NOP * 10)
90 90 NOP * 2 ; Pad instruction to preserve
; RIP of next instruction
E9 ?? ?? ?? ?? - JMP NEAR
90 90 90 .. .. - NOP * 13
去混淆脚本:
#!/usr/bin/env sh
# |------------- 11 bytes--------| |-- 5 bytes--| |---------------- - 13 bytes---------|
# Signature: 48 8D 64 24 F8 48 89 2C 24 48 8D 2D ?? ?? ?? ?? 48 87 2C 24 48 8D 64 24 08 FF 64 24 F8 (29 bytes)
# Translate: 90 90 90 90 90 90 90 90 90 90 90 E9 ?? ?? ?? ?? 90 90 90 90 90 90 90 90 90 90 90 90 90
#
# |------------- 12 bytes ----------| |-- 5 bytes--| |---------------- - 13 bytes---------|
# Signature: 48 89 6c 24 f8 48 8d 64 24 f8 48 8d 2d ?? ?? ?? ?? 48 87 2c 24 48 8d 64 24 08 ff 64 24 f8 (30 bytes)
# Translate: 90 90 90 90 90 90 90 90 90 90 90 90 e9 ?? ?? ?? ?? 90 90 90 90 90 90 90 90 90 90 90 90 90
xxd -ps Game_Dumped.exe |
sed -e 's/\(..\)/\1 /g' |
tr '\n' ' ' |
perl -p -e "s/48 8d 64 24 f8 48 \
89 2c 24 48 8d 2d (.. .. .. ..) \
48 87 2c 24 48 8d 64 24 08 ff 64 24 f8/90 90 90 90 90 \
90 90 90 90 90 90 e9 \1 90 90 90 90 90 90 90 90 90 90 \
90 90 90/g ; s/48 89 6c 24 f8 48 8d 64 24 f8 48 8d 2d\
(.. .. .. ..) 48 87 2c 24 48 8d 64 24 08 ff 64 24 \
f8/90 90 90 90 90 90 90 90 90 90 90 90 e9 \1 90 90 90 \
90 90 90 90 90 90 90 90 90 90/g" |
xxd -r -ps > Game_Dumped_NOP2.exe
第三个品种
143fe7a26 48 89 6c 24 f8 mov [rsp-8], rbp
143fe7a2b 48 8d 64 24 f8 lea rsp, [rsp-8]
143fe7a30 e9 60 71 8d ff jmp loc_1438beb95
1438beb95 48 8d 2d 10 b0 3a fd lea rbp, sub_jump_target
1438beb9c 48 87 2c 24 xchg rbp, [rsp]
1438beba0 48 8d 64 24 08 lea rsp, [rsp+8]
1438beba5 ff 64 24 f8 jmp qword ptr [rsp-8]
这与变体 2 相同,只是它被分成了两个部分。计划的解决方案将涉及 distorm3 的流量控制标志,并重写jmp
为0x143fe7a26
.
堆栈操作的变化
48 89 E0 mov rax, rsp
48 05 F8 FF FF FF add rax, 0FFFFFFFFFFFFFFF8h ; Add
48 89 C4 mov rsp, rax
48 89 1C 24 mov [rsp], rbx
减RSP
8 的效果真是太可怕了。但现在,我已经习惯了,它不会打扰我,但我刚刚在 IDA [6.8] 的常规选项中启用了“堆栈指针” ,并意识到 IDA 不包括lea rsp, [rsp+-8]
在它的堆栈指针计算中,这会阻止它正确分析代码。
RSP Bytes Disassembly
--- ------------------- -------------------------------
000 48 89 E0 mov rax, rsp
000 48 05 F8 FF FF FF add rax, 0FFFFFFFFFFFFFFF8h
000 48 89 C4 mov rsp, rax ; It tracked this
-20 48 89 1C 24 mov [rsp], rbx
-20 48 83 EC 20 sub rsp, 20h ; and this
000 48 8B 41 10 mov rax, [rcx+10h]
000 48 89 4C 24 F8 mov [rsp-8], rcx
000 48 8D 64 24 F8 lea rsp, [rsp-8] ; but not this
000 48 8B 1C 24 mov rbx, [rsp]
我也开始怀疑所有这些技术会有很多排列,我需要开始在 IDA 中解决这个问题。
问题是,我能找到的唯一示例源使用 IDAPython idaapi低级函数,所以代码长得离谱,而且当我用 4 字节指令替换 5 字节指令时,我找不到改变操作数的方法我无意中创建的。(幸运的是,在这种情况下,它只是 CLC)。
更新:我已经解决了这个问题,该解决方案大大减少了我的脚本的大小。相关修复如下:
def replace_pattern(ea):
search = [0x48, 0x8d, 0x64, 0x24, 0xf8]
replace = [0x48, 0x83, 0xec, 0x08, 0x90]
current = []
for i in xrange(5):
current.append(idaapi.get_byte(ea+i))
if 0 == cmp(search, current):
for i in xrange(5):
# fixed: replace put_byte with patch_byte
idaapi.patch_byte(ea+i, replace[i])
return 1
return 0
【原代码】顺便说一下,示例代码是我们自己的Rolf Rolles写的
import idaapi
import idc
# Planned task: replace
# 48 8d 64 24 f8 lea rsp,[rsp-0x8]
# with
# 48 83 ec 08 sub rsp,0x8
# 90 nop
#
# Actual result:
# Replaced: 48 8d 64 24 f8 lea rsp,[rsp-0x8]
# with: : 48 83 ec 08 sub rsp,0x8
# f8 clc
#
# Verdict, close enough, but way too much code involved.
def match_pattern(ea):
search = [0x48, 0x8d, 0x64, 0x24, 0xf8]
replace = [0x48, 0x83, 0xec, 0x08, 0x90]
current = []
for i in xrange(5):
current.append(idaapi.get_byte(ea+i))
if 0 == cmp(search, current):
return 1
return 0
# Note: I thought I might be able to simply rewrite
# at a byte level, but it threw an exception.
#
# for i in xrange(4):
# idaapi.put_byte(ea+i, replace[i])
class deobfu_hook(idaapi.IDP_Hooks):
def __init__(self):
idaapi.IDP_Hooks.__init__(self)
self.n = idaapi.netnode("$ X86 Deobfuscator Modifications",0,1)
def custom_ana(self):
# Check first two bytes "by hand" for speed
b = idaapi.get_byte(idaapi.cmd.ea)
if b == 0x48: # First byte
b = idaapi.get_byte(idaapi.cmd.ea+1)
if b == 0x8d: # Second byte
# Discard speed, do a full match
if match_pattern(idaapi.cmd.ea, 0, 0):
# If matched, supply all required values for
# SUB RSP,8 - Surely there is an easier way!
idaapi.cmd.itype = 0xd1
idaapi.cmd.size = 4
idaapi.cmd.auxpref = 0x1810
idaapi.cmd.segpref = 0
idaapi.cmd.insnpref = 0x48
idaapi.cmd.flags = 2
idaapi.cmd.Op1.type = 1
idaapi.cmd.Op1.offb = 0
idaapi.cmd.Op1.offo = 0
idaapi.cmd.Op1.flags = 8
idaapi.cmd.Op1.dtyp = 7
idaapi.cmd.Op1.reg = 4
idaapi.cmd.Op1.phrase = 4
idaapi.cmd.Op1.value = 0
idaapi.cmd.Op1.addr = 0
idaapi.cmd.Op1.specval = 0
idaapi.cmd.Op1.specflag1 = 0
idaapi.cmd.Op1.specflag2 = 0
idaapi.cmd.Op1.specflag3 = 0
idaapi.cmd.Op1.specflag4 = 0
idaapi.cmd.Op2.type = 5
idaapi.cmd.Op2.offb = 3
idaapi.cmd.Op2.offo = 0
idaapi.cmd.Op2.flags = 8
idaapi.cmd.Op2.dtyp = 7
idaapi.cmd.Op2.reg = 0
idaapi.cmd.Op2.phrase = 0
idaapi.cmd.Op2.value = 8
idaapi.cmd.Op2.addr = 0
idaapi.cmd.Op2.specval = 0
idaapi.cmd.Op2.specflag1 = 0
idaapi.cmd.Op2.specflag2 = 0
idaapi.cmd.Op2.specflag3 = 0
idaapi.cmd.Op2.specflag4 = 0
return True
return False
class deobfu_t(idaapi.plugin_t):
flags = idaapi.PLUGIN_PROC | idaapi.PLUGIN_HIDE
comment = "Deobfuscator"
wanted_hotkey = ""
help = "Runs transparently"
wanted_name = "deobx86"
hook = None
def init(self):
self.hook = None
self.hook = deobfu_hook()
self.hook.hook()
print("deobfu init")
return idaapi.PLUGIN_KEEP
def run(self, arg):
pass
def term(self):
print("deobfu term")
if self.hook:
self.hook.unhook()
def PLUGIN_ENTRY():
print("PLUGIN_ENTRY:deobfu")
return deobfu_t()
你今天想去哪里?
我想要一个更好的解决方案,而且我不怕编码。我总是需要一些入门建议,而且我需要确保我没有在这里重新编码轮子。
从那以后,我使用更高级别的idautils编写了一些其他 IDAPython 代码来创建调用树,并整理外部参照等。但我不知道如何重写该级别的实际反汇编代码。IDAPython 存储库中有一个示例:https : //github.com/pfalcon/idapython/blob/master/examples/ex_idphook_asm.py但那是
- 充满愚蠢的错误(我修复了它们)
- 钩住汇编命令,而不是反汇编过程
我在创建 IDA Pro 调试器插件 - API 文档和示例?. 我查看了非常好的 IDAPython 代码的各种示例,这些代码可以:
但是,我没有看到实际更改指令的任何内容。
我没有购买IDA Pro Book,因为我不住在美国,我不想等待n周的按需印刷和交付。我并不反对写一个.idc
,因为我对 C 非常熟悉(对 Python 更熟悉),尽管我怀疑尽管学习曲线较浅(并且假设准备好的例子)它会比使用更高的更难(长期)级别 IDAPython 代码。(我正在学习 Python,但是 .. 好吧,不是我们所有人吗?)
因为我使用的代码完全是 64 位的,所以几乎没有(基本上没有)预先存在的 deobfu 样本或代码。
所以我在这里发现自己,请求您的耐心指导。(如果您真的阅读了所有内容,请非常耐心)。
PS:我花时间记录了我所做的一切,因为我知道我们对那些在大喊大叫之前甚至不尝试做某事的人的尊重是多么的少。
PPS:OMG 2最狂热的RE的成员是伊戈尔Skochinsky,我只能低头谦卑。