热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Unity防破解——加密Dll与Key保护

在阅读这篇文章之前,我在处理mono加密问题时,也是参考了雨凇的文章,所以建议先看一下雨凇写的关于加密Dll的文章:1.Unity3D研究院之Android加密DLL与破解DLL.SO

    在阅读这篇文章之前,我在处理mono加密问题时,也是参考了雨凇的文章,所以建议先看一下雨凇写的关于加密Dll的文章:

1.Unity3D研究院之Android加密DLL与破解DLL .SO

2.Unity3D研究院之Android二次加密.so二次加密DLL

假装读者已经看过上面的两篇文章了,下面我会记录一下我做的整个加密流程。

一.选取加密Dll的算法

    我们主要目的是对程序集: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 
#include
<string.h>
#include

#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中解密

    我们需要修改{$mono_root}/mono/metadata/image.c ,它有一个mono_image_open_from_data_with_name 函数,该方法是加载Dll的入口函数,在这里实现解密。

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 i)
{
data[i] = decryptData[i];
}
g_free(decryptData);
g_message(
"mono: === End Decrypt Dll ========== \n");
}

........

return register_image (image);
}

到此解密和加密过程就结束了,

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 !

三.mono种key保护

    如果顺利完成(二)中的过程,那么就可以防住很大一部分小白破解者了,但是就像雨凇文章中说的,只要是稍微厉害点的玩家还是可以破解的,用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文件头信息,会很有帮助的,因为肯定会踩一些坑的……

1.加密指定的section

    这个方式雨凇已经在文章中给了足够详细的说明和源码,这里就不瞎补充了,但是,这个方案有个致命的缺陷,就是无法兼容x86架构的cpu,骤然一听不兼容x86似乎是一个非常严重的问题,其实有所了解x86的就会明白其实并没有什么大问题,因为x86的机器真的很少,除了华硕和联想有几款小众机型外,其他品牌几乎没有x86的机型,甚至在weTest上也找不到x86的机型 ,这估计也是雨凇没有测出来的原因……,当然在我初步遇到这个问题也是用了一两天时间去尝试修改代码使它兼容x86 cpu,下面是我做的尝试方案:

a.修改保存信息ELF位置

    这个方案的代码有个前提是,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函数寻址错误,还是崩掉,最后证明这个修复方案行不通

b.直接写死偏移(base)和length信息

    既然无法正常保存偏移地址,那么我就尝试手动写死对应的参数,然后测试,结果发现还是会崩掉,和 a.2 中的情况一致,于是判断这个方案行不通

c.解密动态算出偏移和length信息

    根据加密过程动态算出找到加密的section地址,然后解密(可惜当时的代码已经删除了),最终的测试发现,在匹配字符串表时无法找到指定的节信息,很有可能x86在加载时改变了ELF位置信息,所以最终也是失败啦

至此我就放弃了修复的想法,寻找其他方案,当然如果公司可以容忍不兼容那少数的几台x86机器就可以采用这个方案,我咨询过几个朋友,他们采用这个方案的项目已经上线了……

2.对指定的函数进行加密

    这个其实我也并没有看明白,但是我尝试可几次都没成功,这里附上链接,有心的哥们可以参考一下:http://www.cnblogs.com/lanrenxinxin/p/4962470.html

3.折中方案

    我们如果无法容忍不兼容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 
#include

#include

#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

#include

#include


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文件有一定了解,恐怕很难完成这个内容……

4.其他方案

    1)其实一些做加密的服务很多都对加密so有支持,然而都是付费的,sadly……,如果公司有钱可以考虑类似“爱加密”等加密服务

    2)我们可以把获得key和加密函数抽离出来,单独做成decrypt.so,对其进行加密,然后在libmono.so加载前在android层解密并加载decrypt.so,还可以对android层代码混淆等,相当于多做几层防护,加大破解难度。

最后

    加密Dll这件事其实还是无法做到绝对完备,只能加大破解难度,如果有问题请留言


推荐阅读
  • Google在I/O开发者大会详细介绍Android N系统的更新和安全性提升
    Google在2016年的I/O开发者大会上详细介绍了Android N系统的更新和安全性提升。Android N系统在安全方面支持无缝升级更新和修补漏洞,引入了基于文件的数据加密系统和移动版本的Chrome浏览器可以识别恶意网站等新的安全机制。在性能方面,Android N内置了先进的图形处理系统Vulkan,加入了JIT编译器以提高安装效率和减少应用程序的占用空间。此外,Android N还具有自动关闭长时间未使用的后台应用程序来释放系统资源的机制。 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • GreenDAO快速入门
    前言之前在自己做项目的时候,用到了GreenDAO数据库,其实对于数据库辅助工具库从OrmLite,到litePal再到GreenDAO,总是在不停的切换,但是没有真正去了解他们的 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 本文介绍了使用kotlin实现动画效果的方法,包括上下移动、放大缩小、旋转等功能。通过代码示例演示了如何使用ObjectAnimator和AnimatorSet来实现动画效果,并提供了实现抖动效果的代码。同时还介绍了如何使用translationY和translationX来实现上下和左右移动的效果。最后还提供了一个anim_small.xml文件的代码示例,可以用来实现放大缩小的效果。 ... [详细]
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • 本文介绍了计算机网络的定义和通信流程,包括客户端编译文件、二进制转换、三层路由设备等。同时,还介绍了计算机网络中常用的关键词,如MAC地址和IP地址。 ... [详细]
  • 拥抱Android Design Support Library新变化(导航视图、悬浮ActionBar)
    转载请注明明桑AndroidAndroid5.0Loollipop作为Android最重要的版本之一,为我们带来了全新的界面风格和设计语言。看起来很受欢迎࿰ ... [详细]
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
  • 集成电路企业在进行跨隔离网数据交换时面临着安全性问题,传统的数据交换方式存在安全性堪忧、效率低下等问题。本文以《Ftrans跨网文件安全交换系统》为例,介绍了如何通过丰富的审批流程来满足企业的合规要求,保障数据交换的安全性。 ... [详细]
  • 如何提高PHP编程技能及推荐高级教程
    本文介绍了如何提高PHP编程技能的方法,推荐了一些高级教程。学习任何一种编程语言都需要长期的坚持和不懈的努力,本文提醒读者要有足够的耐心和时间投入。通过实践操作学习,可以更好地理解和掌握PHP语言的特异性,特别是单引号和双引号的用法。同时,本文也指出了只走马观花看整体而不深入学习的学习方式无法真正掌握这门语言,建议读者要从整体来考虑局部,培养大局观。最后,本文提醒读者完成一个像模像样的网站需要付出更多的努力和实践。 ... [详细]
  • macOS Big Sur全新设计大版本更新,10+个值得关注的新功能
    本文介绍了Apple发布的新一代操作系统macOS Big Sur,该系统采用全新的界面设计,包括图标、应用界面、程序坞和菜单栏等方面的变化。新系统还增加了通知中心、桌面小组件、强化的Safari浏览器以及隐私保护等多项功能。文章指出,macOS Big Sur的设计与iPadOS越来越接近,结合了去年iPadOS对鼠标的完善等功能。 ... [详细]
author-avatar
可爱竹子16
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有