/NativeLauncher

启动游戏并允许在没有root设备情况下对游戏进行hook

Primary LanguageKotlin

单机游戏破解启动器原理

启动器可以启动游戏并允许在没有root设备情况下对游戏进行hook, 这里游戏模拟MinecraftPE (google native-activity稍作修改). 项目杂糅了java和kotlin希望见谅.

NativeActivity

NativeActivity 是使用纯本地语言(C/C++, 统称C)编写实现的Activity, 因此(类)游戏应用通常使用NativeActivity与C语言编写的 游戏能更好的对接和移植. 游戏代码用C实现,打成动态库(.so)由NativeActivity启动是进行动态链接.

    <!-- Our activity is the built-in NativeActivity framework class.
         This will take care of integrating with our NDK code. -->
    <activity android:name="android.app.NativeActivity"
              android:label="@string/app_name"
              android:configChanges="orientation|keyboardHidden">
      <!-- Tell NativeActivity the name of our .so -->
      <meta-data android:name="android.app.lib_name"
                 android:value="native-activity" />
    </activity>

当应用启动一个NativeActivity(或子类)时, android.app.lib_nameNativeActivity.onCreate中被解析出来, 由版本而查找所需的动态库有所不同, 这部分代码实现基于NativeConfig, 参考不同版本NativeActivity, BaseDexClassLoader, DexPathList等源码理解.

System.loadLibrary搜索路径

在没有root权限下对于任何应用的下lib目录均没有修改的权限, 一般只有读权限. 要启动游戏必须配合lib下的so,正确的加载到 才能将游戏启动, 因此需要设置正确的so搜索路径.

System.loadLibrarySystem.load的差别在于前者只需要libname即可在已有的路径用搜索, 而后者需要传递一个绝对路径. 为了能更好的配合NativeActivity工作,这里将游戏的lib目录拷贝到NativeLauncher/app_patched下

    val mcpeContext = createPackageContext("com.example.native_activity", CONTEXT_IGNORE_SECURITY)
    soDir = getDir("patched", Context.MODE_PRIVATE)
    so = File(soDir, "libnative.so")
    File(mcpeContext.applicationInfo.nativeLibraryDir).copyRecursively(soDir, true)

Hook游戏

使用Tiny Substrate 由于少有文档说明,建议参考cydiasubstrate. cydiasubstrate做hook需要 root的设备, 项目中通过重新映射动态库内存, 修改其读写权限, 使得Substrate API可以正常使用. 以下实现代码来自于 zhuowei/MCPELauncher稍作修改.

MaraudersMap.java marauders_map.c PokerFace.java nativepatch.c

Hook失败:signal 11 (SIGSEGV), code 2 (SEGV_ACCERR)

Android6.0变更

https://developer.android.google.cn/about/versions/marshmallow/android-6.0-changes#behavior-runtime

此版本更新了动态链接程序的行为。动态链接程序现在可以识别库的 soname 与其路径之间的差异(公开错误 6670), 并且现在已实现了按 soname 搜索。之前包含错误的 DT_NEEDED 条目(通常是开发计算机文件系统上的绝对路径) 却仍工作正常的应用,如今可能会出现加载失败。

该变更直接导致了不含有SONAME段的so作为其他so的DT_NEEDED时加载失败: 启动器libtest.so依赖于游戏的libnative.so

$ readelf -d libtest.so|grep NEEDED
 0x00000001 (NEEDED)                     Shared library: [libmcpelauncher_tinysubstrate.so]
 0x00000001 (NEEDED)                     Shared library: [libnative.so]
 0x00000001 (NEEDED)                     Shared library: [liblog.so]
 0x00000001 (NEEDED)                     Shared library: [libstdc++.so]
 0x00000001 (NEEDED)                     Shared library: [libc.so]
 0x00000001 (NEEDED)                     Shared library: [libm.so]
 0x00000001 (NEEDED)                     Shared library: [libdl.so]

$ readelf -d libnative.so|grep SONAME
 0x0000000e (SONAME)                     Library soname: [libnative.so]
  • android6.0以前只要能搜索libnative.so就可以正确加载libtest.so
  • android6.0开始, 如果在libtest.so中查询不到SONAMElibtest.so将报错
  • gcc编译动态库可以通过 -Wl,-soname,libnative.so选项指定SONAME

关于项目的使用

    git clone https://github.com/cntlb/native_activity.git
    git clone https://github.com/cntlb/NativeLauncher.git

先安装native_activity(或直接安装app-debug_v1.0.apk)后启动NativeLauncher

issue

正常启动后下一次启动会崩溃,如此循环, 待修复.