上文中说了怎么给DLL加密来防止别人反编译你的C#代码。 Unity3D研究院之Android加密DLL与破解DLL .SO(八十一) 文章的最后我们发现IDA PRO神器可以解开libmono从而查到你的解密算法,这样你的C#代码又会被别人轻易的拿到。
这两天我就一直在寻找怎样才能更好的保护代码。终于找到了加密so的办法,此法我觉得防小白觉对够用。大神恐怕还是能解开,但是我觉得这就够了。我已经在项目中测试通过,也欢迎大家也能加入一起来测试的队伍。
在啰嗦一句在不远的将来可能我们也不用这么做了, 因为很快unity就全线l2cpp了。但是我觉得等真正稳定恐怕还有很多路要走,所以估计大部分正在开发的项目不会冒这个险升级。
阅读下面之前请大家先看一下这篇大神的文章。http://bbs.pediy.com/showthread.php?t=191649 文章写的很清晰。但是坦白说看了半天我没怎么看懂,逆向工程真是一门深奥的学问。。主要还是技术关注领域不在这里。文章的最后有作者给出的源码,大家记得下载下来。然后我就开始说我是怎么把这个加在unity3d上的。还有我遇到了那些坑。
它的例子工程下载解压后,开始对shellAdder1.c进行编译,编译的方法是
1 2 3 |
gcc -o encry shellAdder1.c |
我开始编译的时候老通不过,提示缺少 elf.h 文件,我看了一下,其实就是少了一些结构体和类型的声明,把下面代码拷贝到shellAdder1.c里面即可。Main函数上面添加如下代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
#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. */ Elf32_Off e_phoff; /* Program header file offset. */ Elf32_Off e_shoff; /* Section header file offset. */ 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; |
最终shellAdder1将编译成一个名叫encry的可执行文件, 用来给libmono进行加密。那么加密算法必然是要写在shellAdder1.c里面,作者给出的是取反你也可以改成自己需要的算法。至于c代码是什么意思,我相信 这篇文章已经写的是非常的全面了 http://0nly3nd.sinaapp.com/?p=695
然后执行 encry libmono.so 就会把libmono.so里 名叫 mytext 的断 进行加密,你要觉得这个名子不好也可以换一个断名,加密后的libmono.so文件会替换原有的。
接着到mono/metadata/image.c里来编写解密.so断的代码。把下面这段代码拷贝到image.c的最上面,关键的两个地方我已添加注释了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
//SO---------------加密---------------------- #include <sys/types.h> #include <elf.h> #include <sys/mman.h> //注意上面说解密算法里面的断.mytext就是这里, //这里把getKey进行了加密,这样对方拿不到你的密钥都没法破解你的dll了 int getKey() __attribute__((section (".mytext"))); int getKey(){ return 2048; }; //这里就是.so初始化的时候,这里进行mytext断的解密工作 void init_getKey() __attribute__((constructor)); unsigned long getLibAddr(); void init_getKey(){ 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; base = getLibAddr(); ehdr = (Elf32_Ehdr *)base; text_addr = ehdr->e_shoff + base; nblock = ehdr->e_entry >> 16; nsize = ehdr->e_entry & 0xffff; g_message("momo: nblock = %d\n", nblock); if(mprotect((void *) base, 4096 * nsize, PROT_READ | PROT_EXEC | PROT_WRITE) != 0){ g_message("momo: mem privilege change failed"); } //注意这里就是解密算法, 要和加密算法完全逆向才行不然就解不开了。 for(i=0;i< nblock; i++){ char *addr = (char*)(text_addr + i); *addr = ~(*addr); } if(mprotect((void *) base, 4096 * nsize, PROT_READ | PROT_EXEC) != 0){ g_message("momo: mem privilege change failed"); } g_message("momo: Decrypt success"); } 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("momo: 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---------------加密---------------------- |
然后在mono_image_open_from_data_with_name方法里
1 2 3 4 5 6 7 |
if(strstr(name,"Assembly-CSharp.dll")){ //这里就能取到密钥,那么这个函数被加密了。 //IDA就看不到它了 g_message("momo: key = %d\n", getKey()); } |
密钥被保护了,代码修改完就是开始编译mono吧。编译完用刚刚我们说过的方法来执行 encry libmono.so 然后把libmono拷贝到项目里打包android就行了。
可以测试一下加密的效果。用Ida 打开。这里的函数已经打不开了
这段密钥进行了保护那么就可以随意的做加密算法了。
我相信这个方法还是存在漏洞,肯定也有大神能破解。也希望各位大神不吝赐教,谢谢啦。使用上有问题欢迎在下面留言大家可以一起讨论。
shellAdder1.c 很多人都问我要我还是都放出来吧。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 |
#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. */ Elf32_Off e_phoff; /* Program header file offset. */ Elf32_Off e_shoff; /* Section header file offset. */ 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){ 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; 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; } 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 = base / 4096 + (base % 4096 == 0 ? 0 : 1); printf("base = %d, length = %d\n", base, length); printf("nblock = %d, nsize = %d\n", nblock, 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; } |
最近有朋友说在Android7.0上遇到这个错误,今天我抽空尝试的解决了一下。
Unable to load library:xxx/arm/libunity.so [dlopen failed: “xxx/arm/libmono.so” .dynamic section header was not found]
测试环境unity4.7.2 并且在github上取下mono 对应的unity4.6(我看前几天刚更新了一些代码)的最新代码编译方法和文中介绍的一样。唯一有一点需要注意,就是NDK编译的时候要用android-ndk-r10e 或者更高版本。 (我测试通过用的就是r10e)
另外,如果你的项目是unity5.x我建议升级到5.4 android使用il2cpp这样就不需要这个方法加密了。如果是unity4.x的项目我建议用r10e在尝试编译一下,测试的过程中有问题欢迎在下面给我留言。
- 本文固定链接: https://www.xuanyusong.com/archives/3571
- 转载请注明: 雨松MOMO 于 雨松MOMO程序研究院 发表
我用的unity2020二次加密libmono后,安卓端还是报错libmonobdwgc-2.0.so” .dynamic section header was not found,用的ndk是r16b
android 7.1.1遇到shdr问题 java.lang.UnsatisfiedLinkError: dlopen failed: “/data/app/com.thomas.crackmeso-1/lib/arm/libverify.so” has invalid shdr offset/size: 12632/2160 at java.lang.Runtime.loadLibrary0(Runtime.java:989) at java.lang.System.loadLibrary(System.java:1530)大神有没有解决
在Unity能出现这个问题一般都是因为将其放在Plugins/Android/libs/arm64-v8a/libxlua.so后没有将libxlua.so的平台改为ARM64导致的
不知道为什么刚刚发的回复被删了。我想问的是 getkey() 返回的是2048吗,2048 是不是要的dll 加密的值相同,我要把2048换成dll加密用的key这样理解对吗?我看到在image.c 下面的函数里面只有getkey().是否要写成 data[0] -= getkey()呢,是不是该这么理解?
7.0确实会闪退,只要用了encry,那么这个.so就不能用了,momo有研究吗
雨凇大大:这边发现一个问题,在Android 7.0 的设备中,会出现Unable to load library:xxx/arm/libunity.so [dlopen failed: “xxx/arm/libmono.so” .dynamic section header was not found] 问题, 请问你们有碰到吗
你可以取一下最新的mono在重新编译一下试试。我们 后来更新unity5.4用il2cpp 了。。
雨神,请问,用il2cpp 还会存在被反编译的节奏吗?
肯定会。。。 但是和之前的不太一样。。 增加了反编译的难度了。。
我也是使用unity5.4用il2cpp编译报错:Failed running C:/Program Files/android-ndk-r10e/toolchains/llvm-3.6/prebuilt/windows-x86_64/bin/clang++ -c “Temp/StagingAreaIl2Cppil2cppOutputBulk_Assembly-CSharp_0.cpp” -o大神知道这是什么问题吗?
我也是使用unity5.4用il2cpp编译报错:Failed running C:/Program Files/android-ndk-r10e/toolchains/llvm-3.6/prebuilt/windows-x86_64/bin/clang -c “Temp/StagingAreaIl2Cppil2cppOutputBulk_Assembly-CSharp_0.cpp” -o大神知道这是什么问题吗?
今天我尝试解决了一下这个问题,取下最新mono代码,然后用android-ndk-r10e 重新编译一下就OK了。
我测试了Android7.0,版本是unity5.4, ndk是r10e, 如果不用encry是可以的,如果加了就进入后闪退.所以暂时只能不encry .为什么5.4不用il2cpp,因为要热更,没办法使用它.
没用lua吗? 还有encry 加密是怎么写的, 我重新编译了没有问题啊。
现在正在做lua. encry用的就是你上面提供的代码. 我只是修改了text的值.我的报错,和下面canders的是一样的 .dynamic section header was not found. 我还专门下载了mono5.4进行修改的.我怀疑是encry之后.7.0就不认这个.so文件了
我也遇到了同样的问题,用的是最新的Unity4.6,ndk是r10e,重新编译libmono,在Android7.0还会闪退,报错日志:Unable to load library:xxx/arm/libunity.so [dlopen failed: “xxx/arm/libmono.so” .dynamic section header was not found]
可能我的测试环境比较简单吧? 但是我重新编译了加密了确实能运行,改天我找个复杂点的工程在试试吧。。
你试试不encry.看是不是就不报错了
不加密在Android7.0下是可以的
我也遇到了同样的问题,用的是最新的Unity4.6,ndk是r10e,重新编译libmono,在Android7.0还会闪退,报错日志:Unable to load library:xxx/arm/libunity.so [dlopen failed: “xxx/arm/libmono.so” .dynamic section header was not found]