首页
论坛
课程
招聘
[原创]E*OS (N*Protect)分析记录
2021-5-14 15:17 8861

[原创]E*OS (N*Protect)分析记录

2021-5-14 15:17
8861

E*OS N*Protect分析记录

 

 

前段时间想玩下这个游戏,模拟器运行时候发现有root检测,想看下它的实现,准备调试过掉,但是没有X86库,又发现之前的调试手机版本太低了,手上有个小米8,然后经历重新下载源码,编译LineageOs17.1,刷机,调试,根据调试情况修改系统源码,中间碰到盲区,又要去翻对应知识点,中间还被其它事打断过,断断续续持续了很长时间,写个文档做下记录。

 

1.       软硬件环境(游戏有点大,就不挂附件了)

 

EOS 2.2.87版本:https://apkpure.com/kr/search?q=EOS&t=app

IDA 7.5

Frida 14.2.2

Gda3.86

JEB

LineageOs 17.1 (android 10)

小米8

 

 

2.       流水账 (直接按实际经历的时间顺序来了)

 

最开始拿到APK后,用GDA打开

 

直接翻了下各个信息,有函数名称混淆,字符串加密等,看起来是有壳的,直接搜相关信息,搜到这篇:https://blog.csdn.net/weixin_30512785/article/details/99559394


这个是旧版本的Nprotect,新版本跟这个比起来,dexso的保护强度都加强了,包括so文件(抹去了文件头等信息,干扰静态分析),so的加载(libengine.so没有使用系统API装载),符号表的加密,信息流也不再直来直去,还有使用了Ollvm(Obfuscator-LLVM clang version 4.0.1)等。

新旧版本虽然有很多不同了,并且跟我看的游戏也不同,但是仍然有很多相似的地方,这篇文章还是有所帮助的,省了不少时间,非常感谢这位作者。

 

我是想看下这个root检测,那篇没提到,可能是旧版本没有,就准备自己看了,这中间穿插了编译系统刷机,也是一番波折,还好系统都刷好了。

 

模拟器中开这个游戏的提示:

uiautomatorviewerStrong看了下,没找到切入点,想通过提示字符串查找关键点,搜不到,字符串都加密了,并且直接打开的APK JAVA代码也是不全的,接着就准备dump class文件了,这个用github上的frida_dump脚本就行,会得到8dex文件:

 

然后就是反编译,翻代码,对我这边相关代码是在class2.dex中,发现字符串都是加密的,

 

加密代码都是比较简单的(so里面的字符串也是加密的,不过是AES的),不过要注意的是,这个加密函数不止一个,不同的字符串可能用的函数不同的,算法类似,只是里面的2XOR常量不同:

    static public String IiIIiiIiii(String p0){ 

       int vi;

       int ilength = p0.length();

       char[] ocharArray = new char[ilength];

       ilength = ilength-1;

       while (ilength >= 0) {   

          vi = ilength-1;

          ocharArray[ilength]=(char)(p0.charAt(ilength)^0x3c);

          if (vi >= 0) {

             ilength = vi-1;

             ocharArray[vi]=(char)(p0.charAt(vi)^0x60);

          }else {

             break

          } 

       }

       return new String(ocharArray);

}

 

后面就是找到感兴趣的类,写程序解密字符串了,最后找到这个提示点:

package com.inca.security.Core;

public class AppGuardEngine implements WeakRefHandler$IOnHandleMessage, BaseEventInvoker

这个类中的代码:

This app will be terminated because a security policy violation has been detected!

后面 code显示是10进制。

 

这里要说下GDA确实强,下面这个JEB反编译不出来的:

 

这里面包括各个检测码的定义,还原字符串后是这样的:

根据这个看,34正好就是DETECT_ROOTING_ENVIRONMENT,表示检测到root环境了,顺便提下,翻代码过程中,发现几个反调试的地方:

类名: com.inca.security.IiIIiiiiIi

 

Debug.isDebuggerConnected()

    public boolean iiIIIiiiIi() {

        return Debug.isDebuggerConnected();

    }

 

通过执行时间差Debug.threadCpuTimeNanos(),判断是否被调试

做100W次加法计算,看时间是否超过100ms (100000000纳秒)

    public boolean iIIIiiiIII() {

        boolean v0 = false;

        long v4 = Debug.threadCpuTimeNanos();

        int v1 = 0;

        int v2;

        for(v2 = 0; v1 < 1000000; v2 = v1) {

            v1 = v2 + 1;

        }

 

        if(Debug.threadCpuTimeNanos() - v4 >= 100000000) {

            v0 = true;

        }

 

        return v0;

}

 

通过引用分析,最后确定出检测框的入口是这个函数:

public void conditionCallback(int arg23, int arg24, byte[] arg25)

 

然后上fridahook这个调用点

MainActivity = Java.use('com.inca.security.Core.AppGuardEngine');

if (MainActivity != null) {       

     MainActivity.conditionCallback.implementation = function (arg0, arg1, arg2) {

                    //send('Statr! Hook!'); //python call back

                    console.log("call conditionCallback");

                    console.log(arg0);

                    console.log(arg1);

                    console.log(arg2);

 

                    showStacks();

                    return this.conditionCallback(arg0, arg1, arg2);               

}

}

frida -U -l E:\node_proj\TcpsocketTest\fridaHook2.js -f com.bluepotiongames.eosm

Spawned `com.bluepotiongames.eosm`. Use %resume to let the main thread start executing!

[MI 8::com.bluepotiongames.eosm]-> hook_eos();

[MI 8::com.bluepotiongames.eosm]-> %resume

 

发现很快就退出了,到不了提示窗口那,那就是被检测到了,要上动态调试了。

这个启动后是3个进程的,互相ptrace,还是用frida启动进程方式,然后通过IDA附加游戏进程调试。

保护相关的SO是下面几个:

libcompatible.so

libstub.so

libengine-hlp.so

libengine.so

 

首先上来肯定就是找JNI_OnLoad了,直接跑IDC脚本:

    //android 10(lineage 17.1)

    //LoadNativeLibrary偏移: 0000007BAE70AC70 - 0000007BAE395000 = 375C70

    auto soBase=0;

    soBase=getModuleBase("libart.so");

    auto addrArtBp=soBase + 0x375C70;

    MakeComm(addrArtBp,"LoadNativeLibrary");   

    auto addrArtCallOnload=soBase + 0x376910;

    AddBpt(addrArtCallOnload); 

    MakeComm(addrArtCallOnload,"call JNI_ONLOAD");

 

进到libcompatible.soJNI_OnLoad

 

有些代码是运行中解压的,这种可以调试时候dump对应的数据下来,然后合并到原so文件中,可以方便IDA分析。

JNI_OnLoad里面包含反调试的相关处理,检查status信息,fork子进程互相ptrace,注册inotify_add_watch,包含下面几个检测:

/proc/xxx/mem

/proc/xxx/maps

/dev/input      这里下面3个是调试libengine.so发现的

/system/bin/input

/system/bin/monkey

 

最后走的都是svc调用.

 

这些检测,直接改内核过滤掉了,status的相关修改网上都很多,关于这个inotify_add_watch的修改,在fs/notify/下:

/fs/notify/inotify/inotify_user.c中的inotify_add_watch

(这里修改给自己挖了个坑,只过滤了了主进程的,导致线程触发的还是被检测了,下面会提到,根本原因是子线程的current->parent task_struct结构直接是主进程的parent的了,导致得到的进程名称不是预想的本进程名称,而是父进程的父进程的名称)

 

处理ptrace:

bionic/libc/bionic/ptrace.cpp

对svc调用的要改内核部分ptrace.c中的ptrace_attach,对这个进程都直接返回就行。

 

这里实际调试花了点时间,中间也输出过so里面各个jni接口函数(包括后续libstub.so,

这里提下,libstub.so中的.init_proc会调用libcompatible.so中的导出函数SoLibraryStart来解密代码,

后来发现这里不是主要关注点,就换了个思路。

 

处理完上面检测后,就可以继续跑hook conditionCallback脚本了,这个时候就可以显示堆栈了:

 

java.lang.Exception
        at com.inca.security.Core.AppGuardEngine.conditionCallback(Native Method)

 

GetMethodID   Pid: 11681Path: conditionCallback

Backtrace:

0x76046fe75c

0x7635df2808

0x7635df2808

 

然后IDA附加上去,去到0x76046fe75c,通过对比分析,确定了这就是libengine.so的代码空间,可以dump出来用IDA打开,然后动态静态结合看,内存中文件头都没了,IDA里面定位有点麻烦,这里可以把调试得到的相关偏移写到idc脚本中,每次新的调试,跑下脚本,就可以识别出之前已经分析的点。

 

双击输出的地址,就可以到达对应代码点,还是很方便的

 

通过0x76046fe75c这个点只是搞清楚了检测码的读取,写入是另外的线程,这里读取到后,就会调用java显示提示窗口了。

因为前面修改inotify_add_watch的坑,准备这里入手找切入点的时候,发现会跑飞。

 

后来尝试了几种方法,没找到切入点,还是回到系统修改上,直接在pthread_create那输出了线程入口地址,然后结合上面堆栈输出的地址,确定了几个相关的线程地址,然后修改修改libengine.so文件的入口地址为00 00 00 14,直接入口循环,然后附加,还原入口代码并断点,确定相关的入口点:

 

Root相关检测就是这个线程了,跑起来后遇到新的问题,就是不是提示检测码34,而是9,查找之前的码表:

       stringArray[7]=("DETECT_INVALID_LIBDVM_SO");

       stringArray[8]=("detectinvVALID_LIBRUNTIME_SO");

       stringArray[9]=("DETECT_INVALID_APPLIB_SO");

       stringArray[10]=("DETECT_INVALID_LIBENGINE_SO");

 

9就是DETECT_INVALID_APPLIB_SO,看起来是修改了libengine.so导致被检测到了,不过现在已经确定了线程入口点了,后面直接顺着调试了。

 

主要就是hash比较,这个文件校验会涉及到assets\appguard目录下的3个文件:

81,936 sign.axml

256 sign.crt

510,368 sign.mf

涉及到的算法有sha256 ,RSA.

 

检测到修改后,会设置检测码9,如下:

 

对于这个检测,通过修改内核的目录列表返回,过滤掉了libengine.so

对应修改点fs/readdir.c中的filldir64,相关的修改都是一些字符串比较,就是过滤,并且不同系统内核也不同,就不复制代码占篇幅了。

现在就可以继续调试了,找到root检测点,包括su文件检测和apk包检测:

 

相关的路径和包名:

/system/bin/su

/system/xbin/su

/sbin/su

/system/su

/system/xbin/ku.sud

/system/xbin/sutemp

/su/bin/su

/root/magisk

 

/sbin/adbd

daemonsu

eu.chainfire.supersu

 

com.topjohn

com.topjohnwu.magisk

 

1、  查找上面几个su文件路径。

2、  调用命令which su

3、  调用pm list packages,查找是否有

com.topjohn

com.topjohnwu.magisk

 

 

回过头来看,其实也可以用frida hook java.lang.Runtime.exec找到app调用的命令:

hookAllOverloads: exec

arguments: pm,path,com.bluepotiongames.eosm

arguments: which,su

arguments: pm,list,packages

 

处理方法,也是直接在内核中过滤su相关路径,pm list中过滤包返回。

 

顺便提下,还有个读取__system_property相关,这个有JAVA层,也有native层的,可以根据情况过滤。

对于通过SystemProperties.get读取build.prop文件信息的,这个我是直接在内核open函数那,重定向到/data/local/tmp/build2.prop了,后面直接改这个文件就好了.

 

在处理完文件校验,找到线程切入点后,基本后面各需求都可以顺着调试了,提下模拟器相关检测:

/system/bin/nox   

/system/bin/ttVM-prop  

/system/app/MOMOStore/MOMOStore.apk  

/system/lib/vboxsf.ko

/system/lib/vboxguest.ko  

/system/lib/vboxvideo.ko  

/system/bin/nemuVM-nemu-service

boolean isEmulator = SystemProperties.get("ro.kernel.qemu").equals("1");

 

附:

处理完这个root检测后,安装建 行的appver 5.0.2)试了下,发现还是被检测(这个看网上很多也是说新版本用面具隐藏模块也不行,梆梆保护),看了下,是多了下面路径访问检测的,比如/data目录非root是访问不了的:

/system/bin

/system/xbin

/system/product/bin

/odm/bin

/vendor/bin

/data/local/tmp

/postinstall

/data

这些路径对线程名称getprop过滤掉就可以了。

 



[公告] 欢迎大家踊跃尝试高研班11月试题,挑战自己的极限!

收藏
点赞3
打赏
分享
最新回复 (12)
雪    币: 3
活跃值: 活跃值 (668)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
lookzo 活跃值 2021-5-18 13:51
2
0
这是nprotect?
雪    币: 2196
活跃值: 活跃值 (1266)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
caolinkai 活跃值 2021-5-28 09:06
3
0
支持下
雪    币: 153
活跃值: 活跃值 (939)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
Kaining 活跃值 2021-6-2 18:13
4
0
缺个小米8
雪    币: 662
活跃值: 活跃值 (374)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
Himeko 活跃值 2021-6-3 09:05
5
0
这壳强的离谱哦
雪    币: 2475
活跃值: 活跃值 (884)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
木志本柯 活跃值 2021-6-3 09:37
6
0
如果程序检测自身运行速度 来反调试 那么把程序加速不就 可以pass这类反调试
雪    币: 4
活跃值: 活跃值 (1615)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
不吃早饭 活跃值 2021-6-3 19:15
7
0
大佬v5
雪    币: 3139
活跃值: 活跃值 (417)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
疯子Tear 活跃值 2021-6-15 08:48
8
0
dalao联系方式
雪    币: 133
活跃值: 活跃值 (342)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
koflfy 活跃值 1 2021-6-17 21:06
9
0
mark
雪    币: 213
活跃值: 活跃值 (694)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
kakasasa 活跃值 2021-6-18 17:12
10
0
mark,感谢分享,图不够清晰。
雪    币: 196
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
咸鱼王 活跃值 2021-7-1 10:40
11
0
直接hook获取时间相关的函数不会不会更简单
雪    币: 3139
活跃值: 活跃值 (417)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
疯子Tear 活跃值 2021-7-2 22:36
12
0
咸鱼王 直接hook获取时间相关的函数不会不会更简单
大佬有没有联系方式??
雪    币: 12
活跃值: 活跃值 (16)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
xiao妖 活跃值 2021-7-3 11:46
13
0
直接HOOK,还是要绕一下弯
游客
登录 | 注册 方可回帖
返回