How I “hacked” my way through a CS homework

1. Context

I’m currently taking a Computer Science course at Georgia Tech called CS 2110 - Computer Organization and Programming. I have taken the exact same course from the ECE department, but since I changed my major, those classes don’t count toward my credit…

And here we are, grinding through this class again. All of the homeworks/projects so far are simple, so I tend to get bored doing them.

During this week, parts of the homework are about converting LC-3 assembly code into 2-byte hex number.

They also always release a Java jar file for us to run and test our answers before submitting. As soon as I saw that we had to put our answer into a text file and run the jar file to check our answer, I’m 99% percent sure the TAs had buried the answer inside the jar file itself.

2. Test run

I first try running the jar file with some random number in the text file. From their instruction, I know that I have to write my hex numbers without the 0x (so 0x1234 would be written as 1234).

So, I try putting 0x1234 and 0x5678 inside my text file, and I get this when I run the Jar file.

alt text

It seems like it’s showing us which instruction we are getting wrong, but not showing us the answer. This was interesting but it furthers confirm my suspicion that the Jar file is reading the text file line by line, comparing it to the correct answer, and if they are not the same, it exits immediately.

2. Disassemble the Jar file

I decide to disassemble the jar file to get the plain text Java code to see if I can reverse engineer to get my answer without actually doing the homework.

Surprisingly, not a lot of people know that you can disassemble a jar file or class file into readable plaintext Java.

During my time of doing CTF Reverse Engineering challenges, JD-GUI is my favorite Java decomplier to use for this.

Here is what I get when using JD-GUI. There are a lot of files included in our Jar file, but the only thing we need to worry about is MachineCodeTests.class

alt text

3. Reverse Engineering The Answer

On line 19, we see that the EXPECTED_INSTRUCTIONS variable is a 2-D byte array. There are 11 arrays inside this variable, so we can assume that the test expects us to enter 11 instructions in their hex form.

On line 32, we see the EXPECTED_DATA variable as an array containing a single integer 13. The EXPECTED_LINE is the sum of the length of these two array, which is 11 + 1 = 12. Therefore, we need to enter 12 hex, each in 1 line in our machinecode.txt file

Let’s look at the code that is used to check for this.

alt text

We can clearly see that:

1. The scanner is reading each line in from the file, incrementing linesFound everytime we sucessfully read in a line.

2. If linesFound exceeds 12, we throw an exception (line 61). This confirms that our file needs to be exactly 12 lines.

3. If the length of a line exceeds 4, we throw an exception(line 65). This confirms that each line, there can be only 4 characters.

4. If the string can not be converted into an integer as a hex number, we throw an exception(line 74). They already state this condition in the task’s instruction, so we don’t care about this.

alt text

It seems like the lineValue is being disassemble by the function disassemble.

alt text

The function just checks if the line value is between 0x0 and 0xFFFF, disassembles the integer to check if it’s a valid instruction, and converting it into the string form of the instruction

The variable disassembled returned is useless to us because on it is only used on line 95-98 to print out the instruction that we type in.

The variable expectedChecksum is a byte array extracted from EXPECTED_INSTRUCTIONS at a certain index(line number). The fact that they call this the expected checksum let us know that this is the encrypted/hashed form of the answer, not the answer itself.

The SHA-256 hashing part is below.

    MessageDigest md = MessageDigest.getInstance("SHA-256");
    byte[] checksum = md.digest(new byte[] { (byte)(lineValue >> 8 & 0xFF), (byte)(lineValue & 0xFF) });

After hashing the lineValue, the two byte arrays are being compared on line 94. If they match, then we have found our answer.

4. Bruteforcing time

After having known the hashing algorithm they are using, I decide to write a Java program to bruteforce the answer.

Since we know the range of the possible input (0x0 - 0xFFFF), we can loop through all of the possible number, hash it using the same algorithm, and compare their byte array with the given byte array in the Jar file.

    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;
    public class yeet {
        public static void main(String[] args) {
            try {
                byte[][] expected = { 
                    { 
                    111, -103, -4, 29, 31, 31, 66, -3, 9, 93, 
                    59, -4, -75, 113, 16, -65, 16, 72, -89, 61, 
                    63, -12, -44, 11, -69, 125, -60, 23, 54, -2, 
                    10, 86 }, { 
                    56, 5, 1, 4, -32, -113, -127, 68, 64, -17, 
                    95, 41, 96, 121, -20, -12, 42, 94, 106, 40, 
                    -19, -74, -101, 43, -38, 98, -126, -20, -53, -47, 
                    58, 89 }, { 
                    114, 16, -110, 51, -72, -63, 1, 96, -120, -30, 
                    13, 1, -99, -54, -7, -63, -18, -14, 34, 1, 
                    -82, -113, -46, 79, 49, 59, 11, -14, 77, -73, 
                    26, -103 }, { 
                    -71, 117, -50, -117, 3, 116, 50, -38, -91, -11, 
                    -85, 106, 110, -116, 42, 14, -85, 43, -89, 119, 
                    -78, -98, 103, -104, 19, -40, 23, 55, -5, 66, 
                    -4, Byte.MAX_VALUE }, { 
                    -34, 23, 32, 110, 99, -11, -93, 19, 21, -118, 
                    -75, -12, -40, -40, -26, -82, 70, 73, -85, -125, 
                    43, -126, 3, -51, 25, -81, -99, 76, 96, 24, 
                    5, -33 }, { 
                    -26, -8, -100, -59, 49, -109, 55, 105, 63, -97, 
                    -98, 41, -40, 114, -25, -25, 8, -4, 29, 7, 
                    -97, 11, -23, -77, 21, 74, 6, 29, -50, -14, 
                    16, 46 }, { 
                    -55, -81, 121, -29, -83, -68, -96, 50, -7, 0, 
                    -4, -105, -20, 86, -88, 117, 30, -4, 56, 70, 
                    81, 69, -22, -87, 124, -4, -15, -4, -116, -96, 
                    -74, 21 }, { 
                    21, 61, 10, 94, -13, 45, -22, 113, 113, -53, 
                    123, 10, 36, -15, 86, 46, -120, 106, -91, -117, 
                    29, -5, 90, -120, -55, -7, -99, 57, 89, -22, 
                    36, 118 }, { 
                    104, 19, -15, 82, -102, -57, 3, -1, 48, -56, 
                    -55, 101, -14, -14, -110, -17, -11, 75, 74, 56, 
                    -13, -82, -63, 1, -33, 68, 54, -24, -59, -54, 
                    -55, 96 }, { 
                    -120, 49, 20, 102, -119, 106, -25, -20, 126, 16, 
                    29, -30, 21, -16, -88, 105, 123, 21, 116, 112, 
                    -42, -119, 76, 19, 94, -67, -113, 60, -47, 10, 
                    73, 50 }, 
                    { 
                    102, -66, 79, -77, 46, 34, -63, -75, -113, 94, 
                    16, 92, -90, -5, -1, 82, -105, -58, -95, -31, 
                    -117, 115, 120, -45, 70, -11, 111, 46, 121, 113, 
                    75, -95 } };
                    
                int count = 1; // line number
                for (byte[] array : expected) {
                    int lineValue = Integer.parseInt("0000", 16);

                    while (lineValue < Integer.parseInt("FFFF", 16)) {
                        MessageDigest md = MessageDigest.getInstance("SHA-256");
                        byte[] checksum = md.digest(new byte[] { (byte)(lineValue >> 8 & 0xFF), (byte)(lineValue & 0xFF) });
        
                        if (MessageDigest.isEqual(checksum, array)) { // Found it!!
                            System.out.println(count + ": " + Integer.toHexString(lineValue));
                            count++;
                            break;
                        }
                        lineValue++;
                    }
                }
            } catch (NoSuchAlgorithmException ex){
                System.out.println("Something went wrong...");
            }
        }
    }

5. Result

After running the Java code, this is the result I get!! After putting it into my text file to submit, I get full points for the section without having to try and do it on my own!!

alt text

6. Remark

This is just a fun Reverse Engineering attempt because I am so bored at home nowadays, and I am definitely encouraging people to do their homework to learn instead of pulling something like this.

Since I already know how to do it manually, I can justify my lazy actions of hacking my way through this assignment :stuck_out_tongue_winking_eye:.

For my fellow CS major, I hope you have learned the wonderful tool JD-GUI to disassemble any .class or Jar file you encounter with.

Since some of my class use these file to let us autograde ourselves, I think it is sometime useful to look at the source code of these tests to check and make sure our answers are correct and that we full understand whatever we are implementing!

Thank you for reading this, and see ya next time!