第二代殼,我的理解中有以下幾種:
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文件加密
- 如果是再java中編寫的話需要自定義so文件格式解析的class,這里放在linux下編寫,直接調(diào)用了
elf.h頭文件,自動(dòng)解析elf文件格式;- 讀取so文件通過elf文件頭中的字符串表索引;
- 通過節(jié)區(qū)頭部表偏移獲取節(jié)區(qū)頭部表地址;
- 通過節(jié)區(qū)頭部表的地址 + 節(jié)區(qū)字符串表索引 * 節(jié)區(qū)頭部表固定大小,獲取字符串表的地址;
- 將字符串表的內(nèi)容復(fù)制出來;
- 然后通過對(duì)每個(gè)section的sh_name與我們的自定義section名字對(duì)比,找到自定義段的偏移地址和大小;
- 然后即可對(duì)偏移地址的內(nèi)容進(jìn)行加密算法運(yùn)算;
- 將自定義段的偏移和大小寫入elf頭文件的section部分;
- 上一步的原因是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)目,
- 使用
__attribute__自定義section,將關(guān)鍵函數(shù)getString()寫入自定義section中- 然后在
__attribute__中自定義init_getString()函數(shù),此函數(shù)中完成so解密工作;- 首先通過進(jìn)程id,讀取maps文件找到so基地址;
- 然后讀取elf頭中的寫入的自定義section的偏移和長度,計(jì)算出實(shí)際地址,然后進(jìn)行解密工作;
- 由于是內(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ù)加密
- 函數(shù)的結(jié)構(gòu)是保存在動(dòng)態(tài)符號(hào)段
.dynsym段中的,而函數(shù)名是保存在動(dòng)態(tài)字符串段.dynstr中的,沒有辦法可以直接通過字符串段的函數(shù)名找到符號(hào)表的地址;- 通過hash表可以找到對(duì)應(yīng)的函數(shù)地址;
- 而
.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- 程序頭phdr中的標(biāo)記dynamic段和節(jié)區(qū)頭shdr中的類型為SHT_DYNAMIC的段實(shí)際上是同一段;
- 首先找到
.dynamic段,有很多種方法可以找到,最簡單的直接遍歷phdr中的type為PT_DYNAMIC (2)的段,因?yàn)檫@是唯一的;或者通過遍歷節(jié)區(qū)表中的段名為.dynamic的段。- 然后解析
.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;
- 遍歷dynamic段找到
.hash .dynsym .dynstr這三個(gè)段;- 然后再去定位我們的要加密的函數(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>
- 然后對(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;
}



