Click here to Skip to main content
15,867,330 members
Articles / Security

Hot Patching C/C++ Functions with Intel Pin

Rate me:
Please Sign up or sign in to vote.
5.00/5 (5 votes)
27 Aug 2019CPOL5 min read 7K   7  
This article discusses the idea of Hot Patching C/C++ functions using Intel Pin in order to remove known vulnerabilities

5 years ago, I said in one of my articles that I shall return, one day, with a method of hot patching functions inside live processes; So… I guess this is that day.

What we’ll try to achieve here is to replace, from outside, a function inside a running executable, without stopping/freezing the process (or crashing it…).

In my opinion, applying hot patches is quite a daunting task, if implemented from scratch, since:

  • it requires access to a different process’ memory (most operating systems are fans of process isolation)
  • has software compatibility constraints (Windows binaries vs Linux binaries)
  • has architecture compatibility constraints (32bit vs 64bit)
  • it implies working with machine code and brings certain issues to the table
  • it has only a didactic purpose — probably no one would actually use a ‘from-scratch’ method since there are tools that do this better

Considering these, I guess it is better to use something that was actually written for this task and not coding something manually. Therefore, we’ll be looking at a way to do this with Intel Pin. I stumbled upon this tool while working at a completely different project but it seems to be quite versatile. Basically, it is described as a Dynamic Binary Instrumentation Tool, however we’ll be using it to facilitate the procedure of writing code to another process' memory.

1. Initial Preparations

Start by downloading Intel Pin and extract it somewhere in your workspace.

Disclaimer: I’m doing this tutorial on Ubuntu x86_64. You might see some bash.

Now, I imagine this turns out to be useful for endpoints that provide remote services to clients - i.e.: a server receives some sort of input and is expected to also return something. Let's say that someone discovered that a service is vulnerable to certain inputs - so it can be compromised by the first attacker who submits a specially crafted request. We'll consider that taking the service down, compiling, deploying and launching a new instance is not a desirable solution so hot patching is wanted until a new version is ready.

I’ll use the following dummy C program to illustrate the aforementioned model - to keep it simple, I'm reading inputs from stdin (instead of a TCP stream / network).

C++
#include <stdio.h>

// TODO: hot patch this method
void read_input()
{
    printf("Tell me your name:\n");
    
    char name[11];
    scanf("%s", name); // this looks bad
    
    printf("Hello, %s!\n\n", name);
}

int main()
{
    // not gonna end too soon
    while(1 == 1)
        read_input();
    
    return 0;
}

Some of you probably noticed that the read_input() function is not very well written since it's reading inputs using scanf("%s", name); and thus enabling an attacker to hijack the program's execution using buffer overflow.

Image 1

Scanf() reading exceeds the limits of the allocated buffer

We intend to patch this vulnerability by "replacing" the vulnerable reading function (read_input()) with another that we know is actually safe. I'm using quotes there to express the fact that it will act more like a re-routing procedure - the code of the original (vulnerable) function will still be in the process' memory, but all the calls will be forwarded to the new (patched) method.

I hope it makes sense for now.

2. Project’s Structure

Intel Pin works by performing actions, indicated in tools, to targeted binaries or processes. As an example, you may have a tool that says ‘increase a counter each time you find a RET instruction’ that you can attach to an executable and get the value of the counter at a certain time.

It offers a directory with examples of tools which can be found at: pin/source/tools/. In order to avoid updating makefile dependencies, we’ll work here so continue by creating a new directory (mine’s named Hotpatch) — this is where the coding happens.

Also, copy a makefile to your new directory, if you don’t feel like writing one:

BAT
cp ../SimpleExamples/makefile .

And use the following as your makefile.rules file:

BAT
TEST_TOOL_ROOTS := hotpatch # for hotpatch.cpp
SANITY_SUBSET := $(TEST_TOOL_ROOTS) $(TEST_ROOTS)

Finally, create a file named hotpatch.cpp with some dummy code and run the make command. If everything works fine, you should end up with something like this…

Image 2

Directory structure for the Hot Patch tool

3. Coding the Hot Patcher

The whole idea revolves around registering a callback which is called everytime the binary loads an image (see IMG_AddInstrumentFunction()). Since the method is defined in the running program, we're interested when the process loads its own image. In this callback, we look for the method that we want to hot patch (replace) - in my example, it's read_input().

You can list the functions that are present in a binary using:

BAT
nm targeted_binary_name

The process of replacing a function ( RTN_ReplaceSignatureProbed()) is based on probes - as you can tell by the name, which, according to Intel's claims, ensure less overhead and are less intrusive. Under the hood, Intel Pin will overwrite the original function's instructions with a JMP that points to the replacement function. It is up to you to call the original function, if needed.

Without further ado, the code I ended up with:

C++
#include "pin.H"
#include <iostream>
#include <stdio.h>


char target_routine_name[] = "read_input";

// replacement routine's code (i.e. patched read_input)
void read_input_patched(void *original_routine_ptr, int *return_address)
{
    printf("Tell me your name:\n");
    
    // 5 stars stdin reading method
    char name[12] = {0}, c;
    fgets(name, sizeof(name), stdin);
    name[strcspn(name, "\r\n")] = 0;

    // discard rest of the data from stdin
    while((c = getchar()) != '\n' && c != EOF);

    printf("Hello, %s!\n\n", name);
}

void loaded_image_callback(IMG current_image, void *v)
{
    // look for the routine in the loaded image
    RTN current_routine = RTN_FindByName(current_image, target_routine_name);
    
    // stop if the routine was not found in this image
    if (!RTN_Valid(current_routine))
        return;

    // skip routines which are unsafe for replacement
    if (!RTN_IsSafeForProbedReplacement(current_routine))
    {
        std::cerr << "Skipping unsafe routine " << target_routine_name << 
                     " in image " << IMG_Name(current_image) << std::endl;
        return;
    }

    // replacement routine's prototype: returns void, default calling standard, 
    // name, takes no arugments 
    PROTO replacement_prototype = PROTO_Allocate(PIN_PARG(void), 
           CALLINGSTD_DEFAULT, target_routine_name, PIN_PARG_END());

    // replaces the original routine with a jump to the new one 
    RTN_ReplaceSignatureProbed(current_routine, 
                               AFUNPTR(read_input_patched), 
                               IARG_PROTOTYPE, 
                               replacement_prototype,
                               IARG_ORIG_FUNCPTR,
                               IARG_FUNCARG_ENTRYPOINT_VALUE, 0,
                               IARG_RETURN_IP,
                               IARG_END);

    PROTO_Free(replacement_prototype);

    std::cout << "Successfully replaced " << target_routine_name << 
                 " from image " << IMG_Name(current_image) << std::endl;
}

int main(int argc, char *argv[])
{
    PIN_InitSymbols();

    if (PIN_Init(argc, argv))
    {
        std::cerr << "Failed to initialize PIN." << std::endl; 
        exit(EXIT_FAILURE);
    }

    // registers a callback for the "load image" action
    IMG_AddInstrumentFunction(loaded_image_callback, 0);
    
    // runs the program in probe mode
    PIN_StartProgramProbed();
    
    return EXIT_SUCCESS;
}

After running make, use a command like the following one to attach Intel Pin to a running instance of the targeted process.

BAT
sudo ../../../pin -pid $(pidof targeted_binary_name) -t obj-intel64/hotpatch.so

4. Results and Conclusions

Aaand it seems to be working:

Image 3

Testing the Hot Patched version against Buffer Overflow

To conclude, I’m pretty sure Intel Pin is capable of more complex stuff than what I’m presenting here — which I believe is examples-level (actually, it’s inspired by an example). To me, it seems rather strange that it is not a more popular tool — and no, I’m not paid by Intel to endorse it.

However, I hope this article manages to provide support and solutions/ideas to those who are looking at hot patching methods and who, like me, never heard of Intel Pin before.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Student
Romania Romania
Master's student @ ACS / UPB (Advanced Cybersecurity), Graduate Teaching Assistant, Junior Security Researcher. Also, webmaster of coding.vision

Comments and Discussions

 
-- There are no messages in this forum --