上文中说了怎么给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进行编译,编译的方法是
gcc -o encry shellAdder1.c
我开始编译的时候老通不过,提示缺少 elf.h 文件,我看了一下,其实就是少了一些结构体和类型的声明,把下面代码拷贝到shellAdder1.c里面即可。Main函数上面添加如下代码。
#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的最上面,关键的两个地方我已添加注释了。
//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 = %dn", 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方法里
if(strstr(name,"Assembly-CSharp.dll")){
//这里就能取到密钥,那么这个函数被加密了。
//IDA就看不到它了
g_message("momo: key = %dn", getKey());
}
密钥被保护了,代码修改完就是开始编译mono吧。编译完用刚刚我们说过的方法来执行 encry libmono.so 然后把libmono拷贝到项目里打包android就行了。
可以测试一下加密的效果。用Ida 打开。这里的函数已经打不开了
这段密钥进行了保护那么就可以随意的做加密算法了。
我相信这个方法还是存在漏洞,肯定也有大神能破解。也希望各位大神不吝赐教,谢谢啦。使用上有问题欢迎在下面留言大家可以一起讨论。
shellAdder1.c 很多人都问我要我还是都放出来吧。
#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 failedn", 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 %sn", 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 = %dn", base, length);
printf("nblock = %d, nsize = %dn", 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]