安卓加固之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[];
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 >>;//加密节的大小
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), * nsize, PROT_READ | PROT_EXEC | PROT_WRITE) != ){
puts("mem privilege change failed");
LOGD("mem privilege change failed");
}
//进行解密,是针对加密算法的
for(i=;i<nblock;i++){
char *addr=(char*)(text_addr+i);
*addr=~(*addr);
}
if(mprotect((void *) (text_addr / PAGE_SIZE * PAGE_SIZE), * nsize, PROT_READ | PROT_EXEC) != ){
puts("mem privilege change failed");
}
puts("Decrypt success");
}
//获取到SO文件加载到内存中的起始地址,只有找到起始地址才能够进行解密;
unsigned long getLibAddr(){
unsigned long ret=;
char name[]="libJniTest.so";
char buf[];
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, ); //获取地址
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 = ; char* szFileData = NULL;
unsigned int ulLow;
HANDLE hFile;
ULONG ulHigh = ;
ULONG ulReturn = ; //读取文件到内存
hFile = CreateFileA(szSoPath,GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if (hFile==INVALID_HANDLE_VALUE)
{
printf("打开的文件不存在!");
return -;
}
ulLow = GetFileSize(hFile,&ulHigh);
szFileData = new char[ulLow + ];
printf("Read File at 0x%x\r\n",szFileData);
if (ReadFile(hFile,szFileData,ulLow,&ulReturn,NULL)==)
{
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 = ; i < ehdr->e_shnum; i++){
//根据字符串表的名称比较
if(strcmp(shstr + Shdr->sh_name, szSection) == ){
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 / + (base % == ? : );
printf("base = 0x%x, length = 0x%x\n", base, length);
printf("nblock = %d, nsize = %d\n", nblock, nsize); //将节的地址和大小写入
ehdr->e_entry = (length << ) + nsize;
ehdr->e_shoff = base; //节的地址 printf("content is %x",content);
//加密
for(i=;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 -;
}
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 ;
}
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 = ,g;
while(*name)
{
h = (h<<)+*name++;
if(g = h & 0xf0000000)
{
h^=g>>;
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 = ;
ULONG ulReturn = ; 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 -;
}
ulLow = GetFileSize(hFile,&ulHigh);
szFileData = new char[ulLow + ];
if (ReadFile(hFile,szFileData,ulLow,&ulReturn,NULL)==)
{
CloseHandle(hFile);
delete szFileData;
return FALSE;
} //通过hash段中chain链获得的索引,获取在dynsym对应的条目
if(getTargetFuncInfo(szFileData, funcNameAdd, &info) == -){
printf("Find function %s failed\n", funcNameAdd);
goto _error;
} //得到函数地址
for(i=;i<info.st_size-;i++){
char *content = (char*)(szFileData + info.st_value - + i);
*content = ~(*content);
} //通过hash段中chain链获得的索引,获取在dynsym对应的条目
if(getTargetFuncInfo(szFileData, funcNameSub, &info) == -){
printf("Find function %s failed\n", funcNameSub);
goto _error;
}
//得到函数地址
for(i=;i<info.st_size-;i++){
char *content = (char*)(szFileData + info.st_value - + i);
*content = ~(*content);
}
//通过hash段中chain链获得的索引,获取在dynsym对应的条目
if(getTargetFuncInfo(szFileData, funcNameMul, &info) == -){
printf("Find function %s failed\n", funcNameMul);
goto _error;
}
//得到函数地址
for(i=;i<info.st_size-;i++){
char *content = (char*)(szFileData + info.st_value - + i);
*content = ~(*content);
}
//通过hash段中chain链获得的索引,获取在dynsym对应的条目
if(getTargetFuncInfo(szFileData, funcNameDiv, &info) == -){
printf("Find function %s failed\n", funcNameDiv);
goto _error;
}
//得到函数地址
for(i=;i<info.st_size-;i++){
char *content = (char*)(szFileData + info.st_value - + 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 -;
}
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 ;
} 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;
} static char getTargetFuncInfo(char* szFileData, const char *funcName, funcInfo *info){
char flag = -;
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=;i < ehdr->e_phnum; i++){
//获得动态链接节
if(phdr->p_type == PT_DYNAMIC){
dyn_size = phdr->p_filesz;
dyn_off = phdr->p_offset;
flag = ;
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 = ; 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=;i < dyn_size / sizeof(Elf32_Dyn); i++){ //符号表位置
if(dyn->d_tag == DT_SYMTAB){
dyn_symtab = dyn->d_un.d_ptr;
flag += ;
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 += ;
printf("Find .hash, addr = 0x%x\n", dyn_hash);
}
//保存函数字符串的位置
if(dyn->d_tag == DT_STRTAB){
dyn_strtab = dyn->d_un.d_ptr;
flag += ;
printf("Find .dynstr, addr = 0x%x\n", dyn_strtab);
}
//字符串长度
if(dyn->d_tag == DT_STRSZ){
dyn_strsz = dyn->d_un.d_val;
flag += ;
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 + );//获得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 + + funHash * );//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) != ){ //如果索引y对应的符号表不是所需要的,那么chain[y]则给出了具有相同哈希值的下一个符号表项
while(){
//我们可以沿着chain链一直搜索,直到所选中的符号表项包含了所需要的符号
printf("hash:%x,nbucket:%d,funIndex:%d\n",dyn_hash,nbucket,funIndex);
funIndex = *(int*)(szFileData + dyn_hash + *(+nbucket+funIndex)); //搜索chain链
printf("funcIndex:%d\n", funIndex); if(funIndex == ){
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) == ){
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 ; _error:
free(dynstr);
return -;
}
②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 +);
} JNIEXPORT jint JNICALL native_Sub
(JNIEnv *env, jobject obj, jdouble num1, jdouble num2)
{
return (jint)(num1 - num2 +);
} JNIEXPORT jint JNICALL native_Mul
(JNIEnv *env, jobject obj, jdouble num1, jdouble num2)
{
return (jint)(num1 * num2 +);
} JNIEXPORT jint JNICALL native_Div
(JNIEnv *env, jobject obj, jdouble num1, jdouble num2)
{
if (num2 == ) return ;
return (jint)(num1 / num2 +);
} //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) < ){
return JNI_FALSE;
} return JNI_TRUE;
} int register_ndk_load(JNIEnv *env)
{ return registerNativeMethods(env, "com/example/caculate/MainActivity",
gMethods,sizeof(gMethods) / sizeof(gMethods[]));
//NELEM(gMethods));
} JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv* env = NULL;
jint result = -; 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 = , g; while(*name) {
h = (h << ) + *name++;
g = h & 0xf0000000;
h ^= g;
h ^= g >> ;
}
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) == -){
LOGD("Find native_Add failed");
return ;
} npage = info.st_size / PAGE_SIZE + ((info.st_size % PAGE_SIZE == ) ? : );
LOGD("npage = 0x%d", npage);
LOGD("npage = 0x%d", PAGE_SIZE); if(mprotect((void *) ((base + info.st_value) / PAGE_SIZE * PAGE_SIZE), *npage, PROT_READ | PROT_EXEC | PROT_WRITE) != ){
LOGD("mem privilege change failed");
} for(i=;i< info.st_size - ; i++){
char *addr = (char*)(base + info.st_value - + i);
*addr = ~(*addr);
} if(mprotect((void *) ((base + info.st_value) / PAGE_SIZE * PAGE_SIZE), *npage, PROT_READ | PROT_EXEC) != ){
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) == -){
LOGD("Find native_Sub failed");
return ;
} npage = info.st_size / PAGE_SIZE + ((info.st_size % PAGE_SIZE == ) ? : );
LOGD("npage = 0x%d", npage);
LOGD("npage = 0x%d", PAGE_SIZE); if(mprotect((void *) ((base + info.st_value) / PAGE_SIZE * PAGE_SIZE), *npage, PROT_READ | PROT_EXEC | PROT_WRITE) != ){
LOGD("mem privilege change failed");
} for(i=;i< info.st_size - ; i++){
char *addr = (char*)(base + info.st_value - + i);
*addr = ~(*addr);
} if(mprotect((void *) ((base + info.st_value) / PAGE_SIZE * PAGE_SIZE), *npage, PROT_READ | PROT_EXEC) != ){
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) == -){
LOGD("Find native_Div failed");
return ;
} npage = info.st_size / PAGE_SIZE + ((info.st_size % PAGE_SIZE == ) ? : );
LOGD("npage = 0x%d", npage);
LOGD("npage = 0x%d", PAGE_SIZE); if(mprotect((void *) ((base + info.st_value) / PAGE_SIZE * PAGE_SIZE), *npage, PROT_READ | PROT_EXEC | PROT_WRITE) != ){
LOGD("mem privilege change failed");
} for(i=;i< info.st_size - ; i++){
char *addr = (char*)(base + info.st_value - + i);
*addr = ~(*addr);
} if(mprotect((void *) ((base + info.st_value) / PAGE_SIZE * PAGE_SIZE), *npage, PROT_READ | PROT_EXEC) != ){
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) == -){
LOGD("Find native_Mul failed");
return ;
} npage = info.st_size / PAGE_SIZE + ((info.st_size % PAGE_SIZE == ) ? : );
LOGD("npage = 0x%d", npage);
LOGD("npage = 0x%d", PAGE_SIZE); if(mprotect((void *) ((base + info.st_value) / PAGE_SIZE * PAGE_SIZE), *npage, PROT_READ | PROT_EXEC | PROT_WRITE) != ){
LOGD("mem privilege change failed");
} for(i=;i< info.st_size - ; i++){
char *addr = (char*)(base + info.st_value - + i);
*addr = ~(*addr);
} if(mprotect((void *) ((base + info.st_value) / PAGE_SIZE * PAGE_SIZE), *npage, PROT_READ | PROT_EXEC) != ){
LOGD("mem privilege change failed");
}
}
//获取到SO文件加载到内存中的起始地址,只有找到起始地址才能够进行解密;
unsigned long getLibAddr(){
unsigned long ret=;
char name[]="libJniTest.so";
char buf[];
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, ); //获取地址
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 = -, *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 = ; i < ehdr->e_phnum; ++i) {
LOGD("[+]phdr = 0x%p\n", phdr);
//获得动态链接节
if(phdr->p_type == PT_DYNAMIC){
flag = ;
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 = ;
for (i = ; 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 += ;
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 += ;
LOGD("[+]Find .hash section, addr = 0x%x\n", dyn_hash);
}
//保存函数字符串的位置
if(dyn->d_tag == DT_STRTAB){
dyn_strtab = (dyn->d_un).d_ptr;
flag += ;
LOGD("[+]Find .dynstr section, addr = 0x%x\n", dyn_strtab);
}
//字符串长度
if(dyn->d_tag == DT_STRSZ){
dyn_strsz = (dyn->d_un).d_val;
flag += ;
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 + );//bucket链
chain = (unsigned int *)(dyn_hash + * ( + nbucket));//越过bucket链,到达chain链 flag = -;
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 != ; i = chain[i]){
LOGD("[+]Find index = %d\n", i);
if(strcmp(dynstr + ((Elf32_Sym*)((char*)funSym + i* sizeof(Elf32_Sym)))->st_name, funcName) == ){
flag = ;
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 ;
_error:
return -;
}
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
安卓加固之so文件加固的更多相关文章
- [转]安卓加固之so文件加固
一.前言 最近在学习安卓加固方面的知识,看到了jiangwei212的博客,其中有对so文件加固的两篇文章通过节加密函数和通过hash段找到函数地址直接加密函数,感觉写的特别好,然后自己动手实践探索s ...
- Windows下文件加固
今天学到一种Windows下简单的文件加固方法,可以防止文件被(普通)删除. CMD找到要加固的文件. 例:C盘下有个 1516.txt 文本文件需要加固. 然后 copy 该文件.(注意:这里并非普 ...
- 安卓工作室 android studio文件和代码模板,以及汉化出错问题
安卓工作室 android studio文件和代码模板,以及汉化出错问题 作者:韩梦飞沙 Author:han_meng_fei_sha 邮箱:313134555@qq.com E-mail: 313 ...
- 梆梆加固还原DEX文件
0x01 先说总结: 参照https://www.cnblogs.com/jiaoxiake/p/6818786.html 最后说的步骤, 参考:https://www.52pojie.cn/thre ...
- 关于安卓应用(APK文件)的二次打包
http://blog.csdn.net/baiyuliang2013/article/details/40426681 很多开发者,不管是个人或是公司都不太注重自己开发的应用的安全性,即是 否会被不 ...
- 程序员带你学习安卓开发系列-Android文件存储
这是程序员带你学习安卓开发系列教程.本文章致力于面向对象程序员可以快速学习开发安卓技术. 上篇文章:.Net程序员快速学习安卓开发-布局和点击事件的写法 主要讲解了布局和点击事件的写法. 上篇文章补充 ...
- cocos2d移植到安卓引入第三方so文件时候编译会删除解决方式
在游戏中对接支付的SDK的时候引入支付的so文件的时候在编译的时候总是被删除,后来经过查找资料自己整理出了一个解决方式 方案例如以下 在项目导入安卓中之后.在相应的jni目录中创建一个prebuilt ...
- Unity发布安卓无法读取StreamingAssets文件下数据库的问题
在移动端StreamingAssets下的文件是只读的,但大家可能跟我一样遇到了发布安卓以后放在StreamingAssets下的数据库文件一样读取不了, 但其实这个文件夹其实是可以读取到的,所以我们 ...
- 安卓项目R,java文件不能自动更新,clean之后,R.java消失 (转自 Cynosure鱼)
今天整了个安卓项目,新增加了个跳转页面,添加完背景图,发现有个R.id找不到了,于是clean了一下,这下出问题了,发现各处的R.id都找不到,报错.结果一看是R.java没了然后各种百度结果:有 ...
随机推荐
- linux的定制和发布(二)
Linux的发布 有时候希望将定制好的Linux移植到其他的机器上使用,所以我们将定制好的Linux制作 成安装光盘的形式,可以方便在其他机器上安装. 为此我们要先制作一个引导系统,由 ...
- 大坑记录 - shell脚本删除操作
背景 jenkins执行去执行shell命令,其中引用了一些jenkins的变量,如${WORKSPACE}这种,因为每次执行jenkins比较慢,于是想复制脚本出来想本地调试一下,直接复制了脚本过来 ...
- 随手记录: MVC自定义提交form
function mySubmit() { var frm = $('#frm'); var result = frm.valid(); if (ret) { frm.submit(); } else ...
- log4net 日志打印不全
程序用的是log4net打印日志,偶现日志打印不全的问题,程序的log4net配置如下: <log4net> <root> <level value="ALL& ...
- ceph: health_warn clock skew detected on mon的解决办法
造成集群状态health_warn:clock skew detected on mon节点的原因有两个,一个是mon节点上ntp服务器未启动,另一个是ceph设置的mon的时间偏差阈值比较小. 排查 ...
- 【OCP|052】OCP 11g最新考题收集整理-第6题
6.You are installing Oracle Grid Infrastructure by using the Oracle Universal Installer (OUI). You s ...
- JavaScript多个h5播放器video,点击一个播放其他暂停
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- Windows安装Node.js报错:2503、2502的解决方法
以管理员身份用msiexec安装 1.以管理员身份运行cmd命令 (Win + X, A) 以管理员身份运行cmd 2.cd到自己msi路径 用msiexec安装 用msiexec安装nodejs
- 数论入门2——gcd,lcm,exGCD,欧拉定理,乘法逆元,(ex)CRT,(ex)BSGS,(ex)Lucas,原根,Miller-Rabin,Pollard-Rho
数论入门2 另一种类型的数论... GCD,LCM 定义\(gcd(a,b)\)为a和b的最大公约数,\(lcm(a,b)\)为a和b的最小公倍数,则有: 将a和b分解质因数为\(a=p1^{a1}p ...
- BZOJ 4002--有意义的字符串(矩阵乘法)
4002: [JLOI2015]有意义的字符串 Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 963 Solved: 416[Submit][Sta ...