安卓加固之so文件加固


一、前言

  最近在學習安卓加固方面的知識,看到了jiangwei212的博客,其中有對so文件加固的兩篇文章通過節加密函數和通過hash段找到函數地址直接加密函數,感覺寫的特別好,然后自己動手實踐探索so加密,這里記錄一下學習遇到的困難和所得吧,收獲還是非常大的。

 

二、通過加密節的方式加密函數

 1、加解密思路

  加密:我們自己寫一個Demo根據ELF文件格式,找到我們要加密的節,加密保存在ELF文件中
  解密:這里有一個屬性__attribute__((constructor)),這個屬性使用的節優於main先執行,使我們解密有了可能。

 

 2、實現流程

  ①編寫我們的native代碼,在native中將要加密的函數置於一個節中,並將解密函數賦予__attribute__((constructor))屬性

    a.在函數申明后面加上 __attribute__((section(".mytext"))) ,將函數定義在我們自己的section中

    b.我們需要編寫一個解密函數,屬性用__attribute((constructor))申明,這樣就可以在在so被加載的時候,在main之前將我們的節解密。
     然后使用ndk-build將native代碼編譯成so文件

  ②編寫加密程序(我這里使用VS2010)
    a.解析so文件,找到.mytext段的起始地址和大小,這里是遍歷所有節,根據其在字符串節中的名稱,確定.mytext節
    b.找到.mytext之后,進行加密,我們這里只是簡單的異或,可以使用其他加密手段,最后寫入文件

    ③將加密之后的so文件作為第三方庫加載,注意這里不能直接編譯后打包,要進行加密操作,android studio的加載方式可以參考我之前寫的

         Android Studio使用JNI中的使用第三方庫加載,這里就不在多余的說明了。

 

  3.代碼實現

  ①Native代碼,我們將要加密的函數置於一個新節中,利用__attribute__((section(".mytext")))屬性

jint JNICALL native_Add(JNIEnv* env, jobject obj, jdouble num1, jdouble num2)  __attribute__((section (".mytext")));
jint JNICALL native_Sub(JNIEnv *env, jobject obj, jdouble num1, jdouble num2)  __attribute__((section (".mytext")));
jint JNICALL native_Mul(JNIEnv *env, jobject obj, jdouble num1, jdouble num2)  __attribute__((section (".mytext")));
jint JNICALL native_Div(JNIEnv *env, jobject obj, jdouble num1, jdouble num2)  __attribute__((section (".mytext")));

  ②Native代碼,我們編寫解密函數,我們給我們的解密函數__attribute__((constructor))屬性,在so加載的時候優先執行

//此屬性在so被加載時,優於main執行,開始解密
void init_native_Add() __attribute__((constructor));
unsigned long getLibAddr();



void init_native_Add(){
    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;
    base=getLibAddr(); //在/proc/id/maps文件中找到我們的so文件,活動so文件地址
    ehdr=(Elf32_Ehdr *)base;
    text_addr=ehdr->e_shoff+base;//加密節的地址
    nblock=ehdr->e_entry >>16;//加密節的大小
    nsize=ehdr->e_entry&0xffff;//加密節的大小
    LOGD("nblock =  0x%d,nsize:%d", nblock,nsize);
    LOGD("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");
        LOGD("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");
}
//獲取到SO文件加載到內存中的起始地址,只有找到起始地址才能夠進行解密;
unsigned long getLibAddr(){
    unsigned long ret=0;
    char name[]="libJniTest.so";
    char buf[4096];
    char *temp;
    int pid;
    FILE *fp;
    pid=getpid();
    sprintf(buf,"/proc/%d/maps",pid);  //這個文件中保存了進程映射的模塊信息  cap /proc/id/maps  查看
    fp=fopen(buf,"r");
    if(fp==NULL){
        LOGD("Error open maps file in progress %d",pid);
        puts("open failed");
        goto _error;
    }
    while (fgets(buf,sizeof(buf),fp)){
        if(strstr(buf,name)){
            temp = strtok(buf, "-");  //分割字符串,返回 - 之前的字符
            LOGD("Target so is %s\r\n",temp);
            ret = strtoul(temp, NULL, 16);  //獲取地址
            LOGD("Target so address is %x",ret);
            break;
        }
    }
    _error:
    fclose(fp);
    return ret;
}

  解密函數的實現很簡單,這里我們首先在getLibAddr函數中通過/proc/<pid>/maps文件獲得加載的so文件路徑,其中<pid>是該程序的id,maps文件中存放了加載的所有so文件的路徑和基址,可通過shell命令 cat /proc/id/maps獲得所有模塊信息,也可以通過cat /proc/id/maps | grep libJniTest.so獲得libJniTest.so模塊的信息

  然后我們通過ehdr->e_entry這個變量獲取到被加密節的大小,ehdr->e_shoff獲得加密節的地址偏移(加密的時候將加密節的信息寫入這兩個變量中,所以這里可以直接讀取解密)。

  然后在實現native中的注冊代碼,這里也不多說明了,可以看之前的Android Studio使用JNI,也可以看上傳的代碼

 

  ③使用VS2010編寫加密程序

  這里需要熟悉ELF格式文件,找到我們自己定義的節.mytext,將節使用加密算法加密,將基地址和大小存入e_shoff和e_entry中

int _tmain(int argc, _TCHAR* argv[])
{

    char szSoPath[MAX_PATH] = "libJniTest.so";
    char szSection[] = ".mytext";
    
    char *shstr = NULL;
    char *content = NULL;
    

    int i;
    unsigned int base, length;
    unsigned short nblock;
    unsigned short nsize;
    unsigned char block_size = 16;

    char* szFileData = NULL;
    unsigned int ulLow;
    HANDLE hFile;
    ULONG ulHigh = 0;
    ULONG ulReturn = 0;

    //讀取文件到內存
    hFile = CreateFileA(szSoPath,GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
    if (hFile==INVALID_HANDLE_VALUE)
    {
        printf("打開的文件不存在!");
        return -1;
    }
    ulLow = GetFileSize(hFile,&ulHigh); 
    szFileData = new char[ulLow + 20];
    printf("Read File at 0x%x\r\n",szFileData);
    if (ReadFile(hFile,szFileData,ulLow,&ulReturn,NULL)==0)
    {
        CloseHandle(hFile);
        delete szFileData;
        return FALSE;
    }

    Elf32_Ehdr* ehdr = (Elf32_Ehdr*)(szFileData);
    Elf32_Shdr* shdrstr =  (Elf32_Shdr*)(szFileData + ehdr->e_shoff + sizeof(Elf32_Shdr) * ehdr->e_shstrndx); //字符串表的索引,偏移到字符串表
    shstr = (char*)(szFileData + shdrstr->sh_offset);//偏移到字符串表
    Elf32_Shdr* Shdr = (Elf32_Shdr*)(szFileData + ehdr->e_shoff);


    for(i = 0; i < ehdr->e_shnum; i++){
        //根據字符串表的名稱比較
        if(strcmp(shstr + Shdr->sh_name, szSection) == 0){
            base = Shdr->sh_offset;
            length = Shdr->sh_size;
            printf("Find section %s at 0x%x the size is 0x%x\n", szSection,base,length);
            break;
        }
        Shdr++;
    }

    content= (char*)(szFileData + base);
    nblock = length / block_size;
    nsize = base / 4096 + (base % 4096 == 0 ? 0 : 1);
    printf("base = 0x%x, length = 0x%x\n", base, length);
    printf("nblock = %d, nsize = %d\n", nblock, nsize);

    //將節的地址和大小寫入
    ehdr->e_entry = (length << 16) + nsize;
    ehdr->e_shoff = base; //節的地址
    
    printf("content is %x",content);
    //加密
    for(i=0;i<length;i++){
        content[i] = ~content[i];
    }

    strcat(szSoPath,"_");
    HANDLE hFile1 = CreateFileA(szSoPath,GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
    if (hFile1==INVALID_HANDLE_VALUE)
    {
        printf("創建文件失敗!");
        return -1;
    }
    BOOL bRet = WriteFile(hFile1,szFileData,ulLow,&ulReturn,NULL);
    if(bRet)
    {
        printf("寫入成功!\r\n");
    }
    else
    {
        int a = GetLastError();
        printf("寫入失敗:%d\r\n",a);
    }

_error:
    delete(szFileData);
    CloseHandle(hFile);

    return 0;
}

  1)作為動態鏈接庫,e_entry入口地址是無意義的,因為程序被加載時,設定的跳轉地址是動態連接器的地址,這個字段是可以被作為數據填充的。

  2)so裝載時,與鏈接視圖沒有關系,即e_shoff、e_shentsize、e_shnum和e_shstrndx這些字段是可以任意修改的。被修改之后,使用readelf和ida等工具打開,會報各種錯誤,相信讀者已經見識過了。

 

  ④運行結果

  1)加密前

  

 

  2)加密后

  

 

  3)運行結果

  (注:這里結果在原結果上加2)

 

  4)使用grep命令查看加載模塊

  

 

  4.相關知識點

  ①將要加密的函數置於一個節中,解密函數使用__attribute__((constructor))屬性優先執行,對於so動態鏈接庫e_entry和e_shoff是可以修改存放我們加密節的大小和地址的,便於解密。

  ②掌握ELF文件格式知識,遍歷節表,對應於字符串表中的節名,找到要加密的節,進行加密操作。

 

三、直接加密指定函數

   1.原理

  在so文件中,每個函數的結構描述是存放在.dynsym段中,每個函數名稱保存在.dynstr段中,在ELF格式中有一個.hash段,由Elf32_Word對象組成的哈希表支持符號表訪問。

    

  bucket數組包含nbucket個項目,chain數組包含了nchain個項目,下標都是從0開始。

  bucket和chain中都保存符號表索引,chain和符號表存在對應關系,符號表項的數目應該和nchain相等,所以符號表的索引也可用來選取chain表項,哈希函數能夠接受符號名並且返回一個可以用來計算bucket的索引。
  因此,如果哈希函數針對某個名字返回了數值X,則bucket[X%nbucket]給出了一個索引y,該索引可用於符號表,也可用於chain表。如果符號表不是所需要的,那么chain[y]則給出了具有相同哈希值的下一個符號表項。我們可以沿着chain鏈一直搜索,直到所選中的符號表項包含了所需要的符號,或者chain項中包含值STN_UNDEF。

  上面的話有些復雜,簡單來說就是用函數名稱在hash函數中得到一個hash值,通過這個hash在chain中的位置就可以找到這個函數對應在.dynsym中對應的條目了。

  hash函數如下

unsigned long elf_hash(const unsigned char* name){
  unsigned long h = 0,g;
  while(*name)
  {
      h = (h<<4)+*name++;
      if(g = h & 0xf0000000)
      {
          h^=g>>24;
          h&=-g;
      }
      return h;
  }
}

  那么我們只用得到.hash段即可,但是我們怎么得到這個section呢?

  

  由於so被加載到內存之后,就沒有section了,對應的是segment了,而一個section包含多個section,相同的section可以被包含到不同的segment中。.dynamic段一般用於動態鏈接,所以.dynsym和.dynstr,.hash肯定包含在這里。我們可以解析了程序頭信息之后,通過type獲取到.dynamic程序頭信息,然后獲取到這個segment的偏移地址和大小,在進行解析成elf32_dyn結構。

 

  2.實現方案

  我們給函數加密,加密和解密都是基於裝載視圖實現,需要注意的是,被加密函數如果用static聲明,那么函數是不會出現在.dynsym中,是無法在裝載視圖中通過函數名找到進行解密的。

  ①加密流程:
  1)讀取文件頭,獲取e_phoff、e_phentsize和e_phnum信息
  2)通過Elf32_Phdr中的p_type字段,找到DYNAMIC。其實,DYNAMIC就是.dynamic section。從p_offset和p_filesz字段得到文件中的起始位置和長度。
  3)遍歷.dynamic,找到.dynsym、.dynstr、.hash section文件中的偏移和.dynstr的大小,
  4)根據函數名稱,計算hash值
  5)根據hash值,找到下標hash%nbuckets的bucket;根據bucket中的值,讀取.dynsym中的對應索引的Elf32_Sym符號,從符號的st_name索引找到在.dynstr中對應的字符串與函數名進行比較。若不等,則根據chain[hash%nbuckets]找下一個Elf32_Sym符號,直到找到或者chain終止為止。

  6)找到函數對應的Elf32_Sym符號之后,即可以根據st_value和st_size字段找到函數的位置和大小

  7)加密,寫入文件

 

   ②解密流程為加密逆過程,找到函數地址的方式和加密流程中的方法是一致的,都是通過chain在dynsym中找到對應的函數項,然后在函數地址處進行解密。

 

   3.代碼實現

   ①使用VS2010編寫加密代碼

// Encrypting.cpp : 定義控制台應用程序的入口點。
//
#include "stdafx.h"
#include <stdio.h>
#include <iostream>
using namespace std;
#include <Windows.h>
#include "elf.h"

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


static Elf32_Off findTargetSectionAddr(char* szFileData, const char *szSection);
static char getTargetFuncInfo(char* szFileData, const char *funcName, funcInfo *info);
static unsigned elfhash(const char *_name); 



int _tmain(int argc, _TCHAR* argv[])
{
        char *shstr = NULL;
        funcInfo info;  
        int i;
        char* szFileData = NULL;
        unsigned int ulLow;
        HANDLE hFile;
        ULONG ulHigh = 0;
        ULONG ulReturn = 0;
 
        char funcNameAdd[] = "native_Add";
        char funcNameSub[] = "native_Sub";
        char funcNameMul[] = "native_Mul";
        char funcNameDiv[] = "native_Div";
        char szSoPath[MAX_PATH] = "libJniTest.so";
        char szSection[] =  ".text";
        Elf32_Off secOff;  

        //讀入文件在內存中
        hFile = CreateFileA(szSoPath,GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
        if (hFile==INVALID_HANDLE_VALUE)
        {
            printf("打開的文件不存在!");
            return -1;
        }
        ulLow = GetFileSize(hFile,&ulHigh); 
        szFileData = new char[ulLow + 20];
        if (ReadFile(hFile,szFileData,ulLow,&ulReturn,NULL)==0)
        {
            CloseHandle(hFile);
            delete szFileData;
            return FALSE;
        }
        
        //通過hash段中chain鏈獲得的索引,獲取在dynsym對應的條目
        if(getTargetFuncInfo(szFileData, funcNameAdd, &info) == -1){  
            printf("Find function %s failed\n", funcNameAdd);  
            goto _error;  
        }  

        //得到函數地址
        for(i=0;i<info.st_size-1;i++){
            char *content = (char*)(szFileData + info.st_value -1 + i);
            *content = ~(*content);
        }

        //通過hash段中chain鏈獲得的索引,獲取在dynsym對應的條目
        if(getTargetFuncInfo(szFileData, funcNameSub, &info) == -1){  
            printf("Find function %s failed\n", funcNameSub);  
            goto _error;  
        }  
        //得到函數地址
        for(i=0;i<info.st_size-1;i++){
            char *content = (char*)(szFileData + info.st_value -1 + i);
            *content = ~(*content);
        }
        //通過hash段中chain鏈獲得的索引,獲取在dynsym對應的條目
        if(getTargetFuncInfo(szFileData, funcNameMul, &info) == -1){  
            printf("Find function %s failed\n", funcNameMul);  
            goto _error;  
        }  
        //得到函數地址
        for(i=0;i<info.st_size-1;i++){
            char *content = (char*)(szFileData + info.st_value -1 + i);
            *content = ~(*content);
        }
        //通過hash段中chain鏈獲得的索引,獲取在dynsym對應的條目
        if(getTargetFuncInfo(szFileData, funcNameDiv, &info) == -1){  
            printf("Find function %s failed\n", funcNameDiv);  
            goto _error;  
        }  
        //得到函數地址
        for(i=0;i<info.st_size-1;i++){
            char *content = (char*)(szFileData + info.st_value -1 + i);
            *content = ~(*content);
        }

        //寫入文件保存
        strcat(szSoPath,"_");
        HANDLE hFile1 = CreateFileA(szSoPath,GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
        if (hFile1==INVALID_HANDLE_VALUE)
        {
            printf("創建文件失敗!");
            return -1;
        }
        BOOL bRet = WriteFile(hFile1,szFileData,ulLow,&ulReturn,NULL);
        if(bRet)
        {
            printf("寫入成功!\r\n");
        }
        else
        {
            int a = GetLastError();
            printf("寫入失敗:%d\r\n",a);
        }
_error:
        delete(szFileData);
        CloseHandle(hFile);

    return 0;
}


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 char getTargetFuncInfo(char* szFileData, const char *funcName, funcInfo *info){  
    char flag = -1;
    char *dynstr = NULL;  
    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;  
    Elf32_Ehdr* ehdr = (Elf32_Ehdr*)szFileData;

    //視圖模式
    phdr = (Elf32_Phdr*)(szFileData + ehdr->e_phoff);
    for(i=0;i < ehdr->e_phnum; i++){  
        //獲得動態鏈接節
        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;  
        }  
        phdr++;
    }  
    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);  


    dyn = (Elf32_Dyn*)(szFileData + dyn_off);
    for(i=0;i < dyn_size / sizeof(Elf32_Dyn); i++){  
        
        //符號表位置
        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);  
        }  
        //獲得hash段
        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);  
        }  
        dyn++;
    }  

    if((flag & 0x0f) != 0x0f){  
        puts("Find needed .section failed\n");  
        goto _error;  
    }  

    dynstr = (char*) malloc(dyn_strsz);  
    if(dynstr == NULL){  
        printf("Malloc .dynstr space failed");  
        goto _error;  
    }  
    memcpy(dynstr,szFileData + dyn_strtab,dyn_strsz);

/*     nbucket                                                                 
 *-----------------
 *       nchain
 *------------------
 *      bucket[0]
 *       ...
 *   bucket[nbucket-1]
 * ------------------
 *     chain[0]
 *       ...
 *   chain[nchain-1]
 */
    funHash = elfhash(funcName);  //獲得函數名稱經過hash運行后的值
    printf("Function %s hashVal = 0x%x\n", funcName, funHash);  

    nbucket = *(int*)(szFileData + dyn_hash); //獲得nbucket的值
    printf("nbucket = %d\n", nbucket);  

    nchain = *(int*)(szFileData + dyn_hash + 4);//獲得nchain的值
    printf("nchain = %d\n", nchain);  

    funHash = funHash % nbucket;        //bucket[X%nbucket]給出了一個索引y,該索引可用於符號表,也可用於chain表
    printf("funHash mod nbucket = %d \n", funHash);  

    funIndex = *(int*)(szFileData + dyn_hash + 8 + funHash * 4);//y = bucket[X%nbucket]返回的索引y
    printf("funcIndex:%d\n", funIndex);  
    funSym = (Elf32_Sym*)(szFileData + dyn_symtab + funIndex*sizeof(Elf32_Sym));//該索引對應的符號表
    

    if(strcmp(dynstr + funSym->st_name, funcName) != 0){  //如果索引y對應的符號表不是所需要的,那么chain[y]則給出了具有相同哈希值的下一個符號表項
        while(1){  
            //我們可以沿着chain鏈一直搜索,直到所選中的符號表項包含了所需要的符號
            printf("hash:%x,nbucket:%d,funIndex:%d\n",dyn_hash,nbucket,funIndex);  
            funIndex = *(int*)(szFileData + dyn_hash + 4*(2+nbucket+funIndex));  //搜索chain鏈
            printf("funcIndex:%d\n", funIndex);  

            if(funIndex == 0){  
                puts("Cannot find funtion!\n");  
                goto _error;  
            }  
            funSym = (Elf32_Sym*)(szFileData + dyn_symtab + funIndex*sizeof(Elf32_Sym)); //chain[]中對應的符號表
            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;  
}  

  

  ②Native代碼

#include <jni.h>
#include <stdio.h>
//#include <assert.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <android/log.h>
#include <elf.h>
#include <sys/mman.h>

#define LOG_TAG "Jiami"
#define LOGD(fmt,args...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,fmt,##args)

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




JNIEXPORT jint JNICALL native_Add
(JNIEnv *env, jobject obj, jdouble num1, jdouble num2)
{
    return (jint)(num1 + num2 +3);
}


JNIEXPORT jint JNICALL native_Sub
        (JNIEnv *env, jobject obj, jdouble num1, jdouble num2)
{
    return (jint)(num1 - num2 +3);
}


JNIEXPORT jint JNICALL native_Mul
        (JNIEnv *env, jobject obj, jdouble num1, jdouble num2)
{
    return (jint)(num1 * num2 +3);
}

JNIEXPORT jint JNICALL native_Div
        (JNIEnv *env, jobject obj, jdouble num1, jdouble num2)
{
    if (num2 == 0) return 0;
    return (jint)(num1 / num2 +3);
}

//Java和JNI函數的綁定表
static JNINativeMethod gMethods[] = {
        {"Add", "(DD)I", (void *)native_Add},
        {"Sub", "(DD)I", (void *)native_Sub},
        {"Mul", "(DD)I", (void *)native_Mul},
        {"Div", "(DD)I", (void *)native_Div},
};




//注冊native方法到java中
static int registerNativeMethods(JNIEnv* env, const char* className,
                                JNINativeMethod* gMethods, int numMethods)
{
    jclass clazz;
    clazz = (*env)->FindClass(env, className);
    if (clazz == NULL) {
        return JNI_FALSE;
    }
    if ((*env)->RegisterNatives(env, clazz, gMethods,numMethods) < 0){
        return JNI_FALSE;
    }

    return JNI_TRUE;
}


int register_ndk_load(JNIEnv *env)
{

    return registerNativeMethods(env, "com/example/caculate/MainActivity",
                                 gMethods,sizeof(gMethods) / sizeof(gMethods[0]));
                                 //NELEM(gMethods));
}


JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
    JNIEnv* env = NULL;
    jint result = -1;

    if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        return result;
    }

    register_ndk_load(env);

    // 返回jni的版本
    return JNI_VERSION_1_4;
}


//此屬性在so被加載時,優於main執行,開始解密
void init_native_Add() __attribute__((constructor));
void init_native_Sub();
void init_native_Mul();
void init_native_Div();
unsigned long getLibAddr();

static char getTargetFuncInfo(unsigned long base, const char *funcName, funcInfo *info);


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;
}

void init_native_Add(){

    const char target_fun[] = "native_Add";
    funcInfo info;
    int i;
    unsigned int npage, base = getLibAddr();

    LOGD("base addr is 0x%x",base);
    if(getTargetFuncInfo(base, target_fun, &info) == -1){
          LOGD("Find native_Add failed");
          return ;
    }

    npage = info.st_size / PAGE_SIZE + ((info.st_size % PAGE_SIZE == 0) ? 0 : 1);
    LOGD("npage =  0x%d", npage);
    LOGD("npage =  0x%d", PAGE_SIZE);

    if(mprotect((void *) ((base + info.st_value) / PAGE_SIZE * PAGE_SIZE), 4096*npage, PROT_READ | PROT_EXEC | PROT_WRITE) != 0){
          LOGD("mem privilege change failed");
     }

      for(i=0;i< info.st_size - 1; i++){
          char *addr = (char*)(base + info.st_value -1 + i);
          *addr = ~(*addr);
      }

      if(mprotect((void *) ((base + info.st_value) / PAGE_SIZE * PAGE_SIZE), 4096*npage, PROT_READ | PROT_EXEC) != 0){
          LOGD("mem privilege change failed");
      }
    init_native_Sub();
    init_native_Mul();
    init_native_Div();
}

void init_native_Sub(){

    const char target_fun[] = "native_Sub";
    funcInfo info;
    int i;
    unsigned int npage, base = getLibAddr();

    LOGD("base addr is 0x%x",base);
    if(getTargetFuncInfo(base, target_fun, &info) == -1){
          LOGD("Find native_Sub failed");
          return ;
    }

    npage = info.st_size / PAGE_SIZE + ((info.st_size % PAGE_SIZE == 0) ? 0 : 1);
    LOGD("npage =  0x%d", npage);
    LOGD("npage =  0x%d", PAGE_SIZE);

    if(mprotect((void *) ((base + info.st_value) / PAGE_SIZE * PAGE_SIZE), 4096*npage, PROT_READ | PROT_EXEC | PROT_WRITE) != 0){
          LOGD("mem privilege change failed");
     }

      for(i=0;i< info.st_size - 1; i++){
          char *addr = (char*)(base + info.st_value -1 + i);
          *addr = ~(*addr);
      }

      if(mprotect((void *) ((base + info.st_value) / PAGE_SIZE * PAGE_SIZE), 4096*npage, PROT_READ | PROT_EXEC) != 0){
          LOGD("mem privilege change failed");
      }
}

void init_native_Div(){

    const char target_fun[] = "native_Div";
    funcInfo info;
    int i;
    unsigned int npage, base = getLibAddr();

    LOGD("base addr is 0x%x",base);
    if(getTargetFuncInfo(base, target_fun, &info) == -1){
          LOGD("Find native_Div failed");
          return ;
    }

    npage = info.st_size / PAGE_SIZE + ((info.st_size % PAGE_SIZE == 0) ? 0 : 1);
    LOGD("npage =  0x%d", npage);
    LOGD("npage =  0x%d", PAGE_SIZE);

    if(mprotect((void *) ((base + info.st_value) / PAGE_SIZE * PAGE_SIZE), 4096*npage, PROT_READ | PROT_EXEC | PROT_WRITE) != 0){
          LOGD("mem privilege change failed");
     }

      for(i=0;i< info.st_size - 1; i++){
          char *addr = (char*)(base + info.st_value -1 + i);
          *addr = ~(*addr);
      }

      if(mprotect((void *) ((base + info.st_value) / PAGE_SIZE * PAGE_SIZE), 4096*npage, PROT_READ | PROT_EXEC) != 0){
          LOGD("mem privilege change failed");
      }
}

void init_native_Mul(){

    const char target_fun[] = "native_Mul";
    funcInfo info;
    int i;
    unsigned int npage, base = getLibAddr();

    LOGD("base addr is 0x%x",base);
    if(getTargetFuncInfo(base, target_fun, &info) == -1){
          LOGD("Find native_Mul failed");
          return ;
    }

    npage = info.st_size / PAGE_SIZE + ((info.st_size % PAGE_SIZE == 0) ? 0 : 1);
    LOGD("npage =  0x%d", npage);
    LOGD("npage =  0x%d", PAGE_SIZE);

    if(mprotect((void *) ((base + info.st_value) / PAGE_SIZE * PAGE_SIZE), 4096*npage, PROT_READ | PROT_EXEC | PROT_WRITE) != 0){
          LOGD("mem privilege change failed");
     }

      for(i=0;i< info.st_size - 1; i++){
          char *addr = (char*)(base + info.st_value -1 + i);
          *addr = ~(*addr);
      }

      if(mprotect((void *) ((base + info.st_value) / PAGE_SIZE * PAGE_SIZE), 4096*npage, PROT_READ | PROT_EXEC) != 0){
          LOGD("mem privilege change failed");
      }
}
//獲取到SO文件加載到內存中的起始地址,只有找到起始地址才能夠進行解密;
unsigned long getLibAddr(){
    unsigned long ret=0;
    char name[]="libJniTest.so";
    char buf[4096];
    char *temp;
    int pid;
    FILE *fp;
    pid=getpid();
    sprintf(buf,"/proc/%d/maps",pid);  //這個文件中保存了進程映射的模塊信息  cap /proc/id/maps  查看
    fp=fopen(buf,"r");
    if(fp==NULL){
        LOGD("Error open maps file in progress %d",pid);
        puts("open failed");
        goto _error;
    }
    while (fgets(buf,sizeof(buf),fp)){
        if(strstr(buf,name)){
            temp = strtok(buf, "-");  //分割字符串,返回 - 之前的字符
            LOGD("Target so is %s\r\n",temp);
            ret = strtoul(temp, NULL, 16);  //獲取地址
            LOGD("Target so address is %x",ret);
            break;
        }
    }
    _error:
    fclose(fp);
    return ret;
}


static char getTargetFuncInfo(unsigned long base, const char *funcName, funcInfo *info){
    char flag = -1, *dynstr;
    int i;
    Elf32_Ehdr *ehdr;
    Elf32_Phdr *phdr;
    Elf32_Off dyn_vaddr;
    Elf32_Word dyn_size, dyn_strsz;
    Elf32_Dyn *dyn;
    Elf32_Addr dyn_symtab, dyn_strtab, dyn_hash;
    Elf32_Sym *funSym;
    unsigned funHash, nbucket;
    unsigned *bucket, *chain;

    ehdr = (Elf32_Ehdr *)base;
    phdr = (Elf32_Phdr *)(base + ehdr->e_phoff);//視圖模式
    LOGD("[+]phdr =  0x%p, size = 0x%x\n", phdr, ehdr->e_phnum);
    for (i = 0; i < ehdr->e_phnum; ++i) {
        LOGD("[+]phdr =  0x%p\n", phdr);
        //獲得動態鏈接節
        if(phdr->p_type ==  PT_DYNAMIC){
            flag = 0;
            LOGD("Find .dynamic segment");
            break;
        }
        phdr ++;
    }
    if(flag)
        goto _error;
    dyn_vaddr = phdr->p_vaddr + base;
    dyn_size = phdr->p_filesz;
    LOGD("[+]dyn_vadd =  0x%x, dyn_size =  0x%x", dyn_vaddr, dyn_size);
    flag = 0;
    for (i = 0; i < dyn_size / sizeof(Elf32_Dyn); ++i) {
        dyn = (Elf32_Dyn *)(dyn_vaddr + i * sizeof(Elf32_Dyn));
        //符號表位置
        if(dyn->d_tag == DT_SYMTAB){
            dyn_symtab = (dyn->d_un).d_ptr;
            flag += 1;
            LOGD("[+]Find .dynsym section, addr = 0x%x\n", dyn_symtab);
        }
        //獲得hash段
        if(dyn->d_tag == DT_HASH){
            dyn_hash = (dyn->d_un).d_ptr;
            flag += 2;
            LOGD("[+]Find .hash section, addr = 0x%x\n", dyn_hash);
        }
        //保存函數字符串的位置
        if(dyn->d_tag == DT_STRTAB){
            dyn_strtab = (dyn->d_un).d_ptr;
            flag += 4;
            LOGD("[+]Find .dynstr section, addr = 0x%x\n", dyn_strtab);
        }
        //字符串長度
        if(dyn->d_tag == DT_STRSZ){
            dyn_strsz = (dyn->d_un).d_val;
            flag += 8;
            LOGD("[+]Find strsz size = 0x%x\n", dyn_strsz);
        }
    }
    if((flag & 0x0f) != 0x0f){
        LOGD("Find needed .section failed\n");
        goto _error;
    }
    dyn_symtab += base;
    dyn_hash += base;
    dyn_strtab += base;
    dyn_strsz += base;

/*     nbucket                                                                 
 *-----------------
 *       nchain
 *------------------
 *      bucket[0]
 *       ...
 *   bucket[nbucket-1]
 * ------------------
 *     chain[0]
 *       ...
 *   chain[nchain-1]
 */
    funHash = elfhash(funcName);//獲得函數名稱經過hash運行后的值
    funSym = (Elf32_Sym *) dyn_symtab;
    dynstr = (char*) dyn_strtab;
    nbucket = *((int *) dyn_hash);//獲得nbucket的值
    bucket = (int *)(dyn_hash + 8);//bucket鏈
    chain = (unsigned int *)(dyn_hash + 4 * (2 + nbucket));//越過bucket鏈,到達chain鏈

    flag = -1;
    LOGD("[+]hash = 0x%x, nbucket = 0x%x\n", funHash, nbucket);
    //bucket[X%nbucket]給出了一個索引y,該索引可用於符號表,也可用於chain表
    int mod = (funHash % nbucket);
    LOGD("[+]mod = %d\n", mod);
    LOGD("[+]i = 0x%d\n", bucket[mod]);
    //i = mod = bucket[funHash%nbucket],通過遍歷i = chain[i]表,找到funSym對應的符號表
    for(i = bucket[mod]; i != 0; i = chain[i]){
        LOGD("[+]Find index = %d\n", i);
        if(strcmp(dynstr + ((Elf32_Sym*)((char*)funSym + i* sizeof(Elf32_Sym)))->st_name, funcName) == 0){
            flag = 0;
            LOGD("[+]Find %s\n", funcName);
            break;
        }
    }
    if(flag) goto _error;
    info->st_value = ((Elf32_Sym*)((char*)funSym + i* sizeof(Elf32_Sym)))->st_value;//函數對應符號表中保存函數的地址
    info->st_size =((Elf32_Sym*)((char*)funSym + i* sizeof(Elf32_Sym)))->st_size;//函數符號表中保存函數的大小
    LOGD("[+]st_value = %d,st_size = %d",info->st_value,info->st_size);
    return 0;
_error:
    return -1;
}

  Android.mk文件

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -llog
LOCAL_PRELINK_MODULE := false
LOCAL_MODULE := JniTest
LOCAL_SRC_FILES := MyJniCalc.c
LOCAL_SHARED_LIBRARIES := libandroid_runtime
include $(BUILD_SHARED_LIBRARY)

 

  4.執行結果

  1)加密之前

  

 

  2)加密之后

  

 

  3)運行結果

  (注:這里結果為原結果加3)

 

 四、總結

  這里的so文件加固相對於windows平台還是比較簡單的,沒有復雜的跳轉,學習起來比較容易的,只需熟悉ELF格式,找到對應的位置進行加解密。這篇屬於拿來主義,不過自己在實踐學習的過程中也學習到特別多的知識,比如安卓模擬器使用grep命令,加載第三方庫的方法,也犯了很多小錯誤也一一解決了,期待以后更加深入的學習和分享~

  代碼下載:http://pan.baidu.com/s/1eSHl9GA


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM