nszdhd1's blog.

nszdhd1's blog.

🌱 又是努力逆向的一天呀 ~

SO加密

so加密笔记

基础知识

一、ELF文件

(更详细可看 elf讲解

ELF是Linux下的可执行连接格式文件,类似于windows下的PE文件。

ELF文件(目标文件)格式主要三种:

  1. 可重定向文件(Relocatable file):文件保存着代码和适当的数据,用来和其他的目标文件一起来创建一个可执行文件或者是一个共享目标文件。(目标文件或者静态库文件,即linux通常后缀为.a和.o的文件)这是由汇编器汇编生成的 .o 文件。后面的链接器(link editor)拿一个或一些 Relocatable object files 作为输入,经链接处理后,生成一个可执行的对象文件 (Executable file) 或者一个可被共享的对象文件(Shared object file),内核可加载模块 .ko 文件也是 Relocatable object file

  2. 可执行文件(Executable file):文件保存着一个用来执行的程序。(例如bash,gcc等)

  3. 共享目标文件:即 .so 文件。如果拿前面的静态库来生成可执行程序,那每个生成的可执行程序中都会有一份库代码的拷贝。如果在磁盘中存储这些可执行程序,那就会占用额外的磁盘空 间;另外如果拿它们放到Linux系统上一起运行,也会浪费掉宝贵的物理内存。如果将静态库换成动态库,那么这些问题都不会出现

1.1 ELF 文件结构

elf文件主要有两种格式,一是链接状态、二是运行状态。

image-20200722200900804

这里再放一张 32位数据类型,方便理解。

image-20200722202815692

Unity保护之assetbundle

AssetBundle加密

1.简介

1.1 什么是AssetBundle

AssetBundle是Unity pro提供的一种用来存储资源的文件格式,它可以存储任意一种Unity引擎能够识别的资源,如Scene、Mesh、Material、Texture、Audio、noxss等等,同时,AssetBundle也可以包含开发者自定义的二进制文件,只需要将自定义文件的扩展名改为.bytes,Unity就可以把它识别为TextAsset,进而就可以被打包到AssetBundle中。Unity引擎所能识别的资源我们称为Asset,AssetBundle就是Asset的一个集合。

AssetBundle 加载,可分为请求服务器和本地资源,一般Assetbundle静态文件,

1.2 AssetBundle的特点

压缩(缺省)、动态载入、本地缓存

1.3 AssetBundle 使用(开发视角)

a、创建AssetBundle,并打包;

b、上传到Server;

c、游戏运行时根据需要下载(或者从本地cache中加载)AssetBundle文件;

d、解析加载Assets;

e、使用完毕后释放;

将资源文件打包成ab包,在unity里使用:

BuildPipeline.BuildAssetBundles(string outputPath, BuildAssetBundleOptions assetBundleOptions, BuildTarget targetPlatform)

加载静态ab文件,在unity里使用(从服务器下载没有静态文件,暂不考虑):

AssetBundle.Load (name : string)

Unity保护之il2cpp

IL2CPP 保护方案

背景

如果 Unity 游戏 选择使用IL2CPP编译的话,那么会将代码编译到libil2cpp.so,并且将字符串信息保存在一个叫global-metadata.dat的资源文件里。

IL2CPP详细介绍:https://blog.csdn.net/feibabeibei_beibei/artic le/details/95922520

IL2CPP 加载过程

github上随便找一个使用il2cpp项目的源码,搜索global-metadata.dat,发现只有函数MetadataCache::Initialize()处使用。

1
2
3
4
5
6
7
void MetadataCache::Initialize()
{
s_GlobalMetadata = vm::MetadataLoader::LoadMetadataFile("global-metadata.dat");
s_GlobalMetadataHeader = (const Il2CppGlobalMetadataHeader*)s_GlobalMetadata;
...

}
基于va的luajit hook

hook时机

根据 jithook中介绍,luaopen_jit 是 最后一个加载的库 lua lib_init.c

所以选择在 hook luaopen_jit 来加载hook.lua

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
static const luaL_Reg lj_lib_load[] = {
{ "", luaopen_base },
{ LUA_LOADLIBNAME, luaopen_package },
{ LUA_TABLIBNAME, luaopen_table },
{ LUA_IOLIBNAME, luaopen_io },
{ LUA_OSLIBNAME, luaopen_os },
{ LUA_STRLIBNAME, luaopen_string },
{ LUA_MATHLIBNAME, luaopen_math },
{ LUA_DBLIBNAME, luaopen_debug },
{ LUA_BITLIBNAME, luaopen_bit },
{ LUA_JITLIBNAME, luaopen_jit },
{ NULL, NULL }
};

static const luaL_Reg lj_lib_preload[] = {
#if LJ_HASFFI
{ LUA_FFILIBNAME, luaopen_ffi },
#endif
{ NULL, NULL }
};

LUALIB_API void luaL_openlibs(lua_State *L)
{
const luaL_Reg *lib;
for (lib = lj_lib_load; lib->func; lib++) {
lua_pushcfunction(L, lib->func);//将一个 C 函数压入堆栈。 这个函数接收一个 C 函数指针,并将一个类型为 function 的 Lua 值 压入堆栈。当这个栈定的值被调用时,将触发对应的 C 函数。(其实就是注册函数,不过传入的函数还是要遵循lua_CFunction的那个规则,此处不重要)
lua_pushstring(L, lib->func);//同理,把指针lib->name指向的以零结尾的字符串压栈
lua_call(L, 1, 0);//执行 lib->func(lib->func),相似的还有个pcall,多了个返回错误

}
luaL_findtable(L, LUA_REGISTRYINDEX, "_PRELOAD",
sizeof(lj_lib_preload)/sizeof(lj_lib_preload[0])-1);
for (lib = lj_lib_preload; lib->func; lib++) {
lua_pushcfunction(L, lib->func);
lua_setfield(L, -2, lib->name);
}
lua_pop(L, 1);
}

/*lua_call
要调用一个函数请遵循以下协议:首先,要调用的函数应该被压入栈;接着,把需要传递给这个函数的参数按正序压栈; 这是指第一个参数首先压栈。最后调用一下 lua_call;nargs 是你压入栈的参数个数。 当函数调用完毕后,所有的参数以及函数本身都会出栈。 而函数的返回值这时则被压栈。 返回值的个数将被调整为 nresults 个,除非 nresults 被设置成 LUA_MULTRET。 在这种情况下,所有的返回值都被压入堆栈中。 Lua 会保证返回值都放入栈空间中。 函数返回值将按正序压栈(第一个返回值首先压栈), 因此在调用结束后,最后一个返回值将被放在栈顶。
*/
网易保护分析

网易保护研究

目的

分析网易游戏保护,提取可借鉴优点

分析思路

我理解的功能范围:加壳、各个检测项、数据上报。
所以我的两种思路:

  1. 收集当下流行工具、攻击方式对网易游戏进行测试(缺点:最多只能察觉哪些操作会触发检测,对于非敏感的检测点(不能直接判定为作弊者的点)意义不大,且反馈只有崩溃、正常、和部分模糊的提示,主观猜测成分太大。优点:简单,可用于所有厂商的保护分析)

  2. 过保护,拿到正常的so对源码进行逆向分析(缺点:难度大,只针对网易)

magisk-riru 使用

1. riru-core riru框架实现原理

riru框架入口是 riru-core/jni/main/main.cpp中的函数constructor().

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
extern "C" void constructor() __attribute__((constructor));

void constructor() {
static int loaded = 0;
if (loaded)
return; // 如果已经加载就退出,保证只加载一次

loaded = 1;

if (getuid() != 0) //不是root用户就退出
return;

char cmdline[ARG_MAX + 1];
get_self_cmdline(cmdline);

if (!strstr(cmdline, "--zygote"))
return;

LOGI("Riru %s in %s", VERSION_NAME, ZYGOTE_NAME);

LOGI("config dir is %s", get_config_dir());

char path[PATH_MAX];
snprintf(path, PATH_MAX, "%s/.disable", get_config_dir());

if (access(path, F_OK) == 0) {
LOGI("%s exists, do nothing.", path); // 判断该框架是否停用,停用了就退出
return;
}

read_prop();
//使用了 iqiyi 的 xhook ,有兴趣可以去github上看。https://github.com/iqiyi/xhook
XHOOK_REGISTER(".*\\libandroid_runtime.so$", jniRegisterNativeMethods);

if (xhook_refresh(0) == 0) {
xhook_clear();
LOGI("hook installed");
} else {
LOGE("failed to refresh hook");
}

load_modules();
}
Magisk检测

Magisk 原理

Xposed 和 Magisk原理图

image-20200220161119719

Xposed 原理

详细讲解:https://blog.csdn.net/ascii2/article/details/47974217

Xposed修改了app_process程序,在执行第一个java程序(com.Android.internal.os.ZygoteInit)之前进行截获,改变执行流程,进入到XposedBridge.jar,通过INI方法hookMethodNative指向Native方法xposedCallHandler,xposedCallHandler在转入handleHookedMethod这个Java方法执行用户规定的Hook Func

xposed 检测方法

1. 检测包名

检测是否安装de.robv.android.xposed.installer

2.调用栈

抛出一个异常并捕获,将堆栈信息打印出来:

image-20200220164108776

可以看到每个App是先执行的XposedBridge.jar的main方法,之后再调用的Zygote的main方法。通过检测堆栈中是否包含Xposed等字样即可知道是否安装了Xposed

avatar
nszdhd1
🌱 又是努力逆向的一天呀 ~