寻找so中符号的地址

寻找so中符号的地址

总述

我们在使用so中的函数的时候可以使用dlopen和dlsym配合来寻找该函数的起始地址,但是在安卓高版本中android不允许打开白名单之外的so,这就让我们很头疼,比如我们hook libart.so中的函数都没有办法来找到函数的具体位置,所以有了此文,这里介绍3种方法来获得符号的地址,网上方案挺多的我这里主要介绍原理

通过程序头获得符号地址

首先是如何找到so的首地址,这个android系统中提供了maps文件来记录so的内存分步,所以我们可以遍历maps文件来寻找so的首地址,如下

   char line[1024];
    int *start;
    int *end;
    int n=1;
    FILE *fp=fopen("/proc/self/maps","r");
    while (fgets(line, sizeof(line), fp)) {
        if (strstr(line, "libart.so") ) {
            __android_log_print(6,"r0ysue","");
            if(n==1){
                start = reinterpret_cast<int *>(strtoul(strtok(line, "-"), NULL, 16));
                end = reinterpret_cast<int *>(strtoul(strtok(NULL, " "), NULL, 16));
            }
            else{
                strtok(line, "-");
                end = reinterpret_cast<int *>(strtoul(strtok(NULL, " "), NULL, 16));
            }
            n++;
        }
    }

通过elf头结构我们可以找到程序头的地址,ndk中自带了elf.h就很友好,就是e_phoff是相对于我们上面扫到的so首地址的偏移,e_phnum是我们的程序头表中结构体的总个数,程序头中存着elf装载信息,如下图

这里有一个问题就是上面的地址是so的起始地址,不是load_bias,所以我们在计算物理偏移的时候要减去一个首段的物理偏移,这里需要遍历程序头,得到第一个e_type为1的段记录下它的p_vaddr。其中对我们索引符号地址有用的就是Dynamic Segment,也就是type为2的段,这部分可以写一个循环来找到,去记录下其中的字符串表和符号表就可以了

    Elf64_Ehdr header;
    memcpy(&header, startr, sizeof(Elf64_Ehdr));
   memcpy(&cc, ((char *) (startr) + header.e_phoff), sizeof(Elf64_Phdr));
    for (int y = 0; y < header.e_phnum; y++) {//寻找首段偏移
        memcpy(&cc, (char *) (startr) + header.e_phoff + sizeof(Elf64_Phdr) * y,
               sizeof(Elf64_Phdr));
        if (cc.p_type == 1) {
            phof =cc.p_paddr
            break;
        }

    }
   for (int y = 0; y < header.e_phnum; y++) {
        memcpy(&cc, (char *) (startr) + header.e_phoff + sizeof(Elf64_Phdr) * y,
               sizeof(Elf64_Phdr));
        if (cc.p_type == 2) {
            Elf64_Dyn dd;
            for (y = 0; y == 0 || dd.d_tag != 0; y++) {
                memcpy(&dd, (char *) (startr) + cc.p_offset + y * sizeof(Elf64_Dyn) + 0x1000,
                       sizeof(Elf64_Dyn));

                if (dd.d_tag == 5) {//符号表
                    strtab_ = reinterpret_cast< char *>((char *) startr + dd.d_un.d_ptr - phof);
                }
                if (dd.d_tag == 6) {//字符串表
                    symtab_ = reinterpret_cast<Elf64_Sym *>((
                            (char *) startr + dd.d_un.d_ptr - phof));
                }
                if (dd.d_tag == 10) {//字符串表大小
                    strsz = dd.d_un.d_val;
                }


            }
        }
    }

接下来遍历符号表就可以了,这里有一个问题就是如何确定符号表的大小,这里观察一下ida反编译的结果,发现符号表后面接的就是字符串表,那么用字符串表的首地址减去符号表的首地址就是符号表的大小,之后再用Elf64_Sym结构体解析,st_value就是该函数相对于load_bias的物理偏移,所以我们最后.再减去之前记录的首段偏移即可

   char strtab[strsz];
    memcpy(&strtab, strtab_, strsz);
    Elf64_Sym mytmpsym;
    for (n = 0; n < (long) strtab_ - (long) symtab_; n = n + sizeof(Elf64_Sym)) {//遍历符号表
    memcpy(&mytmpsym,(char*)symtab_+n,sizeof(Elf64_Sym));
 if(strstr(strtab_+mytmpsym.st_name,"artFindNativeMethod"))
   {    __android_log_print(6,"r0ysue","%p %s",mytmpsym.st_value,strtab_+mytmpsym.st_name);
        break;
   }
    }
    return (char*)start+mytmpsym.st_value-phof;

通过节头获得符号地址

通过elf头结构我们也可以找到节头的地址,也就是e_shoff,节头表相对于程序头表就友好许多,它的项非常多,唯一不好的一点就是它不会加载到内存中,所以Execution View中就没有这个东西,所以我们只能通过绝对路径找到它,手动解析文件

    int fd;
    void *start;
    struct stat sb;
    fd = open(lib, O_RDONLY);
    fstat(fd, &sb);
    start = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);

在这种解析方式中我们在elf头中需要的值是e_shoff、e_shentsize、e_shnum、e_shstrndx,就是节头表偏移,节大小,节个数,节头表字符串,不过我们最终的目标仍然是拿到符号表和字符串表,也就是下面的symtab和strtab中的sh_offset

 Elf64_Ehdr header;
    memcpy(&header, start, sizeof(Elf64_Ehdr));
    int secoff = header.e_shoff;
    int secsize = header.e_shentsize;
    int secnum = header.e_shnum;
    int secstr = header.e_shstrndx;
    Elf64_Shdr strtab;
    memcpy(&strtab, (char *) start + secoff + secstr * secsize, sizeof(Elf64_Shdr));
    int strtaboff = strtab.sh_offset;
    char strtabchar[strtab.sh_size];

    memcpy(&strtabchar, (char *) start + strtaboff, strtab.sh_size);
    Elf64_Shdr enumsec;
    int symoff = 0;
    int symsize = 0;
    int strtabsize = 0;
    int stroff = 0;
    for (int n = 0; n < secnum; n++) {

        memcpy(&enumsec, (char *) start + secoff + n * secsize, sizeof(Elf64_Shdr));


        if (strcmp(&strtabchar[enumsec.sh_name], ".symtab") == 0) {
            symoff = enumsec.sh_offset;
            symsize = enumsec.sh_size;

        }
        if (strcmp(&strtabchar[enumsec.sh_name], ".strtab") == 0) {
            stroff = enumsec.sh_offset;
            strtabsize = enumsec.sh_size;

        }


    }

最后和上面一样遍历符号表即可可得到物理偏移

    int realoff=0;
    char relstr[strtabsize];
    Elf64_Sym tmp;
    memcpy(&relstr, (char *) start + stroff, strtabsize);

    for (int n = 0; n < symsize; n = n + sizeof(Elf64_Sym)) {
        memcpy(&tmp, (char *)start + symoff+n, sizeof(Elf64_Sym));
        if(tmp.st_name!=0&&strstr(relstr+tmp.st_name,sym)){
            realoff=tmp.st_value;
            break;
        }
    }
    return realoff;

这种方式能够找到非导出符号的地址,还是有一定作用的,比如我在寻找soinfo地址的时候就用到了寻找soinfo_map在linker中的相对地址

模仿安卓通过hash寻找符号

这种方式就是dlsym的官方写法,由于libart.so这种so自动就会加载到内存种所以就不需要dlopen了,我们只需要在map里面找到它的首地址就可以了,代码和上面一样就不贴了,这里我们主要看看官方如何实现的,一路追踪do_dlopen最终找到了函数soinfo::gnu_lookup,这里面是他的主要实现逻辑,我们只需要实现它即可,这里多了4个项我们之前没有提到,就是它的导出表4项,所以这种方法只能找到导出表当中的函数或者变量

size_t gnu_nbucket_ = 0;
    // skip symndx
    uint32_t gnu_maskwords_ = 0;
    uint32_t gnu_shift2_ = 0;
    ElfW(Addr) *gnu_bloom_filter_ = nullptr;
    uint32_t *gnu_bucket_ = nullptr;
    uint32_t *gnu_chain_ = nullptr;
    int phof = 0;
    Elf64_Ehdr header;
    memcpy(&header, startr, sizeof(Elf64_Ehdr));
    uint64 rel = 0;
    size_t size = 0;
    long *plt = nullptr;
    char *strtab_ = nullptr;
    Elf64_Sym *symtab_ = nullptr;
    Elf64_Phdr cc;
    memcpy(&cc, ((char *) (startr) + header.e_phoff), sizeof(Elf64_Phdr));
    for (int y = 0; y < header.e_phnum; y++) {
        memcpy(&cc, (char *) (startr) + header.e_phoff + sizeof(Elf64_Phdr) * y,
               sizeof(Elf64_Phdr));
        if (cc.p_type == 6) {
            phof = cc.p_paddr - cc.p_offset;//改用程序头的偏移获得首段偏移用之前的方法也行
        }
    }
    for (int y = 0; y < header.e_phnum; y++) {
        memcpy(&cc, (char *) (startr) + header.e_phoff + sizeof(Elf64_Phdr) * y,
               sizeof(Elf64_Phdr));
        if (cc.p_type == 2) {
            Elf64_Dyn dd;
            for (y = 0; y == 0 || dd.d_tag != 0; y++) {
                memcpy(&dd, (char *) (startr) + cc.p_offset + y * sizeof(Elf64_Dyn) + 0x1000,
                       sizeof(Elf64_Dyn));

                if (dd.d_tag == 0x6ffffef5) {//0x6ffffef5为导出表项

                    gnu_nbucket_ = reinterpret_cast<uint32_t *>((char *) startr + dd.d_un.d_ptr -
                                                                phof)[0];
                    // skip symndx
                    gnu_maskwords_ = reinterpret_cast<uint32_t *>((char *) startr + dd.d_un.d_ptr -
                                                                  phof)[2];
                    gnu_shift2_ = reinterpret_cast<uint32_t *>((char *) startr + dd.d_un.d_ptr -
                                                               phof)[3];

                    gnu_bloom_filter_ = reinterpret_cast<ElfW(Addr) *>((char *) startr +
                                                                       dd.d_un.d_ptr + 16 - phof);
                    gnu_bucket_ = reinterpret_cast<uint32_t *>(gnu_bloom_filter_ + gnu_maskwords_);
                    // amend chain for symndx = header[1]
                    gnu_chain_ = reinterpret_cast<uint32_t *>( gnu_bucket_ +
                                                               gnu_nbucket_ -
                                                               reinterpret_cast<uint32_t *>(
                                                                       (char *) startr +
                                                                       dd.d_un.d_ptr - phof)[1]);

                }
                if (dd.d_tag == 5) {
                    strtab_ = reinterpret_cast< char *>((char *) startr + dd.d_un.d_ptr - phof);
                }
                if (dd.d_tag == 6) {
                    symtab_ = reinterpret_cast<Elf64_Sym *>((
                            (char *) startr + dd.d_un.d_ptr - phof));
                }
           
            }
        }
    }

之后模仿gnu_lookup函数即可,hashmap的查询方法

 char* name_=symname;//直接抄的安卓源码
    uint32_t h = 5381;
    const uint8_t* name = reinterpret_cast<const uint8_t*>(name_);
    while (*name != 0) {
        h += (h << 5) + *name++; // h*33 + c = h + h * 32 + c = h + h << 5 + c
    }
    int index=0;
    uint32_t h2 = h >> gnu_shift2_;
    uint32_t bloom_mask_bits = sizeof(ElfW(Addr))*8;
    uint32_t word_num = (h / bloom_mask_bits) & gnu_maskwords_;
    ElfW(Addr) bloom_word = gnu_bloom_filter_[word_num];
    n = gnu_bucket_[h % gnu_nbucket_];
    do {
        Elf64_Sym * s = symtab_ + n;
        char * sb=strtab_+ s->st_name;
        if (strcmp(sb ,reinterpret_cast<const char *>(name_)) == 0 ) 
<!-- @import "[TOC]" {cmd="toc" depthFrom=1 depthTo=6 orderedList=false} -->

<!-- code_chunk_output -->

- [寻找so中符号的地址](#寻找so中符号的地址)
  - [总述](#总述)
  - [通过程序头获得符号地址](#通过程序头获得符号地址)
  - [通过节头获得符号地址](#通过节头获得符号地址)
  - [模仿安卓通过hash寻找符号](#模仿安卓通过hash寻找符号)
- [总结](#总结)

<!-- /code_chunk_output -->

## 寻找so中符号的地址
### 总述
我们在使用so中的函数的时候可以使用dlopen和dlsym配合来寻找该函数的起始地址,但是在安卓高版本中android不允许打开白名单之外的so,这就让我们很头疼,比如我们hook libart.so中的函数都没有办法来找到函数的具体位置,所以有了此文,这里介绍3种方法来获得符号的地址,网上方案挺多的我这里主要介绍原理
### 通过程序头获得符号地址
首先是如何找到so的首地址,这个android系统中提供了maps文件来记录so的内存分步,所以我们可以遍历maps文件来寻找so的首地址,如下
```c
   char line[1024];
    int *start;
    int *end;
    int n=1;
    FILE *fp=fopen("/proc/self/maps","r");
    while (fgets(line, sizeof(line), fp)) {
        if (strstr(line, "libart.so") ) {
            __android_log_print(6,"r0ysue","");
            if(n==1){
                start = reinterpret_cast<int *>(strtoul(strtok(line, "-"), NULL, 16));
                end = reinterpret_cast<int *>(strtoul(strtok(NULL, " "), NULL, 16));
            }
            else{
                strtok(line, "-");
                end = reinterpret_cast<int *>(strtoul(strtok(NULL, " "), NULL, 16));
            }
            n++;
        }
    }

通过elf头结构我们可以找到程序头的地址,ndk中自带了elf.h就很友好,就是e_phoff是相对于我们上面扫到的so首地址的偏移,e_phnum是我们的程序头表中结构体的总个数,程序头中存着elf装载信息,如下图

这里有一个问题就是上面的地址是so的起始地址,不是load_bias,所以我们在计算物理偏移的时候要减去一个首段的物理偏移,这里需要遍历程序头,得到第一个e_type为1的段记录下它的p_vaddr。其中对我们索引符号地址有用的就是Dynamic Segment,也就是type为2的段,这部分可以写一个循环来找到,去记录下其中的字符串表和符号表就可以了

    Elf64_Ehdr header;
    memcpy(&header, startr, sizeof(Elf64_Ehdr));
   memcpy(&cc, ((char *) (startr) + header.e_phoff), sizeof(Elf64_Phdr));
    for (int y = 0; y < header.e_phnum; y++) {//寻找首段偏移
        memcpy(&cc, (char *) (startr) + header.e_phoff + sizeof(Elf64_Phdr) * y,
               sizeof(Elf64_Phdr));
        if (cc.p_type == 1) {
            phof =cc.p_paddr
            break;
        }

    }
   for (int y = 0; y < header.e_phnum; y++) {
        memcpy(&cc, (char *) (startr) + header.e_phoff + sizeof(Elf64_Phdr) * y,
               sizeof(Elf64_Phdr));
        if (cc.p_type == 2) {
            Elf64_Dyn dd;
            for (y = 0; y == 0 || dd.d_tag != 0; y++) {
                memcpy(&dd, (char *) (startr) + cc.p_offset + y * sizeof(Elf64_Dyn) + 0x1000,
                       sizeof(Elf64_Dyn));

                if (dd.d_tag == 5) {//符号表
                    strtab_ = reinterpret_cast< char *>((char *) startr + dd.d_un.d_ptr - phof);
                }
                if (dd.d_tag == 6) {//字符串表
                    symtab_ = reinterpret_cast<Elf64_Sym *>((
                            (char *) startr + dd.d_un.d_ptr - phof));
                }
                if (dd.d_tag == 10) {//字符串表大小
                    strsz = dd.d_un.d_val;
                }


            }
        }
    }

接下来遍历符号表就可以了,这里有一个问题就是如何确定符号表的大小,这里观察一下ida反编译的结果,发现符号表后面接的就是字符串表,那么用字符串表的首地址减去符号表的首地址就是符号表的大小,之后再用Elf64_Sym结构体解析,st_value就是该函数相对于load_bias的物理偏移,所以我们最后.再减去之前记录的首段偏移即可

   char strtab[strsz];
    memcpy(&strtab, strtab_, strsz);
    Elf64_Sym mytmpsym;
    for (n = 0; n < (long) strtab_ - (long) symtab_; n = n + sizeof(Elf64_Sym)) {//遍历符号表
    memcpy(&mytmpsym,(char*)symtab_+n,sizeof(Elf64_Sym));
 if(strstr(strtab_+mytmpsym.st_name,"artFindNativeMethod"))
   {    __android_log_print(6,"r0ysue","%p %s",mytmpsym.st_value,strtab_+mytmpsym.st_name);
        break;
   }
    }
    return (char*)start+mytmpsym.st_value-phof;

通过节头获得符号地址

通过elf头结构我们也可以找到节头的地址,也就是e_shoff,节头表相对于程序头表就友好许多,它的项非常多,唯一不好的一点就是它不会加载到内存中,所以Execution View中就没有这个东西,所以我们只能通过绝对路径找到它,手动解析文件

    int fd;
    void *start;
    struct stat sb;
    fd = open(lib, O_RDONLY);
    fstat(fd, &sb);
    start = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);

在这种解析方式中我们在elf头中需要的值是e_shoff、e_shentsize、e_shnum、e_shstrndx,就是节头表偏移,节大小,节个数,节头表字符串,不过我们最终的目标仍然是拿到符号表和字符串表,也就是下面的symtab和strtab中的sh_offset

 Elf64_Ehdr header;
    memcpy(&header, start, sizeof(Elf64_Ehdr));
    int secoff = header.e_shoff;
    int secsize = header.e_shentsize;
    int secnum = header.e_shnum;
    int secstr = header.e_shstrndx;
    Elf64_Shdr strtab;
    memcpy(&strtab, (char *) start + secoff + secstr * secsize, sizeof(Elf64_Shdr));
    int strtaboff = strtab.sh_offset;
    char strtabchar[strtab.sh_size];

    memcpy(&strtabchar, (char *) start + strtaboff, strtab.sh_size);
    Elf64_Shdr enumsec;
    int symoff = 0;
    int symsize = 0;
    int strtabsize = 0;
    int stroff = 0;
    for (int n = 0; n < secnum; n++) {

        memcpy(&enumsec, (char *) start + secoff + n * secsize, sizeof(Elf64_Shdr));


        if (strcmp(&strtabchar[enumsec.sh_name], ".symtab") == 0) {
            symoff = enumsec.sh_offset;
            symsize = enumsec.sh_size;

        }
        if (strcmp(&strtabchar[enumsec.sh_name], ".strtab") == 0) {
            stroff = enumsec.sh_offset;
            strtabsize = enumsec.sh_size;

        }


    }

最后和上面一样遍历符号表即可可得到物理偏移

    int realoff=0;
    char relstr[strtabsize];
    Elf64_Sym tmp;
    memcpy(&relstr, (char *) start + stroff, strtabsize);

    for (int n = 0; n < symsize; n = n + sizeof(Elf64_Sym)) {
        memcpy(&tmp, (char *)start + symoff+n, sizeof(Elf64_Sym));
        if(tmp.st_name!=0&&strstr(relstr+tmp.st_name,sym)){
            realoff=tmp.st_value;
            break;
        }
    }
    return realoff;

这种方式能够找到非导出符号的地址,还是有一定作用的,比如我在寻找soinfo地址的时候就用到了寻找soinfo_map在linker中的相对地址

模仿安卓通过hash寻找符号

这种方式就是dlsym的官方写法,由于libart.so这种so自动就会加载到内存种所以就不需要dlopen了,我们只需要在map里面找到它的首地址就可以了,代码和上面一样就不贴了,这里我们主要看看官方如何实现的,一路追踪do_dlopen最终找到了函数soinfo::gnu_lookup,这里面是他的主要实现逻辑,我们只需要实现它即可,这里多了4个项我们之前没有提到,就是它的导出表4项,所以这种方法只能找到导出表当中的函数或者变量

size_t gnu_nbucket_ = 0;
    // skip symndx
    uint32_t gnu_maskwords_ = 0;
    uint32_t gnu_shift2_ = 0;
    ElfW(Addr) *gnu_bloom_filter_ = nullptr;
    uint32_t *gnu_bucket_ = nullptr;
    uint32_t *gnu_chain_ = nullptr;
    int phof = 0;
    Elf64_Ehdr header;
    memcpy(&header, startr, sizeof(Elf64_Ehdr));
    uint64 rel = 0;
    size_t size = 0;
    long *plt = nullptr;
    char *strtab_ = nullptr;
    Elf64_Sym *symtab_ = nullptr;
    Elf64_Phdr cc;
    memcpy(&cc, ((char *) (startr) + header.e_phoff), sizeof(Elf64_Phdr));
    for (int y = 0; y < header.e_phnum; y++) {
        memcpy(&cc, (char *) (startr) + header.e_phoff + sizeof(Elf64_Phdr) * y,
               sizeof(Elf64_Phdr));
        if (cc.p_type == 6) {
            phof = cc.p_paddr - cc.p_offset;//改用程序头的偏移获得首段偏移用之前的方法也行
        }
    }
    for (int y = 0; y < header.e_phnum; y++) {
        memcpy(&cc, (char *) (startr) + header.e_phoff + sizeof(Elf64_Phdr) * y,
               sizeof(Elf64_Phdr));
        if (cc.p_type == 2) {
            Elf64_Dyn dd;
            for (y = 0; y == 0 || dd.d_tag != 0; y++) {
                memcpy(&dd, (char *) (startr) + cc.p_offset + y * sizeof(Elf64_Dyn) + 0x1000,
                       sizeof(Elf64_Dyn));

                if (dd.d_tag == 0x6ffffef5) {//0x6ffffef5为导出表项

                    gnu_nbucket_ = reinterpret_cast<uint32_t *>((char *) startr + dd.d_un.d_ptr -
                                                                phof)[0];
                    // skip symndx
                    gnu_maskwords_ = reinterpret_cast<uint32_t *>((char *) startr + dd.d_un.d_ptr -
                                                                  phof)[2];
                    gnu_shift2_ = reinterpret_cast<uint32_t *>((char *) startr + dd.d_un.d_ptr -
                                                               phof)[3];

                    gnu_bloom_filter_ = reinterpret_cast<ElfW(Addr) *>((char *) startr +
                                                                       dd.d_un.d_ptr + 16 - phof);
                    gnu_bucket_ = reinterpret_cast<uint32_t *>(gnu_bloom_filter_ + gnu_maskwords_);
                    // amend chain for symndx = header[1]
                    gnu_chain_ = reinterpret_cast<uint32_t *>( gnu_bucket_ +
                                                               gnu_nbucket_ -
                                                               reinterpret_cast<uint32_t *>(
                                                                       (char *) startr +
                                                                       dd.d_un.d_ptr - phof)[1]);

                }
                if (dd.d_tag == 5) {
                    strtab_ = reinterpret_cast< char *>((char *) startr + dd.d_un.d_ptr - phof);
                }
                if (dd.d_tag == 6) {
                    symtab_ = reinterpret_cast<Elf64_Sym *>((
                            (char *) startr + dd.d_un.d_ptr - phof));
                }
           
            }
        }
    }

之后模仿gnu_lookup函数即可,hashmap的查询方法

 char* name_=symname;//直接抄的安卓源码
    uint32_t h = 5381;
    const uint8_t* name = reinterpret_cast<const uint8_t*>(name_);
    while (*name != 0) {
        h += (h << 5) + *name++; // h*33 + c = h + h * 32 + c = h + h << 5 + c
    }
    int index=0;
    uint32_t h2 = h >> gnu_shift2_;
    uint32_t bloom_mask_bits = sizeof(ElfW(Addr))*8;
    uint32_t word_num = (h / bloom_mask_bits) & gnu_maskwords_;
    ElfW(Addr) bloom_word = gnu_bloom_filter_[word_num];
    n = gnu_bucket_[h % gnu_nbucket_];
    do {
        Elf64_Sym * s = symtab_ + n;
        char * sb=strtab_+ s->st_name;
        if (strcmp(sb ,reinterpret_cast<const char *>(name_)) == 0 ) {
            break;
        }
    } while ((gnu_chain_[n++] & 1) == 0);
    Elf64_Sym * mysymf=symtab_+n;
    long* finaladdr= reinterpret_cast<long*>(sb->st_value + (char *) start-phof);
    return finaladdr;

总结

这里介绍了三种得到符号地址的方法,都比较简单,只是我们写hook或者主动调用框架的一个基础,只有深刻的了解了elf格式才能完成我们的目标
有兴趣可以加微信:roysu3一起学习呀{
break;
}
} while ((gnu_chain_[n++] & 1) == 0);
Elf64_Sym * mysymf=symtab_+n;
long* finaladdr= reinterpret_cast<long*>(sb->st_value + (char *) start-phof);
return finaladdr;

## 总结
这里介绍了三种得到符号地址的方法,都比较简单,只是我们写hook或者主动调用框架的一个基础,只有深刻的了解了elf格式才能完成我们的目标
有兴趣可以加微信:roysu3一起学习呀
posted on 2021-10-28 12:50  r0ysue  阅读(385)  评论(0编辑  收藏  举报