Reverse engineering for beginners: . NET and Java

Depov

Moderator
Staff member
MODERATOR
ULTIMATE
SUPREME
PREMIUM
MEMBER
Joined
Feb 18, 2025
Messages
648
Reaction score
1,365
Why . NET and Java – the best start in reverse engineering

All Russian-language guides on reverse engineering for beginners start the same: “learn the assembler x86, put Ghidra, open the ELF-binary.” The way is working, but hard – the beginner sees the wall of mov, cmp, jmp and loses motivation on the second crackme. And after all, there is a class of tasks where the decompiler gives almost source code without a single line of the assembly. More details in our detailed analysis of binary analysis of vulnerabilities.

The C# compiler turns the source code not into machine instructions, but into an intermediate IL code. NET (Intermediate Language) It is performed by the CLR virtual machine. The Java compiler does the same thing – generates JVM bytecode. A fundamental difference with native languages: in a compiled file, metadata is stored – names of classes, methods, fields, often even local variables.

The difference between a disassember and a decompiler (disassembler vs decompiler) is critical. A disassembler for native code translates machine instructions into the assembly mnemonics – useful, but it is hard to read. The intentional code decompiler restores high-level source code: C# for . NET, Java for JVM. The result looks like a normal program written by a developer.

A specific example: native crackme on C++ after disassembly in Ghidra will give a function FUN_00401230 with a dozen unnamed registers. A similar crackme on C# in dnSpy will show namespace CrackMe → class Program → bool CheckPassword(string input). The difference between "two days of torment" and "fifteen minutes to a decision."

This does not mean that the curb crack is always trivial. The authors use obfuscation - renaming of characters, encrypting lines, inserting garbage code. In the terminology of MITRE ATT & CK, this is a technique of T1027 (Obfuscated Files or Information), which is also found in real malware. There are also checks on the debugger (T1622, Debugger Evasion). But even with obfuscation, the starting position is readable code, not a naked assembler.
Reverse Engineering Tools: dnSpy and JD-GUI
dnSpy decompiler — three in one for . NET

dnSpy is a decompiler, debugger and editor. NET builds in one window. For decompilation. NET applications are the main tool. Open .exe or .dll - you get a tree of names and classes on the left, decompiled C#-code on the right.

Three possibilities that decide crackme:

Decompilation. Automatic recovery of C#-code from IL-code . NET. In most cases, the result is read as a source - with formatting, variable names, compiler comments.

Debugging. A full-fledged debagger with breakpoint-s – like Visual Studio, only for someone else’s code without sources. Put a point of stop on the method CheckPassword, run the program and step by step, observe the variable values in the Locals window. When the verification logic is too confusing for manual analysis, the breakpoint on the comparison line will show the “right” response in real time. It saves hours.

Patching. Editing the IL code. NET is right in the program. Two modes: Edit Method Body (C#) — rewrite the method to C# and recompile; Edit IL Instructions — correct IL instructions directly. The first is faster for typical tasks, the second is more accurate for non-standard situations.

The alternative is ILSpy. Decompiles. NET is not worse, but it can neither debug nor patch. For reading the code is suitable, for the full cycle of the crackme solution - no.
JD-GUI — decompilation of Java applications

JD-GUI (Java Decompiler GUI) solves a similar task for Java: opens .jar or .class files and shows the restored Java code. The interface is minimalist - the packet tree on the left, the code on the right. No extra panels.

For in-depth analysis of Java bytecode, one JD-GUI is not enough. If the decompiler has stumbled on a complex design (lambda, nested classes, switch-expressions), connect javap – utility from the standard JDK. Team javap -c -p ClassName.class output raw bytecode JVM with all methods. JVM byte code is an analogue of IL code, but in terms of Java machine: aload, invokevirtual, if_icmpne. He does not lie – unlike a decompiler, which sometimes incorrectly reconstructs conditions.

Bytecode Viewer is an alternative to JD-GUI with advanced features. It combines several decompilers (Procyon, CFR, FernFlower) and allows you to compare their output side by side. When one decompiler is wrong in the cycle reconstruction or ternar operator, the other often shows the correct result. In practice, I open Bytecode Viewer when JD-GUI issues explicit nonsense in the verify method.
The hack. NET crackme step by step: from opening to patch
Find the verification method in the decompiled code

Let's say we have a file CrackMe.exe – console application on C#, which requests a password and outputs “Wrong!” or “Correct!” A typical crackme for beginners from the crackmes.one site - there are hundreds of similar tasks, sorted in complexity from 1 to 6.

Step 1. Open the file in dnSpy: File → Open or drag. On the left, the build tree appears – it resembles Solution Explorer in Visual Studio.

Step 2. Reveal the knots of the tree. Typical structure: CrackMe.exe → {} CrackMe (namespace) → Program (class). Inside the class are methods. Looking for Main as an entry point and any methods with speaking names: Check, Validate, Verify, IsCorrect. If the names are obfussed (see a(), b(), c()), start with Main and follow the challenges.

Step 3. Click on the method - on the right there is a decompiled C#. Here is a typical example:

private static bool CheckPassword(string input)
{
byte[] data = Convert.FromBase64String("czNjcjN0X2ZsNGc=");
string expected = Encoding.UTF8.GetString(data);
return input == expected;
}

The logic is transparent: the password is coded in Base64. Decode the string czNjcjN0X2ZsNGc= – get s3cr3t_fl4g. Enter in the program - the flag is taken.

But this is the simplest case. In more complex crackme, verification can include XOR encryption, hashing, bit operations, calculation of checksums. The algorithm does not change: find the method of verification, read the logic, invert.

Step 4. If the logic is too swirling, connect the debugger. Right click on the line return input == expected → Add Breakpoint. Run via Debug → Start Debugging, enter an arbitrary password. The execution will stop at the breakpoint, and in the Locals window we will see the value of the variable expected. Here it is, the correct answer is without the need to reverse the algorithm manually.

Separate reception: if crackme compares strings through String.Equals() or operator ==, put breakpoint directly on this operation and in the Watch window add both sides of the comparison. dnSpy will show both the entered line and the standard next.
Patching IL code. NET – two approaches

Sometimes it is impossible to calculate a password - for example, when crackme checks the hash amount of input and reverse conversion is non-trivial. It is easier to patch the check so that it takes any input.

Approach 1: Edit Method Body (C#). Right click on the method → Edit Method Body. The C#-code editor opens. Replace the body of the method with return true; and press Compile. dnSpy recompiles C# back into IL code . NET automatically. Works in ten seconds.

Approach 2: Edit IL Instructions. Right click → Edit IL Instructions. Here you can see raw IL-instructions: ldarg.0, call, ceq, ret. So that the method always returns true, replace the entire body with two instructions: ldc.i4.1 (put unit on the stack) and ret (Return the value). The rest are removed.

When what to use? Edit Method Body – for typical tasks when you need to quickly change logic. Edit IL Instructions – when the C#-compiler refuses to compile due to missing links to libraries or when you need a point edit of one instruction.

After the patch – necessarily File → Save Module. dnSpy will ask for the file name. Save under a new name (do not re-record the original - it will still be useful for analysis). Run the saved file - crackme accepts any password.
Disassembling Java crackme with JD-GUI
Navigation and analysis of Java byte code

Javame is distributed as .jar files. Open .jar in JD-GUI - the file will turn into a packet tree. Looking for a class with method main(String[] args) – entry point – and follow calls.

A typical Java crackme structure:

public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("Enter key: ");
String input = sc.nextLine();
if (verify(input)) {
System.out.println("Access granted!");
} else {
System.out.println("Wrong key.");
}
}

Method verify - our goal. Click on it in JD-GUI and read the decompiled logic. Decompiling Java applications through JD-GUI usually gives a clean result: cycles, conditions, methods calls - everything is in place, names are saved.

If JD-GUI shows unreadable variable names (a, b, c) and methods (a(), b()), it's a sign of obfuscation. The authors of crackme use ProGuard or other obfuscators that rename the characters. The structure of the code is saved - it is read harder, but you can understand. Strategy: Start with main, rename the elements mentally by their function. A method that takes a string and returns a boolean — most likely, verify. Not rocket Science.

To verify what the decompiler shows, use javap. Extract the desired .class from .jar (jar is a ZIP archive, opens by any archiver) and run javap -c -p ClassName.class. The output will show the bytecode of JVM: aload_0, invokevirtual, if_icmpne. Helps when the decompiler is mistaken in the reconstruction of difficult conditions or ternar operators.
Bypass protection crackme — keygen instead of patch

Unlike. NET, Java executables patching is a cumbersome process. It is necessary to extract .class, modify bytecode with a specialized tool, reassemble .jar with the correct manifest (META-INF/MANIFEST.MF). For simple crackme it is overkill.

A more elegant approach is to write keygen. See the verification logic in the decompiled code: crackme takes the entered string, performs a series of transformations and compares the result with the reference. The task is to invert the transformations.

An example from practice: crackme takes each password symbol, adds to the ASCII code its position in the line and compares with the sear-kone array bytes. Reverse operation is to subtract the position from each byte of the array. In Python, these are three lines: cycle, subtraction, chr(). Keygen is ready in a minute.

On Habra there is a similar analysis on the example of Linux-crackme: the author found the formula password = welcome[5 + i] + 5 and calculated the password by shifting each character by five positions. The principle is the same – the platform and the tool are different.

Another reception for Java: if crackme uses String.equals() for the final check, you can write a minimum Java class that downloads the original crackme through the reflection and causes internal methods directly – bypassing the input interface. Works because Java allows you to call private methods through Method.setAccessible(true). For . NET similar trick is done through System.Reflection. Reflexion - it's like a laundry: the documentation is not supposed to, and the lock opens.
Typical errors in decompilation. NET and Java

For dozens of crackme solved, I collected a list of rakes that every newcomer comes to. Check if something went wrong.

Emignition Edit IL Instructions and Edit Method Body in dnSpy. Edit Method Body – write C#, dnSpy compiles in IL. Edit IL Instructions is a manual bit code editing. If you don’t understand IL, use Edit Method Body. To IL, go only when C#-compilation does not work.

Forgot to save the module. The patch is used in the memory of dnSpy, but not written to the disk. File → Save Module is a mandatory final step. Without it, the original .exe remains intact. I myself came across it three times before I developed a reflex.

Violation of the balance of the IL stack. After removing the instructions, the program drops with InvalidProgramException or VerificationException. Reason: The IL machine works with the stack, and each method must return exactly one value of the desired type. Removed excess - the stack is unbalanced. Solution: Instead of selective removal, replace the entire body of the method with ldc.i4.1 + ret (for bool) or ldc.i4.0 + ret.

Analysis of the obfusced. NET code directly. If dnSpy shows unreadable names and suspicious designs, drive the build through the de4dot (the most famous) before the analysis. The deobfuscator will restore some of the names, decrypt the lines, remove the trash code. Saving the clock. In terms of MITRE ATT&CK, the reverse procedure is T1140 (Deobfuscate/Decode Files or Information).

Ignoring javap problems with JD-GUI. JD-GUI sometimes incorrectly restores ternary operators, lambdas, and nested cycles. If the verification logic looks pointless, open the same class through javap -c and read the byte code. The JVM byte code is ground truth: it exactly matches what the virtual machine performs.

Do not check .jar after modification. If patching .class inside .jar, save directory structure: file com/example/Main.class must lie in com/example/ inside the archive. The wrong way = ClassNotFoundException at launch.

Do not isolate the environment. Crackme with crackmes.one or CTF platforms run in a virtual machine. Managed code can call native libraries via P/Invoke (. NET) or JNI (Java) - and then the sandbox managed-environment will not save.
 
Top Bottom