插件代码https://github.com/EEEEhex/detx
一
寄存器间接跳转混淆
//-----------------
if (Cond)
jmp(true_addr)
else
jmp(false_addr)
//-----------------
变为了->
//-----------------
if (Cond)
var1 = 0;
else
var1 = 1;
var2 = data_1fd630[var1];
var3 = var2 - 0x7218df2;
jump(var3);
//-----------------
3.1. 其中在汇编层面是通过条件选择指令(csel, cset, cinc等)来改变值导致最终跳转地址的变化(就是上面红框中x11#1=8和x11#2=0x30)
3.2. 因此可将条件选择指令改为mov等指令直接赋值, 模拟执行两次来分别获取if和else的真实跳转地址
0x9cc60 cmp w12, w23
....... 改变跳转寄存器x12
0x9cc7c br x12
0x9cc60 cmp w12, w23
....... ............
0x9cc70 cmp w12, w15
....... ............
0x9cc78 b.lt 满足条件地址
0x9cc7c b 不满足条件地址
1. 一次混淆!至少!涉及以下7个指令(中间穿插着其他逻辑的指令):
mov w10, #0x60
...
mov w11, #0x58
...
cmp w7, w22
...
csel x23, x11, x10, lt
...
ldr x25, [x12, x23]
...
add x7, x25, x13
...
br x7
2. 改为如下:
mov w10, #0x60 <- 可以nop掉 不nop也不影响结果
...
mov w11, #0x58
...
nop <- cmp w7, w22 [cmp语句要最后统一nop 因为会可能有多个逻辑共用同一个cmp]
...
nop <- csel x23, x11, x10, lt
...
nop <- 其他涉及到的指令
...
cmp w7, w22 <- ldr x25, [x12, x23]
b.lt ... <- add x7, x25, x13
b ... <- br x7
大多只有第一次混淆的时候这些混淆指令会穿插在一起, 之后基本都是ldr+add+br一个整体了
二
编写插件代码
代码逻辑分为: ①模拟执行 ②信息获取 ③Patch逻辑 三部分
#如果是csinc指令, 不满足条件应该改为add x24, x1, #1 | csinc是条件不满足则xd=xm+1, cinc是条件满足则xd=xn+1
if ((insn_token[0] == 'csinc' ) and (index == 1)) or ((insn_token[0] == 'cinc') and (index == 0)):
if value == 'xzr':#如果是xzr寄存器就不能用add, 相当于赋值为了1
mov_opcode = bv.arch.assemble(f"mov {cond_set_reg}, #1", condition_insn_addr)
else:
mov_opcode = bv.arch.assemble(f"add {cond_set_reg}, {value}, #1", condition_insn_addr)
elif (insn_token[0] == 'csinv') and (index == 1):
mov_opcode = bv.arch.assemble(f"mvn {cond_set_reg}, {value}", condition_insn_addr) #按位取反
elif (insn_token[0] == 'sneg') and (index == 1):
mov_opcode = bv.arch.assemble(f"neg {cond_set_reg}, {value}", condition_insn_addr) #取负值
else:
mov_opcode = bv.arch.assemble(f"mov {cond_set_reg}, {value}", condition_insn_addr) #汇编mov x4, x9
def get_involve_insns(jmp_insn: MediumLevelILJump):
def get_right_ssa_var(expr, vars: list):
if isinstance(expr, SSAVariable):
vars.append(expr)
return
elif isinstance(expr, list):
for ope in expr:
if isinstance(ope, SSAVariable):
vars.append(ope)
returnif hasattr(expr, 'operands'):
for ope in expr.operands:
get_right_ssa_var(ope, vars)
returninvolve_insns = [] #涉及到的指令
jmp_var = jmp_insn.dest.var
var_stack = []
var_stack.append(jmp_var)
while len(var_stack) != 0: #拿到一次寄存器间接跳转混淆涉及到的所有指令
cur_ssa_var = var_stack.pop()
insn_ = cur_ssa_var.def_site #一条指令 应该是MediumLevelILSetVarSsa或MediumLevelILVarPhi
if insn_ == None:
breakif insn_ in involve_insns:
break #如果拿到的指令已经在之前获取到的指令中了, 说明遇到循环了
else:
involve_insns.append(insn_) #添加涉及到的指令if 'cond' in insn_.dest.name:#遇到cond:20#1 = x8#2 == 0x586b6221这种就不再继续了 要不然有可能遇到phi节点导致死循环
breakinsn_right = insn_.src #这条指令=右边的表达式
get_right_ssa_var(insn_right, var_stack) #拿到表达式中的变量return involve_insns
involve_asm_addrs = [] #涉及到的汇编指令的地址 可能少csx赋值指令 后面补上
for mlssa_insn in involve_insns:
llil_insns = mlssa_insn.llils
for insn_ in llil_insns:
if insn_.address not in involve_asm_addrs:
involve_asm_addrs.append(insn_.address)
#0. 拿到所有要操作的指令
obf_insns_index = [] #指在csx2br_insns_text中的index
csx2br_insns_text = [] #从csx到br中的所有指令文本 (包含csx不包含br)
#1. 将混淆指令全转为nop, 并删除最后两个nop(一个nop改bcc, 一个nop改cmp)
for i in obf_insns_index:
csx2br_insns_text[i] = 'nop'
csx2br_insns_text.pop(obf_insns_index[-1])
csx2br_insns_text.pop(obf_insns_index[-2]) #index本身就是从小到大排序的, 所以直接pop不影响#2. 下沉cmp
cmp_txt = bv.get_disassembly(cmp_addr)
csx2br_insns_text.append(cmp_txt)#3. 获取select指令的寄存器 并添加跳转
csx_tokens = (bv.get_disassembly(cond_addr)).split() #获取csel/cset/csinc等的token
csx_cond = csx_tokens[-1] #条件eq/lt等
bcc_cond = 'b.' + csx_cond
bcc_txt = f"{bcc_cond} {hex(tbr_addr)}"
csx2br_insns_text.append(bcc_txt)
b_txt = f"b {hex(fbr_addr)}"
csx2br_insns_text.append(b_txt)
logger.log_info(f"csx2br_insns_text: {csx2br_insns_text}")
三
效果
看雪ID:0xEEEE
https://bbs.kanxue.com/user-home-901761.htm
# 往期推荐
球分享
球点赞
球在看
点击阅读原文查看更多