How to create thunk in x64?

痞子三分冷 提交于 2019-12-14 03:22:29

问题


I've found nice example how to create thunk for closure, but it's 32-bit version:

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>

struct env {
  int x;
};

struct __attribute__((packed)) thunk {
  unsigned char push;
  struct env * env_addr;
  unsigned char call;
  signed long call_offset;
  unsigned char add_esp[3];
  unsigned char ret;
};

struct thunk default_thunk = {0x68, 0, 0xe8, 0, {0x83, 0xc4, 0x04}, 0xc3};

typedef void (* cfunc)();

struct thunk * make_thunk(struct env * env, void * code)
{
  struct thunk * thunk = (struct thunk *)mmap(0,sizeof(struct thunk), PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  *thunk = default_thunk;
  thunk->env_addr = env;
  thunk->call_offset = code - (void *)&thunk->add_esp[0]; // Pretty!                                                                               
  mprotect(thunk,sizeof(struct thunk), PROT_EXEC);
  return thunk;
}


void block(struct env * env) {
  env->x += 1;
  printf ("block: x is %d\n", env->x);
}

cfunc foo (int x)
{
  struct env * env = (struct env *)malloc(sizeof(struct env));
  env->x = x;

  printf ("x is %d\n",env->x);

  return (cfunc)make_thunk(env,(void *)&block);
}

int main() {
  cfunc c = foo(5);

  c();
  c();
}

How can I rewrite it for 64-bit version?

I'm using Linux x86_64. I've been able to cross-compile it with gcc -m32, which worked perfectly.


回答1:


The code below is designed to be used with GCC on Linux and should support 32 and 64 bit compilation.

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>

struct env {
    int x;
};

#if __x86_64__
struct __attribute__((packed)) thunk {
    unsigned char mov[2];
    struct env * env_addr;
    unsigned char movrax[2];
    void (*call_address)();
    unsigned char jmp[2];
};

struct thunk default_thunk = {{0x48, 0xbf}, 0x0, {0x48, 0xb8}, 0x0, {0xff, 0xe0} };
#elif __i386__
struct __attribute__((packed)) thunk {
    unsigned char push;
    struct env * env_addr;
    unsigned char call;
    signed long call_offset;
    unsigned char add_esp[3];
    unsigned char ret;
};
struct thunk default_thunk = {0x68, 0, 0xe8, 0, {0x83, 0xc4, 0x04}, 0xc3};
#else
#error Architecture unsupported
#endif


typedef void (* cfunc)();

struct thunk * make_thunk(struct env * env, void * code)
{
    struct thunk * thunk = (struct thunk *)mmap(0,sizeof(struct thunk), 
                            PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    *thunk = default_thunk;
#if __x86_64__
    thunk->env_addr = env;
    thunk->call_address = code; /* Pretty! */
#else
    thunk->env_addr = env;
    thunk->call_offset = code - (void *)&thunk->add_esp[0]; /* Pretty! */
#endif
    mprotect(thunk,sizeof(struct thunk), PROT_EXEC);
    return thunk;
}


void block(struct env * env) {
    env->x += 1;
    printf ("block: x is %d\n", env->x);
}

cfunc foo (int x)
{
    struct env * env = (struct env *)malloc(sizeof(struct env));
    env->x = x;

    printf ("x is %d\n",env->x);

    return (cfunc)make_thunk(env,(void *)&block);
}

int main() {
    cfunc c = foo(5);
    c();
    c();

    return 0;
}

Assuming that the OS is using System V 64bit ABI (Which Linux uses) calling convention then the first parameter that will be passed to the function will be in register %rdi. Then we just have to mov the environment address (env_addr) to %rdi and then do a call. The call uses an indirect jump to an absolute location through %rax. So the instruction sequence looks like (at&t syntax):

mov    $env_addr, %rdi
movabs $call_pointer, %rax
jmpq  *%rax                   # Tail call instead of call/retq


来源:https://stackoverflow.com/questions/32413697/how-to-create-thunk-in-x64

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!