Android中GOT表HOOK手动实现

网上对于Android中各种HOOK的实现都有很多的介绍了,之前看懂了原理相关的东西,一直没有去尝试手动实现,最近刚好想起就手动实现了一下,简单尝试记录下实现过程以及坑点。

如何理解HOOK?
我理解的HOOK就是去动态的修改代码段中相关跳转地址或者指令,执行我们的代码,然后跳转回去接着执行。

那既然这样,肯定要保证动态修改的操作先于该函数被执行,因此一般会将HOOK代码放在比较靠前被执行的地方,如init段、JNI_OnLoad等等。

实际操作:
实现了一个这样的函数,给他传递一个参数值,然后他返回这个参数值:

JNIEXPORT jint JNICALL ReturnGiveValue(int val)
{
    return val;
}

此时,我想用另外的函数替换这个函数的执行,比如这个函数:

JNIEXPORT jint JNICALL ReturnFakeValue(int val)
{
    return val * 100;
}

将这两个函数封装一个动态库,用以下代码构造的可执行文件去调用:

#include <jni.h>
#include <dlfcn.h>
#include <stdio.h>

typedef int (*RetValFunc)(int);

int main()
{
    int a = 10;
    void* testlib = dlopen("/data/local/tmp/libtest.so",RTLD_LAZY);
    if(testlib != NULL)
    {
        RetValFunc retval = (RetValFunc)dlsym(testlib,"ReturnGiveValue");
        int nBase = (int*)(testlib+0x8C);
        printf("ret val :  %x\n",nBase);
        printf("ret val :  %d\n",retval(a));
        dlclose(testlib);
    }
    return 0;
}

测试结果:
这里写图片描述

可以看到我输入的是10,输出的也是10。

接下来开始构造GOT表HOOK,我们需要阅读相关源码,加以理解文件结构方便找寻相应地址进行替换。

1、获取动态库基值
文件我直接放在固定目录下,然后硬编码的方式再次打开文件获取到文件的基值(参考soinfo结构体)

void* testlib = dlopen("/data/local/tmp/libtest.so",RTLD_LAZY);
int nBase = *(int*)(testlib+0x8C);
LOGD("nBase : %x",nBase);

2、计算program header table实际地址
通过ELF文件头获取到程序表头的偏移地址及表头的个数

    ElfW(Ehdr) *header = (ElfW(Ehdr)*)(nBase);
    if (memcmp(header->e_ident, "\177ELF", 4) != 0) {
        return 0;
    }

    int phOffset = header->e_phoff;
    int phNumber = header->e_phnum;
    int phPhyAddr = phOffset + nBase;

3、遍历program header table,ptype等于2即为dynameic,获取到p_offset
这里需要参照程序表头结构体的相关信息,程序表头结构体结构如下:

struct Elf32_Phdr {
  Elf32_Word p_type;   // Type of segment
  Elf32_Off  p_offset; // File offset where segment is located, in bytes
  Elf32_Addr p_vaddr;  // Virtual address of beginning of segment
  Elf32_Addr p_paddr;  // Physical address of beginning of segment (OS-specific)
  Elf32_Word p_filesz; // Num. of bytes in file image of segment (may be zero)
  Elf32_Word p_memsz;  // Num. of bytes in mem image of segment (may be zero)
  Elf32_Word p_flags;  // Segment flags
  Elf32_Word p_align;  // Segment alignment constraint
};

因此得到dynam段对应的地址

    for (int i = 0; i < phNumber; i++)
    {
        if (phdr_table[i].p_type == PT_DYNAMIC)
        {
            dynamicAddr = phdr_table[i].p_vaddr + nBase;
            dynamicSize = phdr_table[i].p_memsz;
            break;
        }
    }

4、开始遍历dynamic段结构,d_tag为6即为GOT表地址
同样需要参考动态链接段每项的结构体:

typedef struct dynamic {
    Elf32_Sword d_tag;
    union {
    Elf32_Sword d_val;
    Elf32_Addr d_ptr;
    } d_un;
} Elf32_Dyn;

因此知道遍历的方式:

    for(i=0;i < dynamicSize / 8;i ++)
    {
        int val = dynamic_table[i].d_un.d_val;
        if (dynamic_table[i].d_tag == 6)
        {
            systemTableAddr = val + nBase;
            break;
        }
    }

5、遍历GOT表,查找GOT表中标记的ReturnGiveValue函数地址,替换为ReturnFakeValue的地址
最后一步我们要知道表项的结构:

typedef struct elf32_sym {
    Elf32_Word st_name;
    Elf32_Addr st_value;
    Elf32_Word st_size;
    unsigned char st_info;
    unsigned char st_other;
    Elf32_Half st_shndx;
} Elf32_Sym;

然后进行替换目标函数的st_value,

    while(1)
    {
        LOGD("func Addr : %x",symTab[i].st_value);
        if(symTab[i].st_value == giveFunc)
        {
            symTab[i].st_value = fakeFunc;
            LOGD("New Give func Addr : %x",symTab[i].st_value);
            break;
        }
        i ++;
    }

HOOK之后的效果:
这里写图片描述

成功调用了我们hook的函数。

注意点:
1、我们知道代码段一般都只会设置为可读可执行的,因此需要使用mprotect改变内存页为可读可写可执行;
2、如果执行完这些操作后hook并没有生效,可能是由于缓存的原因,需要使用cacheflush函数对该内存进行操作。

附上SO完整代码:

#include <jni.h>
#include <dlfcn.h>
#include <elf.h>
#include <android/log.h> 
#include <sys/mman.h>
#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, "ror_got_hook", __VA_ARGS__))

#define MEM_PAGE_SIZE 4096
#define MEM_PAGE_MASK (MEM_PAGE_SIZE-1)
#define MEM_PAGE_START(x)  ((x) & ~MEM_PAGE_MASK)
#define MEM_PAGE_END(x)    MEM_PAGE_START((x) + (MEM_PAGE_SIZE-1))
#define ElfW(type) Elf32_ ## type


JNIEXPORT jint JNICALL ReturnGiveValue(int val)
{
    return val;
}

JNIEXPORT jint JNICALL ReturnFakeValue(int val)
{
    return val * 100;
}


int __attribute__((constructor))  gothook()
{
    //获取动态库基值
    void* testlib = dlopen("/data/data/com.test.test/libtest.so",RTLD_LAZY);
    int nBase = *(int*)(testlib+0x8C);
    LOGD("nBase : %x",nBase);

    //计算program header table实际地址
    ElfW(Ehdr) *header = (ElfW(Ehdr)*)(nBase);
    if (memcmp(header->e_ident, "\177ELF", 4) != 0) {
        return 0;
    }

    int phOffset = header->e_phoff;
    int phNumber = header->e_phnum;
    int phPhyAddr = phOffset + nBase;
    LOGD("phOffset  : %x",phOffset);
    LOGD("phNumber  : %x",phNumber);
    LOGD("phPhyAddr : %x",phPhyAddr);
    int i = 0;

    ElfW(Phdr)* phdr_table = (ElfW(Phdr)*)(nBase + phOffset);
    if (phdr_table == 0)
    {
        LOGD("phdr_table address : 0");
        return 0;
    }

/*
// Program header for ELF32.
struct Elf32_Phdr {
  Elf32_Word p_type;   // Type of segment
  Elf32_Off  p_offset; // File offset where segment is located, in bytes
  Elf32_Addr p_vaddr;  // Virtual address of beginning of segment
  Elf32_Addr p_paddr;  // Physical address of beginning of segment (OS-specific)
  Elf32_Word p_filesz; // Num. of bytes in file image of segment (may be zero)
  Elf32_Word p_memsz;  // Num. of bytes in mem image of segment (may be zero)
  Elf32_Word p_flags;  // Segment flags
  Elf32_Word p_align;  // Segment alignment constraint
};
*/
    //遍历program header table,ptype等于2即为dynameic,获取到p_offset
    unsigned long dynamicAddr = 0;
    unsigned int dynamicSize = 0;
    for (int i = 0; i < phNumber; i++)
    {
        if (phdr_table[i].p_type == PT_DYNAMIC)
        {
            dynamicAddr = phdr_table[i].p_vaddr + nBase;
            dynamicSize = phdr_table[i].p_memsz;
            break;
        }
    }
    LOGD("Dynamic Addr : %x",dynamicAddr);
    LOGD("Dynamic Size : %x",dynamicSize);

/*
typedef struct dynamic {
    Elf32_Sword d_tag;
    union {
    Elf32_Sword d_val;
    Elf32_Addr d_ptr;
    } d_un;
} Elf32_Dyn;
*/
    //开始遍历dynamic段结构,d_tag为6即为GOT表地址
    int systemTableAddr = 0;
    ElfW(Dyn)* dynamic_table = (ElfW(Dyn)*)(dynamicAddr);

    for(i=0;i < dynamicSize / 8;i ++)
    {
        int val = dynamic_table[i].d_un.d_val;
        if (dynamic_table[i].d_tag == 6)
        {
            systemTableAddr = val + nBase;
            break;
        }
    }
    LOGD("System Table Addr : %x",systemTableAddr);

/*
typedef struct elf32_sym {
    Elf32_Word st_name;
    Elf32_Addr st_value;
    Elf32_Word st_size;
    unsigned char st_info;
    unsigned char st_other;
    Elf32_Half st_shndx;
} Elf32_Sym;
*/
    //遍历GOT表,查找GOT表中标记的ReturnGiveValue函数地址,替换为ReturnFakeValue的地址
    int giveValuePtr = 0;
    int fakeValuePtr = 0;
    int fakeFunc = (int)ReturnFakeValue - nBase;
    int giveFunc = (int)ReturnGiveValue - nBase;
    i = 0;
    LOGD("fakeFunc Addr : %x",fakeFunc);
    LOGD("giveFunc Addr : %x",giveFunc);

    void* pstart = (void*)MEM_PAGE_START(((ElfW(Addr))systemTableAddr));
    mprotect(pstart,MEM_PAGE_SIZE,PROT_READ | PROT_WRITE | PROT_EXEC);
    ElfW(Sym)* symTab = (ElfW(Sym)*)(systemTableAddr);
    while(1)
    {
        LOGD("func Addr : %x",symTab[i].st_value);
        if(symTab[i].st_value == giveFunc)
        {
            symTab[i].st_value = fakeFunc;
            LOGD("New Give func Addr : %x",symTab[i].st_value);
            break;
        }
        i ++;
    }
    mprotect(pstart,MEM_PAGE_SIZE,PROT_READ | PROT_EXEC);

    return 0;
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值