0x00 前言
之前的兩篇文章從鏈接視圖和執行視圖分析了elf文件的大致結構,這篇文章主要內容是對於so文件進行簡單的加密工作,針對Ida等靜態分析工具的分析,一旦開始動態調試就應該很容易就可以dump出內存,直接修復了。
0x01 思路
主要是兩種思路,
- 對文件中指定的section加密,然后在運行時由.initarray進行解密;
- 對指定的函數進行加密,在運行時由.initarray進行解密。
兩種不同的方法說到底也就是不同的View而已。
①基於鏈接視圖,對指定的section進行加密工作。原理很簡單,就是借助GCC 編譯器的__attribute__關鍵字增加一個自定義的section,然后對增加的section進行加密,最后利用.initarray在so被加載時調用進行解密工作。
②基於執行視圖,對指定的函數進行加密工作。同樣也要利用的GCC編譯器的__attribute__關鍵字來將解密代碼放置於.initarray段,在so被加載時調用。
0x02 實現
基於鏈接視圖
非常簡單的程序,點擊Button,然后調用jni層的getString()函數,返回一串字符串
JNIEXPORT jstring JNICALL getString(JNIEnv* env,jobject clazz) { LOGD("getString invoke"); return env->NewStringUTF("Hello_CC"); }
但是函數的聲明要注意:
JNIEXPORT jstring JNICALL getString(JNIEnv*,jobject)__attribute__((section(".mytext")));
將函數放置在”.mytext” section。
這里Java 層和native層的映射關系是自己手動向虛擬機注冊,對於JNI不太了解可以移步這篇博文Android JNI 初體驗
static JNINativeMethod g_methods[] = { { "getString", "()Ljava/lang/String;", (void*)getString } };
jint JNI_OnLoad(JavaVM *vm,void * reserved) { jint result = -1; JNIEnv *env = NULL; if(vm->GetEnv((void**)&env,JNI_VERSION_1_4) != JNI_OK) { result = -1; } //com.example.protectsection jclass clazz = env->FindClass("com/example/protectsection/MainActivity"); if(clazz == NULL) { LOGD("find class failed"); return result; } if(env->RegisterNatives(clazz,g_methods,sizeof(g_methods)/sizeof(g_methods[0]))<0) { LOGD("register natives failed"); return result; } result = JNI_VERSION_1_4; return result; }
然后就開始我們的加密之旅了,下面是加密程序,基於鏈接視圖,找到目標名為”.mytext”的section,然后將該section內的代碼進行簡單的取反工作,最后寫回文件中。

#include <stdio.h> #include <stdlib.h> #include <elf.h> #include <fcntl.h> int main(int argc,char ** argv) { char section_name[] = ".mytext"; Elf32_Ehdr ehdr; Elf32_Shdr shdr; char * ptr_shstrtab = NULL; Elf32_Off target_section_offset = 0; Elf32_Word target_section_size = 0; char * ptr_section_content = NULL; int page_size = 4096; int fd; if(argc < 2) { puts("input so file\n"); return -1; } fd = open(argv[1],O_RDWR); if(fd < 0) { goto _error; } if(read(fd,&ehdr,sizeof(Elf32_Ehdr)) != sizeof(Elf32_Ehdr)) { puts("read elf header failed\n"); 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 .shstrtab header failed\n"); goto _error; } ptr_shstrtab = (char*)malloc(shdr.sh_size); if(NULL == ptr_shstrtab) { puts("apply mem failed\n"); goto _error; } //read shstrtab lseek(fd,shdr.sh_offset,SEEK_SET); if(read (fd,ptr_shstrtab,shdr.sh_size) != shdr.sh_size) { goto _error; } lseek(fd,ehdr.e_shoff,SEEK_SET); int i = 0; for(i = 0;i < ehdr.e_shnum;i++) { if(read(fd,&shdr,sizeof(Elf32_Shdr)) != sizeof(Elf32_Shdr)) { puts("find target section faile\nd"); goto _error; } if( !strcmp(ptr_shstrtab + shdr.sh_name , section_name) ) { target_section_offset = shdr.sh_offset; target_section_size = shdr.sh_size; break; } } lseek(fd,target_section_offset,SEEK_SET); ptr_section_content = (char*)malloc(target_section_size); if(NULL ==ptr_section_content) { goto _error; } if(read(fd,ptr_section_content,target_section_size) != target_section_size) { goto _error; } int num_page = target_section_size/page_size + 1; ehdr.e_entry = target_section_size; ehdr.e_shoff = target_section_offset; for(i = 0; i<target_section_size;i++) { ptr_section_content[i] =~ ptr_section_content[i]; } lseek(fd,0,SEEK_SET); if(write(fd,&ehdr,sizeof(Elf32_Ehdr)) != sizeof(Elf32_Ehdr)) { goto _error; } lseek(fd , target_section_offset , SEEK_SET); if(write(fd , ptr_section_content,target_section_size) != target_section_size) { goto _error; } puts("completed\n"); _error: if(NULL != ptr_section_content) { free(ptr_section_content); } if(NULL != ptr_shstrtab) { free(ptr_shstrtab); } if(NULL != fd) { close(fd); } return 0; }
用ida查看的效果圖:
接下來就是運行時的解密工作,通過我們自己的init_getString函數在so初始化時對目標section進行解密:
void init_getString()__attribute__((constructor)); //initarray
完整的init_getString函數如下:
void init_getString() { //LOGD("Hello init_getString"); unsigned long lib_addr; Elf32_Ehdr* ptr_ehdr = NULL; Elf32_Shdr* ptr_shdr = NULL; unsigned long mytext_addr; unsigned long mytext_size = 0; unsigned long page_size = 0x1000; lib_addr = get_cur_lib_addr(); if(NULL == lib_addr) { return ; } ptr_ehdr = (Elf32_Ehdr*)lib_addr; mytext_size = ptr_ehdr->e_entry; //size mytext_addr = ptr_ehdr->e_shoff + lib_addr; //offset unsigned long offset = mytext_addr % page_size; LOGD("invoke mprotect first"); if(mprotect((const void*)(mytext_addr-offset) , mytext_size,PROT_READ | PROT_WRITE | PROT_EXEC) != 0) { LOGD("change the mem failed"); return ; } for(int i=0;i<mytext_size;i++) { ((char*)mytext_addr)[i] =~ ((char*)mytext_addr)[i]; } LOGD("invoke mprotect to resume the page"); if(mprotect((const void*)(mytext_addr-offset),mytext_size,PROT_READ | PROT_EXEC)!=0) { LOGD("resume mem failed"); return ; } }
獲得自己的so模塊在內存中的加載基地址是通過linux的“/proc”文件系統得到的,進程的運行時的狀態都會顯示在”/proc”文件系統中。例如下圖就是某個進程的內存中模塊的的信息。
unsigned long get_cur_lib_addr() { unsigned long addr = 0; char section_name[] = "libprotect_section.so"; pid_t pid = 0; //<types.h> char buf[1024] = {0}; FILE* fp = NULL; char* tmp = NULL; pid = getpid(); //<unistd.h> sprintf(buf,"/proc/%d/maps",pid); //<stdio.h> fp = fopen(buf,"r"); if(NULL == fp) { return 0; } while(fgets(buf,sizeof(buf),fp)) { if(strstr(buf,section_name)) { tmp = strtok(buf,"-"); addr = strtoul(tmp,NULL,0x10); //<stdlib.h> break; } } fclose(fp); return addr; }
最后的運行結果:
完整代碼:https://github.com/ChengChengCC/Android-demo/tree/master/ProtectSection
基於執行視圖
就是基於執行視圖解釋ELF文件格式,如果不太了解請移步這篇博文從dlsym()源碼看動態鏈接過程
就是通過動態鏈接的過程,通過.dynamic段定位.symtab , .dynstr, .hash,然后找到目標函數的文件偏移和大小,然后將目標函數取反,達到簡單的加密,ida查看的效果圖如下

#include <stdio.h> #include <stdlib.h> #include <stddef.h> #include <elf.h> #include <fcntl.h> #define SYMTAB 0x01 #define HASH 0X02 #define STRTAB 0x04 #define STRSZ 0X08 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; } int main(int argc ,char** argv) { Elf32_Ehdr ehdr; Elf32_Phdr phdr; Elf32_Word dyn_size,dyn_strsz; Elf32_Off dyn_off; Elf32_Dyn dyn; Elf32_Addr dyn_sym,dyn_str,dyn_hash; unsigned func_hash,nbucket,nchain,func_index; char * ptr_dynstr = NULL; char * ptr_func_content = NULL; Elf32_Sym func_sym; int flag = 0; int i = 0; char func_name[] = "getString"; if(argc < 2) { printf("input the so file\n"); return 0; } int fd = open(argv[1],O_RDWR); if(fd < 0) { printf("open file failed\n"); goto _error; } lseek(fd,0,SEEK_SET); if(read(fd,&ehdr,sizeof(Elf32_Ehdr)) != sizeof(Elf32_Ehdr)) { printf("read elf header failed\n"); goto _error; } lseek(fd,ehdr.e_phoff,SEEK_SET); for(i=0; i<ehdr.e_phnum;i++) { memset(&phdr,0,sizeof(phdr)); if(read(fd,&phdr,sizeof(Elf32_Phdr)) != sizeof(Elf32_Phdr)) { printf("read segment failed\n"); goto _error; } if(phdr.p_type == PT_DYNAMIC) { dyn_off = phdr.p_offset; dyn_size = phdr.p_filesz; printf("find .dynamic section\n"); break; } } lseek(fd,dyn_off,SEEK_SET); //for(i = 0;i<dyn_size/sizeof(Elf32_Dyn);i++) do{ if(read(fd,&dyn,sizeof(Elf32_Dyn)) != sizeof(Elf32_Dyn)) { printf("read .dynamic failed\n"); goto _error; } if(dyn.d_tag == DT_SYMTAB) { flag |= SYMTAB; dyn_sym = dyn.d_un.d_ptr; } if(dyn.d_tag == DT_STRTAB) { flag |= STRTAB; dyn_str = dyn.d_un.d_ptr; } if(dyn.d_tag == DT_STRSZ) { flag |= STRSZ; dyn_strsz = dyn.d_un.d_val; } if(dyn.d_tag == DT_HASH) { flag |= HASH; dyn_hash = dyn.d_un.d_ptr; } } while(dyn.d_tag != DT_NULL); if((flag & 0x0f) != 0x0f) { printf("find the needed dynamic section failed\n"); goto _error; } ptr_dynstr = (char*)malloc(dyn_strsz); if(ptr_dynstr == NULL) { printf("malloc .dynstr failed\n"); goto _error; } lseek(fd,dyn_str,SEEK_SET); if(read(fd,ptr_dynstr,dyn_strsz) != dyn_strsz) { printf("read .dynstr failed\n"); goto _error; } func_hash = elfhash(func_name); lseek(fd,dyn_hash,SEEK_SET); if(read(fd,&nbucket,4) != 4) { printf("read hash nbucket failed\n"); goto _error; } if(read(fd,&nchain,4) != 4) { printf("read hash nchain failed\n"); goto _error; } func_hash = func_hash%nbucket; lseek(fd,func_hash*4,SEEK_CUR); if(read(fd,&func_index,4) != 4)//索引是符號表或者chain { printf("read funck index failed\n"); goto _error; } lseek(fd,dyn_sym+func_index*sizeof(Elf32_Sym),SEEK_SET); if(read(fd,&func_sym,sizeof(Elf32_Sym)) != sizeof(Elf32_Sym)) { printf("read func sym entry failed\n'"); } if(strcmp(ptr_dynstr+func_sym.st_name,func_name) != 0) { while(1) //純C語言是沒有true的 { lseek(fd,dyn_hash+4*(2+nbucket+func_index),SEEK_SET); if(read(fd,&func_index,4) != 4) { printf("read func index failed\n"); goto _error; } lseek(fd,dyn_sym + func_index*sizeof(Elf32_Sym),SEEK_SET); memset(&func_sym,0,sizeof(Elf32_Sym)); if(read(fd,&func_sym,sizeof(Elf32_Sym)) != sizeof(Elf32_Sym)) { goto _error; } if(strcmp(func_name,dyn_str+func_sym.st_name) == 0) { break; } } } printf("find target func addr: %x,sizeo:%x\n",func_sym.st_value,func_sym.st_size); ptr_func_content = (char*)malloc(func_sym.st_size); if(ptr_func_content == NULL) { printf("alloc for func failed\n"); goto _error; } lseek(fd,func_sym.st_value,SEEK_SET); if(read(fd,ptr_func_content,func_sym.st_size) != func_sym.st_size) { printf("read func content failed\n"); goto _error; } for(i=0;i<func_sym.st_size;i++) { ptr_func_content[i] = ~ptr_func_content[i]; } lseek(fd,func_sym.st_value,SEEK_SET); if(write(fd,ptr_func_content,func_sym.st_size) != func_sym.st_size) { printf("write to func failed\n"); goto _error; } printf("Complete \n"); _error: if(ptr_dynstr != NULL) { free(ptr_dynstr); } if(ptr_func_content != NULL) { free(ptr_func_content); } return 0; }
同樣的是在so初始化時進行解密
void init_getString() __attribute__((constructor));
void init_getString() { unsigned long lib_addr = 0; Elf32_Ehdr* ptr_ehdr = NULL; Elf32_Phdr* ptr_phdr = NULL; Elf32_Dyn* ptr_dyn = NULL;; Elf32_Sym* ptr_dynsym = NULL; Elf32_Sym* sym = NULL; const char* ptr_hashtab = NULL; const char* ptr_dynstr = NULL; int flag = 0; unsigned long strtab_size = 0; unsigned func_hash = 0; const char func_name[] = "getString"; unsigned long func_addr = 0; unsigned func_size =0; unsigned long func = (unsigned long)getString; size_t nbucket; size_t nchain; unsigned* bucket; unsigned* chain; const unsigned page_size = 0x1000; int i =0; lib_addr = get_cur_lib_addr(); if(0 == lib_addr) { LOGD("get_cur_lib_addr failed"); return; } ptr_ehdr = (Elf32_Ehdr*)lib_addr; ptr_phdr = (Elf32_Phdr*)(lib_addr+ptr_ehdr->e_phoff); for(i=0;i<ptr_ehdr->e_phnum;i++,ptr_phdr++) { if(PT_DYNAMIC == ptr_phdr->p_type) { ptr_dyn = (Elf32_Dyn*)(lib_addr+ptr_phdr->p_vaddr); break; } } if(NULL == ptr_dyn) { LOGD("find .dynamic failed"); return ; } // .dynsym .dynstr .hash . for (Elf32_Dyn* d = ptr_dyn; d->d_tag != DT_NULL; ++d) { switch(d->d_tag) { case DT_SYMTAB: ptr_dynsym = (Elf32_Sym*)(lib_addr+d->d_un.d_ptr); flag |= SYMTAB; break; case DT_STRTAB: ptr_dynstr = (const char*)(lib_addr + d->d_un.d_ptr); flag |= STRTAB; break; case DT_STRSZ: strtab_size = d->d_un.d_val; flag |= STRSZ; break; case DT_HASH: ptr_hashtab = (const char*)(lib_addr + d->d_un.d_ptr); flag |= HASH; break; } } if(flag & 0xf == 0xf) { LOGD("all segement get"); } nbucket = *(unsigned*)ptr_hashtab; nchain = *(unsigned*)(ptr_hashtab+4); bucket = (unsigned*)(ptr_hashtab+8); chain = bucket + nbucket; func_hash = elfhash(func_name); for (unsigned n = bucket[func_hash % nbucket]; n != 0; n = chain[n]) { sym = ptr_dynsym + n; if (strcmp(ptr_dynstr + sym->st_name, func_name)) continue; switch(ELF32_ST_BIND(sym->st_info)) { case STB_GLOBAL: case STB_WEAK: if (sym->st_shndx == SHN_UNDEF) { continue; } } func_addr = lib_addr+sym->st_value; func_size = sym->st_size; break; } unsigned page_off = func_addr % page_size; if(mprotect((const void*)(func_addr-page_off),func_size+page_off,PROT_WRITE|PROT_READ|PROT_EXEC)!= JNI_OK) { int n = errno; char *msg = strerror(errno); LOGD("change page protect failed"); LOGD(msg); return; } for(int i =0;i<func_size;i++) { ((char*)func_addr)[i] = ~((char*)func_addr)[i]; } if(mprotect((const void*)(func_addr-page_off),func_size+page_off,PROT_READ|PROT_EXEC) != JNI_OK) { int n = errno; char *msg = strerror(errno); LOGD("resume page protect failed"); LOGD(msg); return; } }
運行效果圖:
完整代碼:https://github.com/ChengChengCC/Android-demo/tree/master/ProtectFunc