This is far from your full problem (C#...) but it could give you some clues.
This example is done in C with gcc on Linux-64; you may have to adapt it on
your platform.
The first step was to write a function with some inline assembly (gnu-asm
syntax) in order to see how it looks with objdump (it is never called
actually).
The trick to jump to a constant address was found here:
https://stackoverflow.com/a/53876008/11527076
After that we have to create an executable memory segment and fill
it with bytes inspired from the previous function (thanks to objdump).
If the only thing that may be changed is the address, then we just overwrite
this address in the previous code.
In this example, the address of another function is used for this purpose.
Then this executable memory segment can be considered as a function
(hum... hopefully) and we use it through a function pointer.
And it seems to work!
The only problem I can see is the instruction movb $0xff,0xa(%ecx,%ebx,4)
which does something bad because I don't know exactly what the registers
should contain.
I decided to replace this instruction by six nop
to occupy the same place
and keep this example similar to the original problem.
(I suppose that in the context of your problem these registers will have a
relevant value).
/**
gcc -std=c99 -o prog_c prog_c.c \
-pedantic -Wall -Wextra -Wconversion \
-Wc++-compat -Wwrite-strings -Wold-style-definition -Wvla \
-g -O0 -UNDEBUG -fsanitize=address,undefined
**/
#undef __STRICT_ANSI__ // for MAP_ANONYMOUS
#include <sys/mman.h>
#include <stdio.h>
#include <string.h>
#include <stddef.h>
void
target_function(void)
{
printf("~~~~ %s ~~~~\n", __func__);
}
void
inline_asm_example(void)
{
__asm__ __volatile__(
"\tnop\n"
"\tmovb $0xff,0xa(%ecx,%ebx,4)\n"
"\tjmpq *0x0(%rip)\n"
".quad 0xAA00BB11CC22DD33\n");
// the jump relative to rip is inspired from
// https://stackoverflow.com/a/53876008/11527076
}
int
main(void)
{
// create an executable page
void *page=mmap(NULL, 6+6+8,
PROT_READ|PROT_WRITE|PROT_EXEC,
MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
if(page==MAP_FAILED)
{
fprintf(stderr, "cannot allocate memory page\n");
return 1;
}
// initialise code pattern
// objdump --disassemble=inline_asm_example prog
char *code_pattern=(char *)page;
#if 0 // this instruction causes something wrong
memcpy(code_pattern+0,
"\x67\xc6\x44\x99\x0a\xff", 6); // movb $0xff, 10(%ecx,%ebx,4)
#else // use some useless instructions instead
memcpy(code_pattern+0,
"\x90\x90\x90\x90\x90\x90", 6); // 6x nop
#endif
memcpy(code_pattern+6,
"\xff\x25\x00\x00\x00\x00", 6); // jmpq *0x0(%rip)
// insert into the pattern the address we want to jump to
ptrdiff_t target_address=(ptrdiff_t)target_function;
memcpy(code_pattern+6+6, &target_address, sizeof(target_address));
// consider the code pattern as a function
void (*fnct_ptr)(void)=NULL;
memcpy(&fnct_ptr, &code_pattern, sizeof(code_pattern));
// here we go
printf("about to do something I will probably regret...\n");
fnct_ptr();
printf("ah? it was not so painful after all!\n");
// let's forget everything about that
munmap(code_pattern, 6+6+8);
return 0;
}