Protecting Java Bytecode from Hackers with the Invoke
Protecting Java Bytecode from Hackers with the Invoke. Dynamic Instruction Ivan Kinash, Mikhail Dudarev Licel Corporation Java. One 2015 1
About us Licel Corporation Web: https: //licelus. com 2
Java Bytecode • High level JVM instruction set • Stack oriented • Instruction format Example: opcode invokevirtual (0 xb 6) operand 1 index 1 operand 2 index 2 … 3
Native Code vs Java Bytecode C-code: main(){ printf("Hello Java. One 2015n"); } Java-code: public class Test { public static void main(String[] args) { System. out. println("Hello Java. One 2015"); } } 4
Native Code Dump vs Byte. Code Dump _main pushq %rbp movq %rsp, %rbp subq $0 x 10, %rsp ## "Hello Java. One'2015n" leaq 0 x 37(%rip), %rdi movb $0 x 0, %al ## symbol stub for: _printf callq 0 x 100000 f 66 movl $0 x 0, %ecx movl %eax, -0 x 4(%rbp) movl %ecx, %eax addq $0 x 10, %rsp popq %rbp retq public static void main(java. lang. String[]); descriptor: ([Ljava/lang/String; )V Flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 // Field java/lang/System. out: Ljava/io/Print. Stream; 0: getstatic #2 // String Hello Java. One 2015 3: ldc #3 // Method // java/io/Print. Stream. println: (Ljava/lang/String; )V 5: invokevirtual #4 8: return 5
Java Bytecode Dump (structured) 0: getstatic #2 3: ldc #3 5: invokevirtual #4 8: return opcode // Field java/lang/System. out: Ljava/io/Print. Stream; // String Hello Java. One 2015 // Method java/io/Print. Stream. println: (Ljava/lang/String; )V operands constant pool values 0 getstatic Field#2 java/lang/System. out: Ljava/io/Print. Stream; 3 ldc String#3 Hello Java. One 2015 5 invokevirtual Method#4 java/io/Print. Stream. println: (Ljava/lang/String; )V 8 return 6
Java Bytecode Decompiler Original code with lamdas: public static void main(String[] args) { Runnable r = () -> System. out. println("Hello Java. One 2015"); r. run(); } Bytecode dump 0: invokedynamic #2, 0 // Invoke. Dynamic #0: run: ()Ljava/lang/Runnable; 5: astore_1 6: aload_1 7: invokeinterface #3, 1 // Interface. Method java/lang/Runnable. run: ()V 12: return 7
Java Bytecode Decompiler Launching Procyon: $ java –jar procyon. jar Hello. Java. One. With. Lamdas. class Procyon output: public static void main(String[] args) { Runnable r = () -> System. out. println("Hello Java. One 2015"); r. run(); } 8
Java Bytecode Attacks • Reverse engineering • Bypassing critical routines • Easy decompilation and modification 9
Popular Java Decompilers Decompiler Rating Java 8 support Anti-obfuscation techniques GUI Procyon + Luyten 8/10 Yes Yes CFR 7/10 Yes - JD 6/10 - - Yes Fernflower 5/10 - Yes (Minecraft support) - Krakatau 5/10 - Yes - Candle 4/10 - - 10
Java Bytecode Protection • Codeflow obfuscation • Name obfuscation • Call hiding 11
Obfuscator/Decompiler Demo TECHNIQUES SHOWN HERE ARE FOR EDUCATIONAL PURPOSES ONLY. WE DO NOT ASSUME ANY RESPONSIBILITY FOR ANY UNLAWFUL ACTIONS AND/OR DAMAGES RESULTING FROM THE USE OF THESE TECHNIQUES. 12
Constant Pool & Bytecode Constant pool: #1 = Methodref #2 = Fieldref #3 = String #4 = Methodref #6. #15 #16. #17 #18 #19. #20 // java/lang/Object. "<init>": ()V // java/lang/System. out: Ljava/io/Print. Stream; // Hello Java. One 2015 // java/io/Print. Stream. println: (Ljava/lang/String; )V public static void main(java. lang. String[]); Code: 0: getstatic #2 // Field java/lang/System. out: Ljava/io/Print. Stream; 3: ldc #3 // String Hello Java. One 2015 5: invokevirtual #4 // Method java/io/Print. Stream. println: (Ljava/lang/String; )V 8: return 13
Bytecode Execution stack Current instruction: 0: getstatic #2 0 1 Console: opcode java/lang/System. out operands constant pool values 0 getstatic Field#2 java/lang/System. out: Ljava/io/Print. Stream; 3 ldc String#3 Hello Java. One 2015 5 invokevirtual Method#4 java/io/Print. Stream. println: (Ljava/lang/String; )V 8 return 14
Bytecode Execution stack Current instruction: 3: ldc #3 Console: opcode 0 “Hello Java. One 2015” 1 java/lang/System. out operands constant pool values 0 getstatic Field#2 java/lang/System. out: Ljava/io/Print. Stream; 3 ldc String#3 Hello Java. One 2015 5 invokevirtual Method#4 java/io/Print. Stream. println: (Ljava/lang/String; )V 8 return 15
Bytecode Execution stack Current instruction: 5: invokevirtual #4 Console: Hello Java. One 2015 opcode 0 “Hello Java. One 2015” 1 java/lang/System. out operands constant pool values 0 getstatic Field#2 java/lang/System. out: Ljava/io/Print. Stream; 3 ldc String#3 Hello Java. One 2015 5 invokevirtual Method#4 java/io/Print. Stream. println: (Ljava/lang/String; )V 8 return 16
Notes #1 • Constant pool contains all the symbolic information • invoke* instruction is used to call a method • JVM requires the stack to be consistent before method execution 17
Call Hiding via Reflection // Hide Fieldref #2 Object out = Class. for. Name("java. lang. System"). get. Field("out"). get(null); // Hide Methodref #4 Class. for. Name("java. io. Print. Stream"). get. Method("println", new Class[]{String. class}). invoke(System. out, new Object[]{"Hello Java. One 2015"}) 18
Call Hiding via Reflection $ javac Hello. Java. One. With. Reflection. java $ javap -c -v Hello. Java. One. class | grep 'Methodref|Fieldref’ | grep ’System. out|println’ >#2 = Fieldref #16. #17 // java/lang/System. out: Ljava/io/Print. Stream; #4 = Methodref #19. #20 // java/io/Print. Stream. println: (Ljava/lang/String; )V $ javap -c –v Hello. Java. One. With. Reflection. class | grep 'Methodref|Fieldref’ | grep ’System. out|println’ > 19
Call Hiding via Reflection Constant pool: #1 = Methodref #2 = Fieldref #3 = String #4 = Methodref #6. #15 #16. #17 #18 #19. #20 // java/lang/Object. "<init>": ()V // java/lang/System. out: Ljava/io/Print. Stream; // Hello Java. One 2015 // java/io/Print. Stream. println: (Ljava/lang/String; )V 20
Call Hiding via Reflection • Pros + Method. Ref and Field. Ref are removed from Constant. Pool • Cons - Performance Bytecode size overhead The need for boxing/unboxing args and return value Call super. Method(…) is not possible Security breach when calling/accessing private methods/fields 21
Classic invoke* Instructions Math. random(); // Call static method invokestatic java/lang/Math. random: ()D No dispatch System. out. println("Hello Java. One 2015"); // Call virtual method invokevirtual java/io/Print. Stream. println: (Ljava/lang/String; )V Single dispatch via table it. has. Next(); // Call interface method invokeinterface java/util/Iterator. has. Next: ()Z Single dispatch via search new String. Builder(); // Call special method invokespecial java/lang/String. Builder. "<init>": ()V No dispatch 22
Invoke. Dynamic (JSR-292) • Features of dynamic languages in Java Platform • Shipped in Java 7 • The basic building block for a lot of new Java features, such as Lambdas http: //cr. openjdk. java. net/~vlivanov/talks/2015 -Indy_Deep_Dive. pdf Invokedynamic: Deep Dive. Vladimir Ivanov Hot Spot JVM Compiler, Oracle 23
bytecode + bootsrap method handles invokedynamic 24
Invoke. Dynamic Execution Bytecode 0: invokedynamic #2, 0 Constant. Pool 1. Resolving the bootstrap method …. . 2 … 3. Linking Call. Site 2. Executing invokedynamic #0: #22 Bootstrap method with arguments 18 Boostrap. Section 19 0 Call. Site Lambda. Metafactory. metafactory(. . ) 20 1 Method. Handle 21 2 Method. Type 25
Call. Site Method. Handles. Lookup String caller. Name Method. Type caller. Type arg 4 arg 255 bootstrap method Call. Site 1 Mutable. Call. Site Constant. Call. Site Mutable. Call. Site 1 26
Call Hiding via Invoke. Dynamic Plan - Generate bootstrap method - Replace invokevirtual/invokeinterface/invokestatic instructions with invokedynamic https: //github. com/licel/indyprotectordemo 27
Bootstrap Method - Signature private static Object bootstrap$0( Method. Handles. Lookup lookup, String caller. Name, Method. Type caller. Type, int original. Opcode, String original. Class. Name, String original. Method. Signature) User-defined params 28
Bootstrap Method - Structure Method. Handle mh = null; try { // variables initialization Class clazz = Class. for. Name(original. Class. Name); Class. Loader current. Class. Loader = Bootstrap. Method. Template. class. get. Class. Loader(); Method. Type original. Method. Type = Method. Type. from. Method. Descriptor. String(original. Method. Signature, current. Class. Loader); // lookup method handle …… mh = mh. as. Type(caller. Type); } catch (Exception ex) { throw new Bootstrap. Method. Error(); } return new Constant. Call. Site(mh); 29
Lookup Method. Handle switch (original. Opcode) { case 0 x. B 8: // invokestatic opcode mh = lookup. find. Static(clazz, original. Method. Name, original. Method. Type); break; case 0 x. B 6: // invokevirtual opcode case 0 x. B 9: // invokeinterface opcode mh = lookup. find. Virtual(clazz, original. Method. Name, original. Method. Type); break; default: throw new Bootstrap. Method. Error(); } 30
org. objectweb. asm • Library for low-level bytecode manipulation • Visitor API (same as SAX) • Tree API (same as DOM) 31
Visitor API Class. Visitor visit. Method. Visitor visit. Code visit*Insn visit. End visit. Field. Visitor visit. Annotation visit. End visit. Attribute 32
Hello. World in ASM mv = cw. visit. Method(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String; )V", null); mv. visit. Code(); mv. visit. Field. Insn(GETSTATIC, "java/lang/System", "out", "Ljava/io/Print. Stream; "); mv. visit. Ldc. Insn("Hello Java. One 2015"); mv. visit. Method. Insn(INVOKEVIRTUAL, "java/io/Print. Stream", "println", "(Ljava/lang/String; )V"); mv. visit. Insn(RETURN); mv. visit. Maxs(2, 1); mv. visit. End(); 33
ASMifier in Action Source code: Bytecode: public class Test { public static void main(String[] args) { System. out. println("Hello Java. One 2015"); } } javac 0: getstatic #2 3: ldc #3 5: invokevirtual #4 8: return org. objectweb. asm. util. ASMifier output: compile & run mv = cw. visit. Method(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String; )V", null); mv. visit. Code(); mv. visit. Field. Insn(GETSTATIC, "java/lang/System", "out", "Ljava/io/Print. Stream; "); mv. visit. Ldc. Insn("Hello Java. One 2015"); mv. visit. Method. Insn(INVOKEVIRTUAL, "java/io/Print. Stream", "println", "(Ljava/lang/String; )V"); mv. visit. Insn(RETURN); mv. visit. Maxs(2, 1); mv. visit. End(); 34
Bootstrap. Method. Generator. java String bootstrap. Method. Name = "bootstrap$0"; String bootstrap. Method. Signature = "(Ljava/lang/invoke/Method. Handles$Lookup; Ljava/lang/String; Ljava/lang/invoke/Met hod. Type; ILjava/lang/String; )Ljava/lang/Object; "; Method. Visitor mv = cv. visit. Method(ACC_PRIVATE + ACC_STATIC, bootstrap. Method. Name, bootstrap. Signature, null, true); mv. visit. Code(); Label l 0 = new Label(); Label l 1 = new Label(); mv. visit. Try. Catch. Block(l 0, l 1, l 0, "java/lang/Exception"); mv. visit. Insn(ICONST_0); mv. visit. Var. Insn(ISTORE, 13); mv. visit. Insn(ACONST_NULL); …. . 35
Call Hiding via Invoke. Dynamic Plan ✔ Generate bootstrap method - Replace invokevirtual/invokeinterface/invokestatic instructions with invokedynamic 36
Method. Visitor class Method. Indy. Protector extends Method. Visitor implements Opcodes { Handle bootsrap. Method. Handle = null; public Method. Indy. Protector(Method. Visitor mv, String class. Name) { super(ASM 4, mv); bootsrap. Method. Handle = new Handle(Opcodes. H_INVOKESTATIC, class. Name, bootstrap. Method. Signature); } } @Override public void visit. Method. Insn(int opcode, String owner, String name, String desc) { // replace invokestatice/invokevirtual, invokeinterface instructions } 37
Replace invoke* Instructions @Override public void visit. Method. Insn(int opcode, String owner, String name, String desc, boolean itf ) { // generate a new. Name and a generic new. Sig (String. class->Object. class, . . ) // … switch(opcode){ case INVOKESTATIC: case INVOKEVIRTUAL: case INVOKESINTERFACE: mv. visit. Invoke. Dynamic. Insn(new. Name, new. Sig, bootsrap. Method. Handle, opcode, owner, name, desc); default: mv. visit. Method. Insn(opcode, owner, name, desc, itf); } } 38
Result – Call Hiding via Invoke. Dynamic JD-GUI: 39
Result – Call Hiding via Invoke. Dynamic $ java -jar procyon. jar indyp/Hello. Java. One. class public static void main(final String[] array) { } // invokedynamic(937623782: (Ljava/lang/Object; )V, System. out, "Hello Java. One 2015") 40
Result – Call Hiding via Invoke. Dynamic $ java -jar cfr. jar indyp/Hello. Java. One. class public static void main(String[] arrstring) { LHello. World; . bootstrap$0(182, "java. io. Print. Stream", "println", "(Ljava/lang/String; )V", System. out, "Hello Java. One 2015"); } 41
Result – Call Hiding via Invoke. Dynamic Procyon output after Stringer Java Obfuscator: public static void main(final String[] array) { } // invokedynamic(q. Bt. Srv. LX: (Ljava/lang/Object; )V, o, Hello. Java. One$1. F("u 21 ccu 2058u 1 a 61u 351 euff 1 du 30 d 4u 34 c 0u 3539ua 8 c 3uaf 0 dub 61 aua 5 f 7uecc 2ub 638ue 7 fbuf 02 cu 56 dfu 5333")) 42
Conclusion • Protection for all classic invoke* instructions • No performance impact (excellent JVM optimization for Invoke. Dynamic) • Battle tested in our products 43
Our Products • Stringer Java Obfuscator (java bytecode) – Used for large scale Java EE production systems • financial institutions, payment systems – End user thin Java clients - 3 M+ users • Java. FX support • Dex. Protector (dalvik bytecode) – Several Thousand licenses sold – Thousands of protected applications – Millions of end users using Dex. Protected apps 44
Contact Email: kinash@licelus. com Twitter: @ivan_kinash Email: dudarev@licelus. com Twitter: @Mikhail. Dudarev Demo project: https: //github. com/licel/indyprotectordemo Licel Corporation Web: https: //licelus. com 45
- Slides: 45