# LLVM Pass 编写及去除 —— 控制流平坦化

# 编写

ollvm 更改了原始代码块的跳转关系,将代码分割为基本块,由分发块统一控制,如下图

基本块为由终结指令结尾的代码块,每个基本块都会跳转到返回块,返回块回到分发块进行下一次跳转

具体代码如下

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
#include <llvm/IR/Function.h>
#include <llvm/Pass.h>
#include "llvm/IR/LegacyPassManager.h"
#include "llvm/Transforms/IPO/PassManagerBuilder.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/IR/Instructions.h"
#include "llvm/Transforms/Utils.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Transforms/Utils/Local.h"
#include <SplitBasicBlock.h>
#include <Utils.h>
#include <vector>
#include <cstdlib>
#include <ctime>
using namespace llvm;
using std::vector;

namespace{
class Flattening : public FunctionPass{
public:
static char ID;
Flattening() : FunctionPass(ID){
srand(time(0));
}

void flatten(Function &F);

bool runOnFunction(Function &F);
};
}

bool Flattening :: runOnFunction(Function &F)
{
INIT_CONTEXT(F);
// 分割成更小块增加代码混淆程度
FunctionPass *pass = createSplitBasicBlockPass();
pass->runOnFunction(F);
flatten(F);
return true;
}

void Flattening :: flatten(Function &F)
{
if(F.size() <= 1)
{
return;
}

// 获取基本块
vector<BasicBlock*> origBB;
for(BasicBlock &BB : F)
{
origBB.push_back(&BB);
}
// 去掉入口块
origBB.erase(origBB.begin());
// 获取入口块
BasicBlock &entryBB = F.getEntryBlock();
// 检查入口块终结指令是否为分支跳转指令 是的话放回基本块vector中
if(BranchInst *br = dyn_cast<BranchInst>(entryBB.getTerminator()))
{
if(br->isConditional())
{
BasicBlock *newBB = entryBB.splitBasicBlock(br,"newBB");
origBB.insert(origBB.begin(),newBB);
}
}

// 创建分发块
BasicBlock *dispatchBB = BasicBlock :: Create(*CONTEXT,"dispatchBB",&F,&entryBB);
// 创建返回块
BasicBlock *returnBB = BasicBlock :: Create(*CONTEXT,"returnBB",&F,&entryBB);
// 调整顺序 将入口块调至最前
entryBB.moveBefore(dispatchBB);
// 去除入口块的跳转指令
entryBB.getTerminator()->eraseFromParent();
// 创建跳转指令 入口块跳转到分发块
BranchInst *brDispatchBB = BranchInst::Create(dispatchBB,&entryBB);
int randNumCase = rand();
// 跳转指令之前新建一个指针
AllocaInst *swVarPtr = new AllocaInst(TYPE_I32,0,"swVar.ptr",brDispatchBB);
// 将生成的随机数存到指针中
new StoreInst(CONST_I32(randNumCase),swVarPtr,brDispatchBB);
// 获取随机数
LoadInst *swVar = new LoadInst(TYPE_I32,swVarPtr,"swVar",false,dispatchBB);
// 创建默认块
BasicBlock *swDefault = BasicBlock :: Create(*CONTEXT,"swDefault",&F,returnBB);
// 创建跳转指令 使默认块跳转到返回块
BranchInst::Create(returnBB,swDefault);
// 创建跳转指令 使返回块跳转到分发块
BranchInst::Create(dispatchBB, returnBB);
// 创建swich指令
SwitchInst *swInst = SwitchInst::Create(swVar,swDefault,0,dispatchBB);

// 每一个基本块都加入case
for(BasicBlock *BB : origBB)
{
BB->moveBefore(returnBB);
swInst->addCase(CONST_I32(randNumCase),BB);
randNumCase = rand();
}

for(BasicBlock *BB : origBB)
{
if(BB->getTerminator()->getNumSuccessors() == 0)
{
continue;
}
// 后继块只有一个
else if(BB->getTerminator()->getNumSuccessors() == 1)
{
// 获取后继块
BasicBlock *sucBB = BB->getTerminator()->getSuccessor(0);
// 找到后继块跳转的case 保存到指针中
ConstantInt *numCase = swInst->findCaseDest(sucBB);
// 删除尾部跳转
BB->getTerminator()->eraseFromParent();
new StoreInst(numCase,swVarPtr,BB);
// 创建跳转指令 跳回返回块
BranchInst::Create(returnBB,BB);
}
else if(BB->getTerminator()->getNumSuccessors() == 2)
{
// 获取后继块
ConstantInt *numCaseTrue = swInst->findCaseDest(BB->getTerminator()->getSuccessor(0));
ConstantInt *numCaseFalse = swInst->findCaseDest(BB->getTerminator()->getSuccessor(1));
BranchInst *br = cast<BranchInst>(BB->getTerminator());
// 选择条件对应的case
SelectInst *sel = SelectInst::Create(br->getCondition(),KeyTrue,keyFalse,"",BB->getTerminator());
// 删除终结指令
BB->getTerminator()->eraseFromParent();
// 存储case值
new StoreInst(sel,swVarPtr,BB);
// 创建跳转指令 跳回返回块
BranchInst::Create(returnBB,BB);
}
}
// 修复PHI指令和逃逸变量
fixStack(F);
}

char Flattening::ID = 0;
static RegisterPass<Flattening> X("fla", "Flatten basic blocks");
# 去除

利用 swich case 结构控制下一个跳转的基本快,但是这种方式写出来的 case 值会直接暴露在代码中,很容易找到跳转关系

我们可以尝试手动去除一下这种 ollvm

在 loc_4011DF 这个基本块中,给存放 case 变量的指针赋值,然后回到返回块,此时 0x5CAFE12C 就是分发器要找的下一个 case 值

找到比较 0x5CAFE12C 的分发器,就可以顺藤摸瓜发现跳转的下一个块为 loc_4011F2

也就可以据此恢复跳转关系去除混淆

知道原理之后就可以写出 idc 脚本批量操作

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
// 基本块入手
#include <idc.idc>

static NopCode(Addr, Length)
{
auto i;
for (i = 0; i < Length; i++)
{
PatchByte(Addr + i, 0x90);
}
}

static findCaseVal(targetByte,cmovAddr)
{
while(1)
{
auto currentByte = Byte(cmovAddr);
auto caseVal = Dword(cmovAddr + 1);
if(currentByte == targetByte)
{
msg("find caseVal : %X\n",caseVal);
return caseVal;
}
cmovAddr = prev_head(cmovAddr,cmovAddr - 8);
}
}

static findCaseFunc(start_addr,end_addr,caseVal)
{
msg("find case : %X\n",caseVal);
while(start_addr != BADADDR && start_addr < end_addr)
{
auto op = Byte(start_addr);
auto checkCase = Dword(start_addr + 1);
if(op == 0x2D && checkCase == caseVal)
{
auto jz_addr = start_addr + 5;
auto offest = Dword(jz_addr + 2);
auto caseFunc = offest + jz_addr + 6;
return caseFunc;
}
start_addr = next_head(start_addr, end_addr);
}
}


static main()
{
auto seg;
for (seg = get_first_seg(); seg != BADADDR; seg = get_next_seg(seg))
{
auto seg_name = get_segm_name(seg);
//msg(seg_name + "\n");
if (seg_name == ".text")
{
auto start_addr = 0x401130;
auto current_addr = 0x401130;
auto end_addr = 0x40128D;
while (current_addr != BADADDR && current_addr < end_addr)
{
auto setValPtr = Word(current_addr + 1);
auto jmpCode = Byte(current_addr + 7);
auto jmpCodeBranch = Byte(current_addr + 3);
// jmp
if(setValPtr == 0xD845 && jmpCode == 0xE9)
{
auto caseVal;
auto caseFunc;
auto returnAddr;
auto jmpOffset;
auto patchByte;
//msg("setValPtr : %X",current_addr);
// jmp
caseVal = Dword(current_addr + 3);
msg("caseVal : %X\n",caseVal);
caseFunc = findCaseFunc(start_addr,end_addr,caseVal);
returnAddr = current_addr + 7;
msg("case func : %X\n",caseFunc);
msg("return addr : %X\n",returnAddr);
//msg("current addr : %X\n",current_addr);
jmpOffset = caseFunc - returnAddr;
if(jmpOffset >= -126 && jmpOffset <= 129)
{
patchByte = jmpOffset - 2;
PatchByte(returnAddr,0xEB);
PatchByte(returnAddr + 1,patchByte);
msg("patch byte : %X\n",patchByte);
NopCode(returnAddr + 2,3);
}
else
{
patchByte = jmpOffset - 5;
PatchDword(returnAddr + 1,patchByte);
msg("patch byte : %X\n",patchByte);
}
}
// branch
if(setValPtr == 0xD845 && jmpCodeBranch == 0xE9)
{
msg("branch jmp addr : %X\n",current_addr);
auto cmovAddr = prev_head(current_addr,start_addr);
auto cmovOp = print_insn_mnem(cmovAddr);
auto op1 = print_operand(cmovAddr,0);
auto op2 = print_operand(cmovAddr,1);
auto brByte;
auto brShortByte;
auto regByte;
auto caseFunc_op1;
auto caseFunc_op2;
auto brOffest;
auto jmpAddr;
if(cmovOp == "cmovb")
{
brShortByte = 0x72;
brByte = 0x820F;
}
// else comv...
if(op1 == "eax")
{
regByte = 0xB8;
caseVal = findCaseVal(regByte,cmovAddr);
msg("caseVal : %X\n",caseVal);
caseFunc_op1 = findCaseFunc(start_addr,end_addr,caseVal);
msg("case func : %X\n",caseFunc_op1);
}
//else op1...
if(op2 == "ecx")
{
regByte = 0xB9;
caseVal = findCaseVal(regByte,cmovAddr);
msg("caseVal : %X\n",caseVal);
caseFunc_op2 = findCaseFunc(start_addr,end_addr,caseVal);
msg("case func : %X\n",caseFunc_op2);
}
// else op2...
returnAddr = current_addr + 3;
brOffest = caseFunc_op2 - cmovAddr;
msg("br offest : %X\n",brOffest);
NopCode(cmovAddr,returnAddr + 5 - cmovAddr);
msg("nop addr : %X len : %X\n",cmovAddr,returnAddr + 5 - cmovAddr);
// short br
if(brOffest > -127 && brOffest < 130)
{
PatchByte(cmovAddr,brShortByte);
patchByte = brOffest - 2;
PatchByte(cmovAddr + 1,patchByte);
msg("patch byte : %X\n",patchByte);
}
else
{
PatchWord(cmovAddr,brByte);
patchByte = brOffest - 5;
PatchByte(cmovAddr + 1,patchByte);
msg("patch byte : %X\n",patchByte);
}

jmpAddr = next_head(cmovAddr,end_addr);
jmpOffset = caseFunc_op1 - jmpAddr;
msg("jmp offest : %X\n",jmpOffset);
// short jmp
if(jmpOffset >= -126 && jmpOffset <= 129)
{
patchByte = jmpOffset - 2;
PatchByte(jmpAddr,0xEB);
PatchByte(jmpAddr + 1,patchByte);
msg("patch byte : %X\n",patchByte);
}
else
{
patchByte = jmpOffset - 5;
PatchByte(jmpAddr,0xE9);
PatchDword(jmpAddr + 1,patchByte);
msg("patch byte : %X\n",patchByte);
}
}

current_addr = next_head(current_addr, end_addr);
}
}
}
}

脚本从基本块入手,从 pass 编写脚本可知每个基本块的最后两条指令为给 swValPtr 赋值和跳转到返回块,且基本块必定只有一个跳转指令,可以根据这些特征轻松定位基本块并进行处理

效果如下

原始伪代码:

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
__int64 __fastcall enc(unsigned __int8 *a1)
{
__int64 result; // rax
int v2; // eax
int swValPtr; // [rsp+4h] [rbp-28h]
int v4; // [rsp+8h] [rbp-24h]
unsigned __int64 v5; // [rsp+14h] [rbp-18h]
unsigned __int8 *v6; // [rsp+1Ch] [rbp-10h]
unsigned __int8 v7; // [rsp+27h] [rbp-5h]
int v8; // [rsp+28h] [rbp-4h]

swValPtr = 0x5DE91606;
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( swValPtr == 0x17070602 )
{
v4 = v8;
swValPtr = 0x5CAFE12C;
}
if ( swValPtr != 0x26ADAA22 )
break;
v2 = 955080127;
if ( v5 < 8 )
v2 = 0x5EA2D108;
swValPtr = v2;
}
if ( swValPtr != 0x32A4BB49 )
break;
*v6 = v7 ^ 0xA;
swValPtr = 1094857714;
}
result = (unsigned int)(swValPtr - 0x38ED5DBF);
if ( swValPtr == 0x38ED5DBF )
break;
switch ( swValPtr )
{
case 0x414233F2:
v8 = v4 + 1;
swValPtr = 386336258;
break;
case 0x5CAFE12C:
v5 = v4;
swValPtr = 648915490;
break;
case 0x5DE91606:
v4 = 0;
swValPtr = 0x5CAFE12C;
break;
default:
v6 = &a1[v4];
v7 = *v6;
swValPtr = 0x32A4BB49;
break;
}
}
return result;
}

使用 idc 脚本去混淆后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
__int64 __fastcall enc(unsigned __int8 *a1)
{
__int64 result; // rax
int i; // [rsp+8h] [rbp-24h]

for ( i = 0; ; ++i )
{
result = 955080127;
if ( (unsigned __int64)i >= 8 )
break;
a1[i] ^= 0xAu;
}
return result;
}

可以成功去除

# 魔改

但是这样的 ollvm 混淆,case 值直接暴露在汇编中,使用 d810 等工具就可以一把梭,为了增加混淆去除难度,可以对 case 值的生成进行魔改

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
for(BasicBlock *BB : origBB)
{
if(BB->getTerminator()->getNumSuccessors() == 0)
{
continue;
}
// 后继块只有一个
else if(BB->getTerminator()->getNumSuccessors() == 1)
{
// 获取后继块
BasicBlock *sucBB = BB->getTerminator()->getSuccessor(0);
// 找到后继块跳转的case 保存到指针中
ConstantInt *numCase = swInst->findCaseDest(sucBB);
// 新增魔改case
ConstantInt *nowCase = swInst->findCaseDest(BB);
// 当前case值
int nowCaseVal = nowCase->getZExtValue();
// 后继块case值
int numCaseVal = numCase->getZExtValue();
// 计算出异或运算需要的值
int xorKey = nowCaseVal ^ numCaseVal;
LoadInst *currentSwVar = new LoadInst(TYPE_I32,swVarPtr,"curVar",false,BB->getTerminator());
ConstantInt *keyStone = CONST_I32(xorKey);
// 增加异或运算
BinaryOperator *nextSwVar = BinaryOperator::CreateXor(currentSwVar, keyStone, "nextSwVar", BB->getTerminator());
// 删除尾部跳转
BB->getTerminator()->eraseFromParent();
new StoreInst(nextSwVar,swVarPtr,BB);
// 创建跳转指令 跳回返回块
BranchInst::Create(returnBB,BB);
}
else if(BB->getTerminator()->getNumSuccessors() == 2)
{
// 获取后继块
ConstantInt *numCaseTrue = swInst->findCaseDest(BB->getTerminator()->getSuccessor(0));
ConstantInt *numCaseFalse = swInst->findCaseDest(BB->getTerminator()->getSuccessor(1));
BranchInst *br = cast<BranchInst>(BB->getTerminator());
// 新增魔改case
ConstantInt *nowCase = swInst->findCaseDest(BB);
int nowCaseVal = nowCase->getZExtValue();
// 正确分支case值
int trueCaseVal = numCaseTrue->getZExtValue();
// 错误分支case值
int falseCaseVal = numCaseFalse->getZExtValue();
int keyTrueVal = nowCaseVal ^ trueCaseVal;
int keyFalseVal = nowCaseVal ^ falseCaseVal;

ConstantInt *KeyTrue = CONST_I32(keyTrueVal);
ConstantInt *keyFalse = CONST_I32(keyFalseVal);
// 选择条件对应的case
SelectInst *sel = SelectInst::Create(br->getCondition(),KeyTrue,keyFalse,"",BB->getTerminator());
LoadInst *currentSwVar = new LoadInst(TYPE_I32,swVarPtr,"curVal",BB->getTerminator());
BinaryOperator *nextSwVar = BinaryOperator::CreateXor(currentSwVar,sel,"newSwVar",BB->getTerminator());
// 删除终结指令
BB->getTerminator()->eraseFromParent();
// 存储case值
new StoreInst(nextSwVar,swVarPtr,BB);
// 创建跳转指令 跳回返回块
BranchInst::Create(returnBB,BB);
}
}

如上,在处理基本块时,对 case 值的生成新增了异或运算,间接运算 case 值可以使 d810 失效

依旧手动去除一下

# 去除

图中可以看到 loc_4011F6 的 case 值为 0x772811A7,自身异或的值为 0x15FBEF3,运算后得到 0x7677AF54

得到后继块为 loc_40120E

如果有两个后继块,图中条件为 cmovb,相应应该更改为 jb + jmp

编写 idc 脚本去除

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
// 分发块入手
#include <idc.idc>

static NopCode(Addr, Length)
{
auto i;
for (i = 0; i < Length; i++)
{
PatchByte(Addr + i, 0x90);
}
}

static findCaseVal(targetByte,cmov_addr)
{
while(1)
{
auto currentByte = Byte(cmov_addr);
auto caseVal = Dword(cmov_addr + 1);
if(currentByte == targetByte)
{
msg("find caseVal : %X\n",caseVal);
return caseVal;
}
cmov_addr = prev_head(cmov_addr,cmov_addr - 8);
}
}

static findCaseFunc(start_addr,end_addr,caseVal)
{
msg("find case : %X\n",caseVal);
while(start_addr != BADADDR && start_addr < end_addr)
{
auto op = Byte(start_addr);
auto checkCase = Dword(start_addr + 1);
if(op == 0x2D && checkCase == caseVal)
{
auto jz_addr = start_addr + 5;
auto offest = Dword(jz_addr + 2);
auto caseFunc = offest + jz_addr + 6;
return caseFunc;
}
start_addr = next_head(start_addr, end_addr);
}
}

static findJmpAddr(start_addr,end_addr)
{
auto i;
for(i = start_addr; i < end_addr; i = next_head(i,end_addr))
{
auto jmp_byte = Byte(i);
if(jmp_byte == 0xE9)
{
return i;
}
}
}

static checkType(start_addr,end_addr)
{
auto i;
for(i = start_addr; i < end_addr; i = next_head(i,end_addr))
{
auto xor_byte = Byte(i);
auto movSwValPtr_byte = Byte(i + 5);
auto movSwValPtr_branch = Byte(i + 3);
if(xor_byte == 0x35 && movSwValPtr_byte == 0x89)
{
return 1;
}
else if(xor_byte == 0x33 && movSwValPtr_branch == 0x89)
{
return 2;
}
}
return 0;
}

static findXorData(start_addr)
{
while(1)
{
auto xor_op = Byte(start_addr);
auto mov_op = Byte(start_addr + 5);
if(xor_op == 0x35 && mov_op == 0x89)
{
auto data = Dword(start_addr + 1);
break;
}
start_addr = next_head(start_addr, start_addr + 8);
}
return data;
}

static main()
{
auto seg;
for (seg = get_first_seg(); seg != BADADDR; seg = get_next_seg(seg))
{
auto seg_name = get_segm_name(seg);
//msg(seg_name + "\n");
if (seg_name == ".text")
{
auto start_addr = 0x401130;
auto current_addr = 0x401130;
auto end_addr = 0x4012A5;
while (current_addr != BADADDR && current_addr < end_addr)
{
auto sub_code = Byte(current_addr);
auto jz_addr = current_addr + 5;
auto jz_code = Word(jz_addr);
auto jz_offest = Dword(jz_addr + 2);
auto check_dword = Dword(current_addr + 1);
if(sub_code == 0x2D && jz_code == 0x840F)
{
auto basic_block_addr = jz_addr + jz_offest + 6;
msg("check dword : %X\n",check_dword);
msg("basic block addr : %X\n",basic_block_addr);
auto type = checkType(basic_block_addr,end_addr);
msg("type : %X\n",type);
// jmp
if(type == 1)
{
auto xor_data = findXorData(basic_block_addr);
msg("xor data : %X\n",xor_data);
auto swVar = xor_data ^ check_dword;
auto caseFunc = findCaseFunc(start_addr,end_addr,swVar);
msg("case func : %X\n",caseFunc);
auto jmp_addr = findJmpAddr(basic_block_addr,end_addr);
auto offest = caseFunc - jmp_addr;
auto patchByte;
if(offest >= -126 && offest <= 129)
{
patchByte = offest - 2;
PatchByte(jmp_addr,0xEB);
PatchByte(jmp_addr + 1,patchByte);
msg("patch byte : %X\n",patchByte);
NopCode(jmp_addr + 2,3);
}
else
{
patchByte = offest - 5;
PatchDword(jmp_addr + 1,patchByte);
}
NopCode(jmp_addr - 8,8);
}

// branch
if(type == 2)
{
jmp_addr = findJmpAddr(basic_block_addr,end_addr);
auto mov_addr = prev_head(jmp_addr,basic_block_addr);
auto xor_addr = prev_head(mov_addr,basic_block_addr);
auto cmov_addr = prev_head(xor_addr,basic_block_addr);
auto cmov_op = print_insn_mnem(cmov_addr);
auto op1 = print_operand(cmov_addr,0);
auto op2 = print_operand(cmov_addr,1);
auto br_byte;
auto br_short;
auto caseFunc_op1;
auto caseFunc_op2;
auto regByte;
if(cmov_op == "cmovb")
{
br_short = 0x72;
br_byte = 0x820F;
}
// other comv...
if(op1 == "eax")
{
regByte = 0xB8;
xor_data = findCaseVal(regByte,cmov_addr);
msg("xor data : %X\n",xor_data);
swVar = check_dword ^ xor_data;
caseFunc_op1 = findCaseFunc(start_addr,end_addr,swVar);
msg("case func : %X\n",caseFunc_op1);
}
// other op1...
if(op2 == "ecx")
{
regByte = 0xB9;
xor_data = findCaseVal(regByte,cmov_addr);
msg("xor data : %X\n",xor_data);
swVar = check_dword ^ xor_data;
caseFunc_op2 = findCaseFunc(start_addr,end_addr,swVar);
msg("case func : %X\n",caseFunc_op2);
}
// other op2...
auto brOffest = caseFunc_op2 - cmov_addr;
msg("br offest : %X\n",brOffest);
NopCode(cmov_addr,jmp_addr + 5 - cmov_addr);
msg("nop addr : %X len : %X\n",cmov_addr,jmp_addr + 5 - cmov_addr);
// short br
if(brOffest > -127 && brOffest < 130)
{
PatchByte(cmov_addr,br_short);
patchByte = brOffest - 2;
PatchByte(cmov_addr + 1,patchByte);
msg("patch byte : %X\n",patchByte);
}
else
{
PatchWord(cmov_addr,br_byte);
patchByte = brOffest - 5;
PatchByte(cmov_addr + 1,patchByte);
msg("patch byte : %X\n",patchByte);
}

auto jmpAddr = next_head(cmov_addr,end_addr);
auto jmpOffset = caseFunc_op1 - jmpAddr;
msg("jmp offest : %X\n",jmpOffset);
// short jmp
if(jmpOffset >= -126 && jmpOffset <= 129)
{
patchByte = jmpOffset - 2;
PatchByte(jmpAddr,0xEB);
PatchByte(jmpAddr + 1,patchByte);
msg("patch byte : %X\n",patchByte);
}
else
{
patchByte = jmpOffset - 5;
PatchByte(jmpAddr,0xE9);
PatchDword(jmpAddr + 1,patchByte);
msg("patch byte : %X\n",patchByte);
}
}
}
current_addr = next_head(current_addr, end_addr);
}
}
}
}

因为异或运算需要自身 case 值,所以可以从分发块开始解析

去混淆效果如下

去混淆前

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
__int64 __fastcall enc(unsigned __int8 *a1)
{
__int64 result; // rax
int v2; // eax
int swValPtr; // [rsp+4h] [rbp-28h]
int v4; // [rsp+8h] [rbp-24h]
unsigned __int64 v5; // [rsp+14h] [rbp-18h]
unsigned __int8 *v6; // [rsp+1Ch] [rbp-10h]
unsigned __int8 v7; // [rsp+27h] [rbp-5h]
int v8; // [rsp+28h] [rbp-4h]

swValPtr = 1392335309;
while ( 1 )
{
while ( 1 )
{
while ( swValPtr == 80288048 )
{
v6 = &a1[v4];
v7 = *v6;
swValPtr = 1219708018;
}
if ( swValPtr != 188876390 )
break;
v8 = v4 + 1;
swValPtr = 1299578078;
}
result = (unsigned int)(swValPtr - 989136729);
if ( swValPtr == 989136729 )
break;
switch ( swValPtr )
{
case 1219708018:
*v6 = v7 ^ 0xA;
swValPtr = 188876390;
break;
case 1299578078:
v4 = v8;
swValPtr = 1999114663;
break;
case 1392335309:
v4 = 0;
swValPtr = 1999114663;
break;
case 1987555156:
v2 = 1283631117;
if ( v5 < 8 )
v2 = 1925101156;
swValPtr ^= v2;
break;
case 1999114663:
v5 = v4;
swValPtr = 1987555156;
break;
}
}
return result;
}

去除混淆后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
__int64 __fastcall enc(unsigned __int8 *a1)
{
__int64 result; // rax
int i; // [rsp+8h] [rbp-24h]

for ( i = 0; ; ++i )
{
result = 1283631117;
if ( (unsigned __int64)i >= 8 )
break;
a1[i] ^= 0xAu;
}
return result;
}

idc 脚本去除 ollvm 混淆的思路是通用的,但是文中代码只是根据我手里被混淆过的文件特征进行去除,如果想在不同的文件中使用,需要修改使用字节码定位的部分,或直接写成函数识别,这部分之后遇到实例再进行补充吧