/de4dot.blocks/cflow/SwitchCflowDeobfuscator.cs
C# | 483 lines | 359 code | 62 blank | 62 comment | 115 complexity | 4342127925970b307dcdda56116a2aa8 MD5 | raw file
Possible License(s): GPL-3.0
1/*
2 Copyright (C) 2011-2013 de4dot@gmail.com
3
4 This file is part of de4dot.
5
6 de4dot is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 de4dot is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with de4dot. If not, see <http://www.gnu.org/licenses/>.
18*/
19
20using System;
21using System.Collections.Generic;
22using dnlib.DotNet;
23using dnlib.DotNet.Emit;
24
25namespace de4dot.blocks.cflow {
26 class SwitchCflowDeobfuscator : BlockDeobfuscator {
27 InstructionEmulator instructionEmulator = new InstructionEmulator();
28
29 protected override bool Deobfuscate(Block switchBlock) {
30 if (switchBlock.LastInstr.OpCode.Code != Code.Switch)
31 return false;
32
33 if (IsSwitchTopOfStack(switchBlock) && DeobfuscateTOS(switchBlock))
34 return true;
35
36 if (IsLdlocBranch(switchBlock, true) && DeobfuscateLdloc(switchBlock))
37 return true;
38
39 if (IsStLdlocBranch(switchBlock, true) && DeobfuscateStLdloc(switchBlock))
40 return true;
41
42 if (IsSwitchType1(switchBlock) && DeobfuscateType1(switchBlock))
43 return true;
44
45 if (IsSwitchType2(switchBlock) && DeobfuscateType2(switchBlock))
46 return true;
47
48 if (switchBlock.FirstInstr.IsLdloc() && FixSwitchBranch(switchBlock))
49 return true;
50
51 return false;
52 }
53
54 static bool IsSwitchTopOfStack(Block switchBlock) {
55 return switchBlock.Instructions.Count == 1;
56 }
57
58 static bool IsLdlocBranch(Block switchBlock, bool isSwitch) {
59 int numInstrs = 1 + (isSwitch ? 1 : 0);
60 return switchBlock.Instructions.Count == numInstrs && switchBlock.Instructions[0].IsLdloc();
61 }
62
63 static bool IsSwitchType1(Block switchBlock) {
64 return switchBlock.FirstInstr.IsLdloc();
65 }
66
67 bool IsSwitchType2(Block switchBlock) {
68 Local local = null;
69 foreach (var instr in switchBlock.Instructions) {
70 if (!instr.IsLdloc())
71 continue;
72 local = Instr.GetLocalVar(blocks.Locals, instr);
73 break;
74 }
75 if (local == null)
76 return false;
77
78 foreach (var source in switchBlock.Sources) {
79 var instrs = source.Instructions;
80 for (int i = 1; i < instrs.Count; i++) {
81 var ldci4 = instrs[i - 1];
82 if (!ldci4.IsLdcI4())
83 continue;
84 var stloc = instrs[i];
85 if (!stloc.IsStloc())
86 continue;
87 if (Instr.GetLocalVar(blocks.Locals, stloc) != local)
88 continue;
89
90 return true;
91 }
92 }
93
94 return false;
95 }
96
97 bool IsStLdlocBranch(Block switchBlock, bool isSwitch) {
98 int numInstrs = 2 + (isSwitch ? 1 : 0);
99 return switchBlock.Instructions.Count == numInstrs &&
100 switchBlock.Instructions[0].IsStloc() &&
101 switchBlock.Instructions[1].IsLdloc() &&
102 Instr.GetLocalVar(blocks.Locals, switchBlock.Instructions[0]) == Instr.GetLocalVar(blocks.Locals, switchBlock.Instructions[1]);
103 }
104
105 bool DeobfuscateTOS(Block switchBlock) {
106 bool modified = false;
107 if (switchBlock.Targets == null)
108 return modified;
109 var targets = new List<Block>(switchBlock.Targets);
110
111 modified |= DeobfuscateTOS(targets, switchBlock.FallThrough, switchBlock);
112
113 return modified;
114 }
115
116 bool DeobfuscateLdloc(Block switchBlock) {
117 bool modified = false;
118
119 var switchVariable = Instr.GetLocalVar(blocks.Locals, switchBlock.Instructions[0]);
120 if (switchVariable == null)
121 return modified;
122
123 if (switchBlock.Targets == null)
124 return modified;
125 var targets = new List<Block>(switchBlock.Targets);
126
127 modified |= DeobfuscateLdloc(targets, switchBlock.FallThrough, switchBlock, switchVariable);
128
129 return modified;
130 }
131
132 bool DeobfuscateStLdloc(Block switchBlock) {
133 bool modified = false;
134
135 var switchVariable = Instr.GetLocalVar(blocks.Locals, switchBlock.Instructions[0]);
136 if (switchVariable == null)
137 return modified;
138
139 if (switchBlock.Targets == null)
140 return modified;
141 var targets = new List<Block>(switchBlock.Targets);
142
143 modified |= DeobfuscateStLdloc(targets, switchBlock.FallThrough, switchBlock);
144
145 return modified;
146 }
147
148 // Switch deobfuscation when block uses stloc N, ldloc N to load switch constant
149 // blk1:
150 // ldc.i4 X
151 // br swblk
152 // swblk:
153 // stloc N
154 // ldloc N
155 // switch (......)
156 bool DeobfuscateStLdloc(IList<Block> switchTargets, Block switchFallThrough, Block block) {
157 bool modified = false;
158 foreach (var source in new List<Block>(block.Sources)) {
159 if (!isBranchBlock(source))
160 continue;
161 instructionEmulator.Initialize(blocks, allBlocks[0] == source);
162 instructionEmulator.Emulate(source.Instructions);
163
164 var target = GetSwitchTarget(switchTargets, switchFallThrough, instructionEmulator.Pop());
165 if (target == null)
166 continue;
167 source.ReplaceLastNonBranchWithBranch(0, target);
168 source.Add(new Instr(OpCodes.Pop.ToInstruction()));
169 modified = true;
170 }
171 return modified;
172 }
173
174 // Switch deobfuscation when block uses ldloc N to load switch constant
175 // blk1:
176 // ldc.i4 X
177 // stloc N
178 // br swblk / bcc swblk
179 // swblk:
180 // ldloc N
181 // switch (......)
182 bool DeobfuscateLdloc(IList<Block> switchTargets, Block switchFallThrough, Block block, Local switchVariable) {
183 bool modified = false;
184 foreach (var source in new List<Block>(block.Sources)) {
185 if (isBranchBlock(source)) {
186 instructionEmulator.Initialize(blocks, allBlocks[0] == source);
187 instructionEmulator.Emulate(source.Instructions);
188
189 var target = GetSwitchTarget(switchTargets, switchFallThrough, instructionEmulator.GetLocal(switchVariable));
190 if (target == null)
191 continue;
192 source.ReplaceLastNonBranchWithBranch(0, target);
193 modified = true;
194 }
195 else if (IsBccBlock(source)) {
196 instructionEmulator.Initialize(blocks, allBlocks[0] == source);
197 instructionEmulator.Emulate(source.Instructions);
198
199 var target = GetSwitchTarget(switchTargets, switchFallThrough, instructionEmulator.GetLocal(switchVariable));
200 if (target == null)
201 continue;
202 if (source.Targets[0] == block) {
203 source.SetNewTarget(0, target);
204 modified = true;
205 }
206 if (source.FallThrough == block) {
207 source.SetNewFallThrough(target);
208 modified = true;
209 }
210 }
211 }
212 return modified;
213 }
214
215 // Switch deobfuscation when block has switch contant on TOS:
216 // blk1:
217 // ldc.i4 X
218 // br swblk
219 // swblk:
220 // switch (......)
221 bool DeobfuscateTOS(IList<Block> switchTargets, Block switchFallThrough, Block block) {
222 bool modified = false;
223 foreach (var source in new List<Block>(block.Sources)) {
224 if (!isBranchBlock(source))
225 continue;
226 instructionEmulator.Initialize(blocks, allBlocks[0] == source);
227 instructionEmulator.Emulate(source.Instructions);
228
229 var target = GetSwitchTarget(switchTargets, switchFallThrough, instructionEmulator.Pop());
230 if (target == null) {
231 modified |= DeobfuscateTos_Ldloc(switchTargets, switchFallThrough, source);
232 }
233 else {
234 source.ReplaceLastNonBranchWithBranch(0, target);
235 source.Add(new Instr(OpCodes.Pop.ToInstruction()));
236 modified = true;
237 }
238 }
239 return modified;
240 }
241
242 // ldloc N
243 // br swblk
244 // or
245 // stloc N
246 // ldloc N
247 // br swblk
248 bool DeobfuscateTos_Ldloc(IList<Block> switchTargets, Block switchFallThrough, Block block) {
249 if (IsLdlocBranch(block, false)) {
250 var switchVariable = Instr.GetLocalVar(blocks.Locals, block.Instructions[0]);
251 if (switchVariable == null)
252 return false;
253 return DeobfuscateLdloc(switchTargets, switchFallThrough, block, switchVariable);
254 }
255 else if (IsStLdlocBranch(block, false))
256 return DeobfuscateStLdloc(switchTargets, switchFallThrough, block);
257
258 return false;
259 }
260
261 static bool isBranchBlock(Block block) {
262 if (block.Targets != null)
263 return false;
264 if (block.FallThrough == null)
265 return false;
266 switch (block.LastInstr.OpCode.Code) {
267 case Code.Switch:
268 case Code.Leave:
269 case Code.Leave_S:
270 return false;
271 default:
272 return true;
273 }
274 }
275
276 static bool IsBccBlock(Block block) {
277 if (block.Targets == null || block.Targets.Count != 1)
278 return false;
279 if (block.FallThrough == null)
280 return false;
281 switch (block.LastInstr.OpCode.Code) {
282 case Code.Beq:
283 case Code.Beq_S:
284 case Code.Bge:
285 case Code.Bge_S:
286 case Code.Bge_Un:
287 case Code.Bge_Un_S:
288 case Code.Bgt:
289 case Code.Bgt_S:
290 case Code.Bgt_Un:
291 case Code.Bgt_Un_S:
292 case Code.Ble:
293 case Code.Ble_S:
294 case Code.Ble_Un:
295 case Code.Ble_Un_S:
296 case Code.Blt:
297 case Code.Blt_S:
298 case Code.Blt_Un:
299 case Code.Blt_Un_S:
300 case Code.Bne_Un:
301 case Code.Bne_Un_S:
302 case Code.Brfalse:
303 case Code.Brfalse_S:
304 case Code.Brtrue:
305 case Code.Brtrue_S:
306 return true;
307 default:
308 return false;
309 }
310 }
311
312 bool DeobfuscateType1(Block switchBlock) {
313 Block target;
314 if (!EmulateGetTarget(switchBlock, out target) || target != null)
315 return false;
316
317 bool modified = false;
318
319 foreach (var source in new List<Block>(switchBlock.Sources)) {
320 if (!source.CanAppend(switchBlock))
321 continue;
322 if (!WillHaveKnownTarget(switchBlock, source))
323 continue;
324
325 source.Append(switchBlock);
326 modified = true;
327 }
328
329 return modified;
330 }
331
332 bool DeobfuscateType2(Block switchBlock) {
333 bool modified = false;
334
335 var bccSources = new List<Block>();
336 foreach (var source in new List<Block>(switchBlock.Sources)) {
337 if (source.LastInstr.IsConditionalBranch()) {
338 bccSources.Add(source);
339 continue;
340 }
341 if (!source.CanAppend(switchBlock))
342 continue;
343 if (!WillHaveKnownTarget(switchBlock, source))
344 continue;
345
346 source.Append(switchBlock);
347 modified = true;
348 }
349
350 foreach (var bccSource in bccSources) {
351 if (!WillHaveKnownTarget(switchBlock, bccSource))
352 continue;
353 var consts = GetBccLocalConstants(bccSource);
354 if (consts.Count == 0)
355 continue;
356 var newFallThrough = CreateBlock(consts, bccSource.FallThrough);
357 var newTarget = CreateBlock(consts, bccSource.Targets[0]);
358 var oldFallThrough = bccSource.FallThrough;
359 var oldTarget = bccSource.Targets[0];
360 bccSource.SetNewFallThrough(newFallThrough);
361 bccSource.SetNewTarget(0, newTarget);
362 newFallThrough.SetNewFallThrough(oldFallThrough);
363 newTarget.SetNewFallThrough(oldTarget);
364 modified = true;
365 }
366
367 return modified;
368 }
369
370 static Block CreateBlock(Dictionary<Local, int> consts, Block fallThrough) {
371 var block = new Block();
372 foreach (var kv in consts) {
373 block.Instructions.Add(new Instr(Instruction.CreateLdcI4(kv.Value)));
374 block.Instructions.Add(new Instr(OpCodes.Stloc.ToInstruction(kv.Key)));
375 }
376 fallThrough.Parent.Add(block);
377 return block;
378 }
379
380 Dictionary<Local, int> GetBccLocalConstants(Block block) {
381 var dict = new Dictionary<Local, int>();
382 var instrs = block.Instructions;
383 for (int i = 0; i < instrs.Count; i++) {
384 var instr = instrs[i];
385 if (instr.IsStloc()) {
386 var local = Instr.GetLocalVar(blocks.Locals, instr);
387 if (local == null)
388 continue;
389 var ldci4 = i == 0 ? null : instrs[i - 1];
390 if (ldci4 == null || !ldci4.IsLdcI4())
391 dict.Remove(local);
392 else
393 dict[local] = ldci4.GetLdcI4Value();
394 }
395 else if (instr.IsLdloc()) {
396 var local = Instr.GetLocalVar(blocks.Locals, instr);
397 if (local != null)
398 dict.Remove(local);
399 }
400 else if (instr.OpCode.Code == Code.Ldloca || instr.OpCode.Code == Code.Ldloca_S) {
401 var local = instr.Operand as Local;
402 if (local != null)
403 dict.Remove(local);
404 }
405 }
406 return dict;
407 }
408
409 bool EmulateGetTarget(Block switchBlock, out Block target) {
410 instructionEmulator.Initialize(blocks, allBlocks[0] == switchBlock);
411 try {
412 instructionEmulator.Emulate(switchBlock.Instructions, 0, switchBlock.Instructions.Count - 1);
413 }
414 catch (NullReferenceException) {
415 // Here if eg. invalid metadata token in a call instruction (operand is null)
416 target = null;
417 return false;
418 }
419 target = GetTarget(switchBlock);
420 return true;
421 }
422
423 bool WillHaveKnownTarget(Block switchBlock, Block source) {
424 instructionEmulator.Initialize(blocks, allBlocks[0] == source);
425 try {
426 instructionEmulator.Emulate(source.Instructions);
427 instructionEmulator.Emulate(switchBlock.Instructions, 0, switchBlock.Instructions.Count - 1);
428 }
429 catch (NullReferenceException) {
430 // Here if eg. invalid metadata token in a call instruction (operand is null)
431 return false;
432 }
433 return GetTarget(switchBlock) != null;
434 }
435
436 Block GetTarget(Block switchBlock) {
437 var val1 = instructionEmulator.Pop();
438 if (!val1.IsInt32())
439 return null;
440 return CflowUtils.GetSwitchTarget(switchBlock.Targets, switchBlock.FallThrough, (Int32Value)val1);
441 }
442
443 static Block GetSwitchTarget(IList<Block> targets, Block fallThrough, Value value) {
444 if (!value.IsInt32())
445 return null;
446 return CflowUtils.GetSwitchTarget(targets, fallThrough, (Int32Value)value);
447 }
448
449 static bool FixSwitchBranch(Block switchBlock) {
450 // Code:
451 // blk1:
452 // ldc.i4 XXX
453 // br common
454 // blk2:
455 // ldc.i4 YYY
456 // br common
457 // common:
458 // stloc X
459 // br swblk
460 // swblk:
461 // ldloc X
462 // switch
463 // Inline common into blk1 and blk2.
464
465 bool modified = false;
466
467 foreach (var commonSource in new List<Block>(switchBlock.Sources)) {
468 if (commonSource.Instructions.Count != 1)
469 continue;
470 if (!commonSource.FirstInstr.IsStloc())
471 continue;
472 foreach (var blk in new List<Block>(commonSource.Sources)) {
473 if (blk.CanAppend(commonSource)) {
474 blk.Append(commonSource);
475 modified = true;
476 }
477 }
478 }
479
480 return modified;
481 }
482 }
483}