How to Patch Functions with OFRAK’s FunctionReplacementModifier

One of the most useful features in OFRAK is the powerful PatchMaker library, providing capabilities to build and inject C source code into existing binaries. OFRAK’s FunctionReplacementModifier provides an easy-to-use API that leverages the PatchMaker to replace one or more functions in a binary. This post will walk through how this works.

Consider the following program, validate_input.c, which rejects any input that contains the character “}”:

				
					int validate_input(char* user_input){
    int i = 0;
    
    while(user_input[i]){
        if (user_input[i] == '}'){
            return 0;
        }
        i++;
    }
    return 1;
}

int main(int argc, char** argv){
    char* input = argv[1];
    
    if (validate_input(input)){
        printf("Input accepted!\n");
        return 0;
    }else{
        printf("Input rejected!\n");
        return 1;
    }
}
				
			

We can build this program and quickly validate that it works as expected:

				
					>> gcc -o validate_input validate_input.c
>> ./validate_input "input1"
Input accepted!
>> ./validate_input "input}1"
Input rejected!
>> 

				
			

Now let’s imagine that, for whatever reason, we need to change this program such that “}” is valid input if it is escaped with a backslash (the Voldemort of ASCII characters – never literally type it out unless absolutely necessary). Why does the customer need “}” in their input sometimes? Don’t ask me, I’m just the engineer.

A typical forward-engineering solution would involve updating the validate_input inside of our source file to something like this:

				
					int validate_input(char* user_input){
    int i = 0;
    
    while(user_input[i]){
        if (user_input[i] == '}'){
            // If this is the first character (no prev char) or the prev char is NOT backslash, fail validation
            if (i == 0 || user_input[i-1] != '\\') return 0;
        }
        i++;
    }
    return 1;
}

				
			

Next, the forward-engineer would recompile the program.

Consider, however, that you need to apply this patch with the constraint that you cannot recompile the entire program (maybe you don’t have the complete source code or toolchains needed to recompile, or just do not want to). How might you approach this?

 

Maybe you are an assembly whiz and enjoy writing, injecting, and debugging shellcode. For the non-masochists, however, OFRAK’s FunctionReplacementModifier allows us to easily take the above C patch (let’s call it validate_input_patch.c) and inject it into the binary without recompiling the entire binary or needing access to source code.

 

The OFRAK script to do this is pretty straightforward! The FunctionReplacementModifier takes a handful of arguments that are easy to summarize:

  • Where are the source files? In this case, we should put all of our source files in a directory called “patch_src”.
  • What function(s) are being replaced and what source file has the replacement?
  • What are the parameters for compiled code? Most of this stuff is honestly irrelevant here, like DON’T force inlines, DO avoid emitting jump tables, etc.
  • What toolchain should be used? x86-64 GNU Linux EABI is used in this case, but if we wanted a patch for different architecture, we can change this line to a different toolchain.

The script looks like this:

				
					from ofrak import *
from ofrak.core import *
from ofrak_patch_maker.toolchain.model import  *
from ofrak_patch_maker.toolchain.gnu_x64 import *

import ofrak_angr

async def main(ofrak_context, input_file):
    target_binary = await ofrak_context.create_root_resource_from_file(input_file)
    
    await target_binary.unpack_recursively()
    
    await target_binary.run(
        FunctionReplacementModifier,
        FunctionReplacementModifierConfig(
            SourceBundle.slurp("patch_src"),
            {"validate_input": "validate_input_patch.c"},
            ToolchainConfig(
                file_format=BinFileType.ELF, 
                force_inlines=False, 
                relocatable=False, 
                no_std_lib=False, 
                no_jump_tables=True, 
                no_bss_section=True, 
                compiler_optimization_level=CompilerOptimizationLevel.SPACE,
            ),
            GNU_X86_64_LINUX_EABI_10_3_0_Toolchain,
        )
    )
    
    await target_binary.flush_to_disk(input_file + ".patched")


o = OFRAK()
o.discover(ofrak_angr)
o.run(main, "validate_input")

				
			

This script takes a few seconds to run. As described previously, OFRAK will unpack and analyze the target (in this case using the angr backend), then build the patch, find the existing validate_input function, and overwrite it with our patch. Just to encourage you to give it a try as much as possible, we’ll leave out the oh-so-exciting payoff of the expected output from the patched binary. Actually applying and running the patch is left as an exercise to the reader.

We’ll leave you with a little bit of food for thought as well:

  1. We indicated that PatchMaker should optimize the patch for space. What happens if we change this to CompilerOptimizationLevel.NONE?

  2. Could we patch the main function instead of validate_input?

  3. What if we patched main and validate_input? Is it even still the same program? Has science gone too far? (This is a philosophical question, but not a rhetorical one. Discuss.)

Learn More at OFRAK.COM

LEVERAGE OUR EXPERTISE FOR YOUR SECURITY NEEDS

Reach out to learn more about our embedded security offering and to schedule a demo.

LEVERAGE OUR EXPERTISE FOR YOUR SECURITY NEEDS

Reach out to learn more about our embedded security offering and to schedule a demo.

LEVERAGE OUR EXPERTISE FOR YOUR SECURITY NEEDS

Reach out to learn more about our embedded security offering and to schedule a demo.

LEVERAGE OUR EXPERTISE FOR YOUR SECURITY NEEDS

Reach out to learn more about our embedded security offering and to schedule a demo.