【Android逆向】反调试绕过

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

小编点评

**Code Summary:** The code you provided is a Java program that dynamically debugs and signs an APK file. It achieves this by using the following steps: 1. **Get the process ID (PID)** of the APK file. 2. **Open a file descriptor for the APK file** and read its contents. 3. **Iterate over the lines in the APK file** and print the line number and content. 4. **Check if the line contains a specific string** (presumably the process ID). 5. **If the line contains the string, exit the program** with an exit code of 0. 6. **Repeat step 3 until the end of the file is reached.** 7. **Close the file descriptor** and sleep for a short period. 8. **Repeat steps 1-7** for the signed APK file. **Troubleshooting:** * The code requires the following dependencies: * APKtool_2.2.4 * signapk * Android Killer * Ensure that the APK file is in the correct directory relative to the executable. * The `testkey.x509.pem` and `testkey.pk8` files should be present in the same directory as the APK file. * The `anti-debug-new.apk` file should be generated using the `buildAppSignature` method. * To manually patch the APK, use the following command: ``` androidkiller -r anti-debug-new.apk anti-debug-signed.apk7 ``` **Output:** The code will print the line number and content of each line in the APK file, excluding the line containing the process ID. **Note:** The code assumes that the APK file is a legitimate Android application. If it is not signed with a valid certificate, debugging may not work as expected.

正文

1. 拿到52pojie的反调试挑战apk

链接: https://www.52pojie.cn/thread-742686-1-1.html 的附件中

2. 项目进行安装,点开app,同时挑战成功,不慌

3. 使用IDA attach到目的进程观察,发现app立刻闪退,证明app必然存在反调试逻辑

4. apk拖入到JEB中观察到,有调用到一个native函数

public class MainActivity extends AppCompatActivity {
    @Override  // android.support.v7.app.AppCompatActivity
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.setContentView(0x7F04001B);  // layout:activity_main
        Toast.makeText(this, myJNI.checkport(), 0).show();
    }
}


public class myJNI {
    static {
        System.loadLibrary("six");
    }

    public static native String checkport() {
    }
}

那么基本定位到应该是在so层做了这些处理

5. 将apk解压开拿到libsix.so,并通过IDA进行反汇编分析

1. 在导出表中搜索checkport,发现没有,说明是动态注册的
2. 搜索JNI_OnLoad, F5查看反编译代码
jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
  JNIEnv *env; // [sp+4h] [bp-14h] BYREF

  _android_log_print(4, "JNI_LOG", byte_C860B588);
  env = 0;
  if ( !(*vm)->GetEnv(vm, (void **)&env, 65540) )
  {
    dword_C860D004 = (int)(*env)->FindClass(env, "demo2/jni/com/myapplication/myJNI");
    SearchObjProcess();
    if ( dword_C860D004 )
    {
      if ( (*env)->RegisterNatives(env, (jclass)dword_C860D004, (const JNINativeMethod *)off_C860CE44, 1) >= 0 )
      {
        printf("--------JNI_OnLoad-----");
        return 65540;
      }
      puts("register native method failed!");
    }
    else
    {
      printf("cannot get class:%s\n", "demo2/jni/com/myapplication/myJNI");
    }
  }
  return -1;
}

说明注册函数表在off_C860CE44中,双击进去

.data.rel.ro.local:C860CE44                               ; ===========================================================================
.data.rel.ro.local:C860CE44
.data.rel.ro.local:C860CE44                               ; Segment type: Pure data
.data.rel.ro.local:C860CE44                               AREA .data.rel.ro.local, DATA
.data.rel.ro.local:C860CE44                               ; ORG 0xC860CE44
.data.rel.ro.local:C860CE44 0D B6 60 C8                   off_C860CE44 DCD aCheckport             ; DATA XREF: JNI_OnLoad+50↑o
.data.rel.ro.local:C860CE44                                                                       ; JNI_OnLoad+54↑o
.data.rel.ro.local:C860CE44                                                                       ; .text:off_C860A4FC↑o
.data.rel.ro.local:C860CE44                                                                       ; "checkport"
.data.rel.ro.local:C860CE48 17 B6 60 C8                   DCD aLjavaLangStrin                     ; "()Ljava/lang/String;"
.data.rel.ro.local:C860CE4C 75 A1 60 C8                   DCD sub_C860A174+1
.data.rel.ro.local:C860CE4C                               ; .data.rel.ro.local ends

果然代码在.data.rel.ro.local段中,这里描述的是java方法名,java方法签名,和对应的native方法,双击 sub_C860A174 进入

FILE *__fastcall sub_C860A174(JNIEnv *env)
{
  FILE *result; // r0
  FILE *v3; // r5
  char v4[4104]; // [sp+4h] [bp+0h] BYREF

  _android_log_print(4, "JNI_LOG", byte_C860B494);
  memset(v4, 0, 0x1000u);
  result = popen("cat /proc/net/tcp |grep :5D8A", "r");
  v3 = result;
  if ( result )
  {
    if ( fgets(v4, 4096, result) )
      exit(0);
    pclose(v3);
    return (FILE *)(*env)->NewStringUTF(env, "恭喜你,挑战成功!");
  }
  return result;
}

从这里可以看到,这个方法里在检查监听端口23946(十六进制5D8A),打开汇编代码,把这里nop掉
(Edit -> Patch program -> Change byte)

.text:C860A174                               CODE16
.text:C860A174
.text:C860A174                               ; =============== S U B R O U T I N E =======================================
.text:C860A174
.text:C860A174                               ; Attributes: bp-based frame fpd=0x1008
.text:C860A174
.text:C860A174                               ; FILE *__fastcall sub_C860A174(JNIEnv *env)
.text:C860A174                               sub_C860A174                            ; DATA XREF: .data.rel.ro.local:C860CE4C↓o
.text:C860A174
.text:C860A174                               var_C= -0xC
.text:C860A174
.text:C860A174                               ; __unwind { // dword_C8609000
.text:C860A174 F0 B5                         PUSH            {R4-R7,LR}
.text:C860A176 AD F5 80 5D                   SUB.W           SP, SP, #0x1000
.text:C860A17A 21 4C                         LDR             R4, =(__stack_chk_guard_ptr - 0xC860A188)
.text:C860A17C 83 B0                         SUB             SP, SP, #0xC
.text:C860A17E 0D F5 80 52                   ADD.W           R2, SP, #0x100C+var_C
.text:C860A182 20 49                         LDR             R1, =(aJniLog - 0xC860A190) ; "JNI_LOG"
......
.text:C860A1CA FF F7 16 EF                   BLX             exit ; 把这里nop掉
.text:C860A1CA
.text:C860A1CE                               ; ---------------------------------------------------------------------------
......

.text:C860A1F6
.text:C860A1F6                               loc_C860A1F6                            ; CODE XREF: sub_C860A174+7C↑j
.text:C860A1F6 0D F5 80 5D                   ADD.W           SP, SP, #0x1000
.text:C860A1FA 03 B0                         ADD             SP, SP, #0xC
.text:C860A1FC F0 BD                         POP             {R4-R7,PC}
.text:C860A1FC
.text:C860A1FC                               ; End of function sub_C860A174
3. JNI_OnLoad里还有一个可以的方法 SearchObjProcess(); 点进去看看
FILE *SearchObjProcess()
{
  FILE *result; // r0
  FILE *v1; // r5
  int v2; // r11
  int v3; // r10
  int v4; // r9
  unsigned __int8 v5; // r0
  char *v6; // [sp+4h] [bp-103Ch]
  int v7; // [sp+8h] [bp-1038h]
  char v8[4140]; // [sp+14h] [bp-102Ch] BYREF

  memset(v8, 0, 0x1000u);
  result = popen("ps", "r");
  v1 = result;
  if ( result )
  {
    while ( fgets(v8, 4096, v1) )
    {
      _android_log_print(4, "JNI_LOG", byte_C860B544, v8);
      v6 = strstr(v8, "android_server");
      v7 = (unsigned __int8)strstr(v8, "gdbserver");
      v2 = (unsigned __int8)strstr(v8, "gdb");
      v3 = (unsigned __int8)strstr(v8, "fuwu");
      v4 = (unsigned __int8)strstr(v8, "android_ser");
      v5 = (unsigned __int8)strstr(v8, "and_");
      if ( v6 || v7 || v2 || v3 || v4 || v5 )
        exit(0);
    }
    return (FILE *)pclose(v1);
  }
  return result;
}

这里可以看到就是在对进程名关键字进行检查,同上直接把exit给nop掉

4. ctrl+s 看看段列表情况,发现有一个.init_array段,有这个段说明里面有东西,进去看看,有可能有反调试的代码
.init_array:C860CE58                               ; ELF Initialization Function Table
.init_array:C860CE58                               ; ===========================================================================
.init_array:C860CE58
.init_array:C860CE58                               ; Segment type: Pure data
.init_array:C860CE58                               AREA .init_array, DATA
.init_array:C860CE58                               ; ORG 0xC860CE58
.init_array:C860CE58 ED A0 60 C8                   DCD thread_create+1
.init_array:C860CE5C 00 00 00 00                   ALIGN 0x10
.init_array:C860CE5C                               ; .init_array ends
.init_array:C860CE5C

里面有个thread_create函数,看来是有情况,跟进去看看

.text:C860A0EC                               ; int thread_create()
.text:C860A0EC                               EXPORT thread_create
.text:C860A0EC                               thread_create                           ; DATA XREF: .init_array:C860CE58↓o
.text:C860A0EC                               ; __unwind { // dword_C8609000
.text:C860A0EC 10 B5                         PUSH            {R4,LR}
.text:C860A0EE 04 20                         MOVS            R0, #4                  ; prio
.text:C860A0F0 0E 4C                         LDR             R4, =(aJniLog - 0xC860A0F8) ; "JNI_LOG"
.text:C860A0F2 0F 4A                         LDR             R2, =(aEnterThreadCre - 0xC860A0FA) ; "enter thread_create"
.text:C860A0F4 7C 44                         ADD             R4, PC                  ; "JNI_LOG"
.text:C860A0F6 7A 44                         ADD             R2, PC                  ; "enter thread_create"
.text:C860A0F8 21 46                         MOV             R1, R4                  ; tag
.text:C860A0FA FF F7 66 EF                   BLX             __android_log_print
.text:C860A0FA
.text:C860A0FE 0D 48                         LDR             R0, =(t0_ptr - 0xC860A108)
.text:C860A100 0D 4A                         LDR             R2, =(thread_function_ptr - 0xC860A10E)
.text:C860A102 00 21                         MOVS            R1, #0                  ; attr
.text:C860A104 78 44                         ADD             R0, PC                  ; t0_ptr
.text:C860A106 00 68                         LDR             R0, [R0]                ; t0 ; newthread
.text:C860A108 0B 46                         MOV             R3, R1                  ; arg
.text:C860A10A 7A 44                         ADD             R2, PC                  ; thread_function_ptr
.text:C860A10C 12 68                         LDR             R2, [R2]                ; thread_function ; start_routine
.text:C860A10E FF F7 86 EF                   BLX             pthread_create
.text:C860A10E
.text:C860A112 01 30                         ADDS            R0, #1
.text:C860A114 08 D1                         BNE             locret_C860A128
.text:C860A114
.text:C860A116 09 4A                         LDR             R2, =(aFailToCreatePt - 0xC860A120) ; "fail to create pthread t0"
.text:C860A118 04 20                         MOVS            R0, #4                  ; prio
.text:C860A11A 21 46                         MOV             R1, R4                  ; tag
.text:C860A11C 7A 44                         ADD             R2, PC                  ; "fail to create pthread t0"
.text:C860A11E FF F7 54 EF                   BLX             __android_log_print
.text:C860A11E
.text:C860A122 01 20                         MOVS            R0, #1                  ; int
.text:C860A124 FF F7 68 EF                   BLX             exit
.text:C860A124
.text:C860A128                               ; ---------------------------------------------------------------------------
.text:C860A128
.text:C860A128                               locret_C860A128                         ; CODE XREF: thread_create+28↑j
.text:C860A128 10 BD                         POP             {R4,PC}
.text:C860A128
.text:C860A128                               ; End of function thread_create
.text:C860A128
.text:C860A128                               ; ---------------------------------------------------------------------------
.text:C860A12A 00 BF                         ALIGN 4
.text:C860A12C 94 13 00 00                   off_C860A12C DCD aJniLog - 0xC860A0F8   ; DATA XREF: thread_create+4↑r
.text:C860A12C                                                                       ; "JNI_LOG"
.text:C860A130 E8 13 00 00                   off_C860A130 DCD aEnterThreadCre - 0xC860A0FA
.text:C860A130                                                                       ; DATA XREF: thread_create+6↑r
.text:C860A130                                                                       ; "enter thread_create"
.text:C860A134 64 2E 00 00                   off_C860A134 DCD t0_ptr - 0xC860A108    ; DATA XREF: thread_create+12↑r
.text:C860A138 62 2E 00 00                   off_C860A138 DCD thread_function_ptr - 0xC860A10E
.text:C860A138                                                                       ; DATA XREF: thread_create+14↑r
.text:C860A13C D6 13 00 00                   off_C860A13C DCD aFailToCreatePt - 0xC860A120
.text:C860A13C                                                                       ; DATA XREF: thread_create+2A↑r
.text:C860A13C                               ; } // starts at C860A0EC               ; "fail to create pthread t0"
.text:C860A140                               CODE32
.text:C860A140
.text:C860A140                               ; =============== S U B R O U T I N E =======================================
.text:C860A140

开启了一个线程去调用 thread_function_ptr ,该函数反编译分析

FILE *thread_function()
{
  __pid_t v0; // r5
  FILE *result; // r0
  FILE *v2; // r4
  int v3; // r5
  char v4[20]; // [sp+0h] [bp-140h] BYREF
  char v5[256]; // [sp+14h] [bp-12Ch] BYREF

  _android_log_print(4, "JNI_LOG", "enter thread_function");
  v0 = getpid();
  memset(v4, 0, sizeof(v4));
  sprintf(v4, "/proc/%d/status", v0);
  while ( 1 )
  {
    result = fopen(v4, "r");
    v2 = result;
    if ( !result )
      return result;
    v3 = 6;
    while ( !feof(v2) )
    {
      fgets(v5, 255, v2);
      _android_log_print(4, "JNI_LOG", "linestr:%s", v5);
      if ( !--v3 )
      {
        if ( getnumberfor_str((int)v5) > 0 )
          exit(0);
        break;
      }
    }
    fclose(v2);
    sleep(1u);
  }
}

这里就是在检查/proc/目标进程id/status 第六行的值,例子

tiffany:/ # cat /proc/4398/status
Name:   m.myapplication
State:  S (sleeping)
Tgid:   4398
Pid:    4398
PPid:   640
TracerPid:      0  //就是这里,如果被attach,这里会是其他进程的id,不会是0
Uid:    10151   10151   10151   10151
Gid:    10151   10151   10151   10151
Ngid:   0
FDSize: 128
......

直接把这里的exit也nop掉,,注意是改thread_function 的,而不是外部调用函数的,

6. IDA->Edit -> Patch program -> apply patch into file

ps: 这次我用androidkiller重打包成功,但是运行提示某个资源找不到,选择手动打ok了,运行成功
看来还是得多会几种方式

java -jar ../apktool_2.2.4.jar b anti-debug -o anti-debug-new.apk
java -jar ../signapk.jar ../testkey.x509.pem ../testkey.pk8 anti-debug-new.apk anti-debug-signed.apk

7. 重打包+签名+安装,发现APP可以正常动态调试了,反调试成功绕过

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

【Android逆向】反调试绕过

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

【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逆向】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中,