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