【Android逆向】反调试绕过(nop 绕过)

android,逆向,调试,绕过,nop · 浏览次数 : 309

小编点评

**代码分析:** ```java // 打开动态链接库 hook_dlopen("libnative-lib.so") // 启动应用程序 Java.perform(new Runnable() { @Override public void run() { // 设置应用程序启动类 Class cls = Java.use("com.r0ysue.test1.MainActivity"); // 设置应用程序启动方法 cls.mystr.implementation = new MyStrImplement(); } }); // 监听字符串事件 strStr.class.addStrListener(new StrListener() { @Override public void onStr(Str str) { // 检查字符串包含 "frida" 或 "re.frida.server" if (strStr.contains("frida") || strStr.contains("re.frida.server")) { // 设置标志,表示字符串包含frida flag_frida = true; } else if (strStr.contains("re.frida.server")) { // 设置标志,表示字符串包含re.frida.server flag_reFrida = true; } // 检查标志,打印日志信息 if (flag_frida) { System.out.println("[*]onEnter strstr hooked \"+str+\",\"+strStr+\")\"; } else if (flag_reFrida) { System.out.println("[*]onEnter re.frida.server hooked \"+str+\",\"+strStr+\")\"; } // 处理字符串事件 str.trim(); } }); ``` **主要逻辑:** 1. 打开动态链接库。 2. 启动应用程序并设置启动类和启动方法。 3. 监听字符串事件。 4. 当字符串包含 "frida" 或 "re.frida.server" 时,设置标志,表示字符串包含frida或re.frida.server。 5. 当字符串包含 "re.frida.server" 时,设置标志,表示字符串包含re.frida.server。 6. 打印日志信息,显示字符串包含frida或re.frida.server的日志信息。 7. 处理字符串事件,并将其处理。 **注意:** * 代码需要在运行应用程序之前加载动态链接库。 * 代码中没有检查字符串包含其他关键字的日志信息。 * 代码中没有展示如何处理字符串事件的具体逻辑。

正文

1. 这是看雪上的一个题目,要求显示出 it is success

https://www.kanxue.com/work-task_read-800648.htm
第三题

2. apk 安装到手机,发现闪退

3. apk拖入到jadx中,观察

public class MainActivity extends AppCompatActivity {
    public native String stringFromJNI();

    static {
        System.loadLibrary("native-lib");
    }

    /* JADX INFO: Access modifiers changed from: protected */
    @Override // androidx.appcompat.app.AppCompatActivity, androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView tv = (TextView) findViewById(R.id.sample_text);
        stringFromJNI();
        if (mystr()) {
            tv.setText("it is success");
        }
    }

    public boolean mystr() {
        return false;
    }
}

4. java层平平无奇,看来答案在native层

5. so拖入到IDA中进行分析,导出表中直接就有JNI_OnLoad 和静态注册对应的函数,没有init_array段,那么猜测反调试代码应该在JNI_OnLoad中,查看JNI_OnLoad

jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
  __int64 v2; // x0
  __int64 v3; // x0
  void *arg; // [xsp+18h] [xbp-38h]
  __int64 v6; // [xsp+20h] [xbp-30h]
  pthread_t newthread; // [xsp+38h] [xbp-18h] BYREF
  __int64 v8[2]; // [xsp+40h] [xbp-10h] BYREF

  v8[1] = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
  sub_109B8(vm, v8, 65540LL);
  v2 = sub_10184();                             // 这里在检查maps 中的frida agent
  v3 = sub_102BC(v2);                           // 检查是否存在 /data/local/tmp/re.frida.server
  sub_10310(v3);                                // 检查frida监听端口27042
  v6 = sub_109F8(v8[0], &unk_36030);
  arg = (void *)sub_10A30(v8[0], v6, &unk_3604F, &unk_36055);
  pthread_create(&newthread, 0LL, (void *(*)(void *))sub_10448, arg);
  return 65540;
}

查看sub_10184,可以看到在检查内存中是否存在frida字符串,frdia工作时会把frida_agent加载进进程,会有一些特征字符串,这里就在搞这个事情

__int64 sub_10184()
{
  __int64 result; // x0
  FILE *v1; // [xsp+10h] [xbp-470h]
  char v2[1024]; // [xsp+68h] [xbp-418h] BYREF
  __int64 v3; // [xsp+468h] [xbp-18h]

  v3 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
  v1 = fopen("/proc/self/maps", "r");
  while ( 1 )
  {
    result = __fgets_chk(v2, 1024LL, v1, 1024LL);
    if ( !result )
      break;
    if ( strstr(v2, "frida") )
      *(int *)((char *)&dword_0 + 1) = 45465456;
  }
  _ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2));
  return result;
}

查看sub_102BC,这里再检查/data/local/tmp/re.frida.server 目录是否存在,frida_server启动会默认创建这个目录

__int64 sub_102BC()
{
  __int64 result; // x0

  result = access("/data/local/tmp/re.frida.server", 0);
  if ( (int)result >= 0 )
    *(int *)((char *)&dword_0 + 1) = 45465456;
  return result;
}

查看sub_10310,这里再查看是否有再监听0x69A2(27042)frida的默认端口

// write access to const memory has been detected, the output may be wrong!
__int64 sub_10310()
{
  __int64 result; // x0
  FILE *v1; // [xsp+10h] [xbp-470h]
  char v2[1024]; // [xsp+68h] [xbp-418h] BYREF
  __int64 v3; // [xsp+468h] [xbp-18h]

  v3 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
  v1 = fopen("/proc/net/tcp", "r");
  while ( 1 )
  {
    result = __fgets_chk(v2, 1024LL, v1, 1024LL);
    if ( !result )
      break;
    if ( strstr(v2, ":69A2") )
      *(int *)((char *)&dword_0 + 1) = 45465456;
  }
  _ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2));
  return result;
}

然后创建了一个线程去执行sub_10448,查看一下,这里时检查,这里时对入参进行检查,查询JAVA层的方法是否有native话,特征就是(~*(_DWORD *)(a1 + 4) & 0x80000) == 0),推测是检查是否有hook java层的方法,有的话就干掉进程

void __fastcall __noreturn sub_10448(__int64 a1)
{
  while ( 1 )
  {
    while ( (~*(_DWORD *)(a1 + 4) & 0x80000) == 0 )
      ;
    *(int *)((char *)&dword_0 + 1) = 45465456;
  }
}

3. 再看Java_com_r0ysue_test1_MainActivity_stringFromJNI

__int64 __fastcall Java_com_r0ysue_test1_MainActivity_stringFromJNI(JNIEnv *env)
{
  __int64 v2; // [xsp+0h] [xbp-560h]
  __int64 v3; // [xsp+8h] [xbp-558h]
  const char *v4; // [xsp+28h] [xbp-538h]
  const char *v5; // [xsp+40h] [xbp-520h]
  const char *nptr; // [xsp+58h] [xbp-508h]
  FILE *v7; // [xsp+98h] [xbp-4C8h]
  int v8; // [xsp+A4h] [xbp-4BCh]
  unsigned __int64 v9; // [xsp+B0h] [xbp-4B0h]
  unsigned int pthread_create_sym; // [xsp+CCh] [xbp-494h]
  char v12[24]; // [xsp+130h] [xbp-430h] BYREF
  char v13[1024]; // [xsp+148h] [xbp-418h] BYREF
  __int64 v14; // [xsp+548h] [xbp-18h]

  v14 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
  sub_FFF4(v12, "Hello from C++");
  pthread_create_sym = sub_F690("/system/lib64/libc.so", "pthread_create");
  __android_log_print(6, "r0ysue", "%x", pthread_create_sym);
  v8 = 1;
  v7 = fopen("/proc/self/maps", "r");
  while ( __fgets_chk(v13, 1024LL, v7, 1024LL) )
  {
    if ( strstr(v13, "libc.so") )
    {
      if ( v8 == 2 )
      {
        nptr = strtok(v13, "-");
        v9 = strtoul(nptr, 0LL, 16);
        v5 = strtok(0LL, " ");
        strtoul(v5, 0LL, 16);
      }
      else
      {
        strtok(v13, "-");
        v4 = strtok(0LL, " ");
        strtoul(v4, 0LL, 16);
      }
      ++v8;
    }
  }
  sub_1004C(pthread_create_sym, v9);
  v3 = sub_100EC(v12);
  v2 = sub_100B4(env, v3);
  sub_10110(v12);
  _ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2));
  return v2;
}

重点是sub_1004C,其中0xD61F020058000050LL 是一段frida hook native的特征码,这里应该是在检查是否有hook pthread_create 函数

const char *__fastcall sub_1004C(int a1, __int64 a2)
{
  if ( *(_QWORD *)(a2 + a1) == 0xD61F020058000050LL )
    *(int *)((char *)&dword_0 + 1) = 45465456;
  return "Hello from C++";
}

inlinehook 检查内容参考博客
https://www.52pojie.cn/thread-1530251-1-1.html

由此可编写frida脚本

function hook_dlopen(soName) {
    Interceptor.attach(Module.findExportByName(null, "dlopen"),
        {
            onEnter: function (args) {
                var pathptr = args[0];
                if (pathptr !== undefined && pathptr != null) {
                    var path = ptr(pathptr).readCString();
                    if (path.indexOf(soName) >= 0) {
                        this.is_can_hook = true;
                    }
                }
            },
            onLeave: function (retval) {
                if (this.is_can_hook) {
                    console.log("hook start...");
                    hook_func()
                }
            }
        }
    );
 
    Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"),
        {
            onEnter: function (args) {
                var pathptr = args[0];
                if (pathptr !== undefined && pathptr != null) {
                    var path = ptr(pathptr).readCString();
                    if (path.indexOf(soName) >= 0) {
                        this.is_can_hook = true;
                    }
                }
            },
            onLeave: function (retval) {
                if (this.is_can_hook) {
                    console.log("hook start...");
                    hook_func()
                }
            }
        }
    );
}

function hook_func() {
    //把启动线程检查的地方nop掉
    var lib_handler = Process.findModuleByName("libnative-lib.so")
    //0x10984 pthread create 那一行
    var dst_addr = new NativePointer(lib_handler.base.add(0x10984))
    Memory.patchCode(dst_addr, 4, function (code) {
        var cw = new Arm64Writer(code, { pc: dst_addr });
        cw.putNop()
        cw.flush();
    });


    var strStr = Module.findExportByName("libc.so", "strstr");
    console.log("[*] strstr addr: " + strStr);
    if (strStr) {
        Interceptor.attach(strStr, {
            onEnter: function(args) {
                //console.log("[*] strstr hooked")
                var arg0= ptr(args[0]).readCString();
                var arg1= ptr(args[1]).readCString();
                if(arg1.indexOf(":69A2")>=0){
                    console.log("[*]onEnter strstr hooked "+arg0+","+arg1+")");
                    this.flag_69A2=true
                }

                if(arg1.indexOf("frida")>=0){
                    console.log("[*]onEnter strstr hooked "+arg0+","+arg1+")");
                    this.flag_frida=true
                }

                
            },
            onLeave: function(retval) {
                if(this.flag_69A2){
                    console.log("[*]onLeave flag_69A2 hooked "+retval);
                    retval.replace(0x0);
                    this.flag_69A2 = false
                }

                if(this.flag_frida){
                    console.log("[*]onLeave flag_frida hooked "+retval);
                    retval.replace(0x0);
                    this.flag_frida = false
                }
            }
        });
    }

    var access = Module.findExportByName("libc.so", "access");
    console.log("[*] access addr: " + access);

    if (access) {
        Interceptor.attach(access, {
            onEnter: function(args) {
                //console.log("[*] access hooked")
                var arg0= ptr(args[0]).readCString();
                var arg1= args[1];
                if(arg0.indexOf("re.frida.server")>=0){
                    console.log("[*]onEnter re.frida.server hooked"+arg0+","+arg1+")");
                    this.flag_reFrida=true
                }

                
            },
            onLeave: function(retval) {

                if(this.flag_reFrida){
                    console.log("[*]onLeave re.frida.server access hooked"+retval);
                    retval.replace(-1);
                    this.flag_reFrida = false
                }
            }
        });      
    }


}


function main() {
    hook_dlopen("libnative-lib.so")

    Java.perform(function () {
        var cls = Java.use("com.r0ysue.test1.MainActivity")
        cls.mystr.implementation = function () {
            console.log("call MainActivity mystr")
            return true
        }
    })
}





// 不能延时启动,否则线程已经起来了
setImmediate(main)

frida -U -f com.r0ysue.test1 -l lesson12.js --no-pause

日志
......
[*]onEnter strstr hooked    6: 0F01A8C0:BD0B 2B1D467C:1B5C 01 00000000:00000000 00:00000000 00000000 10112        0 2123256 1 0000000000000000 24 4 30 10 -1                  
,:69A2)
[*]onLeave flag_69A2 hooked 0x0
[*]onEnter strstr hooked    7: 0100007F:B167 0100007F:69A2 01 00000000:00000000 00:00000000 00000000  2000        0 2125983 1 0000000000000000 21 4 30 10 16                  
,:69A2)
[*]onLeave flag_69A2 hooked 0x7fca407404
[*]onEnter strstr hooked    8: 0100007F:C105 0100007F:69A2 06 00000000:00000000 03:000013C9 00000000     0        0 0 3 0000000000000000                                      
,:69A2)
[*]onLeave flag_69A2 hooked 0x7fca407404
[*]onEnter strstr hooked    9: 0100007F:69A2 0100007F:B167 01 00000000:00000000 00:00000000 00000000     0        0 2124098 1 0000000000000000 21 4 31 10 16                  
,:69A2)
[*]onLeave flag_69A2 hooked 0x7fca4073f6
[*]onEnter strstr hooked   10: 0100007F:A63D 0100007F:9933 01 00000000:000004A0 00:00000000 00000000  2000        0 2123640 2 0000000000000000 21 4 0 10 16                   
,:69A2)
[*]onLeave flag_69A2 hooked 0x0

脚本原理

  1. hook strstr 方法,对传入的字符串进行检查,凡是和frida相关的都重置返回值绕过
  2. hook access ,不检查目的目录
  3. 把启动线程检查的地方nop掉
  4. hook java层方法
结果:成功显示it is success

与【Android逆向】反调试绕过(nop 绕过)相似的内容:

【Android逆向】反调试绕过(nop 绕过)

1. 这是看雪上的一个题目,要求显示出 it is success https://www.kanxue.com/work-task_read-800648.htm 第三题 2. apk 安装到手机,发现闪退 3. apk拖入到jadx中,观察 public class MainActivity e

【Android逆向】反调试绕过

1. 拿到52pojie的反调试挑战apk 链接: https://www.52pojie.cn/thread-742686-1-1.html 的附件中 2. 项目进行安装,点开app,同时挑战成功,不慌 3. 使用IDA attach到目的进程观察,发现app立刻闪退,证明app必然存在反调试逻辑

【Android逆向】IDA动态调试JNI_OnLoad 和 .init_array

由于 JNI_OnLoad 和 .init_array 执行时间很早,so一加载到内存中就执行了,所以动态调试步骤会稍微要麻烦一些 1. 进入手机, 执行./android_server (如果是64位程序执行./android_server64) 2. 再开一个终端,执行adb forward t

【Android 逆向】VM Kali 中 charles 抓android https 协议

1. 虚拟机调成桥接模式(不用选择 复制物理网络链接状态) 2. 虚拟机中 打开 Charles 4. 选择 Proxy ->SSL Proxying Settings 1. 选择SSL Proxying 菜单 2. 勾选Enable SSL Proxying 3. 在include 中选择Add

【Android 逆向】【ARM汇编】 函数的栈帧

1. 函数的调用约定 ARM32 参数1-4 放入r0-r3 剩下的入栈,函数返回值放入r0 ARM64 参数1-8 放入X0-X7 剩下的入栈,函数返回值放入X0 (浮点数是放入 Dn 或 Sn) 2. ARM 指令的 堆栈平衡 var_4 = -4 ; IDA 生成的变量信息,方便阅读用 STM

【Android逆向】frida 破解 jwxdxnx02.apk

apk 路径: https://pan.baidu.com/s/1cUInoi 密码:07p9 这题比较简单,主要是用于练习frida 1. 安装apk到手机 需要输入账号密码 2. 使用jdax 查看apk package hfdcxy.com.myapplication; import andr

[Android逆向]Exposed 破解 jwxdxnx02.apk

使用exposed 遇到了一些坑,这里记录一下 源码: package com.example.exposedlesson01; import de.robv.android.xposed.IXposedHookLoadPackage; import de.robv.android.xposed.X

[Android 逆向]frida 破解 切水果大战原版.apk

1. 手机安装该apk,运行,点击右上角礼物 提示 支付失败,请稍后重试 2. apk拖入到jadx中,待加载完毕后,搜素失败,找到疑似目标类MymmPay的关键方法payResultFalse 4. adb logcat 或者androidstudio 查看该进程的日志,发现以下日志 com.mf

[Android 逆向]Xposed 破解 切水果大战原版.apk

代码 public class Main implements IXposedHookLoadPackage { boolean flag = false; @Override public void handleLoadPackage(XC_LoadPackage.LoadPackageParam

【Android逆向】frida 破解 滚动的天空

1. apk 安装到手机中 2. 玩十次之后,会提示 充值 3. adb shell dumpsys window | grep mCurrentFocus 查看一些当前activity是哪一个 是 AppActivity 4. 阅读代码,感觉是unity3d做的游戏 5. apk拖入到jadx中,