1 26 package com.yworks.yguard.obf; 27 28 import java.io.*; 29 import java.util.*; 30 import java.util.zip.*; 31 import java.util.jar.*; 32 import java.security.*; 33 import java.lang.reflect.Modifier ; 34 35 import com.yworks.yguard.*; 36 import com.yworks.yguard.obf.classfile.*; 37 import java.util.AbstractMap ; 38 import java.util.jar.Attributes ; 39 import java.util.jar.Manifest ; 40 41 46 public class GuardDB implements ClassConstants 47 { 48 private static final String STREAM_NAME_MANIFEST = "META-INF/MANIFEST.MF"; 50 private static final String MANIFEST_NAME_TAG = "Name"; 51 private static final String MANIFEST_DIGESTALG_TAG = "Digest-Algorithms"; 52 private static final String CLASS_EXT = ".class"; 53 private static final String SIGNATURE_PREFIX = "META-INF/"; 54 private static final String SIGNATURE_EXT = ".SF"; 55 private static final String LOG_MEMORY_USED = " Memory in use after class data structure built: "; 56 private static final String LOG_MEMORY_TOTAL = " Total memory available : "; 57 private static final String LOG_MEMORY_BYTES = " bytes"; 58 private static final String WARNING_SCRIPT_ENTRY_ABSENT = "<!-- WARNING - identifier from script file not found in JAR: "; 59 private static final String ERROR_CORRUPT_CLASS = "<!-- ERROR - corrupt class file: "; 60 61 62 private JarFile[] inJar; private Manifest [] oldManifest; private Manifest [] newManifest; private ClassTree classTree; private boolean hasMap = false; 68 69 70 private transient java.util.ArrayList listenerList; 71 72 73 private boolean replaceClassNameStrings; 74 75 76 private boolean pedantic; 77 78 private ResourceHandler resourceHandler; 79 private String [] digestStrings; 80 81 83 85 87 public GuardDB(File[] inFile) throws java.io.IOException 88 { 89 inJar = new JarFile[inFile.length]; 90 for(int i = 0; i < inFile.length; i++) 91 inJar[i] = new JarFile(inFile[i]); 92 } 93 94 95 protected void finalize() throws java.io.IOException 96 { 97 close(); 98 } 99 100 public void setResourceHandler(ResourceHandler handler) 101 { 102 resourceHandler = handler; 103 } 104 105 public String getOutName(String inName) 106 { 107 return classTree.getOutName(inName); 108 } 109 110 114 public void retain(Collection rgsEntries, PrintWriter log)throws java.io.IOException 115 { 116 117 if (classTree == null || hasMap) 119 { 120 hasMap = false; 121 buildClassTree(log); 122 } 123 124 125 for (Iterator it = rgsEntries.iterator(); it.hasNext();) 127 { 128 YGuardRule entry = (YGuardRule)it.next(); 129 try 130 { 131 switch (entry.type) 132 { 133 case YGuardRule.TYPE_LINE_NUMBER_MAPPER: 134 classTree.retainLineNumberTable(entry.name, entry.lineNumberTableMapper); 135 break; 136 case YGuardRule.TYPE_SOURCE_ATTRIBUTE_MAP: 137 classTree.retainSourceFileAttributeMap(entry.name, entry.obfName); 138 break; 139 case YGuardRule.TYPE_ATTR: 140 classTree.retainAttribute(entry.name); 141 break; 142 case YGuardRule.TYPE_ATTR2: 143 classTree.retainAttributeForClass(entry.descriptor, entry.name); 144 break; 145 case YGuardRule.TYPE_CLASS: 146 classTree.retainClass(entry.name, entry.retainClasses, entry.retainMethods, entry.retainFields, true); 147 break; 148 case YGuardRule.TYPE_METHOD: 149 classTree.retainMethod(entry.name, entry.descriptor); 150 break; 151 case YGuardRule.TYPE_PACKAGE: 152 classTree.retainPackage(entry.name); 153 break; 154 case YGuardRule.TYPE_FIELD: 155 classTree.retainField(entry.name); 156 break; 157 case YGuardRule.TYPE_PACKAGE_MAP: 158 classTree.retainPackageMap(entry.name, entry.obfName); 159 break; 160 case YGuardRule.TYPE_CLASS_MAP: 161 classTree.retainClassMap(entry.name, entry.obfName); 162 break; 163 case YGuardRule.TYPE_METHOD_MAP: 164 classTree.retainMethodMap(entry.name, entry.descriptor, 165 entry.obfName); 166 break; 167 case YGuardRule.TYPE_FIELD_MAP: 168 classTree.retainFieldMap(entry.name, entry.obfName); 169 break; 170 default: 171 throw new ParseException("Illegal type: " + entry.type); 172 } 173 } 174 catch (RuntimeException e) 175 { 176 log.println(WARNING_SCRIPT_ENTRY_ABSENT + entry.name + " -->"); 179 } 180 } 181 } 182 183 184 public void remapTo(File[] out, 185 Filter fileFilter, 186 PrintWriter log, 187 boolean conserveManifest 188 ) throws java.io.IOException , ClassNotFoundException 189 { 190 if (classTree == null) 192 { 193 buildClassTree(log); 194 } 195 196 if (!hasMap) 198 { 199 createMap(log); 200 } 201 202 oldManifest = new Manifest [out.length]; 203 newManifest = new Manifest [out.length]; 204 parseManifest(); 205 206 StringBuffer replaceNameLog = new StringBuffer (); 207 StringBuffer replaceContentsLog = new StringBuffer (); 208 209 JarOutputStream outJar = null; 210 DataInputStream inStream = null; 212 OutputStream os = null; 213 for(int i = 0; i < inJar.length; i++) 214 { 215 os = null; 216 outJar = null; 217 List jarEntries = new ArrayList(); 220 try 221 { 222 Enumeration entries = inJar[i].entries(); 226 fireObfuscatingJar(inJar[i].getName(), out[i].getName()); 227 ByteArrayOutputStream baos = new ByteArrayOutputStream(2048); 228 while (entries.hasMoreElements()) 229 { 230 JarEntry inEntry = (JarEntry)entries.nextElement(); 232 233 if (inEntry.isDirectory()) 235 { 236 continue; 237 } 238 239 inStream = new DataInputStream( 240 new BufferedInputStream( 241 inJar[i].getInputStream(inEntry))); 242 String inName = inEntry.getName(); 243 if (inName.endsWith(CLASS_EXT)) 244 { 245 if (fileFilter == null || fileFilter.accepts(inName)){ 246 ClassFile cf = ClassFile.create(inStream); 248 fireObfuscatingClass(Conversion.toJavaClass(cf.getName())); 249 cf.remap(classTree, replaceClassNameStrings, log); 250 JarEntry outEntry = new JarEntry(cf.getName() + CLASS_EXT); 251 252 DataOutputStream classOutputStream; 253 MessageDigest[] digests; 254 if (digestStrings == null){ 255 digestStrings = new String []{"SHA-1", "MD5"}; 256 } 257 digests = new MessageDigest[digestStrings.length]; 258 OutputStream stream = baos; 259 261 for (int j = 0; j < digestStrings.length; j++) { 262 String digestString = digestStrings[j]; 263 MessageDigest digest = MessageDigest.getInstance(digestString); 264 digests[j] = digest; 265 stream = new DigestOutputStream(stream, digest); 266 } 267 classOutputStream = new DataOutputStream(stream); 268 269 cf.write(classOutputStream); 271 classOutputStream.flush(); 272 jarEntries.add(new Object []{outEntry, baos.toByteArray()}); 273 baos.reset(); 274 updateManifest(i, inName, cf.getName() + CLASS_EXT, digests); 276 } 277 } 278 else if (STREAM_NAME_MANIFEST.equals(inName.toUpperCase()) || 279 (inName.length() > (SIGNATURE_PREFIX.length() + 1 + SIGNATURE_EXT.length()) && 280 inName.indexOf(SIGNATURE_PREFIX) != -1 && 281 inName.substring(inName.length() - SIGNATURE_EXT.length(), inName.length()).equals(SIGNATURE_EXT))) 282 { 283 continue; 285 } 286 else 287 { 288 long size = inEntry.getSize(); 290 if (size != -1) 291 { 292 293 MessageDigest shaDigest = MessageDigest.getInstance("SHA"); 295 MessageDigest md5Digest = MessageDigest.getInstance("MD5"); 296 DataOutputStream dataOutputStream = 297 new DataOutputStream(new DigestOutputStream(new DigestOutputStream(baos, 298 shaDigest), 299 md5Digest)); 300 301 String outName; 302 303 StringBuffer outNameBuffer = new StringBuffer (80); 304 305 if(resourceHandler != null && resourceHandler.filterName(inName, outNameBuffer)) 306 { 307 outName = outNameBuffer.toString(); 308 if(!outName.equals(inName)) 309 { 310 replaceNameLog.append(" <resource name=\""); 311 replaceNameLog.append(ClassTree.toUtf8XmlString(inName)); 312 replaceNameLog.append("\" map=\""); 313 replaceNameLog.append(ClassTree.toUtf8XmlString(outName)); 314 replaceNameLog.append("\"/>\n"); 315 } 316 } 317 else 318 { 319 outName = classTree.getOutName(inName); 320 } 321 322 if(resourceHandler == null || !resourceHandler.filterContent(inStream, dataOutputStream, inName)) 323 { 324 byte[] bytes = new byte[(int)size]; 325 inStream.readFully(bytes); 326 327 dataOutputStream.write(bytes, 0, bytes.length); 330 } 331 else 332 { 333 replaceContentsLog.append(" <resource name=\""); 334 replaceContentsLog.append(ClassTree.toUtf8XmlString(inName)); 335 replaceContentsLog.append("\"/>\n"); 336 } 337 338 dataOutputStream.flush(); 339 JarEntry outEntry = new JarEntry(outName); 340 341 342 jarEntries.add(new Object []{outEntry, baos.toByteArray()}); 343 baos.reset(); 344 MessageDigest[] digests = 346 {shaDigest, md5Digest}; 347 updateManifest(i , inName, outName, digests); 348 } 349 } 350 } 351 352 os = new FileOutputStream(out[i]); 353 if (conserveManifest){ 354 outJar = new JarOutputStream(new BufferedOutputStream(os),oldManifest[i]); 355 } else { 356 outJar = new JarOutputStream(new BufferedOutputStream(os),newManifest[i]); 357 } 358 outJar.setComment( Version.getJarComment()); 359 360 Collections.sort(jarEntries, new Comparator(){ 362 public int compare(Object a, Object b){ 363 Object [] array1 = (Object []) a; 364 JarEntry entry1 = (JarEntry) array1[0]; 365 Object [] array2 = (Object []) b; 366 JarEntry entry2 = (JarEntry) array2[0]; 367 return entry1.getName().compareTo(entry2.getName()); 368 } 369 }); 370 Set directoriesWritten = new HashSet(); 372 for (int j = 0; j < jarEntries.size(); j++){ 373 Object [] array = (Object []) jarEntries.get(j); 374 JarEntry entry = (JarEntry) array[0]; 375 String name = entry.getName(); 376 if (!entry.isDirectory()){ 378 int index = 0; 379 while ((index = name.indexOf("/", index + 1))>= 0){ 380 String directory = name.substring(0, index+1); 381 if (!directoriesWritten.contains(directory)){ 382 directoriesWritten.add(directory); 383 JarEntry directoryEntry = new JarEntry(directory); 384 outJar.putNextEntry(directoryEntry); 385 outJar.closeEntry(); 386 } 387 } 388 } 389 byte[] bytes = (byte[]) array[1]; 391 outJar.putNextEntry(entry); 392 outJar.write(bytes); 393 outJar.closeEntry(); 394 } 395 396 } 397 catch (Exception e) 398 { 399 log.println(); 401 log.println("<!-- An exception has occured."); 402 if (e instanceof java.util.zip.ZipException ){ 403 log.println("This is most likely due to a duplicate .class file in your jar!"); 404 log.println("Please check that there are no out-of-date or backup duplicate .class files in your jar!"); 405 } 406 log.println(e.toString()); 407 e.printStackTrace(log); 408 log.println("-->"); 409 throw new IOException("An error ('"+e.getMessage()+"') occured during the remapping! See the log!)"); 410 } 411 finally 412 { 413 inJar[i].close(); 414 if (inStream != null) 415 { 416 inStream.close(); 417 } 418 if (outJar != null) 419 { 420 outJar.close(); 421 } 422 if (os != null){ 423 os.close(); 424 } 425 } 426 } 427 classTree.dump(log); 429 if(replaceContentsLog.length() > 0 || replaceNameLog.length() > 0) 430 { 431 log.println("<!--"); 432 if(replaceNameLog.length() > 0) 433 { 434 log.println("\n<adjust replaceName=\"true\">"); 435 log.print(replaceNameLog); 436 log.println("</adjust>"); 437 } 438 if(replaceContentsLog.length() > 0) 439 { 440 log.println("\n<adjust replaceContents=\"true\">"); 441 log.print(replaceContentsLog); 442 log.println("</adjust>"); 443 } 444 log.println("-->"); 445 } 446 447 } 448 449 450 public void close() throws java.io.IOException 451 { 452 for(int i = 0; i < inJar.length; i++) 453 { 454 if (inJar[i] != null) 455 { 456 inJar[i].close(); 457 inJar[i] = null; 458 } 459 } 460 } 461 462 private void parseManifest()throws java.io.IOException 464 { 465 for(int i = 0; i < oldManifest.length; i++) 466 { 467 oldManifest[i] = inJar[i].getManifest(); 470 471 if (oldManifest[i] == null){ 472 oldManifest[i] = new Manifest (); 473 } 474 475 newManifest[i] = new Manifest (); 477 478 for (Iterator it = oldManifest[i].getMainAttributes().entrySet().iterator(); it.hasNext();) { 480 Map.Entry entry = (Map.Entry ) it.next(); 481 Attributes.Name name = (Attributes.Name ) entry.getKey(); 482 String value = (String ) entry.getValue(); 483 if (resourceHandler != null) { 484 name = new Attributes.Name (resourceHandler.filterString(name.toString(), "META-INF/MANIFEST.MF")); 485 value = resourceHandler.filterString(value, "META-INF/MANIFEST.MF"); 486 } 487 newManifest[i].getMainAttributes().putValue(name.toString(), value); 488 } 489 490 newManifest[i].getMainAttributes().putValue("Created-by", "yGuard Bytecode Obfuscator " + Version.getVersion()); 491 492 for (Iterator it = oldManifest[i].getEntries().entrySet().iterator(); 494 it.hasNext();){ 495 Map.Entry entry = (Map.Entry ) it.next(); 496 String name = (String ) entry.getKey(); 497 if (name.endsWith("/")){ 498 newManifest[i].getEntries().put(name, (Attributes ) entry.getValue()); 499 } 500 } 501 } 502 } 503 504 private void updateManifest(int manifestIndex, String inName, String outName, MessageDigest[] digests) 506 { 507 509 Manifest nm = newManifest[manifestIndex]; 510 Manifest om = oldManifest[manifestIndex]; 511 512 Attributes oldAtts = om.getAttributes(inName); 513 Attributes newAtts = new Attributes (); 514 516 if (oldAtts != null){ 518 for(Iterator it = oldAtts.entrySet().iterator(); it.hasNext();){ 519 Map.Entry entry = (Map.Entry ) it.next(); 520 Object key = entry.getKey(); 521 String name = key.toString(); 522 if (!name.equalsIgnoreCase(MANIFEST_NAME_TAG) && 523 name.indexOf("Digest") == -1){ 524 newAtts.remove(name); 525 newAtts.putValue(name, (String )entry.getValue()); 526 } 527 } 528 } 529 530 if (digests != null && digests.length > 0) 532 { 533 StringBuffer sb = new StringBuffer (); 535 for (int i = 0; i < digests.length; i++) 536 { 537 sb.append(digests[i].getAlgorithm()); 538 if (i < digests.length -1){ 539 sb.append(", "); 540 } 541 } 542 newAtts.remove(MANIFEST_DIGESTALG_TAG); 543 newAtts.putValue(MANIFEST_DIGESTALG_TAG, sb.toString()); 544 545 for (int i = 0; i < digests.length; i++) 547 { 548 newAtts.remove(digests[i].getAlgorithm() + "-Digest"); 549 newAtts.putValue(digests[i].getAlgorithm() + "-Digest", Tools.toBase64(digests[i].digest())); 550 } 551 } 552 553 if (!newAtts.isEmpty()) { 554 nm.getEntries().put(outName, newAtts); 556 } 557 } 558 559 private void buildClassTree(PrintWriter log)throws java.io.IOException 561 { 562 classTree = new ClassTree(); 564 classTree.setPedantic(isPedantic()); 565 classTree.setReplaceClassNameStrings(replaceClassNameStrings); 566 ClassFile.resetDangerHeader(); 567 568 Map parsedClasses = new HashMap(); 569 for(int i = 0; i < inJar.length; i++) 570 { 571 Enumeration entries = inJar[i].entries(); 572 fireParsingJar(inJar[i].getName()); 573 while (entries.hasMoreElements()) 574 { 575 ZipEntry inEntry = (ZipEntry)entries.nextElement(); 577 String name = inEntry.getName(); 578 if (name.endsWith(CLASS_EXT)) 579 { 580 fireParsingClass(Conversion.toJavaClass(name)); 581 DataInputStream inStream = new DataInputStream( 583 new BufferedInputStream( 584 inJar[i].getInputStream(inEntry))); 585 ClassFile cf = null; 586 try 587 { 588 cf = ClassFile.create(inStream); 589 } 590 catch (Exception e) 591 { 592 log.println(ERROR_CORRUPT_CLASS + createJarName(inJar[i], name) + " -->"); 593 e.printStackTrace(log); 594 throw new ParseException( e ); 595 } 596 finally 597 { 598 inStream.close(); 599 } 600 601 if (cf != null){ 602 Object [] old = (Object []) parsedClasses.get(cf.getName()); 603 if (old != null){ 604 int jarIndex = ((Integer )old[0]).intValue(); 605 String warning = "yGuard detected a duplicate class definition " + 606 "for \n " + Conversion.toJavaClass(cf.getName()) + 607 "\n [" + createJarName(inJar[jarIndex], old[1].toString()) + "] in \n [" + 608 createJarName(inJar[i], name) + "]"; 609 log.write("<!-- \n" + warning + "\n-->\n"); 610 if (jarIndex == i){ 611 throw new IOException(warning + "\nPlease remove inappropriate duplicates first!"); 612 } else { 613 if (pedantic){ 614 throw new IOException(warning + "\nMake sure these files are of the same version!"); 615 } 616 } 617 } else { 618 parsedClasses.put(cf.getName(), new Object []{new Integer (i), name}); 619 } 620 621 cf.logDangerousMethods(log, replaceClassNameStrings); 623 classTree.addClassFile(cf); 624 } 625 626 } 627 } 628 } 629 630 final ClassTree ct = classTree; 632 ct.walkTree(new TreeAction() 633 { 634 public void classAction(Cl cl) 635 { 636 if (cl.isInnerClass()) 637 { 638 Cl parent = (Cl) cl.getParent(); 639 cl.access = parent.getInnerClassModifier(cl.getInName()); 640 } 641 } 642 }); 643 } 644 645 private static String createJarName(JarFile jar, String name){ 646 return "jar:"+jar.getName() + "|" + name; 647 } 648 649 private void createMap(PrintWriter log) throws ClassNotFoundException 651 { 652 classTree.generateNames(); 655 656 classTree.resolveClasses(); 659 660 hasMap = true; 662 663 Runtime rt = Runtime.getRuntime(); 665 rt.gc(); 666 log.println("<!--"); 667 log.println(LOG_MEMORY_USED + Long.toString(rt.totalMemory() - rt.freeMemory()) + LOG_MEMORY_BYTES); 668 log.println(LOG_MEMORY_TOTAL + Long.toString(rt.totalMemory()) + LOG_MEMORY_BYTES); 669 log.println("-->"); 670 671 } 672 673 protected void fireParsingJar(String jar){ 674 if (listenerList == null) return; 675 for (int i = 0, j = listenerList.size(); i < j; i++){ 676 ((ObfuscationListener)listenerList.get(i)).parsingJar(jar); 677 } 678 } 679 protected void fireParsingClass(String className){ 680 if (listenerList == null) return; 681 for (int i = 0, j = listenerList.size(); i < j; i++){ 682 ((ObfuscationListener)listenerList.get(i)).parsingClass(className); 683 } 684 } 685 protected void fireObfuscatingJar(String inJar, String outJar){ 686 if (listenerList == null) return; 687 for (int i = 0, j = listenerList.size(); i < j; i++){ 688 ((ObfuscationListener)listenerList.get(i)).obfuscatingJar(inJar, outJar); 689 } 690 } 691 protected void fireObfuscatingClass(String className){ 692 if (listenerList == null) return; 693 for (int i = 0, j = listenerList.size(); i < j; i++){ 694 ((ObfuscationListener)listenerList.get(i)).obfuscatingClass(className); 695 } 696 } 697 698 701 public synchronized void addListener(com.yworks.yguard.ObfuscationListener listener) 702 { 703 if (listenerList == null ) 704 { 705 listenerList = new java.util.ArrayList (); 706 } 707 listenerList.add(listener); 708 } 709 710 713 public synchronized void removeListener(com.yworks.yguard.ObfuscationListener listener) 714 { 715 if (listenerList != null ) 716 { 717 listenerList.remove(listener); 718 } 719 } 720 721 725 public boolean isReplaceClassNameStrings() 726 { 727 return this.replaceClassNameStrings; 728 } 729 730 734 public void setReplaceClassNameStrings(boolean replaceClassNameStrings) 735 { 736 this.replaceClassNameStrings = replaceClassNameStrings; 737 } 738 739 740 744 public boolean isPedantic() 745 { 746 return this.pedantic; 747 } 748 749 753 public void setPedantic(boolean pedantic) 754 { 755 this.pedantic = pedantic; 756 Cl.setPedantic(pedantic); 757 } 758 759 760 765 public String translateJavaFile(String javaClass) 766 { 767 Cl cl = classTree.findClassForName(javaClass.replace('/','.')); 768 if(cl != null) 769 { 770 return cl.getFullOutName(); 771 } 772 else 773 { 774 return javaClass; 775 } 776 } 777 778 779 public String translateJavaClass(String javaClass) 780 { 781 Cl cl = classTree.findClassForName(javaClass); 782 if(cl != null) 783 { 784 return cl.getFullOutName().replace('/', '.'); 785 } 786 else 787 { 788 return javaClass; 789 } 790 } 791 792 public void setDigests(String [] digestStrings) { 793 this.digestStrings = digestStrings; 794 } 795 } 796 | Popular Tags |