Wednesday, March 18, 2026

Lab 7: Static Software Patching

 Introduction

    Software consists of binary instructions executed by the CPU. Static patching involves altering these instructions on the disk to change program behavior permanently. It is a fundamental skill in reverse engineering used to bypass security checks or modify legacy code.

    For this project, we used Cutter to analyze a Level 1 CrackMe. By identifying and modifying specific assembly instructions, we bypassed a registration check without knowing the original source code.

This Lab will go through these main points:

  1. Introduction => An overview of the chosen CrackMe, my motivation, and the toolset used (Cutter)
  2. Initial Recon => Investigating file architecture, metadata, and extracting clues from embedded strings.
  3. Static Analysis => A deep dive into the main function, including identification of function prologues, epilogues, and key assembly instructions.
  4. Finding the Check => Locating the critical conditional jump by comparing Disassembly and Decompiler views.
  5. Patching Strategy => The technical walkthrough of the binary modification, including specific opcode changes and the final result.
  6. Conclusion => Final thoughts on the challenge, lessons learned, and alternative "KeyGen" approaches.

1. Introduction

    For this analysis, I selected Synok's KeyGenME #1 from crackmes.one. It is categorized as a Level 1 challenge, making it an ideal starting point for learning how programs handle user input and validation logic. To find this crackme, I went to this link (https://crackmes.one/search), and then input the search filters down below: 



From which, then I selected:


    My motivation for selecting this crackme was quite straightforward, i initially found it as an interesting concept. Through this link (https://reasonlabs.com/blog/what-is-a-keygen-how-to-safely-activate-software), i discovered that Keygens are a tool designed to generate serial numbers or product keys for software programs.  

    To actually crack the software, the analysis was performed using Cutter. Cutter is simply a graphical user interface for the actual code, allowing us to bridge the gap between raw assembly and readable logic. This is a really interesting, beginner-friendly tool, since it provides a multi-view environment. This means that we can see the disassembly (raw instructions), the compiler (reconstructed C code), and the graph view (the visual map of the logic) at the same time.

2. Initial Recon

    Before touching the code, we must understand the environment. Initial reconnaissance involves gathering metadata to define the program's context and identify the main pieces that will guide the analysis.
    First, we have to load our crackme into the cutter. This is showcased by the pictures below.






After loading the crackme into cutter, we are presented with the file's architecture and metadata. 


    As we can see, our target is a Windows PE (Portable Executable) file. It is built for the x86 (32-bit) architecture, specifically the i386 machine type. Metadata tells us the rules of the system. This binary was compiled in December 2008 and is a Windows CUI (Console User Interface) application. This confirms that I should look for 32-bit registers (like EAX, EBX, and ESP) and expect the program to run in a command-line environment rather than a graphical window. 

This can also be seen by simply running the keygen.


    The keygen asks us for a name, on which it then logically creates a registration code by itself, and since we dont have the proper code to match it tells us to try again.

    Additionally, we found 823 strings, from which the most important were "eg name ", "Reg Code", "Cracked! :(" and "Try again ! :(". We found these strings by simply first looking at the graph, and then clicking on the cross-reference on the str.cracked part of it, as can be seen below.


We can also see other text that tells us that searching through strings might be useless.

3. Static Analysis

    To understand how a program functions, we must examine its Static Analysis. This is the process of reading the code's structure before it is executed. By using Cutter’s Graph View and Disassembly, we can identify the patterns that govern the program's behavior.

    Every function follows a specific routine in order to manage memory. This is known as the Prologue (at the start) and the Epilogue (at the end). In the main function, the prologue sets up the stack frame. It uses push ebp (extended base pointer, which is usually used as a stable checkpoint) to save the old pointer. Then it uses mov ebp esp (extended stack pointer, which holds the memory of the current program at runtime) to set the new one. Finally, sub esp 0xc0 (it substracts 192 bytes of data in the stack, in this case to allocate space for the function) makes space for your data on the stack. The epilogue at the end uses pop ebp and ret to clean up and go back to the system, both of which can be seen below. 



    To properly understand what the code was writing, and what the program actually does we decided to first research the main assembly functions, and then research what the key logic of the keygen actually is, and how it is usually cracked. 

    For the assembly part, we created a simple table explaining what each function does, which you can find below: 


    When it comes to the keygen usual logic, this program starts by preparing its memory and asking you to type in your name and a registration code. It takes these inputs and keeps them on the stack while it works. The next step is a loop that looks at every character in the name you provided. For every letter the code runs a math algorithm to convert that character into a value. It keeps a running total of all these values in a memory spot. You will notice that it sleeps for a short time after each letter to slow things down. Once it reaches the end of your name it takes the accumulated total and adds it to itself one more time to get the final value.

    After the math is finished the program then takes the code you typed and compares it directly to the number it just calculated. This comparison is the only thing that stands between us and the success message. If the numbers match exactly the program flows into a block of code that prints the cracked message. If the numbers are even slightly different it jumps away to a block that prints the try again message and then exits. This is really simple logic which is present in all keygens, so from now on it is pretty straightforward, we just needed to come up with a solution.

    After searching the internet, we found out that you can beat this challenge in three main ways. One way is to modify the binary file directly to remove the check. This is called patching, and you just replace the jump instruction with a nop instruction so the program never fails. A second way is to run the program in a debugger and set a breakpoint right at the comparison. This lets you pause the program and read the real key directly out of the computer registers. The final way is to study the math loop and reverse the algorithm. Once you understand the math you can write a key generator script that calculates the correct code for any username you want to use. Since we are not familiar with the debugger and reversing algorithms, we tried to simply patch the code so we can crack it. 

4. Finding the Check

    To find the check I started by looking for the success strings I found during the recon phase. I looked up the cracked string in the data section and found its specific address. I used the cross reference tool in cutter to see where this string was being used in the main function. This led me straight to the bottom of the code where the success message is printed. From there I worked backward to find the comparison that happened right before it.

    This brought me to the cmp instruction which is the moment the program checks your work. I saw the jne instruction right after it which acts like a gatekeeper to send you to the failure message. When I switched to the decompiler view it made the logic even clearer. It showed a simple if statement comparing two variables which matched exactly with the assembly instructions.


5. Patching Strategy

    As mentioned before, the best method to actually crack this program is patching. To patch the program I went back to the jump at address 0x00401649. This is the part that sends the user to the failure message when the code is wrong. 

    I right clicked the instruction and selected the edit option to change the code. I replaced the jne instruction with two nop instructions. Nop stands for no operation which means the cpu does nothing and just moves to the next line. By doing this the program can no longer jump away to the error message. It is forced to go straight to the cracked message no matter what code you type in.





    In order to see if the patch actually worked i ran a simple test, i put a random name and password beforehand and after the patch and it gave me these results.

Before the patch:

After the patch:


    Based on the message we got (which is the same as the one where the reg code matches the name within the code), and based on the logic we used, we can conclude that we successfully cracked the Crackme keygen.

6. Conclusion

    Working on this crackme taught me that software is just a series of logical choices. I learned how to identify the specific moments when a cpu decides between success and failure. The hardest part was understanding the math loop that processed the name but finding the strings helped me work backward to the solution. I realized that you do not always need to solve the math if you can just change the logic of the program.

    I could have also solved this by writing a keygen script to calculate the real codes. Another way would be using a debugger to see the real key in the registers while the program is running. This project was a great way to practice the adversarial mindset needed for cybersecurity. It showed me how easy it is to bypass basic checks once you know how to read assembly.

No comments:

Post a Comment

LAB 10: APK Security Scanner - Multi-Agent Static Analysis pt.2

Taking Android Apps Apart: Building an AI-Powered Security Scanner Part 2: The Analysis, Execution and Findings In Part 1 we defined our obj...