很多时候App加密本身并不难,难得是他用了一套新玩意,天生自带加密光环。例如PC时代的VB,直接ida的话,汇编代码能把你看懵。
但是要是搞明白了他的玩法,VB Decompiler一上,那妥妥的就是源码。
Unity 和 Flutter 也是如此。
最近迷上了一个小游戏 Dream Blast,今天就拿他解剖吧。
com.rovio.dream
从apk包里面发现libil2cpp.so,就足以证明是Unity写的游戏了。
在Android下Unity有两种玩法,一种是Mono方式打包,我们可以从包内拿到Assembly-CSharp.dll,如果开发者没有对Assembly-CSharp.dll进行加密处理,那么我们可以很方便地使用ILSpy.exe对其进行反编译。这样看到的就是妥妥的C#源码了。
由于总所周知的原因,这种玩法肯定会被公司开除的。现在工作这么难找,所以大家都采取第二种玩法了,使用IL2CPP方式打包,就没有Assembly-CSharp.dll。这样就不会让人轻易攻破了。
这时候就需要召唤出IL2CPP界的Decompiler了。
https://github.com/Perfare/Il2CppDumper
Il2CppDumper 通过 assets/bin/Data/Managed/Metadata/global-metadata.dat 字符串文件 和 lib/armeabi-v7a/libil2cpp.so 游戏二进制文件来还原C#写的代码逻辑。
目前只有编译好的windows可执行文件,所以目前只能在win下使用。(本例演示的是Arm32)
1、先把global-metadata.dat 和 libil2cpp.so 这两个文件拷贝到同一个目录。
2、运行 Il2CppDumper-x86.exe,在弹出的文件选择框里面,先选择 libil2cpp.so,然后再选择 global-metadata.dat。
Initializing metadata...
Metadata Version: 27
Initializing il2cpp file...
Applying relocations...
WARNING: find JNI_OnLoad
ERROR: This file may be protected.
Il2Cpp Version: 27
Searching...
Change il2cpp version to: 27.1
CodeRegistration : 205f9c8
MetadataRegistration : 205ff3c
Dumping...
Done!
Generate struct...
Done!
Generate dummy dll...
Done!
Press any key to exit...
这就算反编译成功了。
一共会生成 DummyDll 目录, script.json,stringliteral.json,dump.cs,il2cpp.h 等文件。
script.json和stringliteral.json是辅助ida 和ghidra 分析的,可以用 ida.py 这个脚本导入到ida里面去。
这会我们只关心 dump.cs。
为了 好好 玩一个游戏,除了改内存,还一个重要的方案就是改配置文件甚至改存盘文件了。
遥想当年帝国时代非得搞个200的人口上限,直接hook一下,把200改成2000他不香吗? (电脑拖崩溃了)
细心 分析了一下,这个游戏的存盘文件在
/sdcard/Android/data/com.rovio.dream/files/usesr/XXX-XXX-XXX/prefs.json
改它,改它,可是它加密了
这时候显示出 dump.cs 的用处了,这可是活地图呀。
在里面搜一下 “prefs.json”
[CreateAssetMenuAttribute] // RVA: 0x3979B8 Offset: 0x3979B8 VA: 0x3979B8
public class UserPrefs : UserPrefsBase, IInitializable, IInitializableInit // TypeDefIndex: 7278
{
// Fields
private const string EK = "8CSstq6cz1Gp9YSQpr2l";
private const string PrefsFileName = "prefs.json";
....
// RVA: 0xAAE690 Offset: 0xAAE690 VA: 0xAAE690 Slot: 42
public void Init() { }
....
从这里得到两个有用的信息,一个是存盘文件在UserPrefs类里面处理,再一个EK可能就是密钥或者密钥的一部分。
可以上ida了,打开libil2cpp.so细嚼慢咽一下。
首先运行 Il2CppDumper-v6\ida_py3.py (低版本的ida请跑ida.py)
然后 在弹出的文件选择框里面 ,选择刚才反编译出来的script.json,最后再跑一次ida_py3.py 把stringliteral.json 也加进来。
万事俱备了,我们去分析一下 UserPrefs_Init() ,地图告诉我们它在 0xAAE690,
ida里面去到 0xAAE690, 然后Create Function, 再F5以下,代码就出来了。
代码看上去还是有点懵,它似乎 System_Guid__NewGuid(v47, 0); 生成了个guid,然后再加上了EK
v43 = System_String__Concat_23810904(*(_DWORD *)(a1 + 28), StringLiteral_1313, 0);
StringLiteral_1313就是 EK。
不过好消息是 最后 它要初始化一个 CryptoUtility___ctor
int __fastcall CryptoUtility___ctor(int a1)
{
int v2; // r6
_DWORD *UTF8; // r0
if ( !byte_2173DF8 )
{
sub_48CE2C(&System_Security_Cryptography_AesManaged_TypeInfo);
sub_48CE2C(&System_Security_Cryptography_Rfc2898DeriveBytes_TypeInfo);
sub_48CE2C(&StringLiteral_1149);
byte_2173DF8 = 1;
}
v2 = sub_48CF00(System_Security_Cryptography_AesManaged_TypeInfo);
System_Security_Cryptography_AesManaged___ctor(v2, 0);
*(_DWORD *)(a1 + 16) = v2;
System_Object___ctor(a1, 0);
UTF8 = (_DWORD *)System_Text_Encoding__get_UTF8(0);
if ( !UTF8 )
sub_48CF08();
return sub_9DB34C(*UTF8, &StringLiteral_1149, *(_DWORD *)(*UTF8 + 344), *(_DWORD *)(*UTF8 + 340));
}
很明显,算法是 AES, 那么key是啥呢? aes还有cbc和ecb,又应该是哪一个呢?
幸亏咱还是懂点C#的,一个优秀的C#程序员,看到AesManaged和Rfc2898DeriveBytes,就知道套路了。
Rfc2898DeriveBytes的入参是一个password和salt,然后生成一组key和iv,后面就是aes做AES-128-CBC了。
目标很明确了,搞到pwd和salt。
ida双击进到 sub_9DB34C
void __fastcall sub_9DB34C(
int a1,
_DWORD *a2,
int a3,
int (__fastcall *a4)(int, _DWORD),
int a5,
int a6,
int a7,
int a8,
int a9,
int a10)
{
int v10; // r4
int v11; // r5
int v12; // r6
int v13; // r7
int v14; // r6
int v15; // r0
v13 = a4(v12, *a2);
v14 = sub_48CF00(System_Security_Cryptography_Rfc2898DeriveBytes_TypeInfo);
v15 = System_Security_Cryptography_Rfc2898DeriveBytes___ctor(v14, v11, v13, 0);
if ( !v14 )
sub_48CF08(v15);
...
真相只有一个,hook 这个 System_Security_Cryptography_Rfc2898DeriveBytes___ctor 就可以拿到 pwd和salt了。 a2是pwd,a3是 salt。
int __fastcall System_Security_Cryptography_Rfc2898DeriveBytes___ctor_17396484(int a1, int a2, int a3, int a4)
{
int v8; // r6
if ( !byte_2176D99 )
{
sub_48CE2C((int)&System_Security_Cryptography_HMACSHA1_TypeInfo);
byte_2176D99 = 1;
}
System_Security_Cryptography_DeriveBytes___ctor(a1, 0);
System_Security_Cryptography_Rfc2898DeriveBytes__set_Salt(a1, a3);
System_Security_Cryptography_Rfc2898DeriveBytes__set_IterationCount(a1, a4);
*(_DWORD *)(a1 + 20) = a2;
v8 = sub_48CF00(System_Security_Cryptography_HMACSHA1_TypeInfo);
System_Security_Cryptography_HMACSHA1___ctor_22256684(v8, a2, 0);
*(_DWORD *)(a1 + 16) = v8;
return System_Security_Cryptography_Rfc2898DeriveBytes__Initialize(a1);
}
说干就干
var libxx = Process.getModuleByName("libil2cpp.so");
console.log("*****************************************************");
console.log("name: " +libxx.name);
console.log("base: " +libxx.base);
console.log("size: " +ptr(libxx.size));
Interceptor.attach(ptr(libxx.base).add(0x1097304),{
onEnter: function(args){
console.log("=== pwd");
console.log(TAG + hexdump(ptr(this.context.r1), { offset: 0, length: 128, header: true, ansi: true }) );
console.log("=== salt ");
console.log(TAG + hexdump(ptr(this.context.r2), { offset: 0, length: 64, header: true, ansi: true }) );
},
onLeave:function(retval){
}
});
这就尴尬了
Error: unable to find module 'libil2cpp.so'
libil2cpp.so 大概率是动态载入的,所以刚启动app的时候木有libil2cpp.so。
如果我们要hook的函数之后会被多次调用,那么可以延迟几秒钟来载入 setTimeout(main, 1000*3);
不过这里我们要hook的都是init和ctor之类的初始化函数,几秒钟之后可能都初始化完成了。
要第一时间hook 动态载入的so,就需要从so的加载开始搞
function hook_constructor0() {
if (Process.pointerSize == 4) {
var linker = Process.findModuleByName("linker");
} else {
var linker = Process.findModuleByName("linker64");
}
var addr_call_function =null;
var addr_g_ld_debug_verbosity = null;
var addr_async_safe_format_log = null;
if (linker) {
var symbols = linker.enumerateSymbols();
for (var i = 0; i < symbols.length; i++) {
var name = symbols[i].name;
if (name.indexOf("call_function") >= 0){
addr_call_function = symbols[i].address;
}
else if(name.indexOf("g_ld_debug_verbosity") >=0){
addr_g_ld_debug_verbosity = symbols[i].address;
ptr(addr_g_ld_debug_verbosity).writeInt(2);
} else if(name.indexOf("async_safe_format_log") >=0 && name.indexOf('va_list') < 0){
addr_async_safe_format_log = symbols[i].address;
}
}
}
if(addr_async_safe_format_log){
Interceptor.attach(addr_async_safe_format_log,{
onEnter: function(args){
this.log_level = args[0];
this.tag = ptr(args[1]).readCString()
this.fmt = ptr(args[2]).readCString()
if(this.fmt.indexOf("c-tor") >= 0 && this.fmt.indexOf('Done') < 0){
this.function_type = ptr(args[3]).readCString(), // func_type
this.so_path = ptr(args[5]).readCString();
var strs = new Array(); //定义一数组
strs = this.so_path.split("/"); //字符分割
this.so_name = strs.pop();
this.func_offset = ptr(args[4]).sub(Module.findBaseAddress(this.so_name))
if(this.so_name == "libil2cpp.so") {
var targetSo = Module.findBaseAddress(this.so_name);
console.log(TAG +' so_name:',this.so_name);
console.log(TAG +' ptr:',ptr(targetSo));
hookDbg(targetSo);
}
}
},
onLeave: function(retval){
}
})
}
}
function hookDbg(targetSo){
Interceptor.attach(targetSo.add(0xAAE690),{
onEnter: function(args){
console.log(" UserPrefs_ctor *****************************************************");
},
onLeave:function(retval){
}
});
Interceptor.attach(ptr(targetSo).add(0x1097304),{
onEnter: function(args){
console.log("=== pwd");
console.log(TAG + hexdump(ptr(this.context.r1), { offset: 0, length: 128, header: true, ansi: true }) );
console.log("=== salt ");
console.log(TAG + hexdump(ptr(this.context.r2), { offset: 0, length: 64, header: true, ansi: true }) );
},
onLeave:function(retval){
}
});
}
这次的结果就比较完美了

Rfc2898DeriveBytes的入参是String,可以看到String在内存中的布局, 0x0C 开始的4个字节是 字符串长度,0x10开始才是真正的字符串。
password 是存档的文件夹名称+EK
salt 是个固定的字符串
带着这个结果我们再回过头去看 UserPrefs__Init的F5的代码,重点关注那几个 System_String_Concat 就更有心得了。
为了抵抗Il2CppDumper,敌人变狡猾了,所以作者推出了更帅的 Zygisk-Il2CppDumper
现在套路这么多,技能得不断更新才能跟的上,又要掉头发了。
变来变去的都是外围,万变不离其宗的还是arm汇编,最后的定位还是需要你的汇编功底。
网络游戏改存盘是没用的,一联服务器就把你覆盖了。

富贵故如此,营营何所求
目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称
@作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors 1、什么是behaviors 2、behaviors的工作方式 3、创建behavior 4、导入并使用behavior 5、behavior中所有可用的节点 6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors 1、什么是behaviorsbehaviors是小程序中,用于实现
遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg
ES一、简介1、ElasticStackES技术栈:ElasticSearch:存数据+搜索;QL;Kibana:Web可视化平台,分析。LogStash:日志收集,Log4j:产生日志;log.info(xxx)。。。。使用场景:metrics:指标监控…2、基本概念Index(索引)动词:保存(插入)名词:类似MySQL数据库,给数据Type(类型)已废弃,以前类似MySQL的表现在用索引对数据分类Document(文档)真正要保存的一个JSON数据{name:"tcx"}二、入门实战{"name":"DESKTOP-1TSVGKG","cluster_name":"elasticsear
修改(澄清问题)我已经花了几天时间试图弄清楚如何从Facebook游戏中抓取特定信息;但是,我遇到了一堵又一堵砖墙。据我所知,主要问题如下。我可以使用Chrome的检查元素工具手动查找我需要的html-它似乎位于iframe中。但是,当我尝试抓取该iframe时,它是空的(属性除外):如果我使用浏览器的“查看页面源代码”工具,这与我看到的输出相同。我不明白为什么我看不到iframe中的数据。答案不是它是由AJAX之后添加的。(我知道这既是因为“查看页面源代码”可以读取Ajax添加的数据,也是因为我有b/c我一直等到我可以看到数据页面之后才抓取它,但它仍然不存在)。发生这种情况是因为
关闭。这个问题不符合StackOverflowguidelines.它目前不接受答案。要求我们推荐或查找工具、库或最喜欢的场外资源的问题对于StackOverflow来说是偏离主题的,因为它们往往会吸引自以为是的答案和垃圾邮件。相反,describetheproblem以及迄今为止为解决该问题所做的工作。关闭9年前。Improvethisquestion是否有适用于这些的3d游戏引擎?
目录0专栏介绍1平面2R机器人概述2运动学建模2.1正运动学模型2.2逆运动学模型2.3机器人运动学仿真3动力学建模3.1计算动能3.2势能计算与动力学方程3.3动力学仿真0专栏介绍?附C++/Python/Matlab全套代码?课程设计、毕业设计、创新竞赛必备!详细介绍全局规划(图搜索、采样法、智能算法等);局部规划(DWA、APF等);曲线优化(贝塞尔曲线、B样条曲线等)。?详情:图解自动驾驶中的运动规划(MotionPlanning),附几十种规划算法1平面2R机器人概述如图1所示为本文的研究本体——平面2R机器人。对参数进行如下定义:机器人广义坐标
文章目录1.任务背景2.任务目标3.相关知识点4.任务实操4.1安装配置JDK4.2启动FISCOBCOS4.3下载解压WeBASE-Front4.4拷贝sdk证书文件4.5启动节点4.6访问节点4.7检查运行状态5.任务总结1.任务背景FISCOBCOS其实是有控制台管理工具,用来对区块链系统进行各种管理操作。但是对于初学者来说,还是可视化界面更友好,本节就来介绍WeBASE管理平台,这是一款微众银行开源的自研区块链中间件平台,可以降低区块链使用的门槛,大幅提高区块链应用的开发效率。微众银行是腾讯牵头设立的民营银行,在国内民营银行里还是比较出名的。微众银行参与FISCOBCOS生态建设,一定
网站的日志分析,是seo优化不可忽视的一门功课,但网站越大,每天产生的日志就越大,大站一天都可以产生几个G的网站日志,如果光靠肉眼去分析,那可能看到猴年马月都看不完,因此借助网站日志分析工具去分析网站日志,那将会使网站日志分析工作变得更简单。下面推荐两款网站日志分析软件。第一款:逆火网站日志分析器逆火网站日志分析器是一款功能全面的网站服务器日志分析软件。通过分析网站的日志文件,不仅能够精准的知道网站的访问量、网站的访问来源,网站的广告点击,访客的地区统计,搜索引擎关键字查询等,还能够一次性分析多个网站的日志文件,让你轻松管理网站。逆火网站日志分析器下载地址:https://pan.baidu.
一、机器人介绍 此处是基于MATLABRVC工具箱,对ABB-IRB-1200型号的微型机械臂进行正逆向运动学分析,并利Simulink工具实现对机械臂进行具有动力学参数的末端轨迹规划仿真,最后根据机械模型设计Simulink-Adams联合仿真。 图1.ABBIRB 1200尺寸参数示意图ABBIRB 1200提供的两种型号广泛适用于各作业,且两者间零部件通用,两种型号的工作范围分别为700 mm 和 900 mm,大有效负载分别为 7 kg 和5 kg。 IRB 1200 能够在狭小空间内能发挥其工作范围与性能优势,具有全新的设计、小型化的体积、高效的性能、易于集成、便捷的接