# Self-Extraction Using Reachability Analysis and Replication in Java

It's been a while since I polished up HandJar[0] and wrote ClassWar[1]. Both
viruses work similar by generating Java source code and compile that to create
an entrypoint. This technique depends on the existence of a Java compiler tool
chain.

My previous work performed the infection by simply copying files over 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 actual JVM
bytecode level. Too much work to understand the low-level file format, parsing
and modifying the code, keeping track and updating offsets and so on. Sure it
has be 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 for reading, modifying and writing Java class files, 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 that are
  called starting from a specified starting point (e.g. the virus entrypoint)
- Move those methods over to an existing Java class file
- Inject a call to the entrypoint into any existing method of the class file
  using bytecode manipulation

I implemented a PoC using the new API to do exactly that. And being the
logical next step after the ClassWar, I called it Revolution.

## 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, parameters)
- `CodeModel`[8]: the actual bytecode instructions of a `MethodModel`
- `ClassTransform`[9]: modify a class file

Getting instances of those classes is easy as `ClassFile.of().parse(content)`
(to get a `ClassModel`), `classModel.methods()` to access all methods in a
class and `methodModel.code()` to access the `CodeModel`.

## 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
code that actually _is_ executed when searching from a specified starting
point, giving us 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 to dynamically get the name of the class and method that are
currently being executed from the runtime, so we do not need to store the name
of the entrypoint inside the virus but can calculate it when the entrypoint is
executed.

For the sake of simplicity, this PoC puts 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. The
restrictions for now 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 byte code and I
  didn't have the 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
- entrypoint must be a `static void` method with 0 parameters (not actually a
  restriction of the reachability analysis but it makes it easier to call the
  entrypoint in an infected class file)

This isn't much of a restriction since any Java code can be rewritten like
this. We just cannot use classes 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 that get called
starting from any method.

Java bytecode only knows a few ways to call a method:

- `invokedynamic`[12]
- `invokeinterface` [13]
- `invokespecial` [14]
- `invokestatic` [15]
- `invokevirtual` [16]

Analyzing those 5 bytecode instructions, gives us 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 if the method is not
from the standard library, we add it to the dependency tree.

The code could even be spread around 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.

## Moving Around

Okay, now we know the dependencies of our code, what comes next? We need to
actually move the code over into a host file. We can more or less just copy
all `MethodModel`s along with their `CodeModel`s over 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 the name of the
host class.

Here we can also implement a really basic form of polymorphism: when we
already adjust all references, we can also randomly rename the methods. This
brings 2 advantages:

- No conflicts with existing methods of the class. We simply generate new
  random values until we find a name that isn't used yet
- No deterministic method names to look for as patterns to detect 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...)

## We're Moving But Not Running

It's all fun and games until here. 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 the virus and call 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 `invokestatic`
anywhere in the infected class. Using
`ClassTransform#transformingMethodBodies`[17], we can just pick a random
method from the host class and inject a `invokestatic` instruction. I decided
to inject the call before every `*return` instruction so no matter which
branch is executed, the entrypoint is called. `CodeBuilder`[18] makes this
also easy.

Now whenever the method we randomly picked gets executed by the original code,
the virus is executed, too.

## Why is This Cool?

We are able 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, header fields and so on. All that is hidden behind a nice, ergonomic
API.

Also, we do 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 it's 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've nothing left but the virus-relevant methods and move
those to the next target. It keeps working as is, even when we extend the
functionality of the virus.

Revolution offers a nice, extendable 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, polymorphism (e.g. inline or
outline methods, inject junk code) and even metamorphism by rewriting certain
constructs into other 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 not only works 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 kinda elegant and cool new way to infect targets that run on
the JVM.

## Shoutouts

- SPTH for being kind of a mentor all those years ago and who kept me always
  interested in the topic of self-replicating code. Hope you're doing well,
  wherever you are right now.
- TMZ and the whole tmp.0ut staff for still regularly releasing a VX ezine,
  for the time they put into reviews and for giving me the possibility to
  still release some 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 on one hand
  hurt my fragile little ego but also motivated me years later to pick up the
  work again and continue down this path. I just saw 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 to write cool new Java infectors.
- All the other beautiful people with whom I've crossed paths when they dipped
  their toes into this scene over the years, came up with exciting new ideas
  and kept the work from getting boring.

## 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
