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.