BINARY PATCHING
This method is used to flip the logic of a program to the more interesting parts of the code.
This is the process of modifying a compiled executable at the byte level to alter its behavior without access to the original source code. This method is commonly used in software reverse engineering to bypass checks, change program logic, or redirect execution flow to more interesting parts of the code—such as hidden features or protected routines. By identifying and replacing key instructions (e.g., changing a JE to a JNE), reverse engineers can flip conditions, disable security mechanisms, or force specific code paths. This method requires a solid understanding of assembly, file formats, and the program's control flow to ensure stability and effectiveness.
SOURCE
#C SAMPLE CODE SNIPPET
root@dev:~$ visualstudio > 14_scanf_error_check.c
#include <stdio.h>
int main()
{
int x;
printf("Enter X:\n");
if(scanf("%d", %x)==1)
printf("You entered %d...\n", x);
else
printf("Woof.\n");
return 0;
}
PS C:> cl /MD /Od /Zi /FA 14_scanf_error_check.c
* ALT: cl /MD /Od /Zi /FA /Fa14_scanf_error_check_x64 /Fe14_scanf_error_check_x64 /Fo14_scanf_error_check_x64 14_scanf_error_check.c
PROCEDURE
In this example, binary patching will be used to display BOTH output of the printf statement regardless of whether the user input is valid. Aside from this, the main point is to make the changes persistent.
STEP 0: IMPORT AND AUTO-ANALYZE PROJECT FILE
root@dev:~$ .\ghidra
ghidra > File > New Project > Non-Shared Project
Project Directory: {PROJECTS}
Project Name: {BINARY_PATCHING}
ghidra > File > Import File...
Filename: {binaryWODebugSymbols.exe}
ghidra > Code Browser > binaryWODebugSymbols.exe
Analyze: Yes
Analysis Options: Default
* Wait for analysis to finish..."Decompile" Section should look like below
void entry(void)
{
__security_init_cookie();
FUN_140001274();
return;
}
STEP 1: BIG PICTURE OVERVIEW
ghidra > Sub-Menu > Display Function Graph
* get a big picture overview of what the program is doing
- e.g, in this particular example, the program is accepting user input
and displays whether one of two actions/output is followed (if/else)

STEP 2: REBASE
REBASE: IDENTIFY BASE ADDRESS (WINDBG)
PS C:\sre> WinDBG
...
WinDBG > File > Open Executable > anti-debug.exe
//step 1: list the modules and identify the "base address" of the anti-debug program
WinDBG > Command
0:000> lm
Start End module_name
00c00000 00c6b000 anti_debug (deferred)
76a70000 76b60000 KERNEL32 (deferred)
770f0000 7730c000 KERNELBASE (deferred)
77320000 774c4000 ntdll (pdb symbols) c:\ProgramData\dbg\sym\wntdll.
* ghidra will be set to the same "base address" found by WinDBG
- Base Address: 00c00000
REBASE: APPLY IDENTIFIED BASE ADDRESS (GHIDRA)
Ghidra must be set to the same "base address" found by WinDBG. When the interesting code parts are found and identified, no address translation will be required, as the addresses in WinDBG and Ghidra will match.
Ghidra > Sub-Menu > Display Memory Map > Home Icon
Base Image Address: 00c00000
* ghidra's default base address is 00400000
STEP 3: HUNT MAIN()
IDENTIFY PATTERNS (RUNNING PROGRAM)
this may not always be accurate
root@sre:~$ .\antidebugging.exe
Hi, what's your name? Enter as first_last:
LOCATE PATTERNS (GHIDRA)
when you find the function that has similar patterns when the program is run, this could be your clue
#step 1:
ghidra > File > Open > 15_function_args_no_debug.exe
filter: main
* since there is no debug symbols when the executable was compiled,
main won't be found
#step 2:
ghidra > Symbol Tree > Functions
Filter: entry
Decompile Section:
void entry(void)
{
__security_init_cookie();
__scrt_common_main_seh();
return;
}
* clicking on "entry" will automatically decompile it
- entry is NOT main(), but leads to it
- this will drop you to where ghidra thinks the main function can be located
#step 3:
ghidra > Decompile > double-click "__scrt_common_main_seh()"
* The "__scrt_common_main_seh()" is responsible for setting up exception handling and
eventually calling main().
- Goal: Find __scrt_common_main();
- if "__scrt_common_main_seh()" is NOT listed, look for alternatives such as...
"FUN_..."
- e.g., FUN_03ad1274()
* __scrt_common_main_seh(); is a function commonly found in Windows executables
compiled with Microsoft's C runtime (CRT). It is part of the Microsoft Startup Code
and is involved in setting up exception handling for the main program.
- this function is responsible for several things including...
- Calling main() or WinMain(): Once the initialization is complete, it calls the
user-defined main() (for console apps) or WinMain() (for GUI apps).
- __scrt_common_main_seh(); is just a wrapper around main() or WinMain()
* the __security_init_cookie(); is a function that initializes a global security
cookie used to detect stack buffer overflows at runtime. It is part of /GS
(Buffer Security Check), a compiler flag that adds stack canary protection to
functions that may be vulnerable to buffer overflows.
#step 4: continue w/ working in reverse until the main function pattern is identified
...
iVar1 = __cinit(1);
if (iVar1 != 0){
__amsg_exit(iVar1);
}
_DAT_0040cf34 = DAT_0040cf30;
iVar1 = FUN_00401010();
_exit(iVar1);
__cexit();
return iVar1;
* if you see __{arbitraryString}...it means that its coming from imports
- Symbol Tree > Imports > KERNEL32.DLL
#step 5: typically, the first function encountered when working in reverse that DOESN'T start
w/ __ should be the main function
iVar1 = FUN_00401010();
_exit(iVar1);
__cexit();
return iVar1;
* double click the FUN_00401010() in iVar1 = FUN_00401010(); to get to its
memory location
undefined4 FUN_00401010(void)
{
int iVar1;
iVar1 = FUN_00401000(1, 2, 3);
_printf(&DAT_0040c000,iVar1);
return 0;
}
#step 6: rename the function to make it clear to ghidra that this is the main function
***********************
* FUNCTION *
***********************
undefined4 __stdcall FUN_00401010(void) //hit the "l" key on the keyboard or right-click it to rename
- rename FUN... to main
STEP 4: LOCATE PERTINENT INSTRUCTION
#step 3: locate the first compare instuction then patch instruction to ensure that no matter what, the comparison will always be equal!
140001033 83 f8 01 CMP EAX, 0x1 //change 0x1 to EAX to the instruction will become EAX, EAX
#step 4: patch instruction
ghidra > listing window area > right-click on the instruction (CMP EAX, 0x1 > patch instruction > OK
140001033 39 c0 CMP EAX, EAX
140001035 01 ?? 01h // change this to NOP
* comparing the value against itself will always lead to equality; hence, the next jump instruction
won't occur - this ensure that the instruction is always executed
* when patching instruction, the instruction must either be the same number of bytes or less IOT not overwrite the rest of the code
- must use NOP if less
#step 5: view instruction info
ghidra > listing window area > right-click on the instruction 140001033 39 c0 CMP EAX, EAX > instruction info
#step 6: patch extra byte w/ NOP
ghidra > listing window area > right-click on the instruction 140001035 01 ?? 01h > patch instruction
NOP
140001035 90 NOP //this is a valid instruction that means "carry on"
* in the decompiler view, you should notice that the "if" instruction has disappered
#step 7: continue patching until the else statement is removed - this can be done two ways
#method 1: change the JMP instruction to go elsewhere or method 2: patch using one or more NOP instruction!
ghidra > listing window area > right-click on the instruction 140001049 eb 0d JMP LAB_140001048 > patch instruction
change: 140001049 eb 0d JMP 14000104b
* this change jumps to the label LAB_14000104b XREF(1): 140001036(j)
14000104b 48 8d LEA _Argc,[s_woof._140006024].
#step 8: save the binary as a patched executable
#method 1:
ghidra > File > Export Program
Format: PE
Output File: patchedExecutable.exe
#method 2: ghidra script
ghidra > Display Script Manager > Manage Script Directories > + > Desktop\git-sre\resources\
* this will import the "binary_export.java" script into ghidra
ghidra > Display Script Manager > Binary > right-click binary_export.java > Edit with basic editor
* this file may need to be edited if ghidra displays a warning
- change Binary_Export in public class... to "binary_export" - lowercase.
ghidra > Display Script Manager > double-click on binary_export.java
Filename: scriptPatched.exe
Type: All Files (*.*)
PS C:\> .\patchedExecutable.exe
Enter X:
123
You entered 123...
woof.
PS C:\> .\patchedExecutable.exe
Enter X:
c
You entered 0...
woof.
PS C:\> .\scriptPatched.exe
Enter X:
c
You entered 0...
woof.
root@dev:~$ ghidra binaryWODebugSymbols.exe
* re-create and re-compile with and w/o debug symbols and identify the main entry point
as practice
-
Last updated