加殼VS脫殼(第二代)

第二代殼,我的理解中有以下幾種:
1,將源dex文件加密,而非整個(gè)apk,第一代中的解殼過程(Application.attachBaseContext中的方法)放到so中實(shí)現(xiàn),這個(gè)原理和第一代大同小異,增加的難度就是增加了c代碼的逆向。
2,將關(guān)鍵函數(shù)放到so中實(shí)現(xiàn),然后將函數(shù)放到自定義的段中,然后對(duì)自定義段加密,在so文件加載入口init處對(duì)自定義段解密。
3,對(duì)so中的關(guān)鍵函數(shù)進(jìn)行加密:原理與2相同,難點(diǎn)在于函數(shù)定位,具體函數(shù)的定位需要同hash表,首先通過dynamic段找到動(dòng)態(tài)加載相關(guān)的三個(gè)重要段.dynsym(符號(hào)表).dynstr(動(dòng)態(tài)字符串表).hash(哈希表),然后通過傳入函數(shù)名使用hash表找到目標(biāo)函數(shù)在符號(hào)表中的索引,然后再定位具體函數(shù)。

一,自定義段加密

1,編譯apk,生成so文件,然后對(duì)so文件加密

  1. 如果是再java中編寫的話需要自定義so文件格式解析的class,這里放在linux下編寫,直接調(diào)用了elf.h頭文件,自動(dòng)解析elf文件格式;
  2. 讀取so文件通過elf文件頭中的字符串表索引;
  3. 通過節(jié)區(qū)頭部表偏移獲取節(jié)區(qū)頭部表地址;
  4. 通過節(jié)區(qū)頭部表的地址 + 節(jié)區(qū)字符串表索引 * 節(jié)區(qū)頭部表固定大小,獲取字符串表的地址;
  5. 將字符串表的內(nèi)容復(fù)制出來;
  6. 然后通過對(duì)每個(gè)section的sh_name與我們的自定義section名字對(duì)比,找到自定義段的偏移地址和大小;
  7. 然后即可對(duì)偏移地址的內(nèi)容進(jìn)行加密算法運(yùn)算;
  8. 將自定義段的偏移和大小寫入elf頭文件的section部分;
  9. 上一步的原因是elf加載時(shí)是基于裝載視圖,不會(huì)在意section部分的參數(shù)內(nèi)容(注:Android7之后如果寫在了e_shoff會(huì)報(bào)錯(cuò),但是可以寫在其他部分)。
#include <stdio.h>
#include <fcntl.h>
#include "elf.h"
#include <stdlib.h>
#include <string.h>

int main(int argc, char** argv){
  char *encodeSoName = "libdemo.so";
  char target_section[] = ".mytext";
  char *shstr = NULL;
  char *content = NULL;
  Elf32_Ehdr ehdr;
  Elf32_Shdr shdr;
  int i;
  unsigned int base, length;
  unsigned short nblock;
  unsigned short nsize;
  unsigned char block_size = 16;
  
  int fd;
  
  fd = open(encodeSoName, O_RDWR);
  if(fd < 0){
    printf("open %s failed\n", argv[1]);
    goto _error;
  }
  
  if(read(fd, &ehdr, sizeof(Elf32_Ehdr)) != sizeof(Elf32_Ehdr)){
    puts("Read ELF header error");
    goto _error;
  }
  
  lseek(fd, ehdr.e_shoff + sizeof(Elf32_Shdr) * ehdr.e_shstrndx, SEEK_SET);
  
  if(read(fd, &shdr, sizeof(Elf32_Shdr)) != sizeof(Elf32_Shdr)){
    puts("Read ELF section string table error");
    goto _error;
  }
  
  if((shstr = (char *) malloc(shdr.sh_size)) == NULL){
    puts("Malloc space for section string table failed");
    goto _error;
  }
  
  lseek(fd, shdr.sh_offset, SEEK_SET);
  if(read(fd, shstr, shdr.sh_size) != shdr.sh_size){
    puts("Read string table failed");
    goto _error;
  }

  lseek(fd, ehdr.e_shoff, SEEK_SET);
  for(i = 0; i < ehdr.e_shnum; i++){
    if(read(fd, &shdr, sizeof(Elf32_Shdr)) != sizeof(Elf32_Shdr)){
      puts("Find section .text procedure failed");
      goto _error;
    }
    if(strcmp(shstr + shdr.sh_name, target_section) == 0){
      base = shdr.sh_offset;
      length = shdr.sh_size;
      printf("Find section %s\n", target_section);
      break;
    }
  }
  
  lseek(fd, base, SEEK_SET);
  content = (char*) malloc(length);
  if(content == NULL){
    puts("Malloc space for content failed");
    goto _error;
  }
  if(read(fd, content, length) != length){
    puts("Read section .text failed");
    goto _error;
  }
  
  nblock = length / block_size;
  nsize = length / 4096 + (length % 4096 == 0 ? 0 : 1);
  printf("base = %x, length = %x\n", base, length);
  printf("nblock = %d, nsize = %d\n", nblock, nsize);
  printf("entry:%x\n",((length << 16) + nsize));

  ehdr.e_entry = (length << 16) + nsize;
  ehdr.e_shoff = base;
  
  for(i=0;i<length;i++){
    content[i] = ~content[i];
  }

  lseek(fd, 0, SEEK_SET);
  if(write(fd, &ehdr, sizeof(Elf32_Ehdr)) != sizeof(Elf32_Ehdr)){
    puts("Write ELFhead to .so failed");
    goto _error;
  }

  lseek(fd, base, SEEK_SET);
  if(write(fd, content, length) != length){
    puts("Write modified content to .so failed");
    goto _error;
  }
  
  puts("Completed");
_error:
  free(content);
  free(shstr);
  close(fd);
  return 0;
}

2.編寫jni項(xiàng)目,

  1. 使用__attribute__自定義section,將關(guān)鍵函數(shù)getString()寫入自定義section中
  2. 然后在__attribute__中自定義init_getString()函數(shù),此函數(shù)中完成so解密工作;
  3. 首先通過進(jìn)程id,讀取maps文件找到so基地址;
  4. 然后讀取elf頭中的寫入的自定義section的偏移和長度,計(jì)算出實(shí)際地址,然后進(jìn)行解密工作;
  5. 由于是內(nèi)存中操作,需要對(duì)指定頁進(jìn)行mprotect函數(shù)修改權(quán)限操作;
#include <jni.h>
#include <stdio.h>
#include <string>
#include <android/log.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <elf.h>
#include <sys/mman.h>
jstring getString(JNIEnv*) __attribute__((section(".smmtext")));
jstring getString(JNIEnv* env){
    return env->NewStringUTF("Native method return!");
}
void init_getString() __attribute__((constructor));
unsigned long getLibAddr();
void init_getString(){
    char name[15];
    unsigned int nblock;
    unsigned int nsize;
    unsigned long base;
    unsigned long text_addr;
    unsigned int i;
    Elf32_Ehdr *ehdr;
    Elf32_Shdr *shdr;
    __android_log_print(ANDROID_LOG_INFO, "JNITag", "0:Get in init_getString");

    base = getLibAddr();
    __android_log_print(ANDROID_LOG_INFO, "JNITag", "1:base =  0x%x", base);

    ehdr = (Elf32_Ehdr *)base;
    text_addr = ehdr->e_shoff + base;
    __android_log_print(ANDROID_LOG_INFO, "JNITag", "2:text_addr =  0x%x", text_addr);

    nblock = ehdr->e_entry >> 16;
    nsize = ehdr->e_entry & 0xffff;

    __android_log_print(ANDROID_LOG_INFO, "JNITag", "3:nblock =  0x%x,nsize:%d", nblock,nsize);
    __android_log_print(ANDROID_LOG_INFO, "JNITag", "4:base =  0x%x", text_addr);
    printf("nblock = %d\n", nblock);

    if(mprotect((void *) (text_addr / PAGE_SIZE * PAGE_SIZE), 4096 * nsize, PROT_READ | PROT_EXEC | PROT_WRITE) != 0){
        puts("mem privilege change failed");
        __android_log_print(ANDROID_LOG_INFO, "JNITag", "error:mem privilege change failed");
    }

    for(i=0;i< nblock; i++){
        char *addr = (char*)(text_addr + i);
        *addr = ~(*addr);
    }

    if(mprotect((void *) (text_addr / PAGE_SIZE * PAGE_SIZE), 4096 * nsize, PROT_READ | PROT_EXEC) != 0){
        puts("mem privilege change failed");
    }
    puts("Decrypt success");
}

unsigned long getLibAddr(){
    unsigned long ret = 0;
    char name[] = "libnative-lib.so";
    char buf[4096], *temp;
    int pid;
    FILE *fp;
    pid = getpid();
    sprintf(buf, "/proc/%d/maps", pid);
    fp = fopen(buf, "r");
    if(fp == NULL)
    {
        puts("open failed");
        goto _error;
    }
    while(fgets(buf, sizeof(buf), fp)){
        if(strstr(buf, name)){
            temp = strtok(buf, "-");
            ret = strtoul(temp, NULL, 16);
            break;
        }
    }
    _error:
    fclose(fp);
    return ret;
}

extern "C" JNIEXPORT jstring JNICALL
Java_com_smm_mysecenc_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    return getString(env);
}

二,指定函數(shù)加密

  1. 函數(shù)的結(jié)構(gòu)是保存在動(dòng)態(tài)符號(hào)段.dynsym段中的,而函數(shù)名是保存在動(dòng)態(tài)字符串段.dynstr中的,沒有辦法可以直接通過字符串段的函數(shù)名找到符號(hào)表的地址;
  2. 通過hash表可以找到對(duì)應(yīng)的函數(shù)地址;
  3. .hash .dynsym .dynstr等都存在于.dynamic段中,這里來做下檢查,打開一個(gè)so文件,找到他的哈希表、動(dòng)態(tài)符號(hào)表和動(dòng)態(tài)字符串表,分別找出它們的name、off、size字段,然后去他的dynamic段做比較,就會(huì)發(fā)現(xiàn)都在里面;
    .dynsym

    .dynstr

    .hash

    .dynamic
  4. 程序頭phdr中的標(biāo)記dynamic段和節(jié)區(qū)頭shdr中的類型為SHT_DYNAMIC的段實(shí)際上是同一段;
  5. 首先找到.dynamic段,有很多種方法可以找到,最簡單的直接遍歷phdr中的type為PT_DYNAMIC (2)的段,因?yàn)檫@是唯一的;或者通過遍歷節(jié)區(qū)表中的段名為.dynamic的段。
  6. 然后解析.dynamic段,在elf.h中有定義如下結(jié)構(gòu),用c的話可以直接調(diào)用,用java的話則需要自己手動(dòng)解析一下,其中tag是標(biāo)示這個(gè)dyn是什么類型的(.dynsym還是.dynstr),val是大小,ptr則是偏移
typedef struct dynamic {
  Elf32_Sword d_tag;
  union {
    Elf32_Sword d_val;
    Elf32_Addr d_ptr;
  } d_un;
} Elf32_Dyn;
  1. 遍歷dynamic段找到 .hash .dynsym .dynstr這三個(gè)段;
  2. 然后再去定位我們的要加密的函數(shù),過程如下
    (1). 復(fù)制出dynstr的字符串內(nèi)容;
    (2). 根據(jù)傳入函數(shù)名計(jì)算出hash值,例如傳入函數(shù)名Java_com_smm_mysofuncenc_MainActivity_stringFromJNI,hash函數(shù)可以在Android源碼鏈接器中找到:
static unsigned elfhash(const char *_name){
    const unsigned char *name = (const unsigned char *)_name;
    unsigned h = 0,g;
    while (*name){
        h = (h << 4) + *name++;
        g = h & 0xf0000000;
        h ^= g;
        h ^= g >> 24;
    }
    return h;
}

(3). 用計(jì)算出的hash值去對(duì)nbucket取模運(yùn)算,得出的值乘4+8,然后再hash表中找到這個(gè)位置里面的值就是這個(gè)函數(shù)在符號(hào)表.dynsym中的索引;
(4). 然后通過索引值乘dynsym符號(hào)表的大?。╡lf.h文件中有定義Elf32_Sym是16),再加上符號(hào)表偏移即可確認(rèn)目標(biāo)函數(shù)的符號(hào)表
(5). 對(duì)比從符號(hào)表中取出的函數(shù)名是否正確,如果不正確就再去hash表中讀取下一個(gè),直到正確為止;
(6). 然后通過符號(hào)表讀取目標(biāo)函數(shù)的偏移和大?。?/p>

  1. 然后對(duì)目標(biāo)區(qū)域自定義加密算法;
#include <stdio.h>
#include <fcntl.h>
#include "elf.h"
#include <stdlib.h>
#include <string.h>

typedef struct _funcInfo{
  Elf32_Addr st_value;
  Elf32_Word st_size;
}funcInfo;

Elf32_Ehdr ehdr;

//For Test
static void print_all(char *str, int len){
  int i;
  for(i=0;i<len;i++)
  {
    if(str[i] == 0)
      puts("");
    else
      printf("%c", str[i]);
  }
}

static unsigned elfhash(const char *_name)
{
    const unsigned char *name = (const unsigned char *) _name;
    unsigned h = 0, g;

    while(*name) {
        h = (h << 4) + *name++;
        g = h & 0xf0000000;
        h ^= g;
        h ^= g >> 24;
    }
    return h;
}

static Elf32_Off findTargetSectionAddr(const int fd, const char *secName){
  Elf32_Shdr shdr;
  char *shstr = NULL;
  int i;
  
  lseek(fd, 0, SEEK_SET);
  if(read(fd, &ehdr, sizeof(Elf32_Ehdr)) != sizeof(Elf32_Ehdr)){
    puts("Read ELF header error");
    goto _error;
  }
  
  lseek(fd, ehdr.e_shoff + sizeof(Elf32_Shdr) * ehdr.e_shstrndx, SEEK_SET);
  
  if(read(fd, &shdr, sizeof(Elf32_Shdr)) != sizeof(Elf32_Shdr)){
    puts("Read ELF section string table error");
    goto _error;
  }
  
  if((shstr = (char *) malloc(shdr.sh_size)) == NULL){
    puts("Malloc space for section string table failed");
    goto _error;
  }
  
  lseek(fd, shdr.sh_offset, SEEK_SET);
  if(read(fd, shstr, shdr.sh_size) != shdr.sh_size){
    puts(shstr);
    puts("Read string table failed");
    goto _error;
  }
  
  lseek(fd, ehdr.e_shoff, SEEK_SET);
  for(i = 0; i < ehdr.e_shnum; i++){
    if(read(fd, &shdr, sizeof(Elf32_Shdr)) != sizeof(Elf32_Shdr)){
      puts("Find section .text procedure failed");
      goto _error;
    }
    if(strcmp(shstr + shdr.sh_name, secName) == 0){
      printf("Find section %s, addr = 0x%x\n", secName, shdr.sh_offset);
      break;
    }
  }
  free(shstr);
  return shdr.sh_offset;
_error:
  return -1;
}

static char getTargetFuncInfo(int fd, const char *funcName, funcInfo *info){
  char flag = -1, *dynstr;
  int i;
  Elf32_Sym funSym;
  Elf32_Phdr phdr;
  Elf32_Off dyn_off;
  Elf32_Word dyn_size, dyn_strsz;
  Elf32_Dyn dyn;
  Elf32_Addr dyn_symtab, dyn_strtab, dyn_hash;
  unsigned funHash, nbucket, nchain, funIndex;
  
  lseek(fd, ehdr.e_phoff, SEEK_SET);
  for(i=0;i < ehdr.e_phnum; i++){
    if(read(fd, &phdr, sizeof(Elf32_Phdr)) != sizeof(Elf32_Phdr)){
      puts("Read segment failed");
      goto _error;
    }
    if(phdr.p_type ==  PT_DYNAMIC){
      dyn_size = phdr.p_filesz;
      dyn_off = phdr.p_offset;
      flag = 0;
      printf("Find section %s, size = 0x%x, addr = 0x%x\n", ".dynamic", dyn_size, dyn_off);
      break;
    }
  }
  if(flag){
    puts("Find .dynamic failed");
    goto _error;
  }
  flag = 0;

  printf("dyn_size:%d\n",dyn_size);
  printf("count:%d\n",(dyn_size/sizeof(Elf32_Dyn)));
  printf("off:%x\n",dyn_off);
  
  lseek(fd, dyn_off, SEEK_SET);
  for(i=0;i < dyn_size / sizeof(Elf32_Dyn); i++){
    int sizes = read(fd, &dyn, sizeof(Elf32_Dyn));
    if(sizes != sizeof(Elf32_Dyn)){
      puts("Read .dynamic information failed");
      //goto _error;
      break;
    }    
    if(dyn.d_tag == DT_SYMTAB){
      dyn_symtab = dyn.d_un.d_ptr;
      flag += 1;
      printf("Find .dynsym, addr = 0x%x, val = 0x%x\n", dyn_symtab, dyn.d_un.d_val);
    }

    if(dyn.d_tag == DT_HASH){
      dyn_hash = dyn.d_un.d_ptr;
      flag += 2;
      printf("Find .hash, addr = 0x%x\n", dyn_hash);
    }
    if(dyn.d_tag == DT_STRTAB){
      dyn_strtab = dyn.d_un.d_ptr;
      flag += 4;
      printf("Find .dynstr, addr = 0x%x\n", dyn_strtab);
    }
    if(dyn.d_tag == DT_STRSZ){
      dyn_strsz = dyn.d_un.d_val;
      flag += 8;
      printf("Find .dynstr size, size = 0x%x\n", dyn_strsz);
    }
  }

  if((flag & 0x0f) != 0x0f){
    puts("Find needed .section failed\n");
    goto _error;
  }
  
  dynstr = (char*) malloc(dyn_strsz);
  if(dynstr == NULL){
    puts("Malloc .dynstr space failed");
    goto _error;
  }
  
  lseek(fd, dyn_strtab, SEEK_SET);
  if(read(fd, dynstr, dyn_strsz) != dyn_strsz){
    puts("Read .dynstr failed");
    goto _error;
  }
  
  funHash = elfhash(funcName);
  printf("Function %s hashVal = 0x%x\n", funcName, funHash);
  
  lseek(fd, dyn_hash, SEEK_SET);
  if(read(fd, &nbucket, 4) != 4){
    puts("Read hash nbucket failed\n");
    goto _error;
  }
  printf("nbucket = %d\n", nbucket);
  
  if(read(fd, &nchain, 4) != 4){
    puts("Read hash nchain failed\n");
    goto _error;
  }
  printf("nchain = %d\n", nchain);
  
  funHash = funHash % nbucket;
  printf("funHash mod nbucket = %d \n", funHash);
  
  lseek(fd, funHash * 4, SEEK_CUR);
  if(read(fd, &funIndex, 4) != 4){
    puts("Read funIndex failed\n");
    goto _error;
  }

  printf("funcIndex:%d\n", funIndex);
  
  lseek(fd, dyn_symtab + funIndex * sizeof(Elf32_Sym), SEEK_SET);
  if(read(fd, &funSym, sizeof(Elf32_Sym)) != sizeof(Elf32_Sym)){
    puts("Read funSym failed");
    goto _error;
  }
  
  if(strcmp(dynstr + funSym.st_name, funcName) != 0){
    while(1){
        printf("hash:%x,nbucket:%d,funIndex:%d\n",dyn_hash,nbucket,funIndex);
        lseek(fd, dyn_hash + 4 * (2 + nbucket + funIndex), SEEK_SET);
        if(read(fd, &funIndex, 4) != 4){
          puts("Read funIndex failed\n");
          goto _error;
        }

        printf("funcIndex:%d\n", funIndex);
        
        if(funIndex == 0){
          puts("Cannot find funtion!\n");
          goto _error;
        }
        
        lseek(fd, dyn_symtab + funIndex * sizeof(Elf32_Sym), SEEK_SET);
        if(read(fd, &funSym, sizeof(Elf32_Sym)) != sizeof(Elf32_Sym)){
          puts("In FOR loop, Read funSym failed");
          goto _error;
        }
        
        if(strcmp(dynstr + funSym.st_name, funcName) == 0){
          break;
        }
    }
  }
  
  printf("Find: %s, offset = 0x%x, size = 0x%x\n", funcName, funSym.st_value, funSym.st_size);
  info->st_value = funSym.st_value;
  info->st_size = funSym.st_size;
  free(dynstr);
  return 0;
  
_error:
  free(dynstr);
  return -1;
}

int main(int argc, char **argv){
  char secName[] = ".dynamic";
  char funcName[] = "Java_com_example_shelldemo2_MainActivity_getString";
  char *soName = "libdemo.so";
  char *content = NULL;
  int fd, i;
  Elf32_Off secOff;
  funcInfo info;

  unsigned a = elfhash(funcName);
  printf("a:%d\n", a);
  
  fd = open(soName, O_RDWR);
  if(fd < 0){
    printf("open %s failed\n", argv[1]);
    goto _error;
  }
  
  secOff = findTargetSectionAddr(fd, secName);
  if(secOff == -1){
    printf("Find section %s failed\n", secName);
    goto _error;
  }
  if(getTargetFuncInfo(fd, funcName, &info) == -1){
    printf("Find function %s failed\n", funcName);
    goto _error;
  }
  
  content = (char*) malloc(info.st_size);
  if(content == NULL){
    puts("Malloc space failed");
    goto _error;
  }
  
  lseek(fd, info.st_value - 1, SEEK_SET);
  if(read(fd, content, info.st_size) != info.st_size){
    puts("Malloc space failed");
    goto _error;
  }
  
  for(i=0;i<info.st_size -1;i++){
    content[i] = ~content[i];
  }
  
  lseek(fd, info.st_value-1, SEEK_SET);
  if(write(fd, content, info.st_size) != info.st_size){
    puts("Write modified content to .so failed");
    goto _error;
  }
  puts("Complete!");
  
_error:
  free(content);
  close(fd);
  return 0;
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容