在阅读这篇文章以前,我在处理mono加密问题时,也是参考了雨凇的文章,因此建议先看一下雨凇写的关于加密Dll的文章:html
1.Unity3D研究院之Android加密DLL与破解DLL .SOandroid
2.Unity3D研究院之Android二次加密.so二次加密DLLgit
伪装读者已经看过上面的两篇文章了,下面我会记录一下我作的整个加密流程。github
咱们主要目的是对程序集:Assembly-CSharp.dll 进行加密,而后修改mono源码,在mono加载Dll的时候进行解密。显然咱们须要一种可逆、对称的加密算法,其实这类算法不少,如DES、TEA、XXTEA等,通常这类对称秘钥算法的安全性都是基于秘钥的(Key),因此如何在mono解密是保护本身的秘钥就十分重要了。我目前使用的是XXTEA,实现的话不清楚,可是github上有开源实现,因此直接拿来用了:xxtea-c算法
1.先用Unity导出一个android Google工程,在工程路径 {$Project}\assets\bin\Data\Managed\Assembly-CSharp.dll ,这个文件就是须要咱们替换的程序集啦缓存
2.编写加密Dll工具,你们能够把上面开源xxtea项目中的源码:xxtea.h、xxtea.c 和下面的encryptDll.c代码放在同一目录,用MinGW下的gcc编译就能够了:gcc xxtea.c encryptDll.c –o EncryptDll安全
#include <stdio.h> #include <string.h> #include <stdlib.h> #include "xxtea.h" #define SIZE 1024*1024*10 void main() //命令行参数 { FILE *infp = 0;//判断命令行是否正确 if((infp=fopen("Assembly-CSharp.dll","rb"))==NULL) { printf("Assembly-CSharp.dll Read Error\n");//打开操做不成功 return;//结束程序的执行 } //char buffer[SIZE]; char* buffer = (char*)malloc(sizeof(char)*SIZE); memset(buffer,0,sizeof(char)*SIZE); int rc = 0; int total_len = 0; total_len = fread(buffer , sizeof(unsigned char) , SIZE , infp); printf("Read Assembly-CSharp Successfully and total_len : %d \n" , total_len); //加密DLL size_t len; char* key = "123456"; char *encrypt_data = xxtea_encrypt(buffer,total_len, key, &len); printf("Encrypt Dll Successfully and len : %d\n" , len); //写Dll FILE* outfp = 0; if((outfp=fopen("Assembly-CSharp_encrypt.dll","wb+"))==NULL) { printf("Assembly-CSharp_encrypt.dll Read Error\n");//打开操做不成功 return;//结束程序的执行 } int rstCount = fwrite(encrypt_data , sizeof(unsigned char) , len , outfp); fflush(outfp); printf("Write len : %d\n", rstCount); fclose(infp); fclose(outfp); free(buffer); free(encrypt_data); }
在用生成的EncryptDll.exe Dll_Path 就能够直接加密改Dll了架构
咱们须要修改{$mono_root}/mono/metadata/image.c ,它有一个mono_image_open_from_data_with_name 函数,该方法是加载Dll的入口函数,在这里实现解密。eclipse
MonoImage * mono_image_open_from_data_with_name (char *data, guint32 data_len, gboolean need_copy, MonoImageOpenStatus *status, gboolean refonly, const char *name) { if(strstr(name ,"Assembly-CSharp.dll")){ g_message("mono: === Start Decrypt Dll ==========\n"); char key = "123456"; size_t len; char* decryptData = decrypt(data , key);//换成对应的解密函数 int i = 0; for ( i = 0; i < len; ++i) { data[i] = decryptData[i]; } g_free(decryptData); g_message("mono: === End Decrypt Dll ========== \n"); } ........ return register_image (image); }
到此解密和加密过程就结束了,ide
1.咱们能够从新编译修改后的mono,而后用{$mono_root}/embedruntimes/android/*下对应平台libmono.so覆盖掉{$Unity_Root}/Editor/Data/PlaybackEngines/androidplayer/(development | release)/libs/* , 而后就能够从新导出android工程了。
2.导出android工程后,用生面生成的EncryptDll.exe 加密Assembly-CSharp.dll
3.用eclipse 或者 android studio 导出apk,运行 success !
若是顺利完成(二)中的过程,那么就能够防住很大一部分小白破解者了,可是就像雨凇文章中说的,只要是稍微厉害点的玩家仍是能够破解的,用IDA神器,很快就能反编译libmono.so 并找到key,而后解密Dll,而后就又能够冠冕堂皇地修改Dll啦……sadly,那么咱们若是防止这种状况呢,下面有几种方案可供选择,可是在阅读后面的内容时强烈建议先了解一下ELF文件格式,推荐两个连接:http://www.cnblogs.com/xmphoenix/archive/2011/10/23/2221879.html , http://blog.chinaunix.net/uid-21273878-id-1828736.html , 了解一些ELF文件头信息,会颇有帮助的,由于确定会踩一些坑的……
这个方式雨凇已经在文章中给了足够详细的说明和源码,这里就不瞎补充了,可是,这个方案有个致命的缺陷,就是没法兼容x86架构的cpu,骤然一听不兼容x86彷佛是一个很是严重的问题,其实有所了解x86的就会明白其实并无什么大问题,由于x86的机器真的不多,除了华硕和联想有几款小众机型外,其余品牌几乎没有x86的机型,甚至在weTest上也找不到x86的机型 ,这估计也是雨凇没有测出来的缘由……,固然在我初步遇到这个问题也是用了一两天时间去尝试修改代码使它兼容x86 cpu,下面是我作的尝试方案:
这个方案的代码有个前提是,ehdr.e_entry , 和 ehdr.e_shoff 或者其余ELF头其余位置可读写,并不会影响android对动态库so的加载执行,然而在x86架构下,它不允许修改入口地址,即ehdr.e_entry位置,如so入口地址ehdr.e_entry ,不然就拒绝加载,直接崩掉……因而,我尝试修改保存信息位置
1).我把源码中base 和 length信息放在了ehdr.ident后8个字节中,测试仍是会拒绝加载,而后使用 ehdr.e_shoff = base;
2). e_shnum 和 e_shstrndx 保存lenght(由于length是四个字节,而e_shnum 和 e_shstrndx均是两个字节,因此须要同时占用e_shnum 和 e_shstrndx),测试时发现,虽然能够加载了,可是算出的section地址不对,形成加密的sectiong函数寻址错误,仍是崩掉,最后证实这个修复方案行不通
既然没法正常保存偏移地址,那么我就尝试手动写死对应的参数,而后测试,结果发现仍是会崩掉,和 a.2 中的状况一致,因而判断这个方案行不通
根据加密过程动态算出找到加密的section地址,而后解密(惋惜当时的代码已经删除了),最终的测试发现,在匹配字符串表时没法找到指定的节信息,颇有可能x86在加载时改变了ELF位置信息,因此最终也是失败啦
至此我就放弃了修复的想法,寻找其余方案,固然若是公司能够容忍不兼容那少数的几台x86机器就能够采用这个方案,我咨询过几个朋友,他们采用这个方案的项目已经上线了……
这个其实我也并无看明白,可是我尝试可几回都没成功,这里附上连接,有心的哥们能够参考一下:http://www.cnblogs.com/lanrenxinxin/p/4962470.html
咱们若是没法容忍不兼容x86,有没法搞定2中方案,那只能本身想办法了。直接写明文key在mono中确定不行,那么是否是能够把key变通一下存放在ehdr.e_shoff 或者其余位置呢,这样的话除非破解者找到对应的赋值函数,不然也不大容易得到key,具体思路:
1)假设key = fun(c);
2)把c存放到ehdr.e_shoff;
3)在mono加载以前找到 ehdr.e_shoff,并计算出根据fun(c)计算出key
4)缓存key,就能够继续解密Dll了
那么这个方案是否完备,答案确定是no,以上没有完备的方案,只要破解者找到你的解密处的函数就能够反向得到key,从新破解Dll,可是相对写明文来讲多是一个折中的方案,下面贴出参考代码:
加密libmono.so的代码
#include <stdio.h> #include <fcntl.h> #include <stdlib.h> #include <string.h> /* 32-bit ELF base types. */ typedef unsigned int Elf32_Addr; typedef unsigned short Elf32_Half; typedef unsigned int Elf32_Off; typedef signed int Elf32_Sword; typedef unsigned int Elf32_Word; #define EI_NIDENT 16 /* * ELF header. */ typedef struct { unsigned char e_ident[EI_NIDENT]; /* File identification. */ Elf32_Half e_type; /* File type. */ Elf32_Half e_machine; /* Machine architecture. */ Elf32_Word e_version; /* ELF format version. */ Elf32_Addr e_entry; /* Entry point. 4 byte int */ Elf32_Off e_phoff; /* Program header file offset. */ Elf32_Off e_shoff; /* Section header file offset. 4 byte int */ Elf32_Word e_flags; /* Architecture-specific flags. */ Elf32_Half e_ehsize; /* Size of ELF header in bytes. */ Elf32_Half e_phentsize; /* Size of program header entry. */ Elf32_Half e_phnum; /* Number of program header entries. */ Elf32_Half e_shentsize; /* Size of section header entry. */ Elf32_Half e_shnum; /* Number of section header entries. */ Elf32_Half e_shstrndx; /* Section name strings section. */ } Elf32_Ehdr; /* * Section header. */ typedef struct { Elf32_Word sh_name; /* Section name (index into the section header string table). */ Elf32_Word sh_type; /* Section type. */ Elf32_Word sh_flags; /* Section flags. */ Elf32_Addr sh_addr; /* Address in memory image. */ Elf32_Off sh_offset; /* Offset in file. */ Elf32_Word sh_size; /* Size in bytes. */ Elf32_Word sh_link; /* Index of a related section. */ Elf32_Word sh_info; /* Depends on section type. */ Elf32_Word sh_addralign; /* Alignment in bytes. */ Elf32_Word sh_entsize; /* Size of each entry in section. */ } Elf32_Shdr; int main(int argc, char** argv){ Elf32_Ehdr ehdr; Elf32_Ehdr _ehdr; unsigned int key = xxxx;//决定key的因子 int i; int fd; if(argc < 2){ puts("Input .so file"); return -1; } fd = open(argv[1], O_RDWR); if(fd < 0){ printf("open %s failed\n", argv[1]); goto _error; } //读取ELF文件头(mono.so 52个字节) if(read(fd, &ehdr, sizeof(Elf32_Ehdr)) != sizeof(Elf32_Ehdr)){ puts("Read ELF header error"); goto _error; } ehdr.e_shoff = key; //覆盖新ELF文件头 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, 0, SEEK_SET); read(fd, &_ehdr, sizeof(Elf32_Ehdr)); printf("Write Key : %d \n", _ehdr.e_shoff); puts("Completed"); _error: close(fd); return 0; }
mono中解密代码:
//SO---------------加密---------------------- #include <sys/types.h> #include <elf.h> #include <sys/mman.h> unsigned int encrypt_key = 456987; void mono_trace_free_tree() __attribute__((constructor)); unsigned long getLibAddr(); int getKey(); int getKey(){ return luta_encrypt_key; } void mono_trace_free_tree(){ g_message("mono:============= print Elf Start =============\n"); unsigned long base; Elf32_Ehdr *ehdr; base = getLibAddr(); ehdr = (Elf32_Ehdr *)base; unsigned int temp_key = ehdr->e_shoff; encrypt_key = fun(temp_key); g_message("mono: Find luta_encrypt_key = %d\n",encrypt_key); g_message("mono: ============= print Elf End =============\n"); } unsigned long getLibAddr(){ unsigned long ret = 0; char name[] = "libmono.so"; char buf[4096], *temp; int pid; FILE *fp; pid = getpid(); sprintf(buf, "/proc/%d/maps", pid); fp = fopen(buf, "r"); if(fp == NULL) { g_message("mono: 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; } //SO---------------加密----------------------
至此方案3的加密方案接结束,若是很少ELF文件有必定了解,恐怕很难完成这个内容……
1)其实一些作加密的服务不少都对加密so有支持,然而都是付费的,sadly……,若是公司有钱能够考虑相似“爱加密”等加密服务
2)咱们能够把得到key和加密函数抽离出来,单独作成decrypt.so,对其进行加密,而后在libmono.so加载前在android层解密并加载decrypt.so,还能够对android层代码混淆等,至关于多作几层防御,加大破解难度。
加密Dll这件事其实仍是没法作到绝对完备,只能加大破解难度,若是有问题请留言