Bootstrap

APP逆向 day21大姨妈逆向

一.前言

今天来和大家说一款app名叫DYM,我们选择版本v8.6.0,今天通过这个可以学到的知识点有绕过root检测,通过frida-rpc和自己编写一款小的app来调用so文件,然后再来破解登录接口

二.绕过root检测

我们进入app后发现,弹出一个检测到了root了

那绕过之前,我们得先了解一下root检测原理

root检测原来是
    -使用java代码或者so代码---》检测root的特征
    -一个手机一旦root了,在某些目录下,就会有对应的一些文件(su)
    -app做root检测,就是在所有可能的目录下,检索有没有这些特征,如果有,app就不允许运行了
    
   
手机root了,会在那些目录下有特征呢?--》特征一般都是  [su] 
    "/system/bin/su", # 不同root软件,生成的位置不同,面具一般在这个目录下
     "/data/local/bin/su",
    "/data/local/su",
    "/data/local/xbin/su",
    "/sbin/su",
    "/system/app/Superuser.apk",
    "/system/bin/failsafe/su",
    "/su/bin/su",
    "/system/sd/xbin/su",
    "/system/xbin/busybox",
    "/system/xbin/daemonsu",
    "/system/xbin/su",
    "/system/sbin/su",
    
我们手机已经root了,会不会有这些特征
    -/system/bin/su 路径下有su 可执行文件
    -查看手机是否被root
        adb shell  # 进入手机
        su         # 只要有执行,其实手机就是被root了
        cd /system/bin/ # 进入到/system/bin/ 目录下
        ls |grep su  #过滤当前目录下有没有su 可执行文件,发现有,就是被root了

我们手机已经root了,我们要不让app检测到我们root了
    -1 反编译apk,找到检测root的代码--》hook--》让它不执行
    -2 直接修改特征文件-->把这些关键位置的 su  --》改个名字
        -app搜索不到相关特征,他就认为我们没有root
                
改名字,一劳永逸
    - 把su --》su1--》以后获取root权限,就不是执行su ,而是执行 su1

当然改名字是最捞的方法可对,我们就要整其他的绕过检测方法

2.1 通用hook脚本绕过

github上有人开源了绕过的通用脚本,这里给出地址

GitHub - AshenOneYe/FridaAntiRootDetection: A frida script for bypass common root detection,the collection of detection methods is still improving!A frida script for bypass common root detection,the collection of detection methods is still improving! - AshenOneYe/FridaAntiRootDetectionicon-default.png?t=N7T8https://github.com/AshenOneYe/FridaAntiRootDetection 

这里我也贴出代码,方便大家使用

const commonPaths = [
    "/data/local/bin/su",
    "/data/local/su",
    "/data/local/xbin/su",
    "/dev/com.koushikdutta.superuser.daemon/",
    "/sbin/su",
    "/system/app/Superuser.apk",
    "/system/bin/failsafe/su",
    "/system/bin/su",
    "/su/bin/su",
    "/system/etc/init.d/99SuperSUDaemon",
    "/system/sd/xbin/su",
    "/system/xbin/busybox",
    "/system/xbin/daemonsu",
    "/system/xbin/su",
    "/system/sbin/su",
    "/vendor/bin/su",
    "/cache/su",
    "/data/su",
    "/dev/su",
    "/system/bin/.ext/su",
    "/system/usr/we-need-root/su",
    "/system/app/Kinguser.apk",
    "/data/adb/magisk",
    "/sbin/.magisk",
    "/cache/.disable_magisk",
    "/dev/.magisk.unblock",
    "/cache/magisk.log",
    "/data/adb/magisk.img",
    "/data/adb/magisk.db",
    "/data/adb/magisk_simple",
    "/init.magisk.rc",
    "/system/xbin/ku.sud",
    "/data/adb/ksu",
    "/data/adb/ksud"
];

const ROOTmanagementApp = [
    "com.noshufou.android.su",
    "com.noshufou.android.su.elite",
    "eu.chainfire.supersu",
    "com.koushikdutta.superuser",
    "com.thirdparty.superuser",
    "com.yellowes.su",
    "com.koushikdutta.rommanager",
    "com.koushikdutta.rommanager.license",
    "com.dimonvideo.luckypatcher",
    "com.chelpus.lackypatch",
    "com.ramdroid.appquarantine",
    "com.ramdroid.appquarantinepro",
    "com.topjohnwu.magisk",
    "me.weishu.kernelsu"
];



function stackTraceHere(isLog){
    var Exception = Java.use('java.lang.Exception');
    var Log = Java.use('android.util.Log');
    var stackinfo = Log.getStackTraceString(Exception.$new())
    if(isLog){
        console.log(stackinfo)
    }else{
        return stackinfo
    }
}

function stackTraceNativeHere(isLog){
    var backtrace = Thread.backtrace(this.context, Backtracer.ACCURATE)
    .map(DebugSymbol.fromAddress)
    .join("\n\t");
    console.log(backtrace)
}


function bypassJavaFileCheck(){
    var UnixFileSystem = Java.use("java.io.UnixFileSystem")
    UnixFileSystem.checkAccess.implementation = function(file,access){

        var stack = stackTraceHere(false)

        const filename = file.getAbsolutePath();

        if (filename.indexOf("magisk") >= 0) {
            console.log("Anti Root Detect - check file: " + filename)
            return false;
        }

        if (commonPaths.indexOf(filename) >= 0) {
            console.log("Anti Root Detect - check file: " + filename)
            return false;
        }

        return this.checkAccess(file,access)
    }
}

function bypassNativeFileCheck(){
    var fopen = Module.findExportByName("libc.so","fopen")
    Interceptor.attach(fopen,{
        onEnter:function(args){
            this.inputPath = args[0].readUtf8String()
        },
        onLeave:function(retval){
            if(retval.toInt32() != 0){
                if (commonPaths.indexOf(this.inputPath) >= 0) {
                    console.log("Anti Root Detect - fopen : " + this.inputPath)
                    retval.replace(ptr(0x0))
                }
            }
        }
    })

    var access = Module.findExportByName("libc.so","access")
    Interceptor.attach(access,{
        onEnter:function(args){
            this.inputPath = args[0].readUtf8String()
        },
        onLeave:function(retval){
            if(retval.toInt32()==0){
                if(commonPaths.indexOf(this.inputPath) >= 0){
                    console.log("Anti Root Detect - access : " + this.inputPath)
                    retval.replace(ptr(-1))
                }
            }
        }
    })
}

function setProp(){
    var Build = Java.use("android.os.Build")
    var TAGS = Build.class.getDeclaredField("TAGS")
    TAGS.setAccessible(true)
    TAGS.set(null,"release-keys")

    var FINGERPRINT = Build.class.getDeclaredField("FINGERPRINT")
    FINGERPRINT.setAccessible(true)
    FINGERPRINT.set(null,"google/crosshatch/crosshatch:10/QQ3A.200805.001/6578210:user/release-keys")

    // Build.deriveFingerprint.inplementation = function(){
    //     var ret = this.deriveFingerprint() //该函数无法通过反射调用
    //     console.log(ret)
    //     return ret
    // }

    var system_property_get = Module.findExportByName("libc.so", "__system_property_get")
    Interceptor.attach(system_property_get,{
        onEnter(args){
            this.key = args[0].readCString()
            this.ret = args[1]
        },
        onLeave(ret){
            if(this.key == "ro.build.fingerprint"){
                var tmp = "google/crosshatch/crosshatch:10/QQ3A.200805.001/6578210:user/release-keys"
                var p = Memory.allocUtf8String(tmp)
                Memory.copy(this.ret,p,tmp.length+1)
            }
        }
    })

}

//android.app.PackageManager
function bypassRootAppCheck(){
    var ApplicationPackageManager = Java.use("android.app.ApplicationPackageManager")
    ApplicationPackageManager.getPackageInfo.overload('java.lang.String', 'int').implementation = function(str,i){
        // console.log(str)
        if (ROOTmanagementApp.indexOf(str) >= 0) {
            console.log("Anti Root Detect - check package : " + str)
            str = "ashen.one.ye.not.found"
        }
        return this.getPackageInfo(str,i)
    }

    //shell pm check
}

function bypassShellCheck(){
    var String = Java.use('java.lang.String')

    var ProcessImpl = Java.use("java.lang.ProcessImpl")
    ProcessImpl.start.implementation = function(cmdarray,env,dir,redirects,redirectErrorStream){

        if(cmdarray[0] == "mount"){
            console.log("Anti Root Detect - Shell : " + cmdarray.toString())
            arguments[0] = Java.array('java.lang.String',[String.$new("")])
            return ProcessImpl.start.apply(this,arguments)
        }

        if(cmdarray[0] == "getprop"){
            console.log("Anti Root Detect - Shell : " + cmdarray.toString())
            const prop = [
                "ro.secure",
                "ro.debuggable"
            ];
            if(prop.indexOf(cmdarray[1]) >= 0){
                arguments[0] = Java.array('java.lang.String',[String.$new("")])
                return ProcessImpl.start.apply(this,arguments)
            }
        }

        if(cmdarray[0].indexOf("which") >= 0){
            const prop = [
                "su"
            ];
            if(prop.indexOf(cmdarray[1]) >= 0){
                console.log("Anti Root Detect - Shell : " + cmdarray.toString())
                arguments[0] = Java.array('java.lang.String',[String.$new("")])
                return ProcessImpl.start.apply(this,arguments)
            }
        }

        return ProcessImpl.start.apply(this,arguments)
    }
}


console.log("Attach")
bypassNativeFileCheck()
bypassJavaFileCheck()
setProp()
bypassRootAppCheck()
bypassShellCheck()

终端执行 frida -U -f com.yoloho.dayima -l 1-通用绕过root检测脚本.js 就ok了

执行好了就能顺利进入啦

2.2 Shamiko模块绕过

有的一些app会不光会检测是否root,还会检测是否安装了magisk,这个时候我们就需要借助shamiko模块进行隐藏magisk,这里给出网站

Release Shamiko v1.1 · LSPosed/LSPosed.github.io · GitHubicon-default.png?t=N7T8https://github.com/LSPosed/LSPosed.github.io/releases/tag/shamiko-344

我们安装好之后直接把下载好的zip推送到手机里(不要解压)

adb push 下载好的 /sdcard/Download/Shamiko.zip

放到这个目录比较好找,推送好之后,将zip刷入后重启

 进入之后选择你要绕过的app就好,那如何绕过magisk检测呢

我们点击上面的隐藏Magisk应用,改成你想要取的名字,这个时候桌面上就会生成那个app,此时那个才是真正的magisk,原,magisk就会失效

2.3 hook检测点

这个我不太想讲,但是因为学习,所以和大家说一下

我们反编译搜索关键字

找到后我们进入

 

发现这里就是判断是不是模拟器和是否root,我们hook这两个方法让他们返回false就好了

这里给出hook代码

import frida
import sys

rdev = frida.get_remote_device()
pid = rdev.spawn(["com.yoloho.dayima"])
session = rdev.attach(pid)

scr = """
Java.perform(function () {
    var MiscUtil = Java.use("com.yoloho.libcore.util.MiscUtil");
    MiscUtil.isRooted.implementation = function () {
        console.log('绕过')
        return false;
    }

    MiscUtil.isSimulator.implementation = function (ctx) {
        console.log('绕过')
        return false;
    }
});
"""


script = session.create_script(scr)


def on_message(message, data):
    print(message, data)


script.on("message", on_message)
script.load()
rdev.resume(pid)
sys.stdin.read()

2.4 aosp刷机绕过 

这里我们讲不来,得等到后面才能和大家讲 (其实我也不会)

什么是aosp?

Android是开源的,AOSP(Android Open Source Project)为Android开源项目的缩写,自己编译Android系统

例如鸿蒙就是使用了aosp源码,再改底层

而小米之前的miui则是没有修改源码 修改的是ui界面

后面会和大家说aosp的知识

三.抓包分析 

 

抓包发现,url是 https://uicapi.yoloho.com/user/login

需要破解的参数有 device,password,sign

四.破解device

我们搜索device,发现太多了,于是我们就搜索请求头里的其他参数,然后看看附近有没有device

那我们搜索releasever看看

搜索发现五个,但是我们选择第四个,因为第四个是interceptor,拦截器里面的,而且每个请求都有device,那我们点进去,所以大概率就是拦截器生成的

 

发现就是在这里,发现外面是对他进行url编码,所以生成的是在里面那个函数,我们再次点进去

 

发现核心是setDeviceCode,我们点进去

发现主要加密逻辑就是在这里,大概意思就是str6是几个字符串拼接来的,然后再通过sha1加密,最后转成字符串,那我们是不是就可以通过hook messageDigest.update查看里面参数,然后打印参数的字符串形式,就知道str6是多少了,但是问题就是这个方法在很多地方都有打印,所以我们需要同时hook里面的大方法setDeviceCode,打印一下标记,在这个下面的不就是我们要的嘛,这里给出hook代码


Java.perform(function () {


    var PeriodAPIV2 = Java.use("com.yoloho.controller.api.PeriodAPIV2");
    var flag = false;

    PeriodAPIV2.setDeviceCode.implementation = function () {
        console.log("-------------------------setDeviceCode-------------------------")
        flag = true;
        return this.setDeviceCode();
    }

    var MessageDigest = Java.use("java.security.MessageDigest");
    var ByteString = Java.use("com.android.okhttp.okio.ByteString");
    // update是一个重载方法,所以要用overload
    MessageDigest.update.overload("[B").implementation = function (data) {
        if (flag) {
            console.log(ByteString.of(data).utf8(), '\n' );

        }
        return this.update(data);
    }




});
// frida -U -f  com.yoloho.dayima -l hook_str6.js

 

发现这个就是了,前面那个三个看就是包名,所以肯定是第三个了,而且发现,还没有登录就生成了,所以说肯定是在刚开始就生成,接下来每次请求都一样

null 设备id号

2d1fc41fe54b945 安卓id 可以随机生成

cbluelinegooglearm64-v8abluelineRQ3A.211001.001abfarm-01362RQ3A.211001.001GooglePixel 3bluelinerelease-keysuserandroid-build 设备信息

3A:38:93:AD:E6:12 wifi的mac地址 可以伪造

02:00:00:00:00:00 蓝牙的mac地址 可以伪造

然后对这个拼接今昔sha1加密,这种一看就是标准库,所以肯定是标准的,大家也可以去验证一下

五.破解password 

我们搜索 "password"

 

我们搜索到了四个,其中第二个是最像的,因为他的包名翻译就是登录,我们点进去

 小惊喜,我们不光看到了password还看到了sign,一看就知道sign是把这些加在一起进行加密的,那我们就先看password

可以看到password传入了两个参数,一个是账号,一个是密码明文,我们点击进去

可以看到这个加密逻辑不就是aes嘛

其中: 待加密字符串是密码,key是账号进行md5加密取前16位字符串,iv则是固定字符串"yoloho_dayima!%_"

再点进去,发现返回的是base64的,不正是我们抓包所抓到的嘛

大家可以去hook一下判断和抓包的是否一样,我这里就不带大家hook了,我这里去看看md5里有没有加盐

 

发现只update了一次,没有加盐,至此password破解完成

六.破解sign 

sign的位置刚才已经找到,这里我们退到之前的位置

 

sb拼接了:getDeviceCode 上节课破解的device
sb.append(PeriodAPIV2.getInstance().getDeviceCode());
sb拼接了 user/login
sb.append("user/login");
sb拼接了 手机号
sb.append(str);
sb拼接了 加密后的密码
sb.append(privateStrHandle);
把sb这个字符串--》调用encrypt_data 进行加密生成了 sign
arrayList.add(new BasicNameValuePair("sign", Crypt.encrypt_data(0L, sb.toString(), sb.length()))); 

第一个参数是0,第二个参数是sb,第三个参数是sb的长度 

我们点进去,看看

 

发现是个jni方法,在libCrypt.so里面加密(记住这个,后面都要用得到),这里进去非常难读,今天我们介绍个新的知识点,frida-rpc,就通过这个sign来教大家使用,因为这个是今天的重点,所以我单独开一页教大家

七.frida-rpc

什么是frida-rpc?

frida 提供了一种跨平台的 rpc (远程过程调用)机制,通过 frida rpc 可以在主机和目标设备之间进行通信,并在目标设备上执行代码
Frida-RPC 是一个基于 Frida 框架的扩展,用于在不同进程之间进行远程过程调用(RPC)。Frida 是一个功能强大的动态插桩工具,它允许开发人员在运行时监控、修改和控制应用程序的行为。通过使用 Frida-RPC,您可以在不同的进程之间建立通信通道,使得您能够从一个进程中调用另一个进程的函数或方法,就好像它们都在同一个进程中一样

这有一张图方便大家理解

 

使用流程

流程
1 手机端启动 frida-server  # 启动了
2 运行app                 # 手机端要运行app--》手机的内存中,存在 encrpyt_data函数
3 电脑端端口转发,运行脚本   # 端口转发
4 电脑端写脚本,远程调用 

import frida

rdev = frida.get_remote_device()
session = rdev.attach("大姨妈")

scr = """
rpc.exports = {   

    //这里可以放很多键值,但是注意,名字任意 但不要带符号那些
    encryptdata:function(j2,str,j3){  //写传入的参数
         var res;
         Java.perform(function () {  //固定写法
            // 包.类
            var Crypt = Java.use("com.yoloho.libcore.util.Crypt"); //导入包和类
            // 类中的方法
            res = Crypt.encrypt_data(j2,str,j3); //执行类中的方法
         });

         return res;
    }
}
"""

script = session.create_script(scr)
script.load()

# python 调用
# frida低版本使用
# sign = script.exports_sync.aa(0, "bcae572b84d20c385d6d9d2d7d9e645da29da3c0user/login18953675221WA89qByLlDeaGjmVNzXm/w==", 85)

# frida高版本使用
sign = script.exports.encryptdata(0, "bcae572b84d20c385d6d9d2d7d9e645da29da3c0user/login18953675221WA89qByLlDeaGjmVNzXm/w==", 85)
print(sign)

运行结果

 

八.编写app调用so

这里我截图教大家一步步做,首先我们进入AndroidStudio

新建一个这个项目,然后就下一步选择项目就行了,语言选择java

 看到这个页面就算好了,然后我们刚刚之前看到jni 是libCrypto.so,那我们就去找到这个so文件

把这两个包中的so文件删除到只剩 libCrypto.so,然后把这两个文件夹复制到项目的lib下面

然后再在 build.gradle里的android里面加上

sourceSets {
            main {
                jniLibs.srcDirs = ['libs']
            }
        }

 然后创建包 这个包名和你要调用的包名一致

 

然后创建一个类也要叫要调用地方的类名,然后再把这个代码c进去

再给主页面的的文字加上一个id 就叫sign 

 

 再在这个位置加上这些代码

TextView tv=findViewById(R.id.sign);
        String str="64e6176e45397c5989504e76f98ecf2e63b2679euser/login15131255555A4rE0CKaCsUMlKpHjtL0AQ==";
        String sign = Crypt.encrypt_data(0, str, str.length());
        Log.e("sign=", sign);
        tv.setText(sign);

大功告成,然后运行

 

大功告成

九.总结 

今天知识点有点多,主要是给大家说了一下绕过root检测,frida-rpc和安卓编写调用so,这个其实后期用的不多,主要是做个了解,后期遇到难读的so我们会用unidbg,还差两个知识点就能讲到了,讲完那个我们的基础课程也算是结束了,大家就到了app逆向入门了,好了,到时候再说吧,晚安!

补充

有需要源码的看我主页签名名字私信我,有求必应

 

 

 

 

 

 

 

 

 

;