Simple Binary Patcher
Binary Patching Tutorial
This tutorial will guide you through the process of analyzing and patching a binary executable. We'll focus on reversing the logic of a conditional jump in an executable using Ghidra, a hex editor, and a custom patcher. The goal is to change a JNZ
(Jump if Not Zero) instruction to a JZ
(Jump if Zero) instruction.
Patcher Repository - With Code
Step 0: Background - Look at Source Code
Here is the C++ code that represents the logic to be reversed:
#include <iostream>
#include <string>
int main() {
std::string input;
const std::string correctPassword = "password";
std::cout << "Enter your password: ";
std::cin >> input;
if (input == correctPassword) {
std::cout << "Good" << std::endl;
} else {
std::cout << "Bad" << std::endl;
}
return 0;
}
Step 1: Analyze the Binary with Ghidra
Open the Executable
- Launch Ghidra and create a new project.
- Import the target executable (
patchme.exe
) into the project.
Locate the if
Statement
- Navigate to the main function (usually
main
orentry
). - In the decompiled view, find the logic that verifies the password.
Identify the JNZ
Instruction
- Look to the assembly view in Ghidra.
- Locate the
JNZ
(opcode0x75
) instruction that corresponds to the conditional check. - Note the file offset of this instruction, by hovering over the address.
Step 2: Verify Offset in a Hex Editor
To ensure you have the correct offset:
- Open the executable in a hex editor.
- Navigate to the offset identified in Ghidra.
- Verify that the byte at this location is
0x75
(the opcode forJNZ
).
Step 3: Patch the Binary
The patching process modifies the byte corresponding to the JNZ
instruction to JZ
.
Key Lines in the Patcher Code
1. Open the Binary File
This ensures the binary file is opened in read-write mode:
std::fstream file(filePath, std::ios::in | std::ios::out | std::ios::binary);
if (!file.is_open()) {
std::cerr << "Error: Could not open file!" << std::endl;
return;
}
2. Seek to the Offset
The offset is provided by the user and used to locate the JNZ
byte:
file.seekg(offset, std::ios::beg);
if (!file) {
std::cerr << "Error: Offset out of range!" << std::endl;
file.close();
return;
}
3. Validate and Modify the Byte
This reads the byte, checks if it is 0x75
, and changes it to 0x74
:
uint8_t byte = 0;
file.read(reinterpret_cast<char*>(&byte), sizeof(byte));
if (byte == 0x75) {
byte = 0x74;
file.seekp(offset, std::ios::beg);
file.write(reinterpret_cast<const char*>(&byte), sizeof(byte));
std::cout << "Patched successfully!" << std::endl;
} else {
std::cerr << "Error: Byte is not `JNZ` (0x75)!" << std::endl;
}
4. Save and Close the File
After modifying, the file is safely closed:
file.close();
Note: The complete source code for the patcher is available in the project repository.
Step 4: Compile and Run the Patcher
Makefile
A Makefile
automates the build process for both the patcher and the executable, I have included one in the project repository.
Build and Run
- Use
make
to compile the patcher. - Run the patcher and provide the hexadecimal offset of the
JNZ
instruction.
Step 5: Test the Patched Binary
- Execute the modified
patchme.exe
. - Verify that the conditional logic now operates in the reversed manner (e.g., jumps on zero instead of not zero).
Summary
This process demonstrates how to:
- Use Ghidra to locate critical instructions.
- Verify offsets with a hex editor.
- Write a custom patcher to modify binary logic.
Always ensure you have the proper authorization before modifying any software. If this was a bit confusing, I have a much more in depth tutorial below :) Keygen Tutorial