┌───────────────────────┐ ▄▄▄▄▄ ▄▄▄▄▄ ▄▄▄▄▄ │ │ █ █ █ █ █ █ │ │ █ █ █ █ █▀▀▀▀ │ │ █ █ █ █ ▄ │ │ ▄▄▄▄▄ │ │ █ █ │ │ █ █ │ │ █▄▄▄█ │ │ ▄ ▄ │ │ █ █ │ │ █ █ │ │ █▄▄▄█ │ Self-Extraction Using Reachability │ ▄▄▄▄▄ │ Analysis and Replication │ █ │ │ █ │ ~ r3s1stanc3 └───────────────────█ ──┘ ───[ Table of Contents ]───────────────────────────────────────────────────── 0 - Intro 1 - The Class-File API 2 - Shaking the Tree 3 - Moving Around 4 - We're Moving, but Not Running 5 - Why Is This Cool? 6 - Shoutouts 7 - References ───[ 0 - Intro ]───────────────────────────────────────────────────────────── It's been a while since I polished up HandJar[0] and wrote ClassWar[1]. Both viruses work similarly by generating Java source code and compiling it to create an entrypoint. This technique depends on the existence of a Java compiler toolchain. My previous work performed the infection by simply copying files into the host file and hooking the entrypoint using the `Main-Class` attribute in `MANIFEST.MF`, or by wrapping an existing class and calling into the old class using reflection. I could never be bothered to do something at the JVM bytecode level. It was too much work to understand the low-level file format, parse and modify the code, and keep track of and update offsets. Sure, it has been done by others[2][3][4], but I never tried it. One could say I was lazy and waiting for a high-level API to perform low-level bytecode manipulation. And boy, oh boy, did the Java team deliver... Java 24 introduced a new, interesting API into the standard library: the Class-File API[5]. This API allows Java class files to be read, modified, and written without having to worry about the low-level details of the class file format. This API opens some powerful possibilities that I will explore in this article: - Perform reachability analysis to dynamically extract all methods called from a specified starting point (e.g. the virus entrypoint) - Move those methods into an existing Java class file - Inject a call to the entrypoint into any existing method in the class file using bytecode manipulation I implemented a PoC using the new API to do exactly that. As the logical next step after ClassWar, I called it Revolution. ───[ 1 - The Class-File API ]──────────────────────────────────────────────── The new API is really powerful and allows for many shenanigans, but for now, there are only a few relevant classes we need to write a virus: - `ClassModel`[6]: a parsed class file - `MethodModel`[7]: method metadata (name, return type, and parameters) - `CodeModel`[8]: the actual bytecode instructions of a `MethodModel` - `ClassTransform`[9]: modifies a class file Getting instances of those classes is as easy as calling `ClassFile.of().parse(content)` to get a `ClassModel`, `classModel.methods()` to access all methods in a class, and `methodModel.code()` to access the `CodeModel`. ───[ 2 - Shaking the Tree ]────────────────────────────────────────────────── Tree shaking[10] is a kind of dead-code elimination used to find and remove dead code, e.g. code that is never called. We can flip the idea and find only the code that actually _is_ executed when searching from a specified starting point, giving us a nice and simple reachability analysis. Knowing the entrypoint of the virus, we can start from there and walk through the call stack, building a dependency tree of all the code the virus needs to execute. Java allows us to dynamically retrieve the name of the class and method currently being executed from the runtime, so we do not need to store the name of the entrypoint inside the virus; we can calculate it when the entrypoint is executed. For simplicity, this PoC places some restrictions on how the code must be written for the reachability analysis to work. With a bit more work, the analysis can be extended to support every language feature there is. For now, the restrictions are as follows: - Only static methods (preferably private, so consumers of an infected class don't see them) - No `switch` over `enum`s, as this produces some peculiar bytecode and I didn't have time to figure that one out - No method references (e.g. `List.of(1).forEach(System.out::println)`) for the same reason as above - No runtime reflection - The entrypoint must be a `static void` method with zero parameters (not actually a restriction of the reachability analysis, but it makes calling the entrypoint in an infected class file easier) This isn't much of a restriction, since any Java code can be rewritten like this. We just cannot use classes to carry state and instead have to pass state around as method parameters, e.g. in `Map`s. `switch` over `enum`s can be rewritten as `if`/`else` constructs, and method references can be rewritten as lambdas (`i -> System.out.println(i)` in the example above). When the code is structured with these restrictions in mind, it's pretty easy to perform a breadth-first search[11] of all the methods called from any starting method. Java bytecode only knows a few ways to call a method: - `invokedynamic`[12] - `invokeinterface`[13] - `invokespecial`[14] - `invokestatic`[15] - `invokevirtual`[16] Analyzing those five bytecode instructions gives us a simple way to build a call tree. The new Class-File API makes it easy to iterate over the instructions of a method's `CodeModel` and perform the reachability analysis. We just look at those instructions, check which methods are invoked, and add the method to the dependency tree if it is not from the standard library. The code could even be spread across multiple classes, but then the methods cannot be `private`. This reachability analysis only happens at the method level, not at the bytecode level. So, if a method contains dead code, it will not be removed. ───[ 3 - Moving Around ]───────────────────────────────────────────────────── Okay, now we know the dependencies of our code. What comes next? We need to actually move the code into a host file. We can more or less just copy all `MethodModel`s, along with their `CodeModel`s, into the host file. Since `static` methods in Java still carry a reference to their enclosing class (e.g. `MyClass#foo`), we need to adjust the reference to use the name of the host class. Here we can also implement a really basic form of polymorphism: since we are already adjusting all references, we can also randomly rename the methods. This has two advantages: - No conflicts with existing methods of the class. We simply generate new random values until we find a name that isn't already used - No deterministic method names to use as patterns for detecting the virus. The code itself still contains patterns that should make detection trivial, but actually morphing the code is left as an exercise to the reader (or me, but I'm running out of time for this submission...) ───[ 4 - We're Moving, but Not Running ]───────────────────────────────────── It's all fun and games up to this point. We dynamically extracted all the logic our virus needs and moved it into a host file, but it never gets executed... A few entrypoint techniques have already been explored in Java, e.g. changing the `Main-Class` attribute in `MANIFEST.MF` to point to the virus and calling the original entrypoint from there (HandJar), wrapping classes using reflection, and injecting a `static` block to call the virus (ClassWar). Those techniques are somewhat boring and predictable when we're already operating at the bytecode level of the class file. Since the entrypoint is a `static` method without any parameters, we just need to use `invokestatic` somewhere in the infected class. Using `ClassTransform#transformingMethodBodies`[17], we can just pick a random method from the host class and inject an `invokestatic` instruction. I decided to inject the call before every `*return` instruction so that, no matter which branch is executed, the entrypoint is called. `CodeBuilder`[18] makes this easy, too. Now, whenever the method we randomly picked gets executed by the original code, the virus is executed, too. ───[ 5 - Why Is This Cool? ]───────────────────────────────────────────────── We are able to infect JVM bytecode by directly modifying it, without having to take care of the gritty little details of the low-level format, like rewriting offsets and header fields. All that is hidden behind a nice, ergonomic API. We also now have a nice, self-contained, and fully independent way to collect all methods that belong to the virus. We don't really have to know where the code is stored; we just need the name of the class and method of the entrypoint. We can get both dynamically from the Java runtime. From there, we just shake until we have nothing left but the virus-relevant methods, then move those to the next target. It keeps working as is, even when we extend the functionality of the virus. Revolution offers a nice, extensible basis for viruses targeting Java bytecode that can be used to explore new EPO techniques, split the code and inject parts of it into different classes, implement polymorphism (e.g. inlining or outlining methods and injecting junk code), and even achieve metamorphism by rewriting certain constructs into different but equivalent code. Hh86 used the exception table for EPO[19], and I plan on exploring this and other EPO techniques based on this framework. All this works not only for JAR files, but also for class files that are not in an archive. Actually, I don't care at all about the executed host file, since one can load the contents of any class file loaded by a JVM instance using `ClassLoader#getResourceAsStream`[20]. I think that's a kind of elegant and cool new way to infect targets that run on the JVM. ───[ 6 - Shoutouts ]───────────────────────────────────────────────────────── - SPTH for being a kind of mentor all those years ago and for always keeping me interested in self-replicating code. Hope you're doing well, wherever you are right now. - TMZ and the whole tmp.0ut staff for continuing to release a VX ezine regularly, for the time they put into reviews, and for giving me the opportunity to release articles from time to time. Thanks a lot, and see you in the next issue! - Peter Ferrie for his devastating analysis of HandJar.A[21], which hurt my fragile little ego on one hand, but also motivated me years later to pick up the work again and continue down this path. I just saw that you're judging the Rootkit Competition for this volume. Nice to see you're still participating in this beautiful side of the VX scene. - The Java team for specifying and implementing the Class-File API and thereby offering an enterprise(TM) API for writing cool new Java infectors. - All the other beautiful people with whom I've crossed paths as they dipped their toes into this scene over the years, came up with exciting new ideas, and kept the work from getting boring. ───[ 7 - References ]──────────────────────────────────────────────────────── [0] https://tmpout.sh/3/28.html [1] https://tmpout.sh/3/29.html [2] https://86hh.github.io/valhalla/issue%204/codes/hh86/GRIMES/GRIMES.txt [3] https://www.f-secure.com/v-descs/sbrew [4] https://threats.kaspersky.com/en/threat/Virus.Java.BeanHive/ [5] https://openjdk.org/jeps/484 [6] https://docs.oracle.com/en/java/javase/24/docs/api/java.base/java/lang/classfile/ClassModel.html [7] https://docs.oracle.com/en/java/javase/24/docs/api/java.base/java/lang/classfile/MethodModel.html [8] https://docs.oracle.com/en/java/javase/24/docs/api/java.base/java/lang/classfile/CodeModel.html [9] https://docs.oracle.com/en/java/javase/24/docs/api/java.base/java/lang/classfile/ClassTransform.html [10] https://en.wikipedia.org/wiki/Tree_shaking [11] https://en.wikipedia.org/wiki/Breadth-first_search [12] https://docs.oracle.com/javase/specs/jvms/se24/html/jvms-6.html#jvms-6.5.invokedynamic [13] https://docs.oracle.com/javase/specs/jvms/se24/html/jvms-6.html#jvms-6.5.invokeinterface [14] https://docs.oracle.com/javase/specs/jvms/se24/html/jvms-6.html#jvms-6.5.invokespecial [15] https://docs.oracle.com/javase/specs/jvms/se24/html/jvms-6.html#jvms-6.5.invokestatic [16] https://docs.oracle.com/javase/specs/jvms/se24/html/jvms-6.html#jvms-6.5.invokevirtual [17] https://docs.oracle.com/en/java/javase/24/docs/api/java.base/java/lang/classfile/ClassTransform.html#transformingMethodBodies(java.util.function.Predicate,java.lang.classfile.CodeTransform) [18] https://docs.oracle.com/en/java/javase/24/docs/api/java.base/java/lang/classfile/CodeBuilder.html [19] https://86hh.github.io/valhalla/issue%204/articles/hh86/CLASSI.TXT [20] https://docs.oracle.com/en/java/javase/24/docs/api/java.base/java/lang/ClassLoader.html#getResourceAsStream(java.lang.String) [21] https://www.virusbulletin.com/virusbulletin/2013/12/hands-cookie-jar r3s1stanc3 - r3s1stanc3@riseup.net 2026-07 --[ PREV | HOME | NEXT ]--