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 = -;
JNIEnv *env = NULL;
if(vm->GetEnv((void**)&env,JNI_VERSION_1_4) != JNI_OK)
{
result = -;
}
//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[]))<)
{
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 = ;
Elf32_Word target_section_size = ;
char * ptr_section_content = NULL;
int page_size = ;
int fd;
if(argc < )
{
puts("input so file\n");
return -;
}
fd = open(argv[],O_RDWR);
if(fd < )
{
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 = ;
for(i = ;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 + ;
ehdr.e_entry = target_section_size;
ehdr.e_shoff = target_section_offset;
for(i = ; i<target_section_size;i++)
{
ptr_section_content[i] =~ ptr_section_content[i];
}
lseek(fd,,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 ;
}

加密代码

用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 = ;
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) != )
{
LOGD("change the mem failed");
return ;
}
for(int i=;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)!=)
{
LOGD("resume mem failed");
return ;
}
}

获得自己的so模块在内存中的加载基地址是通过linux的“/proc”文件系统得到的,进程的运行时的状态都会显示在”/proc”文件系统中。例如下图就是某个进程的内存中模块的的信息。

unsigned long get_cur_lib_addr()
{
unsigned long addr = ;
char section_name[] = "libprotect_section.so";
pid_t pid = ; //<types.h>
char buf[] = {};
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 ;
}
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 = , g;
while(*name) {
h = (h << ) + *name++;
g = h & 0xf0000000;
h ^= g;
h ^= g >> ;
}
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 = ;
int i = ; char func_name[] = "getString";
if(argc < )
{
printf("input the so file\n");
return ;
}
int fd = open(argv[],O_RDWR);
if(fd < )
{ printf("open file failed\n");
goto _error;
}
lseek(fd,,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=; i<ehdr.e_phnum;i++)
{
memset(&phdr,,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,) != )
{
printf("read hash nbucket failed\n");
goto _error;
}
if(read(fd,&nchain,) != )
{
printf("read hash nchain failed\n");
goto _error;
}
func_hash = func_hash%nbucket; lseek(fd,func_hash*,SEEK_CUR);
if(read(fd,&func_index,) != )//索引是符号表或者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) != )
{
while() //纯C语言是没有true的
{
lseek(fd,dyn_hash+*(+nbucket+func_index),SEEK_SET);
if(read(fd,&func_index,) != )
{
printf("read func index failed\n");
goto _error;
} lseek(fd,dyn_sym + func_index*sizeof(Elf32_Sym),SEEK_SET);
memset(&func_sym,,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) == )
{
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=;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 ;
}

加密代码

同样的是在so初始化时进行解密

void init_getString() __attribute__((constructor));
void init_getString()
{
unsigned long lib_addr = ;
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 = ;
unsigned long strtab_size = ;
unsigned func_hash = ;
const char func_name[] = "getString";
unsigned long func_addr = ;
unsigned func_size =;
unsigned long func = (unsigned long)getString;
size_t nbucket;
size_t nchain;
unsigned* bucket;
unsigned* chain;
const unsigned page_size = 0x1000; int i =;
lib_addr = get_cur_lib_addr();
if( == 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=;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+);
bucket = (unsigned*)(ptr_hashtab+);
chain = bucket + nbucket; func_hash = elfhash(func_name); for (unsigned n = bucket[func_hash % nbucket]; n != ; 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 =;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

Android so 文件进阶<三> so文件的简单加密的更多相关文章

  1. Android 自定义View及其在布局文件中的使用示例(三):结合Android 4.4.2_r1源码分析onMeasure过程

    转载请注明出处 http://www.cnblogs.com/crashmaker/p/3549365.html From crash_coder linguowu linguowu0622@gami ...

  2. Android Animation学习(三) ApiDemos解析:XML动画文件的使用

    Android Animation学习(三) ApiDemos解析:XML动画文件的使用 可以用XML文件来定义Animation. 文件必须有一个唯一的根节点: <set>, <o ...

  3. android中解析文件的三种方式

    android中解析文件的三种方式     好久没有动手写点东西了,最近在研究android的相关技术,现在就android中解析文件的三种方式做以下总结.其主要有:SAX(Simple API fo ...

  4. Android项目实战(三十三):AS下获取获取依赖三方的jar文件、aar 转 jar

    使用 Android studio 开发项目中,有几种引用三方代码的方式:jar 包 ,类库 ,gradle.build 的compile依赖. 大家会发现github上不少的项目只提供compile ...

  5. Android so 文件进阶<二> 从dlsym()源码看android 动态链接过程

    0x00  前言 这篇文章其实是我之前学习elf文件关于符号表的学习笔记,网上也有很多关于符号表的文章,怎么说呢,感觉像是在翻译elf文件格式的文档一样,千篇一律,因此把自己的学习笔记分享出来.dls ...

  6. Android so文件进阶 <一>

    0x00  前言   最近一段时间在弄android方面的东西,今天有人发了张截图,问:在要dump多大的内存? 一时之间我竟然想不起来ELF文件的哪个字段表示的是文件大小,虽然最后给出了解决方法,I ...

  7. [转]在eclipse打开的android虚拟手机,打开File Explorer,下面是空的没有data、mnt、system三个文件

    在eclipse打开的android虚拟手机,打开File Explorer,下面是空的没有data.mnt.system三个文件 这是因为模拟器没有选择打开的缘故,必须首先打开一个模拟器(AVD), ...

  8. Android项目实战(三十一):异步下载apk文件并安装(非静默安装)

    前言: 实现异步下载apk文件 并 安装.(进度条对话框显示下载进度的展现方式) 涉及技术点: 1.ProgressDialog   进度条对话框  用于显示下载进度 2.AsyncTask     ...

  9. Android开发学习---android下的数据持久化,保存数据到rom文件,android_data目录下文件访问的权限控制

    一.需求 做一个类似QQ登录似的app,将数据写到ROM文件里,并对数据进行回显. 二.截图 登录界面: 文件浏览器,查看文件的保存路径:/data/data/com.amos.datasave/fi ...

随机推荐

  1. 从kepware定时取web api内容

    using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...

  2. 准备用有人云平台和tlink.io云平台和电脑做云转发

    初步想的是用有人做国网电表转发,用tlink.io做综合采集模块转发,耗时一天 然后用tlink.io的做二次前端开发,耗时两天 用有人做二次前端开发,耗时两天 最后可以试试用常见的OPC公网转发到这 ...

  3. 二分搜素——(lower_bound and upper_bound)

    因为每个人二分的风格不同,所以在学习二分的时候总是被他们的风格搞晕.有的人二分风格是左闭右开也就是[L,R),有的人是左开右闭的(L,R]. 二分的最基本条件是,二分的序列需要有单调性. 下面介绍的时 ...

  4. Eclipse运行wordcount步骤

    Eclipse运行wordcount步骤 第一步:建立工程,导入代码. 第二步:建立文件写入数据(以空格分开),并上传到hdfs上. 1.创建文件并写入数据: 2.上传hdfs 在hadoop权限下就 ...

  5. hdu 4891 模拟水题

    http://acm.hdu.edu.cn/showproblem.php?pid=4891 给出一个文本,问说有多少种理解方式. 1. $$中间的,(s1+1) * (s2+1) * ...*(sn ...

  6. FastReport 打印模版页(TFrxReportpage)复制

    遇到一个奇葩的需求.一般情况下我们打印单据,用FastReport设置打印格式,也就是就设一个模版页而己,就是一种单据格式.如果打印的单据数据多了就自动打印多页了,他们的格式是一样的.也就是读同一个模 ...

  7. Excel中单元格、超级链接形成超级链接单元格

    使用函数 HYPERLINK(超链接,显示文字) =HYPERLINK("http://www.cnblogs.com/Vpygamalion/","李汉超") ...

  8. spark-mllib 密集向量和稀疏向量

    spark-mllib 密集向量和稀疏向量 MLlib支持局部向量和矩阵存储在单台服务器,也支持存储于一个或者多个rdd的分布式矩阵 . 局部向量和局部矩阵是用作公共接口的最简单的数据模型. 基本的线 ...

  9. python中硬要写抽象类和抽象方法

    由于python没有抽象类.接口的概念,所以要实现这种功能得abc.py这个类库,具体方式如下: # coding: utf-8import abc #抽象类class StudentBase(obj ...

  10. Python(27)--文件相关处理的应用(增、删、改、查)

    文件名为message,文件内容如下: global log 127.0.0.1 local2 daemon maxconn 256 log 127.0.0.1 local2 info default ...