/projects/sandmark-3.4/src/sandmark/obfuscate/scalarmerger/ScalarMerger.java
Java | 459 lines | 225 code | 81 blank | 153 comment | 39 complexity | 21cbb49fd190ca5a5b94d177225cc209 MD5 | raw file
1package sandmark.obfuscate.scalarmerger;
2
3
4
5/**
6 * Provides a method obfuscator that combines two int variables
7 * into a single long, making access to either more confusing.
8 *
9 * @author Gregg Townsend
10 * (<a href="mailto:gmt@cs.arizona.edu">gmt@cs.arizona.edu</a>)
11 * @version 1.0, August 29, 2003
12 */
13
14public class ScalarMerger extends sandmark.obfuscate.MethodObfuscator {
15
16
17
18 private static boolean DEBUG = false;
19
20
21
22 /**
23 * Applies this obfuscation to a single method.
24 */
25 public void apply(sandmark.program.Method meth) throws Exception {
26
27 if (meth.isInterface() || meth.isAbstract() || meth.isNative()) {
28 return; // nothing to do
29 }
30
31 // Choose two local int variables (ix1 and ix2) for merging.
32
33 int[] vscores = tally(meth); // tally integer accesses
34 skipArgs(meth, vscores); // invalidate entries of incoming args
35 int ix1 = best(vscores); // get best integer candidate
36 int ix2 = best(vscores); // get next-best candidate
37 if (ix1 < 0 || ix2 < 0) { // if couldn't find two candidates
38 return;
39 }
40
41 // Allocate a new long variable and initilize it to 1,
42 // effectively initializing the two ints to 0 and 1.
43 // The actual value doesn't matter because the translation
44 // of the old code will reinitialize each half separately.
45
46 int lx = vscores.length; // index of new long variable
47
48 org.apache.bcel.generic.InstructionList il = meth.getInstructionList();
49 org.apache.bcel.generic.InstructionHandle ih =
50 il.insert(new org.apache.bcel.generic.LCONST(1));
51 il.append(ih, new org.apache.bcel.generic.LSTORE(lx));
52
53 // trace actions, if enabled
54
55 if (DEBUG) {
56 System.err.println("ScalarMerger: (" + ix1 + "," + ix2 + ")->" + lx +
57 " in " + meth.getClassName() + "." + meth.getName());
58 }
59
60 // Replace instructions that operate on the ints
61
62 fixup(il, ix1, ix2, lx);
63
64 // Clean up and exit.
65
66 meth.setInstructionList(il);
67 meth.setMaxLocals();
68 meth.setMaxStack();
69 }
70
71
72
73 /**
74 * Checks all local variable instructions in an instruction list and
75 * tallies variable usage.
76 * Returns an array of scores corresponding to local variables.
77 * A positive score counts the number of integer accesses.
78 * A negative score indicates a variable that is used for a
79 * non-integer value.
80 */
81 private int[] tally(sandmark.program.Method meth) {
82
83 int n = meth.getMaxLocals();
84 int[] vscores = new int[n];
85
86 org.apache.bcel.generic.Instruction[] ilist =
87 meth.getInstructionList().getInstructions();
88
89 for (int i = 0; i < ilist.length; i++) {
90 org.apache.bcel.generic.Instruction ins = ilist[i];
91 if (ins instanceof org.apache.bcel.generic.LocalVariableInstruction) {
92 int v = ((org.apache.bcel.generic.LocalVariableInstruction)(ins))
93 .getIndex();
94 if (ins instanceof org.apache.bcel.generic.ILOAD
95 || ins instanceof org.apache.bcel.generic.IINC
96 || ins instanceof org.apache.bcel.generic.ISTORE) {
97 if (vscores[v] >= 0) {
98 vscores[v]++;
99 }
100 } else {
101 vscores[v] = -1;
102 }
103 }
104 }
105 return vscores;
106 }
107
108
109
110 /**
111 * Invalidates tally entries that correspond to method arguments.
112 */
113 private void skipArgs(sandmark.program.Method meth, int[] vscores) {
114 org.apache.bcel.generic.Type[] types = meth.getArgumentTypes();
115 int n = meth.isStatic() ? 0 : 1;
116 for (int i = 0; i < types.length; i++) {
117 n += types[i].getSize();
118 }
119 for (int i = 0; i < n; i++) {
120 vscores[i] = -1;
121 }
122 }
123
124
125
126 /**
127 * Scans an array of tallies and returns the index of the most active
128 * integer local variable after resetting that variable's score to zero.
129 * Ties are broken in favor of lower numbered variables.
130 * Returns -1 if there is no local variable with a positive score.
131 */
132 private int best(int[] vscores) {
133 int leader = -1;
134 int peak = 0;
135 for (int i = 0; i < vscores.length; i++) {
136 if (vscores[i] > peak) {
137 leader = i;
138 peak = vscores[i];
139 }
140 }
141 if (leader >= 0) {
142 vscores[leader] = 0;
143 }
144 return leader;
145 }
146
147
148
149 /**
150 * Scans the instruction list and replaces instructions
151 * that operate on the chosen integer locals.
152 */
153 private void fixup(
154 org.apache.bcel.generic.InstructionList il, int ix1, int ix2, int lx) {
155
156 org.apache.bcel.generic.InstructionHandle[] hlist =
157 il.getInstructionHandles();
158 for (int i = 0; i < hlist.length; i++) {
159 org.apache.bcel.generic.InstructionHandle ih = hlist[i];
160 org.apache.bcel.generic.Instruction ins = ih.getInstruction();
161 if (ins instanceof org.apache.bcel.generic.LocalVariableInstruction) {
162 int ivx =
163 ((org.apache.bcel.generic.LocalVariableInstruction)ins)
164 .getIndex();
165 boolean lefthalf = (ivx == ix1);
166 boolean righthalf = (ivx == ix2);
167 if (lefthalf || righthalf) {
168 if (ins instanceof org.apache.bcel.generic.ILOAD) {
169 fixLoad(il, ih, lx, lefthalf);
170 } else if (ins instanceof org.apache.bcel.generic.ISTORE) {
171 fixStore(il, ih, lx, lefthalf);
172 } else if (ins instanceof org.apache.bcel.generic.IINC) {
173 fixIncr(il, ih, lx, lefthalf);
174 } else {
175 throw new java.lang.Error("non-int access to local " + ivx);
176 }
177 }
178 }
179 }
180 }
181
182
183
184 /**
185 * Replaces an ILOAD with a load from one half of lx.
186 * The generated sequence is as follows:
187 * <PRE>
188 *
189 * left half right half comments
190 * --------- ---------- ---------------------------------------------
191 * LLOAD lx LLOAD lx load combined value
192 * BIPUSH 32 push shift count
193 * LSHR position to low 32 bits
194 * L2I L2I convert low 32 bits to int
195 *
196 * </PRE>
197 */
198 private void fixLoad(
199 org.apache.bcel.generic.InstructionList il,
200 org.apache.bcel.generic.InstructionHandle ih,
201 int lx, boolean lefthalf) {
202
203 ih.setInstruction(new org.apache.bcel.generic.LLOAD(lx));
204 if (lefthalf) {
205 ih = il.append(ih, new org.apache.bcel.generic.BIPUSH((byte) 32));
206 ih = il.append(ih, new org.apache.bcel.generic.LSHR());
207 }
208 ih = il.append(ih, new org.apache.bcel.generic.L2I());
209 }
210
211
212
213 /**
214 * Replace an ISTORE with a store into one half of lx.
215 * The generated sequence is as follows:
216 * <PRE>
217 *
218 * left half right half comments
219 * --------- ---------- ---------------------------------------------
220 * I2L I2L convert the new int to a long
221 * BIPUSH 32 push shift count
222 * LSHL shift into position
223 * LLOAD lx LLOAD lx load the old combined value
224 * DUP2_X2 DUP2_X2 hide a second copy below the new value
225 * LXOR LXOR compute bitwise difference to new value
226 * LCONST_1 LCONST_1 load long constant 1
227 * LNEG LNEG convert to all-ones mask
228 * BIPUSH 32 BIPUSH 32 push shift count
229 * LSHL !=!= LUSHR shift to make half ones, half zeroes
230 * LAND LAND select the change bits for the correct half
231 * LXOR LXOR change the bits in the combined value
232 * LSTORE lx LSTORE lx store new combined value
233 *
234 * </PRE>
235 */
236 private void fixStore(
237 org.apache.bcel.generic.InstructionList il,
238 org.apache.bcel.generic.InstructionHandle ih,
239 int lx, boolean lefthalf) {
240
241 ih.setInstruction(new org.apache.bcel.generic.I2L());
242 if (lefthalf) {
243 ih = il.append(ih, new org.apache.bcel.generic.BIPUSH((byte)32));
244 ih = il.append(ih, new org.apache.bcel.generic.LSHL());
245 }
246 ih = il.append(ih, new org.apache.bcel.generic.LLOAD(lx));
247 ih = il.append(ih, new org.apache.bcel.generic.DUP2_X2());
248 ih = il.append(ih, new org.apache.bcel.generic.LXOR());
249 ih = il.append(ih, new org.apache.bcel.generic.LCONST(1));
250 ih = il.append(ih, new org.apache.bcel.generic.LNEG());
251 ih = il.append(ih, new org.apache.bcel.generic.BIPUSH((byte)32));
252 if (lefthalf) {
253 ih = il.append(ih, new org.apache.bcel.generic.LSHL());
254 } else {
255 ih = il.append(ih, new org.apache.bcel.generic.LUSHR());
256 }
257 ih = il.append(ih, new org.apache.bcel.generic.LAND());
258 ih = il.append(ih, new org.apache.bcel.generic.LXOR());
259 ih = il.append(ih, new org.apache.bcel.generic.LSTORE(lx));
260 }
261 /*
262 * This fixStore code was also tested, but it is longer and less confusing:
263 * I2L I2L convert store value to long
264 * BIPUSH 32 BIPUSH 32 push shift count
265 * LSHL LSHL shift int to top of long
266 * BIPUSH 32 push shift count again
267 * LUSHR shift back down (clears I2D sign bit extension)
268 * LCONST_1 LCONST_1 load long constant 1
269 * LNEG LNEG convert to all-ones mask
270 * BIPUSH 32 BIPUSH 32 push shift count
271 * LUSHR !=!= LSHL shift to make half ones, half zeroes
272 * LLOAD lx LLOAD lx load old combined value
273 * LAND LAND preserve old value of other variable
274 * LADD LADD combine with new value for this variable
275 * LSTORE lx LSTORE lx store combined long value
276 */
277
278
279
280 /**
281 * Replaces an IINC with an increment of half of lx.
282 * The generated sequence is as follows:
283 * <PRE>
284 *
285 * left half right half comments
286 * --------- ---------- ---------------------------------------------
287 * LLOAD lx LLOAD lx load combined value
288 * DUP2 duplicate for later use
289 * DUP2 duplicate for later use
290 * BIPUSH n BIPUSH n push increment value
291 * I2L I2L convert to long
292 * BIPUSH 32 push shift count
293 * LSHL position increment value
294 * LADD LADD add increment to long value
295 * LXOR compute bitwise difference vs. old value
296 * LCONST_1 load long constant 1
297 * LNEG convert to all-ones mask
298 * BIPUSH 32 push shift count
299 * LUSHR shift to fill top half with zeroes
300 * LAND isolate changes to lower half
301 * LXOR apply changes to original value
302 * LSTORE lx LSTORE lx store combined long value
303 *
304 * </PRE>
305 */
306 private void fixIncr(
307 org.apache.bcel.generic.InstructionList il,
308 org.apache.bcel.generic.InstructionHandle ih,
309 int lx, boolean lefthalf) {
310
311 int k =
312 ((org.apache.bcel.generic.IINC)(ih.getInstruction())).getIncrement();
313 ih.setInstruction(new org.apache.bcel.generic.LLOAD(lx));
314 if (!lefthalf) {
315 ih = il.append(ih, new org.apache.bcel.generic.DUP2());
316 ih = il.append(ih, new org.apache.bcel.generic.DUP2());
317 }
318 ih = il.append(ih, new org.apache.bcel.generic.BIPUSH((byte)k));
319 ih = il.append(ih, new org.apache.bcel.generic.I2L());
320 if (lefthalf) {
321 ih = il.append(ih, new org.apache.bcel.generic.BIPUSH((byte)32));
322 ih = il.append(ih, new org.apache.bcel.generic.LSHL());
323 }
324 ih = il.append(ih, new org.apache.bcel.generic.LADD());
325 if (!lefthalf) {
326 ih = il.append(ih, new org.apache.bcel.generic.LXOR());
327 ih = il.append(ih, new org.apache.bcel.generic.LCONST(1));
328 ih = il.append(ih, new org.apache.bcel.generic.LNEG());
329 ih = il.append(ih, new org.apache.bcel.generic.BIPUSH((byte)32));
330 ih = il.append(ih, new org.apache.bcel.generic.LUSHR());
331 ih = il.append(ih, new org.apache.bcel.generic.LAND());
332 ih = il.append(ih, new org.apache.bcel.generic.LXOR());
333 }
334 ih = il.append(ih, new org.apache.bcel.generic.LSTORE(lx));
335 }
336
337
338
339 /**
340 * Returns "Scalar Merger", the short name of this algorithm.
341 */
342 public java.lang.String getShortName() {
343 return "Merge Local Integers";
344 }
345
346
347
348 /**
349 * Returns "Scalar Merger", the long name of this algorithm.
350 */
351 public java.lang.String getLongName() {
352 return "Scalar Merger";
353 }
354
355
356
357 /**
358 * Returns an HTML description of this obfuscator's function.
359 */
360 public java.lang.String getAlgHTML() {
361 return
362 "<HTML><BODY>" +
363 "Scalar Merger combines two int variables into a single long" +
364 "variable, making access to either more confusing.\n" +
365 "<TABLE>" +
366 "<TR><TD>" +
367 "Author: <a href =\"mailto:gmt@cs.arizona.edu\">Gregg Townsend</a> " +
368 "</TR></TD>" +
369 "</TABLE>" +
370 "</BODY></HTML>";
371
372 }
373
374
375
376 /**
377 * Returns the URL within the source tree
378 * of an HTML file describing this obfuscator.
379 */
380 public java.lang.String getAlgURL() {
381 return "sandmark/obfuscate/scalarmerger/doc/help.html";
382 }
383
384
385
386 /**
387 * Returns the name of the author of this obfuscator.
388 */
389 public java.lang.String getAuthor() {
390 return "Gregg Townsend";
391 }
392
393
394
395 /**
396 * Returns the e-mail address of the author of this obfuscator.
397 */
398 public java.lang.String getAuthorEmail() {
399 return "gmt@cs.arizona.edu";
400 }
401
402
403
404 /**
405 * Returns a brief description of this obfuscator.
406 */
407 public java.lang.String getDescription() {
408 return
409 "Scalar Merger combines two int variables into a single long " +
410 "variable, making access to either more confusing.";
411 }
412
413
414
415 /**
416 * Returns a list of modification properties characterizing
417 * this obfuscator.
418 */
419 public sandmark.config.ModificationProperty[] getMutations() {
420 return new sandmark.config.ModificationProperty[] {
421 sandmark.config.ModificationProperty.I_ADD_LOCAL_VARIABLES,
422 sandmark.config.ModificationProperty.I_CHANGE_LOCAL_VARIABLES,
423 sandmark.config.ModificationProperty.I_CHANGE_METHOD_BODIES,
424 sandmark.config.ModificationProperty.I_ADD_METHOD_CODE,
425 sandmark.config.ModificationProperty.I_MODIFY_METHOD_CODE,
426 sandmark.config.ModificationProperty.I_REMOVE_METHOD_CODE,
427 sandmark.config.ModificationProperty.PERFORMANCE_DEGRADE_MED,
428 };
429 }
430
431
432
433 /**
434 * Applies the obfuscation to every method in the jar file
435 * given as the command argument.
436 *
437 * <P> Usage: java sandmark.obfuscate.scalarmerger.ScalarMerger file.jar
438 *
439 * <P> Writes: CHANGED.jar
440 */
441 public static void main(String[] args) throws java.lang.Exception {
442 sandmark.program.Application app =
443 new sandmark.program.Application(args[0]);
444 sandmark.obfuscate.scalarmerger.ScalarMerger obfuscator =
445 new sandmark.obfuscate.scalarmerger.ScalarMerger();
446 java.util.Iterator itr = app.classes();
447 while (itr.hasNext()) {
448 sandmark.program.Class cls = (sandmark.program.Class) itr.next();
449 sandmark.program.Method[] methods = cls.getMethods();
450 for (int i = 0; i < methods.length; i++) {
451 obfuscator.apply(methods[i]);
452 }
453 }
454 app.save("CHANGED.jar");
455 }
456
457
458
459} // class ScalarMerger