# 编写

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 混淆的思路是通用的,但是文中代码只是根据我手里被混淆过的文件特征进行去除,如果想在不同的文件中使用,最好把一些常用代码封装成函数,根据不同的 ollvm 魔改方式进行去除

# 例 :CeackMe

此题为魔改 ollvm,以它为例,讲解如何分析及去除一个陌生的 ollvm,并写出 idc 脚本一键处理

观察 cfg,发现和之前学到的有些不同,只有入口块、分发块和基本块,缺少了返回块

每个基本块都是直接返回到主分发块,而且也没有将 case 值压入指针的操作,那么分发块是如何确定下一个跳转的基本块的呢

再来看主分发块

最后与 case 值比较的是寄存器 eax,计算公式如图,只有 ecx 是不确定的。而在刚刚的基本块中,每个块都会对 ecx 赋值并运算,所以这就是为什么没有返回块的原因:每个基本块都要计算出 ecx 的值,然后传到主分发块去计算 case 值

现在,我们手动计算一下 loc_140001F5C 的 case 值,得到 5DE42860 ,接下来找子分发块,看这个 case 值对应的地址是多少

然而并没有子分发块比较的值等于 5DE42860

观察图中的子分发块,除了最下层是 jz / jnz 这种相等比较,其余都是大小比较,也就是说子分发块没有将所有的 case 值都明写出来,而是通过比大小的方式决定。像刚刚的 5DE42860 ,在层层比较下,最终应该跳转到 loc_140001DF2。

我们知道 ollvm 是将原本块与块的关系打乱,统一由分发块管理,那么去除 ollvm 就是将块与块的关系恢复。上面的例子已经得到了 loc_140001F5C -> loc_140001DF2 的关系,接下来就是对每个分发块进行同样的操作

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
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
#include <idc.idc>

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

static calCaseVal(key_data)
{
auto mul_data = 0x3650F7CD;
auto xor_data = 0x268916D7;
auto caseVal = (((mul_data * key_data) & 0xFFFFFFFF) ^ xor_data) & 0xFFFFFFFF;
return caseVal;
}

static checkJmp(jmp_name)
{
if(jmp_name == "jg" | jmp_name == "jz" | jmp_name == "jnz" | jmp_name == "jle" | jmp_name == "jmp")
{
return 1;
}
return 0;
}

static getRegData(current_addr,start_addr,reg_name)
{
auto xor_reg_name;
auto add_data = 0;
auto sub_data = 0;
auto xor_data = 0;
while(current_addr != BADADDR && current_addr > start_addr)
{
auto insn_name = print_insn_mnem(current_addr);
auto reg = print_operand(current_addr,0);
auto op2 = print_operand(current_addr,1);
auto data = get_operand_value(current_addr,1) & 0xFFFFFFFF;
if(reg == reg_name)
{
if(insn_name == "mov")
{
auto reg_data = data;
//msg("mov data : %X\n",data);
break;
}
if(insn_name == "add")
{
add_data = data;
//msg("add data : %X\n",data);
}
if(insn_name == "sub")
{
sub_data = data;
//msg("sub data : %X\n",data);
}
if(insn_name == "xor")
{
xor_reg_name = op2;
//msg("xor reg name : " + xor_reg_name + "\n");
}
}

if(reg == xor_reg_name)
{
if(insn_name == "mov")
{
xor_data = data;
//msg("xor data : %X\n",xor_data);
}
}

current_addr = prev_head(current_addr,start_addr);
}

//msg("key data = (%X + %X - %X) ^ %X\n",reg_data,add_data,sub_data,xor_data);
auto key_data = ((reg_data + add_data - sub_data) ^ xor_data) & 0xFFFFFFFF;
return key_data;

}

extern ecx_data;
extern other_data;

static getKeyData(current_addr,start_addr,type)
{
if(type == 1)
{
return getRegData(current_addr,start_addr,"ecx");
}
if(type == 2)
{
ecx_data = getRegData(current_addr,start_addr,"ecx");
auto other_reg = getOtherReg(current_addr,start_addr);
other_data = getRegData(current_addr,start_addr,other_reg);
}
}

static getOtherReg(current_addr,start_addr)
{
current_addr = prev_head(current_addr,start_addr);
while(current_addr != BADADDR && current_addr > start_addr)
{
auto cmov_insn = print_insn_mnem(current_addr);
auto other_reg = print_operand(current_addr,1);
//msg("cmov insn :" + cmov_insn + "\n");
if(strstr(cmov_insn, "cmov", 0) != -1)
{
return other_reg;
}
if(checkJmp(cmov_insn))
{
break;
}
current_addr = prev_head(current_addr,start_addr);
}
return 1;
}

static getCmovType(current_addr,start_addr)
{
current_addr = prev_head(current_addr,start_addr);
while(current_addr != BADADDR && current_addr > start_addr)
{
auto cmov_insn = print_insn_mnem(current_addr);
//msg("cmov insn :" + cmov_insn + "\n");
if(strstr(cmov_insn, "cmov", 0) != -1)
{
if(cmov_insn == "cmovb")
{
return "jb";
}
if(cmov_insn == "cmovz")
{
return "jz";
}
if(cmov_insn == "cmovnz")
{
return "jnz";
}
}
if(checkJmp(cmov_insn))
{
break;
}
current_addr = prev_head(current_addr,start_addr);
}
return 1;
}

static checkType(current_addr,start_addr)
{
current_addr = prev_head(current_addr,start_addr);
while(current_addr != BADADDR && current_addr > start_addr)
{
auto cmov_insn = print_insn_mnem(current_addr);
//msg("cmov insn :" + cmov_insn + "\n");
if(strstr(cmov_insn, "cmov", 0) != -1)
{
return 2;
}
if(checkJmp(cmov_insn))
{
break;
}
current_addr = prev_head(current_addr,start_addr);
}
return 1;
}

static isBasicBlock(branch_addr)
{
auto branch_name = print_insn_mnem(branch_addr);
auto op1 = print_operand(branch_addr,0);
if(branch_name == "cmp" && op1 == "eax")
{
return 0;
}
return 1;
}

static findJmpAddr(jmp_basicblock_addr,caseVal,current_addr,end_addr)
{
while (current_addr != BADADDR && current_addr < end_addr)
{
auto op = Byte(current_addr);
auto cmpCase = Dword(current_addr + 1);
auto jmp_insn_addr = current_addr + 5;
auto jmp_name = print_insn_mnem(jmp_insn_addr);
auto next_addr = next_head(jmp_insn_addr,end_addr);
auto insn_len = next_addr - (jmp_insn_addr);
auto offset;
if(insn_len < 5)
{
offset = Byte(current_addr + 6);
}
else
{
offset = Dword(current_addr + 7);
}
if(offset > 0xFF000000)
{
offset = 0xFFFFFFFF00000000 | offset;
}
auto jmp_addr = next_addr + offset;
auto is_jmp = checkJmp(jmp_name);
auto greater_branch;
auto other_branch;
auto equal_branch;
auto branch_addr;
if(op == 0x3D && is_jmp)
{
//msg("swich cmp : %X\n",current_addr);
//msg("cmp data : %X\n",cmpCase);
//msg("my data : %X\n",caseVal);
//msg("jmp addr : %X\n",jmp_addr);
if(jmp_name == "jg")
{
greater_branch = jmp_addr;
other_branch = next_addr;
if(caseVal > cmpCase)
{
//msg("%X -> %X\n",jmp_basicblock_addr,greater_branch);
branch_addr = greater_branch;
}
else
{
//msg("%X -> %X\n",jmp_basicblock_addr,other_branch);
branch_addr = other_branch;
}
}

if(jmp_name == "jz")
{
equal_branch = jmp_addr;
other_branch = next_addr;
if(caseVal == cmpCase)
{
//msg("%X -> %X\n",jmp_basicblock_addr,equal_branch);
branch_addr = equal_branch;
}
else
{
//msg("%X -> %X\n",jmp_basicblock_addr,other_branch);
branch_addr = other_branch;
}

}

if(jmp_name == "jnz")
{
equal_branch = next_addr;
other_branch = jmp_addr;
if(caseVal == cmpCase)
{
//msg("%X -> %X\n",jmp_basicblock_addr,equal_branch);
branch_addr = equal_branch;
}
else
{
//msg("%X -> %X\n",jmp_basicblock_addr,other_branch);
branch_addr = other_branch;
}
}

if(jmp_name == "jle")
{
greater_branch = next_addr;
other_branch = jmp_addr;
if(caseVal > cmpCase)
{
//msg("%X -> %X\n",jmp_basicblock_addr,greater_branch);
branch_addr = greater_branch;
}
else
{
//msg("%X -> %X\n",jmp_basicblock_addr,other_branch);
branch_addr = other_branch;
}
}

if(isBasicBlock(branch_addr))
{
break;
}
current_addr = prev_head(branch_addr,branch_addr - 10);
//msg("current addr : %X\n",current_addr);
}
current_addr = next_head(current_addr, end_addr);
}
msg("%X -> %X\n",jmp_basicblock_addr,branch_addr);
return branch_addr;
}

static findMovAddr(current_addr,start_addr)
{
current_addr = prev_head(current_addr,start_addr);
while(current_addr != BADADDR && current_addr > start_addr)
{
auto reg_name = print_operand(current_addr,0);
auto op = print_insn_mnem(current_addr);
if(op == "mov" && reg_name == "ecx")
{
return current_addr;
}

if(checkJmp(op))
{
break;
}
current_addr = prev_head(current_addr,start_addr);
}
}

static PatchJmp(current_addr,next_insn_addr,start_addr,branch_addr_ecx,branch_addr_other,type,cmov_type)
{
auto mov_addr;
auto nop_len;
auto offset;
mov_addr = findMovAddr(current_addr,start_addr);
nop_len = next_insn_addr - mov_addr;
NopCode(mov_addr,nop_len);
if(type == 1)
{
PatchByte(mov_addr,0xE9);
offset = branch_addr_ecx - mov_addr - 5;
PatchDword(mov_addr + 1,offset);
}
if(type == 2)
{
auto cmov_byte;
if(cmov_type == "jz")
{
cmov_byte = 0x840F;
}
if(cmov_type == "jnz")
{
cmov_byte = 0x850F;
}
auto offest_other = branch_addr_other - mov_addr - 6;
PatchWord(mov_addr,cmov_byte);
PatchDword(mov_addr + 2,offest_other);
auto jmp_addr = mov_addr + 6;
PatchByte(jmp_addr,0xE9);
auto offest_ecx = branch_addr_ecx - jmp_addr - 5;
PatchDword(jmp_addr + 1,offest_ecx);
}
}


static main()
{
auto current_addr = 0x1400017D8;
auto start_addr = 0x1400017D8;
auto end_addr = 0x140001FAF;
while (current_addr != BADADDR && current_addr < end_addr)
{
auto jmp_returnBlock = Byte(current_addr);
auto next_insn_addr = next_head(current_addr,end_addr);
auto jmp_returnBlock_offset = Dword(current_addr + 1);
if(jmp_returnBlock_offset > 0xFF000000)
{
//msg("jmp return offset : %X\n",jmp_returnBlock_offset);
jmp_returnBlock_offset = 0xFFFFFFFF00000000 | jmp_returnBlock_offset;
}
auto swichBlock_addr = 0x1400019E0;
// basicblock
if(jmp_returnBlock == 0xE9 && next_insn_addr + jmp_returnBlock_offset == swichBlock_addr)
{
msg("basicblock addr : %X\n",current_addr);
auto type = checkType(current_addr,start_addr);
auto key_data;
auto caseVal;
auto branch_addr;
// branch == 1
if(type == 1)
{
key_data = getKeyData(current_addr,start_addr,type);
caseVal = calCaseVal(key_data);
branch_addr = findJmpAddr(current_addr,caseVal,start_addr,end_addr);
PatchJmp(current_addr,next_insn_addr,start_addr,branch_addr,0,type,0);
}
// branch == 2
if(type == 2)
{
getKeyData(current_addr,start_addr,type);
key_data = ecx_data;
auto caseVal_ecx = calCaseVal(key_data);
auto caseVal_other = calCaseVal(other_data);
auto cmov_type = getCmovType(current_addr,start_addr);
msg(cmov_type + " ");
auto branch_addr_other = findJmpAddr(current_addr,caseVal_other,start_addr,end_addr);
msg("jmp ");
auto branch_addr_ecx = findJmpAddr(current_addr,caseVal_ecx,start_addr,end_addr);
PatchJmp(current_addr,next_insn_addr,start_addr,branch_addr_ecx,branch_addr_other,type,cmov_type);
}

}

current_addr = next_head(current_addr, end_addr);
}
}

# 脚本解析

延续之前的思路,从基本块开始解析,因为每个基本块的结尾都是跳转到一个固定的地址,之前是跳转到返回块,现在是跳转到主分发块,相对于其他地方更好定位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 获取当前指令的第一个字节 验证是否为跳转指令(0xE9)      
auto jmp_returnBlock = Byte(current_addr);
auto next_insn_addr = next_head(current_addr,end_addr);
// 获取当前指令 + 1处的四字 如果第一个字节为跳转 则此处为偏移
auto jmp_returnBlock_offset = Dword(current_addr + 1);
if(jmp_returnBlock_offset > 0xFF000000)
{
// 处理偏移为负数的情况
jmp_returnBlock_offset = 0xFFFFFFFF00000000 | jmp_returnBlock_offset;
}
auto swichBlock_addr = 0x1400019E0;
// 校验是否为跳转到主分发块的指令
if(jmp_returnBlock == 0xE9 && next_insn_addr + jmp_returnBlock_offset == swichBlock_addr)
{
// 对基本块处理
}

找到基本块后,就该计算出相应的 ecx 值,对基本块的处理应该分后继块的数量讨论

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
static getRegData(current_addr,start_addr,reg_name)
{
auto xor_reg_name;
auto add_data = 0;
auto sub_data = 0;
auto xor_data = 0;
while(current_addr != BADADDR && current_addr > start_addr)
{
auto insn_name = print_insn_mnem(current_addr);
// 操作数1
auto reg = print_operand(current_addr,0);
// 操作数2
auto op2 = print_operand(current_addr,1);
// 操作数2的具体数值
auto data = get_operand_value(current_addr,1) & 0xFFFFFFFF;
// 如果操作数1为我们想要的计算器
if(reg == reg_name)
{
if(insn_name == "mov")
{
// 赋值操作
auto reg_data = data;
//msg("mov data : %X\n",data);
break;
}
if(insn_name == "add")
{
// 加法操作
add_data = data;
//msg("add data : %X\n",data);
}
if(insn_name == "sub")
{
// 减法操作
sub_data = data;
//msg("sub data : %X\n",data);
}
if(insn_name == "xor")
{
// 程序中异或操作并不是异或立即数 而是另一个寄存器
xor_reg_name = op2;
//msg("xor reg name : " + xor_reg_name + "\n");
}
}

// 找对异或寄存器的赋值
if(reg == xor_reg_name)
{
if(insn_name == "mov")
{
xor_data = data;
//msg("xor data : %X\n",xor_data);
}
}

current_addr = prev_head(current_addr,start_addr);
}

// 计算最终返回值
auto key_data = ((reg_data + add_data - sub_data) ^ xor_data) & 0xFFFFFFFF;
return key_data;

}

计算出 ecx 的值之后,就可以根据固定公式得知该基本块的 case 值。

接下来就是处理分发块找下一个跳转块,子分发块也很好定位,每个块都是比较 + 跳转,跳转关系只有两种:大于 / 不大于、等于 / 不等于,因为跳转指令只有 jgjlejzjnz

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
static findJmpAddr(jmp_basicblock_addr,caseVal,current_addr,end_addr)
{
while (current_addr != BADADDR && current_addr < end_addr)
{
auto op = Byte(current_addr);
auto cmpCase = Dword(current_addr + 1);
auto jmp_insn_addr = current_addr + 5;
auto jmp_name = print_insn_mnem(jmp_insn_addr);
auto next_addr = next_head(jmp_insn_addr,end_addr);
auto insn_len = next_addr - (jmp_insn_addr);
auto offset;
// 短跳转的特殊处理
if(insn_len < 5)
{
offset = Byte(current_addr + 6);
}
else
{
offset = Dword(current_addr + 7);
}
// 负数的处理
if(offset > 0xFF000000)
{
offset = 0xFFFFFFFF00000000 | offset;
}
auto jmp_addr = next_addr + offset;
auto is_jmp = checkJmp(jmp_name);
auto greater_branch;
auto other_branch;
auto equal_branch;
auto branch_addr;
if(op == 0x3D && is_jmp)
{
// 跳转指令为大于时
if(jmp_name == "jg")
{
// 要跳转的地址储存到大于分支
greater_branch = jmp_addr;
// 下一个地址储存到其他分支
other_branch = next_addr;
if(caseVal > cmpCase)
{
// 大于则把大于分支储存到跳转地址里
branch_addr = greater_branch;
}
else
{
// 不大于则把其他分支储存到跳转地址里
branch_addr = other_branch;
}
}
// 其他同理
if(jmp_name == "jz")
{
equal_branch = jmp_addr;
other_branch = next_addr;
if(caseVal == cmpCase)
{
//msg("%X -> %X\n",jmp_basicblock_addr,equal_branch);
branch_addr = equal_branch;
}
else
{
//msg("%X -> %X\n",jmp_basicblock_addr,other_branch);
branch_addr = other_branch;
}

}

if(jmp_name == "jnz")
{
equal_branch = next_addr;
other_branch = jmp_addr;
if(caseVal == cmpCase)
{
//msg("%X -> %X\n",jmp_basicblock_addr,equal_branch);
branch_addr = equal_branch;
}
else
{
//msg("%X -> %X\n",jmp_basicblock_addr,other_branch);
branch_addr = other_branch;
}
}

if(jmp_name == "jle")
{
greater_branch = next_addr;
other_branch = jmp_addr;
if(caseVal > cmpCase)
{
//msg("%X -> %X\n",jmp_basicblock_addr,greater_branch);
branch_addr = greater_branch;
}
else
{
//msg("%X -> %X\n",jmp_basicblock_addr,other_branch);
branch_addr = other_branch;
}
}

if(isBasicBlock(branch_addr))
{
break;
}
current_addr = prev_head(branch_addr,branch_addr - 10);
//msg("current addr : %X\n",current_addr);
}
current_addr = next_head(current_addr, end_addr);
}
// 最后打印出执行跳转指令的地址 -> 要跳转的地址
msg("%X -> %X\n",jmp_basicblock_addr,branch_addr);
return branch_addr;
}

这样就分析出了最关键的跳转关系,可以跑一下看结果

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
basicblock addr : 140001A77
140001A77 -> 140001F94
basicblock addr : 140001AEE
jnz 140001AEE -> 140001F5C
jmp 140001AEE -> 140001DB9
basicblock addr : 140001BF9
jz 140001BF9 -> 140001A2F
jmp 140001BF9 -> 140001E7A
basicblock addr : 140001C4B
jz 140001C4B -> 140001E7A
jmp 140001C4B -> 140001AA1
basicblock addr : 140001D16
jnz 140001D16 -> 140001B16
jmp 140001D16 -> 140001E3C
basicblock addr : 140001D45
140001D45 -> 140001E5E
basicblock addr : 140001DB4
jz 140001DB4 -> 140001990
jmp 140001DB4 -> 140001D26
basicblock addr : 140001DED
140001DED -> 140001DF2
basicblock addr : 140001E37
jnz 140001E37 -> 140001E5E
jmp 140001E37 -> 140001C16
basicblock addr : 140001E59
140001E59 -> 140001B16
basicblock addr : 140001E75
140001E75 -> 140001990
basicblock addr : 140001F57
jnz 140001F57 -> 140001DB9
jmp 140001F57 -> 140001D55
basicblock addr : 140001F8F
140001F8F -> 140001DF2

得到之后只需要 patch 计算,改为跳转就可以了

最后记得修一下入口块,这套逻辑对基本块有效,入口块需要特殊处理

因为入口块到主分发块为短跳转,只有两个字节,而一般跳转需要 5 个字节,所以可以折中一下,nop 掉主分发块,改为 jmp 指令

截图对比效果如下

原始程序:

去混淆后:

去除非常干净

# 总结

在接触到一陌生 ollvm 时,可以通过观察:1.case 的计算方式 ,2. 分发块的比较方式获取去除方法。编写脚本时也同理

这篇博客将会持续更新,分享我见过的 ollvm 实例以及 ollvm pass 的编写,大家遇到有趣的 ollvm 程序也可以分享给我!(不过真的会有人看我的博客吗 TxT…)