• android逆向奇技淫巧三十:so加壳&加固原理简述


      1、古人云:没有规矩,不成方圆!任何组织要想正常运作,肯定需要有一系列的规章制度来约束参与其中的每个个体,否则每个个体都是各干各的,从全盘来看就是一群做着布朗运动的散沙!IT技术也一样,最典型的就是计算机网络了:为了确保整个网络能正常收发数据包,早在几十年前就制定了一整套计算机网络的通信协议,这就是大家耳熟能详的TCP/IP协议!无论是源端发送数据包的、中途转发数据包的,还是目的端接受数据包的,大家都按照统一的协议格式生成、转发和解析数据包(本质就是一串字符串),确保了目的端能正确收到和解析源端的数据,由此带来了计算机网络的飞速发展!

      基于计算机网络协议扩展一下:其实windwos下的PE格式、linux/android下的ELF格式本质上讲也是一种介于操作系统和编译器之间的协议!开发人员写好了代码,为了能在操作系统上顺利运行,编译器必须把高级语言的代码转换成操作系统能识别的文件格式+cpu能识别的机器码,只有这样,操作系统才能正确加载可执行文件到内存,然后运行!加壳最关键的原理就在这了:为了防止被静态分析,很多时候要想尽各种办法隐藏真实的代码,等执行的时候才还原!而且为了防止IDA等逆向软件的分析,文件格式可能还被故意破坏,不是标准的PE或ELF格式,导致操作系统无法正确地识别和加载,只能加壳的程序自己加载和执行,相当于“自立门户”了!整个过程大致如下:

         

      加壳工具、loader、被保护SO。

    • SO: 即被保护的目标 SO。
    • loader: 自身也是一个 SO,系统加载时首先加载 loader,loader 首先还原出经过加密、压缩、变换的 SO,再将 SO 加载到内存,并完成链接过程,使 SO 可以正常被其他模块使用(本质是把自定义格式的so修复成操作系统能执行的格式)
    • 加壳工具: 将被保护的 SO 加密、压缩、变换,并将结果作为数据与 loader 整合为 packed SO。

       从上述的描述看,loader本身就是个脱壳工具(只不过是加壳厂家官方的工具),抓住了loader,不就等于找到了脱壳的方法了?为了更好的学习loader,这里有必要研究一下加载到内存、修复链接等核心工作的原理和关键点!

      2、(1)加载到内存!不知道大家平时在写代码调用其他so中函数的时候有没有仔细想过:为啥要用dlopen、dlsym函数,而不直接简单粗暴地用open、fopen等函数打开so了?dlopen和普通的open函数有啥本质区别了?open函数都很熟悉,本质是通过系统调用找到文件在磁盘的位置,然后生成fd,后续通过fd操控文件!相比之下,dlopen要复杂多了:不但要加载到内存,还要解析文件格式,然后修复链接。先来看看第一步加载和解析文件格式都是怎么做的(这里以8.0.1版本为例,源码链接在文章末尾参考处有)!

    • 在/bionic/linker/linker.cpp中有do_dlopen函数(这里多说几句,linux内核有很多do开头的函数,都是内核内部的执行函数),核心代码如下:
          ProtectedDataGuard guard;
    2013    soinfo* si = find_library(ns, translated_name, flags, extinfo, caller);//查找so是否已经被加载
    2014    loading_trace.End();
    2015  
    2016    if (si != nullptr) {
    2017      void* handle = si->to_handle();
    2018      LD_LOG(kLogDlopen,
    2019             "... dlopen calling constructors: realpath=\"%s\", soname=\"%s\", handle=%p",
    2020             si->get_realpath(), si->get_soname(), handle);
    2021      si->call_constructors();//初始化so
    2022      failure_guard.Disable();
    2023      LD_LOG(kLogDlopen,
    2024             "... dlopen successful: realpath=\"%s\", soname=\"%s\", handle=%p",
    2025             si->get_realpath(), si->get_soname(), handle);
    2026      return handle;

      会先在soInfo链表中挨个遍历,看看目标so是否已经加载。如果没有,就继续调用CallConstructor继续初始化so!其中 find_library_internal 函数核心代码如下:

          soinfo* candidate;
    1438  
    1439    if (find_loaded_library_by_soname(ns, task->get_name(), search_linked_namespaces, &candidate)) {  //根据so的名称查找是否已经加载
    1440      task->set_soinfo(candidate);
    1441      return true;
    1442    }
    1443  
    1444    // Library might still be loaded, the accurate detection
    1445    // of this fact is done by load_library.
    1446    TRACE("[ \"%s\" find_loaded_library_by_soname failed (*candidate=%s@%p). Trying harder...]",
    1447        task->get_name(), candidate == nullptr ? "n/a" : candidate->get_realpath(), candidate);
    1448  
    1449    if (load_library(ns, task, zip_archive_cache, load_tasks, rtld_flags, search_linked_namespaces)) { //正式加载so
    1450      return true;
    1451    }

      先根据so的名称查找在soInfo的链表中查找是否已经存在。如果不存在,说明还没加载,继续调用load_library 加载so。代码非常多(在这里:http://aospxref.com/android-8.1.0_r81/xref/bionic/linker/linker.cpp#1178),而且分散在不同的函数,我这里选重点说明整个流程:

    // Open the file. 先打开文件
    1341    int fd = open_library(ns, zip_archive_cache, name, needed_by, &file_offset, &realpath);
    1342    if (fd == -1) {
    1343      DL_ERR("library \"%s\" not found", name);
    1344      return false;
    1345    }
    
    //生成soInfo结构体,后续用于记录so的属性
    soinfo* si = soinfo_alloc(ns, realpath.c_str(), &file_stat, file_offset, rtld_flags);
    1277    if (si == nullptr) {
    1278      return false;
    1279    }
    1280  
    1281    task->set_soinfo(si);
    1282  
    1283    // Read the ELF header and some of the segments.读取so的elf头,用于解析so
    1284    if (!task->read(realpath.c_str(), file_stat.st_size)) {
    1285      soinfo_free(si);
    1286      task->set_soinfo(nullptr);
    1287      return false;
    1288    }
    1289  
    1290    // find and set DT_RUNPATH and dt_soname
    1291    // Note that these field values are temporary and are
    1292    // going to be overwritten on soinfo::prelink_image
    1293    // with values from PT_LOAD segments.
    1294    const ElfReader& elf_reader = task->get_elf_reader();
    1295    for (const ElfW(Dyn)* d = elf_reader.dynamic(); d->d_tag != DT_NULL; ++d) {
    1296      if (d->d_tag == DT_RUNPATH) {
    1297        si->set_dt_runpath(elf_reader.get_string(d->d_un.d_val));
    1298      }
    1299      if (d->d_tag == DT_SONAME) {
    1300        si->set_soname(elf_reader.get_string(d->d_un.d_val));
    1301      }
    1302    }
    1303  
    1304    for_each_dt_needed(task->get_elf_reader(), [&](const char* name) {
    1305      load_tasks->push_back(LoadTask::create(name, si, ns, task->get_readers_map()));
    1306    });
    1307  
    
     //装载so,并给soInfo结构体赋值
    641      ElfReader& elf_reader = get_elf_reader();
    642      if (!elf_reader.Load(extinfo_)) {
    643        return false;
    644      }
    645  
    646      si_->base = elf_reader.load_start();
    647      si_->size = elf_reader.load_size();
    648      si_->set_mapped_by_caller(elf_reader.is_mapped_by_caller());
    649      si_->load_bias = elf_reader.load_bias();
    650      si_->phnum = elf_reader.phdr_count();
    651      si_->phdr = elf_reader.loaded_phdr();
    652  
    653      return true;
    654    }

       整个流程简单清晰:先打开文件,然后解析elf文件头,最后新生成so结构体记录加载的so文件属性!打开和加载的过程中,/bionic/linker/linker_phdr.cpp 中的这两个函数里面包括了整个加载过程的精髓:

     bool ElfReader::Read(const char* name, int fd, off64_t file_offset, off64_t file_size) {
    150    CHECK(!did_read_);
    151    CHECK(!did_load_);
    152    name_ = name;
    153    fd_ = fd;
    154    file_offset_ = file_offset;
    155    file_size_ = file_size;
    156  
    157    if (ReadElfHeader() &&  //读elf头
    158        VerifyElfHeader() && //校验elf头
    159        ReadProgramHeaders() && //读程序头
    160        ReadSectionHeaders() &&  //读接头
    161        ReadDynamicSection()) { //读动态节头
    162      did_read_ = true;
    163    }
    164  
    165    return did_read_;
    166  }
    167  
    168  bool ElfReader::Load(const android_dlextinfo* extinfo) {
    169    CHECK(did_read_);
    170    CHECK(!did_load_);
    171    if (ReserveAddressSpace(extinfo) && //为段开辟内存空间
    172        LoadSegments() && //加载段,这里是很好的脱壳点
    173        FindPhdr()) { //设置程序的加载地址
    174      did_load_ = true;
    175    }
    176  
    177    return did_load_;
    178  }

      自己写壳的加载程序时,完全可以仿照这8个步骤一步一步做(事实上,很多加壳和脱壳loader程序也都是这么干的!)!至此,这个so的代码是不是就能执行了?很明显还不行,原因很简单:链接还没修复了!所谓的链接,本质就是调用其他函数时需要正确的地址

    (2)修复链接

        整个过程最核心的函数就是prelink_image和link_image了,作用分别是:

    •   解析linker文件中dynamic段的各项,例如重定位表,符号表
    •        对全局变量,外部函数等地址进行重定位

       核心代码如下:prelink_image  大都是这种switch case结构,根据不同的tag做不同的解析;

    for (ElfW(Dyn)* d = dynamic; d->d_tag != DT_NULL; ++d) {
    2857      DEBUG("d = %p, d[0](tag) = %p d[1](val) = %p",
    2858            d, reinterpret_cast<void*>(d->d_tag), reinterpret_cast<void*>(d->d_un.d_val));
    2859      switch (d->d_tag) {
    2860        case DT_SONAME:
    2861          // this is parsed after we have strtab initialized (see below).
    2862          break;
    2863  
    2864        case DT_HASH: //导出函数定位
    2865          nbucket_ = reinterpret_cast<uint32_t*>(load_bias + d->d_un.d_ptr)[0];
    2866          nchain_ = reinterpret_cast<uint32_t*>(load_bias + d->d_un.d_ptr)[1];
    2867          bucket_ = reinterpret_cast<uint32_t*>(load_bias + d->d_un.d_ptr + 8);
    2868          chain_ = reinterpret_cast<uint32_t*>(load_bias + d->d_un.d_ptr + 8 + nbucket_ * 4);
    2869          break;
    2870  
    2871        case DT_GNU_HASH:  //导出函数定位
    2872          gnu_nbucket_ = reinterpret_cast<uint32_t*>(load_bias + d->d_un.d_ptr)[0];
    2873          // skip symndx
    2874          gnu_maskwords_ = reinterpret_cast<uint32_t*>(load_bias + d->d_un.d_ptr)[2];
    2875          gnu_shift2_ = reinterpret_cast<uint32_t*>(load_bias + d->d_un.d_ptr)[3];
    2876  
    2877          gnu_bloom_filter_ = reinterpret_cast<ElfW(Addr)*>(load_bias + d->d_un.d_ptr + 16);
    2878          gnu_bucket_ = reinterpret_cast<uint32_t*>(gnu_bloom_filter_ + gnu_maskwords_);
    2879          // amend chain for symndx = header[1]
    2880          gnu_chain_ = gnu_bucket_ + gnu_nbucket_ -
    2881              reinterpret_cast<uint32_t*>(load_bias + d->d_un.d_ptr)[1];
    2882  
    2883          if (!powerof2(gnu_maskwords_)) {
    2884            DL_ERR("invalid maskwords for gnu_hash = 0x%x, in \"%s\" expecting power to two",
    2885                gnu_maskwords_, get_realpath());
    2886            return false;
    2887          }
    2888          --gnu_maskwords_;
    2889  
    2890          flags_ |= FLAG_GNU_HASH;
    2891          break;
    2892  
    2893        case DT_STRTAB:
    2894          strtab_ = reinterpret_cast<const char*>(load_bias + d->d_un.d_ptr);
    2895          break;
    2896  
    2897        case DT_STRSZ:
    2898          strtab_size_ = d->d_un.d_val;
    2899          break;
    2900  
    2901        case DT_SYMTAB:
    2902          symtab_ = reinterpret_cast<ElfW(Sym)*>(load_bias + d->d_un.d_ptr);
    2903          break;
    2904  
    2905        case DT_SYMENT:
    2906          if (d->d_un.d_val != sizeof(ElfW(Sym))) {
    2907            DL_ERR("invalid DT_SYMENT: %zd in \"%s\"",
    2908                static_cast<size_t>(d->d_un.d_val), get_realpath());
    2909            return false;
    2910          }
    2911          break;

      解析完后就是重定位了,根据不同的条件给relocate函数传入不同的参数!

    if (android_relocs_ != nullptr) {
    3300      // check signature
    3301      if (android_relocs_size_ > 3 &&
    3302          android_relocs_[0] == 'A' &&
    3303          android_relocs_[1] == 'P' &&
    3304          android_relocs_[2] == 'S' &&
    3305          android_relocs_[3] == '2') {
    3306        DEBUG("[ android relocating %s ]", get_realpath());
    3307  
    3308        bool relocated = false;
    3309        const uint8_t* packed_relocs = android_relocs_ + 4;
    3310        const size_t packed_relocs_size = android_relocs_size_ - 4;
    3311  
    3312        relocated = relocate(
    3313            version_tracker,
    3314            packed_reloc_iterator<sleb128_decoder>(
    3315              sleb128_decoder(packed_relocs, packed_relocs_size)),
    3316            global_group, local_group);
    3317  
    3318        if (!relocated) {
    3319          return false;
    3320        }
    3321      } else {
    3322        DL_ERR("bad android relocation header.");
    3323        return false;
    3324      }
    3325    }
    3326  
    3327  #if defined(USE_RELA)
    3328    if (rela_ != nullptr) {
    3329      DEBUG("[ relocating %s ]", get_realpath());
    3330      if (!relocate(version_tracker,
    3331              plain_reloc_iterator(rela_, rela_count_), global_group, local_group)) {
    3332        return false;
    3333      }
    3334    }
    3335    if (plt_rela_ != nullptr) {
    3336      DEBUG("[ relocating %s plt ]", get_realpath());
    3337      if (!relocate(version_tracker,
    3338              plain_reloc_iterator(plt_rela_, plt_rela_count_), global_group, local_group)) {
    3339        return false;
    3340      }
    3341    }
    3342  #else
    3343    if (rel_ != nullptr) {
    3344      DEBUG("[ relocating %s ]", get_realpath());
    3345      if (!relocate(version_tracker,
    3346              plain_reloc_iterator(rel_, rel_count_), global_group, local_group)) {
    3347        return false;
    3348      }
    3349    }
    3350    if (plt_rel_ != nullptr) {
    3351      DEBUG("[ relocating %s plt ]", get_realpath());
    3352      if (!relocate(version_tracker,
    3353              plain_reloc_iterator(plt_rel_, plt_rel_count_), global_group, local_group)) {
    3354        return false;
    3355      }
    3356    }
    3357  #endif

      relocate函数内部同样也是switch case结构,根据不同的type修改reloc表、符号表等,还有修改系统函数、全局/静态变量的绝对地址!

    switch (type) {
    2579        case R_GENERIC_JUMP_SLOT:
    2580          count_relocation(kRelocAbsolute);
    2581          MARK(rel->r_offset);
    2582          TRACE_TYPE(RELO, "RELO JMP_SLOT %16p <- %16p %s\n",
    2583                     reinterpret_cast<void*>(reloc),
    2584                     reinterpret_cast<void*>(sym_addr + addend), sym_name);
    2585  
    2586          *reinterpret_cast<ElfW(Addr)*>(reloc) = (sym_addr + addend);
    2587          break;
    2588        case R_GENERIC_GLOB_DAT:
    2589          count_relocation(kRelocAbsolute);
    2590          MARK(rel->r_offset);
    2591          TRACE_TYPE(RELO, "RELO GLOB_DAT %16p <- %16p %s\n",
    2592                     reinterpret_cast<void*>(reloc),
    2593                     reinterpret_cast<void*>(sym_addr + addend), sym_name);
    2594          *reinterpret_cast<ElfW(Addr)*>(reloc) = (sym_addr + addend);
    2595          break;
    2596        case R_GENERIC_RELATIVE:
    2597          count_relocation(kRelocRelative);
    2598          MARK(rel->r_offset);
    2599          TRACE_TYPE(RELO, "RELO RELATIVE %16p <- %16p\n",
    2600                     reinterpret_cast<void*>(reloc),
    2601                     reinterpret_cast<void*>(load_bias + addend));
    2602          *reinterpret_cast<ElfW(Addr)*>(reloc) = (load_bias + addend);
    2603          break;
    2604        case R_GENERIC_IRELATIVE:
    2605          count_relocation(kRelocRelative);
    2606          MARK(rel->r_offset);
    2607          TRACE_TYPE(RELO, "RELO IRELATIVE %16p <- %16p\n",
    2608                      reinterpret_cast<void*>(reloc),
    2609                      reinterpret_cast<void*>(load_bias + addend));
    2610          {
    2611  #if !defined(__LP64__)
    2612            // When relocating dso with text_relocation .text segment is
    2613            // not executable. We need to restore elf flags for this
    2614            // particular call.
    2615            if (has_text_relocations) {
    2616              if (phdr_table_protect_segments(phdr, phnum, load_bias) < 0) {
    2617                DL_ERR("can't protect segments for \"%s\": %s",
    2618                       get_realpath(), strerror(errno));
    2619                return false;
    2620              }
    2621            }
    2622  #endif
    2623            ElfW(Addr) ifunc_addr = call_ifunc_resolver(load_bias + addend);

    (3)加入soInfo结构体

       此时只有loader这个so是被操作系统linker加载的,所以也只有loader的so能和art交互(猜猜为什么?)!为了让自己关键代码的so也能和art交互,需要替换掉loader的so的属性,把其改成关键代码so的属性; PS:soInfo记录了内存中so结构体的核心属性,完全可以用来检索so,核心属性列举如下:

    装载链接期间主要使用的成员:
    
    装载信息
    const ElfW(Phdr)* phdr;
    size_t phnum;
    ElfW(Addr) base;
    size_t size;
    符号信息
    const char* strtab;
    ElfW(Sym)* symtab;

    下一个soInfo节点
     soinfo* next;
    
    重定位信息
    ElfW(Rel)* plt_rel;
    size_t plt_rel_count;
    ElfW(Rel)* rel;
    size_t rel_count;
    init 函数和 finit 函数
    Linker_function_t* init_array;
    size_t init_array_count;
    Linker_function_t* fini_array;
    size_t fini_array_count;
    Linker_function_t init_func;
    Linker_function_t fini_func;
    运行期间主要使用的成员:
    
    导出符号查找(dlsym):
    const char* strtab;
    ElfW(Sym)* symtab;
    size_t nbucket;
    size_t nchain;
    unsigned* bucket;
    unsigned* chain;
    ElfW(Addr) load_bias;
    异常处理:
    unsigned* ARM_exidx;
    size_t ARM_exidx_count;
    load_library 在为 SO 分配 soinfo 后,会将装载结果更新到 soinfo 中,后面的链接过程就可以直接使用soinfo的相关字段去访问 SO 中的信息。

      操作系统为了便于管理,还会把soInfo组织成链表形式,所以在soinfo_alloc函数中就会把新生成的soInfo结构体加入链表,开发人员只要遍历链表就能查找到所有已经加载的so了!这里扩展一下:windows下做安全防护时,经常会把进程或线程的链表断开,让逆向破解人员找不到自己的进程或线程!  修改soInfo结构体的时候建议通过如下基址+偏移的形式修改!每个偏移代表什么含义可通过AOSP源码查询得到!

              

       3、最后总结一下一般加壳的原理流程:

    •   关键文件可以不是elf格式,比如自定义的文件格式;还可以对关键代码加密
    •        执行时先装载loader,由于loader本身是标准的so,所以直接用操作系统的linker加载即可(就是调用操作系统提供现成的dlopen等API)!
    •        loader被加载后,会调用callConstractor函数,该函数内部先后依次执行init、initArray、jni_onload等函数,所以加载自己关键文件、解密代码、还原成elf结构、修复链接/重定位、加入soInfo链表(“解壳”三部曲:加载、链接、替换soInfo结构体等工作都可以在这三个方法中做。具体代码完全可以参考android源码,结合自己文件实际格式即可!

       这里借(抄)鉴(袭)某位大佬整理的ELF格式的解析图凑数:

      

      附上看雪r0ysue大佬的加壳demo 链接: 提取码:kjvm    思路就是自己定义的loader完成了操作系统linker的功能!

    脱壳点:

      1、mprotect函数:以前关注这个函数是在反调试阶段,比如把内存设置为不可执行,等自己代码快要执行到目标内存区域时再调用mprotect把内存设置为可执行。如果检测到被ida调试,就不改内存属性为可执行,让ida调试时跳转到这里时就会报signal异常!除了反调试,在加壳领域mprotect也能用上。比如在脱壳时,肯定需要一段内存存放代码(以任何形式申请的内存默认都没有执行权限),并且同样需要调用mprotect函数把内存设置为可执行!所以逆向时可以通过hook mprotect函数,看看第三个参数是不是设置内存为可执行。同时如果第二个参数len也不小,那么这个mprotect很有可能就是在脱壳用了!参考的hook代码如下:

                

       2、soInfo结构体:因为每个加载的so都会生成soInfo结构体,然后加入链表中,所以soInfo链表保存了所有已加载so的soInfo,从这个链表就能找到自己想要so的信息,然后借此脱壳;poc代码如下: 从maps文件查找linker64的地址,然后继续找solist的地址,最后遍历soInfo!

    void initlinker(){
        char line[1024];
        int *end;
        long* base;
        int n = 1;
        FILE *fp = fopen("/proc/self/maps", "r");
        while (fgets(line, sizeof(line), fp)) {
            if (strstr(line, "linker64")) {
                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++;
            }
        }
        int soheaderoff = findsym("/system/bin/linker64", "__dl__ZL6solist");
        soheader = reinterpret_cast<long *>((char *) start + soheaderoff);
    }
    void *findsobase(const char *soname) {
    
        long* base;
        long* soinfo;
        int  n=0;
        for (_QWORD * result = reinterpret_cast<uint64 *>(*(_QWORD *) soheader); result; result = (_QWORD *)result[5] ){
            if(*(_QWORD *) ((__int64) result + 408)!= 0&&strcmp(reinterpret_cast<const char *>(*(_QWORD *) ((__int64) result + 408)), soname)==0) {
                base= reinterpret_cast<long *>(*(_QWORD *) ((char *) result + 16));
                long* size= reinterpret_cast<long *>(*(_QWORD *) ((char *) result + 24));
                long* load_bias= reinterpret_cast<long *>(*(_QWORD *) ((char *) result + 256));
                const char* name= reinterpret_cast<const char *>(*(_QWORD *) ((__int64) result + 408));
                soinfo= reinterpret_cast<long *>(result);
                break;
            }
        }
        return soinfo;
    }

       3、mmap:从磁盘读取大文件后为了节约内存,用mmap是最合适不过的了!大文件很有可能是加壳的so!

       4、so加载加壳总结:

            

       为了增加内存中so被识别和dump的难度,可以把elf header或section header删除,或者用异或的方式加密混淆,比如下面这样的:

       

        标红部分是改过的,再用ida打开就不行了!总的来说,建议用异或而不是直接删除,能让逆向人员dump还原so的时候怀疑人生: 我是不是dump的位置选错了?代码上就类似这种:

    bool LoadSegments() {
            for (size_t i = 0; i < phdr_num_; ++i) {
                const ElfW(Phdr) *phdr = &phdr_table_[i];
                if (phdr->p_type != PT_LOAD) {
                    continue;
                }
                // Segment addresses in memory.
                ElfW(Addr) seg_start = phdr->p_vaddr^0x5a + load_bias_;
                ElfW(Addr) seg_end = seg_start + phdr->p_memsz^0x5a;
                ElfW(Addr) seg_page_start = PAGE_START(seg_start);
                ElfW(Addr) seg_page_end = PAGE_END(seg_end);
                ElfW(Addr) seg_file_end = seg_start + phdr->p_filesz^0x5a;
                // File offsets.
                ElfW(Addr) file_start = phdr->p_offset^0a5x;
                ElfW(Addr) file_end = file_start^0x5a + phdr->p_filesz;
    
                ElfW(Addr) file_page_start = PAGE_START(file_start);
                ElfW(Addr) file_length = file_end^0x5a - file_page_start;
                long* pp= reinterpret_cast<long *>(seg_page_start);

      选一些自认为比较重要的字段在完全加载后再异或0x5a(当然其他数字也行),达到迷惑逆向人员的目的!

    参考:

    1、https://cloud.tencent.com/developer/article/1071358  android linker与so加壳技术

    2、https://www.cnblogs.com/r0ysue/p/15398936.html 基于linker实现so加壳 上篇

       https://zhuanlan.zhihu.com/p/413630468   下篇

    3、http://aospxref.com/android-8.1.0_r81/   AOSP源码查询

    4、https://wwm0609.github.io/2020/06/12/android-linker/  android dynamic linking简介

  • 相关阅读:
    架构师之路
    责任链设计模式
    Junit框架分析
    线程详解
    课程总结
    IO流
    Java第四次作业
    Character string
    实训
    实训SI
  • 原文地址:https://www.cnblogs.com/theseventhson/p/16366038.html
Copyright © 2020-2023  润新知