0

To be specific, I cannot recompile this binary, nor do I have access to the sourcecode.

The functions are not defined within a shared library.

So, how can I go about changing a function, or preferably detouring it to a new function?

And if it's possible, use dlsym/dlopen to get my new modified code from a shared library, so I don't have to edit the binary by hand every time I want to change something.

Oh, one more thing, editing the actual binary is something I can do/I have access to.

And just for extra info, I have Radare2 installed, as well as GDB with pwndbg, so any solutions using those tools, or builtin GNU/Linux debugging tools would be appreciated.

EDIT:

I am a beginner to reverse engineering things, but not to the point I have zero idea what I'm doing.

  • PLEASE! Be more specific when asking a QUESTION! Provide us with the result of an ldd and a readelf -a of the executable. And, explain more in depth what kind of thing you want to do on this executable. – perror Jan 20 at 16:48
  • I've already used wisks answer as a guide and made a solution for myself. – Walaryne Jan 20 at 19:33
  • Then you should have marked it as "solved" asap... – perror Jan 21 at 10:10
  • I had to write up the code and actually see if it was a solution before marking it as such. That took time, so sorry for the inconveniences. – Walaryne Jan 21 at 21:03
2

You have several options here, but I like this one.

Since the code is already inside the executable, so you have to patch the entrypoint to jump to a new function. To do so, I suggest to use a shared object (.so). In this module you will both: the new function and a 'patcher function'. The latter function must be defined with __attribute__((constructor)), this attribute will force the ELF loader to call this function before the main function from your original executable is called.

The patcher function will set write access at the entrypoint of the old function with mprotect (2), encode the jump instruction, and it's probably better to remove the write access you needed for the patch.

Once you have your shared object, you can inject it using the LD_PRELOAD variable.

Edit with an example, since you didn't mentioned the architecture, I assumed this is for x86-64:

First this is a target, we want to change the function my_rand to return 0xdeadbeef.

#include <stdlib.h>
#include <stdio.h>
#include <time.h>

int my_rand(void)
{
        srand(time(NULL));
        return rand() + rand() ^ rand();
}

int main(void)
{
        printf("random value: %08x\n", my_rand());
}

To find the RVA, we can use nm because we have the symbol. You might have to find it manually.

nm target|grep my_rand
0000000000001169 T my_rand

Finally, the code of the patcher:

#include <sys/mman.h>
#include <unistd.h>
#include <dlfcn.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <link.h>

static int new_random(void)
{
        return 0xdeadbeef;
}

static void emit_jump_to_address(uint64_t address, uint64_t jump_destination)
{
        long page_size = sysconf(_SC_PAGESIZE);
        void* aligned_address = (void*)(address & ~(page_size - 1));
        if (mprotect(aligned_address, page_size, PROT_READ|PROT_WRITE|PROT_EXEC) < 0)
        {
                perror("mprotect");
                return;
        }
        *(uint16_t*)(address + 0x0) = 0xb848;           // mov rax, Iv
        *(uint64_t*)(address + 0x2) = jump_destination; // mov rax, jump_destination
        *(uint16_t*)(address + 0xa) = 0xe0ff;           // jmp rax
        if (mprotect(aligned_address, page_size, PROT_READ|PROT_EXEC) < 0)
        {
                perror("mprotect");
                return;
        }
}

// ref: https://stackoverflow.com/questions/19451791/get-loaded-address-of-a-elf-binary-dlopen-is-not-working-as-expected
uint64_t get_image_base(void)
{
        struct link_map* lm = dlopen(NULL, RTLD_NOW);
        return (uint64_t)lm->l_addr;
}

__attribute__((constructor)) void patcher(void)
{
        uint64_t target;

        target = get_image_base();
        target += 0x1169; // RVA
        printf("[!] targeted function is at %p\n", (void const*)target);
        printf("[!] new function is at %p\n", (void const*)&new_random);
        emit_jump_to_address(target, (uint64_t)&new_random);
}

This source code was compiled with clang and injected using LD_PRELOAD:

$ clang patcher.c  -o patcher.so -shared -ldl
$ env LD_PRELOAD=./patcher.so ./target
[!] targeted function is at 0x7fe05f907169
[!] new function is at 0x7fe05f8c11f0
random value: deadbeef

Note, I'm using fish, so the env is required.

  • Thanks, I really love this answer. It's a really creative way to go about doing this. One thing though, isn't attribute a GCC specific macro? It's not a deal breaker, as I'm pretty sure Clang can handle those macros, but if it can't, is there another way to achieve the same effect? – Walaryne Jan 17 at 17:03
  • Also, if it's not too much trouble, do you think you could write up some example code? Just a simple theoretical function in a binary, and what the shared library code could look like. Even some ASM representation somewhere, if that's possible. I mostly need clarification on how to use the "mprotect() then encode jmp" portion. – Walaryne Jan 17 at 17:45
  • __attribute__((constructor)) is supported by clang. And I'll try to write a quick example. – wisk Jan 21 at 16:18
  • I had tagged the question x86_64, should have said it explicitly in the question though, sorry. Thanks for the example. – Walaryne Jan 21 at 21:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.