好友
阅读权限10
听众
最后登录1970-1-1
|
本帖最后由 SharsDela 于 2025-3-5 00:03 编辑
本文章仅为技术交流目的而创作,旨在分享观点、经验及技术思路。
使用工具: Intelpin, 生成trace记录
Python以及使用triton作为反汇编框架
IDA 9.0
目标程序: 加了壳的elf 程序. 带有花指令和控制流平坦化.壳本身带有花指令, 控制流混淆, 控制流平坦化
壳的功能: 1.对程序完整性进行校验
检测到 hash 值不匹配, rip 会执行到 0xdead 然后 crash
2.检测 /proc/self/statustracer id进行反调试
检测到被调试会调用Kill函数结束自己
目的: 根据trace记录构建CFG, 并且根据执行流程回溯关键条件跳转
CFG构建的效果如下:
间接跳转使用红色, 一眼看到分发器
条件跳转使用绿色, 有助于快速找到关键点
直接跳转使用蓝色
原程序正常执行的CFG执行了4000多万条指令, 去重后,指令数量 18104 个
当程序被patch, 完整性校验不通过时的CFG
去花后的CFG
花指令分析:使用SSA分析过, 等于没有操作, 连续且可以直接移除的查看以下基本块
[Asm] 纯文本查看 复制代码 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | lea rsp, [rsp+8]
pushfq
add rbp, 64h
popfq
lea rbp, [rbp-64h]
pushfq
push rdi
mov rdi, [rsp+8]
lea rdi, [rdi+0Ah]
xchg rdi, [rsp+8]
mov [rsp+8], rdi
mov rdi, [rsp]
lea rsp, [rsp+8]
call $+5
lea rax, [rax]
add qword ptr [rsp+0], 18C8Eh
xchg rax, [rsp+0]
pushfq
add rbp, 29h
popfq
lea rbp, [rbp-29h]
xchg rax, [rsp+8]
push rdx
mov rdx, rdi
pop rdx
xchg rax, [rsp+0]
push rbp
mov rbp, rsp
leave
popfq
push rdx
mov rdx, [rsp+8]
lea rdx, [rdx+32h]
xchg rdx, [rsp+8]
mov [rsp+8], rdx
mov rdx, [rsp]
lea rsp, [rsp+8]
retn
|
====================
可以直接移除的, 可以是任意立即数
pushfq
add rbp, 64h
popfq
lea rbp, [rbp-64h]
====================
可以是任意寄存器
push rdi
mov rdi, [rsp+8]
lea rdi, [rdi+0Ah]
xchg rdi, [rsp+8]
mov [rsp+8], rdi
mov rdi, [rsp]
lea rsp, [rsp+8]
===================
可以是任意寄存器
push rdx
mov rdx, rdi
pop rdx
================
控制流混淆分析:
去掉之后剩下的就是控制流混淆, 没有实际功能
lea rsp, [rsp+8]
pushfq
call $+5
lea rax, [rax]
add qword ptr [rsp+0], 18C8Eh
xchg rax, [rsp+0]
xchg rax, [rsp+8]
xchg rax, [rsp+0]
popfq
retn
关键在call $+5 , 和 add指令, 根据当前基本块内的地址加上偏移跳到下一个基本块, 可以替换成 一条 push imm指令, 都是固定套路,没有变化.
=============
还有一些花指令, 看代码就不说了.
CFG构建的思路就是合并一些花指令基本块,
上一个基本块
call imm1
下一个基本块
lea rsp, [rsp+8]
jmp imm2
在CFG中看就是这样
像这种,两个基本块基本都没有实际功能的, 后继基本块入度为 1 的, 两个基本块 合并成 jmp imm2
优化bcf虚假控制流, 看似走了不同的分支, 实际上没有功能并最终走向同一个节点
手动标记,并进行优化.
找关键条件跳转的思路
根据trace的流程,打印基本块的指令,找到最后一个执行的基本块, 往上找到一个条件跳转,则是关键跳转
下面是代码,有的见附件
Pin instruction trace 代码就不贴了, 自带的例子修改几行代码就行
格式是
0x12345678
0x45678910
python代码, CFG构建部分大部分用Claude进行提示词工程完成的
流程是 : 加载二进制, 遍历trace指令, 构建基本块并执行优化, 构建CFG并执行优化
[Python] 纯文本查看 复制代码 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | def main():
global gVecCleanBbl
trace = load_trace(TRACE_FILE)
ctx.setMode(MODE.ALIGNED_MEMORY, True )
ctx.setMode(MODE.CONSTANT_FOLDING, True )
ctx.setMode(MODE.ONLY_ON_SYMBOLIZED, True )
ctx.setMode(MODE.AST_OPTIMIZATIONS, True )
loadBinary()
purify(ctx, trace)
printResult(gVecCleanBbl)
cfg, edge_counts, bbls = processAndOptimizeCFG(BblTraceOrder, gVecCleanBbl, ctx, bbl_patch.keys())
printBasicBlock(bbls, "merged_bbl.txt" )
saveCFGDot(edge_counts, cfg, bbls, "cfg_colored.dot" )
pass
|
使用lief加载二进制报错不知道为啥,这里我使用gdb进行dump然后加载, 脚本见附件
[Python] 纯文本查看 复制代码 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 | def loadBinary():
dump = ProcessDump(DUMP)
for segment in dump.memory:
if 'perms' not in segment:
continue
if 'start' not in segment:
continue
if 'end' not in segment:
continue
if segment[ 'perms' = = 'r-xp' and segment[ 'start' < 0x7ffff7fbd000 :
ctx.setConcreteMemoryAreaValue(segment[ 'start' ], segment[ 'memory' ])
for reg, value in dump.registers.items():
if 'base' in reg:
continue
ctx.setConcreteRegisterValue(ctx.getRegister(reg), value)
return
|
感觉应该有使用符号执行一把梭的方法, 但是太菜了,希望有大牛点拨, 继续学习.
代码附件:
tracePurify.zip
(11.5 KB, 下载次数: 5)
的]
的
|
免费评分
-
查看全部评分
|