灵感来源:http://schnappi.games/2022/04/09/手脱花指令及ida脚本编写/


在中难题中,出题者往往通过ollvm、花指令、vm等手段妨碍正常逆向分析过程,本篇将介绍使用自动化脚本进行去花。

简单例子

这是一个简单的tea加密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <stdint.h>

void encrypt(uint32_t* v, uint32_t* k) {
uint32_t v0 = v[0], v1 = v[1], sum = 0, i; /* set up */
uint32_t delta = 0x9e3779b9; /* a key schedule constant */
uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3]; /* cache key */
for (i = 0; i < 32; i++) { /* basic cycle start */
sum += delta;
v0 += ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
v1 += ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
} /* end cycle */
v[0] = v0; v[1] = v1;
}
int main() {
int a = 1;
uint32_t flag[] = { 1234,5678 };
uint32_t key[] = { 9,9,9,9 };
encrypt(flag, key);
printf("%d,%d", flag[0], flag[1]);
return 0;
}

我们可以通过加入些花指令来妨碍分析

1
2
3
4
5
6
7
8
9
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
39
40
#include <stdio.h>
#include <stdint.h>

void encrypt(uint32_t* v, uint32_t* k) {
uint32_t v0 = v[0], v1 = v[1], sum = 0, i; /* set up */
uint32_t delta = 0x9e3779b9; /* a key schedule constant */
uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3]; /* cache key */
for (i = 0; i < 32; i++) { /* basic cycle start */
// 这里在7.7已经无法防止分析了,ida能够正常解析
__asm {
jmp junk1
__emit 0x12
junk2:
ret
__emit 0x34
junk1 :
call junk2
}
sum += delta;
v0 += ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
v1 += ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
} /* end cycle */
v[0] = v0; v[1] = v1;
}
int main() {
int a = 1;
uint32_t flag[] = { 1234,5678 };
uint32_t key[] = { 9,9,9,9 };
__asm {
_emit 075h
_emit 2h
_emit 0E9h
_emit 0EDh
}
// 利用x86指令集是可变指令集
// E9 是jmp,会读取四个字节数据作为地址进行跳转ED+后面的3个字节作为地址,做成反汇编异常
encrypt(flag, key);
printf("%d,%d", flag[0], flag[1]);
return 0;
}

在vs中编译后丢到ida里。

image-20220829090353237

不仅汇编分析失败,也无法f5看main的反编译。

回到源码,对第二处汇编代码进行分析,75 02是jnz $+2,即如果结果不为0则跳转到两个指令后,所以e9 ed不会被执行。

对于这种可以手动patch,但是如果数量较多,我们可以采用自动化分析

先上脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def nop(addr, endaddr):
while addr < endaddr:
patch_byte(addr, 0x90)
addr += 1

def undefine(addr, endaddr):
while addr < endaddr:
del_items(addr, 0)
addr += 1

def dejunkcode(addr, endaddr):
while addr < endaddr:
create_insn(addr)
# 匹配模板
if print_insn_mnem(addr) == 'jnz' and \
get_operand_value(addr, 0) == addr + 4:
next = addr + 4
nop(addr, next)
addr = next
continue
addr += get_item_size(addr)

# undefine(0x00401160, 0x004011D4)
dejunkcode(0x00401160, 0x004011D4)
undefine(0x00401160, 0x004011D4)
add_func(0x00401160, -1)
1
2
3
4
5
6
7
8
create_insn(ea) #分析代码区,相当于ida快捷键C
get_item_size(ea) #获取指令或数据长度
print_insn_mnem(ea) #得到addr地址的操作码
get_operand_value(ea,n) #返回指令的操作数的被解析过的值
patch_byte(ea, value) #修改程序字节
get_wide_byte(ea) #将地址解释为Byte
del_items(ea,0) #MakeCode的反过程,相当于ida快捷键U
add_func(ea,end) #将有begin到end的指令转换成一个函数。如果end被指定为BADADDR(-1),IDA会尝试通过定位函数的返回指令,来自动确定该函数的结束地址

我们对main函数的范围进行dejunkcode和undefine,最后重新创建函数。

在dejunkcode的过程中,我们找到jnz的代码,然后判断跳转到的字节,再进行nop。

运行脚本是在file中

image-20220829090657300

跑python脚本就可以了