首页
社区
课程
招聘
[原创]新版dexprotector环境检测分析
发表于: 22小时前 269

[原创]新版dexprotector环境检测分析

22小时前
269

参考:https://bbs.kanxue.com/thread-289170.htm


样本

com.Hyatt.hyt 26.7.0(7月份的一个版本)

目的

分析这个检测的目的是从防护的角度看如何实现,所以这里我们不会选择用最简单的方式绕过,而是把整个检测细节全部分析清楚。

库检测逻辑宏观总结

整个库把环境检测与脱壳结合起来,在jni_onload中对多个维度的环境信息进行检测,同时通过检测结果对解密的密匙加盐,一旦检测失败,即使通过frida强行hook也无法脱出来正确的dex,从而无法启动。

同时起了多个检测线程,持续检测环境变化。

环境准备

这里就不多说了,直接看参考文章即可,这里dexprotector直接通过一个匿名内存页面来自link了一个动态库,然后在这个动态库中解密释放了代码,再从dexprotector的jni_onload中跳到动态内存的代码段中去,核心逻辑如下:


下一步就是直接分析动态内存中dump下来的动态库了,一些ida配置的方式也直接见参考的文章即可。


准备工作

  1. 所有已知类型的变量全部改过来,例如 JavaVM* JNIEnv* 等,不要放过参数透传过程中丢失的类型信息,全部改过来,会大大节约分析的精力

  2. PLT函数,遇到一些明显像plt的函数可以直接通过hook的方式拿到运行时真正调用的内存地址,找到对应的库中的偏移量,直接改名

  3. 一些关键的全局变量做好重命名,例如,下面这段汇编其实很明显是把函数入口的lr保存到了一个全局变量中,后面检测可能会用


分析关键检测点

整个检测逻辑做了深度定制,一些关键的函数都没有用libc的,而是自己实现了,系统调用也是,全部用svc实现的,同时所有的字符串全部做了加密增加了分析难度。

先看几个common关键函数

字符串解密函数:sub_3B000

字符串解密函数,同时这个函数还会自检自己是否被hook,如果没被hook,再用一个全局密匙解密当前一个全局的字符串,而且经过实验发现这个密匙其实也是动态解密出来的,重命名为 decode_string_and_hook_myself,和他类似的函数还有一个是sub_4A114,也是解密字符串的,我们崇明名为decode_xor_string,不再赘述。

反汇编:

_BYTE *__fastcall decode_string_and_hook_myself(_DWORD *a1, _BYTE *a2, __int64 a3)
{
  int v3; // w8
  int v4; // w9
  __int64 v5; // x10
  char *v6; // x11
  _BYTE *v7; // x14
  __int64 v8; // x12
  char v9; // t1
  __int64 v10; // x15
  __int64 i; // x16
  unsigned __int64 v12; // t2
  int v13; // w17
  _DWORD v15[2]; // [xsp+8h] [xbp-8h]
  v3 = a1[1];
  // 这里在检测自己是否被hook
  if ( (*decode_string_and_hook_myself & 0xFC) == 0x14
    || (v4 = *a1, (*decode_string_and_hook_myself & 0x1E) == 0x10)
    && (*(decode_string_and_hook_myself + 3) & 0x9F) == 0x90
    || (*decode_string_and_hook_myself & 0xFE) == 0x50
    && !*(decode_string_and_hook_myself + 1)
    && *(decode_string_and_hook_myself + 3) == 88 )
  {
    v4 = 0;
    --v3;
  }
  if ( a3 )
  {
    v5 = 0;
    v6 = (a1 + 2);
    v7 = a2;
    // 密匙
    v8 = qword_A32D0[7];
    do
    {
      v10 = v5 & 7;
      if ( (v5 & 7) == 0 )
      {
        for ( i = 0; i != 108; i += 4 )
        {
          HIDWORD(v12) = v3;
          LODWORD(v12) = v3;
          v13 = *(v8 + i);
          v3 = v13 ^ ((v12 >> 8) + v4);
          v4 = v3 ^ __ROR4__(v4, 29);
        }
        v15[0] = v4;
        v15[1] = v3;
      }
      ++v5;
      v9 = *v6++;
      *v7++ = *(v15 + v10) ^ v9;
    }
    while ( v5 != a3 );
  }
  // 解密出来的字符串指针
  return a2;
}

绕过措施:在hook的onenter时机把检测自己是否被hook的代码nop掉,同时onleave的时机把解密的字符串打印出来,当然,这个函数分析到后面会发现不光他自己会检测自己是否被hook,sub_25AC8 这个函数也会检测他是不是被hook,所以其实还有一个应对措施就是先把密匙dump下来,然后直接用python重写解密,用于在offline解密字符串。

function hook_decode_string_and_hook_myself(libanon) {
  Interceptor.attach(libanon.add(0x3b000), {
    onEnter: function (args) {
      nop_inst(libanon.add(0x3b01c), 3);
      nop_inst(libanon.add(0x3b04c), 1);
      nop_inst(libanon.add(0x3b07c), 1);
      this.lr = this.context.lr;
      this.strlen = args[2].toInt32();
    },
    onLeave: function (ret) {
      console.log(
        "decode_string_and_hook_myself ret is ",
        ret.readCString(),
        "; strlen is: ",
        this.strlen,
        "; lr is ",
        this.lr.sub(libanon),
      );
    },
  });
}

自实现的libc同功能函数:sub_16864 sub_13BBC sub_26098 sub_260C0 等等

sub_26098 是 strchr

sub_16864 是 strstr

sub_13BBC 是strtoull

sub_260C0 是memcpy


解析进程stat信息:sub_28E74

从/proc/self/stat里拿到当前进程的栈指针,依次获取了argv、env以及auxv,并且把这几个指针存到全局变量中,这里最关键的就是auxv了,因为用auxv可以直接找到r_debug,进程的所有加载的so也就都能遍历了。这函数直接重命名为:collect_process_startup_info。

拿r_debug指针:sub_8BD60 sub_28F94

sub_28F94是遍历刚才刚获取的auxv指针用的,然后用key取值,外层sub_8BD60直接调这个函数然后给r_debug全局指针赋值,核心就下面两行

统一的错误处理函数

sub_380F8 : 错误处理,扔异常,封装错误信息字符串等等

一些列环境相关的初始化函数(这些函数都可以通过hook decode_string_and_hook_myself 得到解密的字符串,语义还是比较明确的)

sub_29098(get_build_version_and_first_api) : 获取build version以及first api

sub_203E0: 根据版本设置一些偏移量,这些偏移明显是为了兼容不同android版本的一些结构体字段的访问。

sub_5A204: 获取自己pagemap的fd

sub_52570: 初始化一系列java环境的信息,例如:currentActivityThread mBoundApplication mPackageName等等,直接通过解密字符串就可知。


检测函数1:inline hook 检测

sub_6F948: 这个函数核心逻辑就是检测 libc,libart,libandroid这三个库是否被inline hook,核心的检测函数就是 sub_1F654, 通过对内存段中的异常跳转指令判断是否是inline hook。同时这个函数整体设计到对内存段的收集,需要定义一个结构体才能看清代码逻辑,同时这个函数的检测逻辑函数指针被当作参数直接pthread create,设置检测线程。

检测函数2:一系列基于字符串的环境检测

sub_1FC2C: debugger检测

sub_5E098: 这个函数很有意思,他是动态的申请了一块内存,然后往这块内存中写了一些指令,然后执行这段指令,而这段指令核心的逻辑就是把自己的返回值从0改成1.

sub_27150: magisk 检测,检测特定目录有没有magisk文件

sub_532BC: xpose 检测

sub_5E0C4:云环境检测

sub_2718C:root检测

sub_1A544:rom检测

sub_1A454:build tag检测

sub_1A1F0: bootloader检测

sub_4F240:adb检测

sub_927E0: 容器化检测

sub_53BF8: xpose file检测

sub_5E2E4:模拟器检测

sub_27244: system检测

sub_5F758: installer检测


生成初始解密密匙

从全局变量生成最终需要作为hmac的密匙,这里这两个全局变量我已经重命名。


验签

sub_6DAE0


内存范围hash+检测

sub_120C4:全库最绝的一个检测,从当前位置以固定步长向前搜索,知道搜索到一个满足特殊要求的位置,停止,然后从这个位置开始向后做一个范围hash,把结果和固定值对比。通过hook发现,其实找到的就是这个库的基地址,也就是相当于从基地址做范围hash,相当于校验inline hook、plt hook、nop指令等所有内存改动痕迹。sub_94ED4 也是同理,只不过不是从基地址开始。


再之后就是生成密匙,脱壳,最后调用jni的findclass loadclass了。


最终的frida hook脚本如下


function nop_inst(start, size) {
  var nop = [0x1f, 0x20, 0x03, 0xd5];
  for (var i = 0; i < size; i += 1) {
    Memory.writeByteArray(start.add(i * 4), nop);
  }
}
function hook_decode_string_and_hook_myself(libanon) {
  Interceptor.attach(libanon.add(0x3b000), {
    onEnter: function (args) {
      if (!g_has_dump) {
        g_has_dump = true;
        var addr = libanon.add(0xA3308).readPointer();
        console.log("decode key is : \n", hexdump(addr, { length: 108 }));
      }
      //console.log("enter decode_string_and_hook_myself")
      // nop
      nop_inst(libanon.add(0x3b01c), 3);
      nop_inst(libanon.add(0x3b04c), 1);
      nop_inst(libanon.add(0x3b07c), 1);
      this.lr = this.context.lr;
      this.strlen = args[2].toInt32();
    },
    onLeave: function (ret) {
      console.log(
        "decode_string_and_hook_myself ret is ",
        ret.readCString(),
        "; strlen is: ",
        this.strlen,
        "; lr is ",
        this.lr.sub(libanon),
      );
    },
  });
}
function hook_decode_xor_string(libanon) {
  Interceptor.attach(libanon.add(0x4a114), {
    onEnter(args) {
      this.out = args[1];
      this.lr = this.context.lr;
    },
    onLeave(retval) {
      console.log(
        "decode_xor_string leave , ret is :",
        this.out.readCString(),
        " ; lr is : ",
        this.lr.sub(libanon),
      );
    },
  });
}
function handle_plt_functions(libanon) {
  // 处理plt函数
  var a = Memory.readPointer(libanon.add(0x9d990));
  var abasename = Process.findModuleByAddress(a).name;
  var abasebase = Process.findModuleByAddress(a).base;
  console.log(
    "abasename is : ",
    abasename,
    "; base is : ",
    abasebase,
    "; function bias is : ",
    a.sub(abasebase),
  );
}
function bypass_pthread_detect(libanon) {
  // 替换线程执行函数
  Interceptor.replace(
    libanon.add(0x1ff04),
    new NativeCallback(
      function () {
        console.log("0x1FF04 : bypassed");
        while (true) {
          Thread.sleep(1);
        }
      },
      "int64",
      [],
    ),
  );
  Interceptor.replace(
    libanon.add(0x36fd4),
    new NativeCallback(
      function () {
        console.log("0x36FD4 : bypassed");
        while (true) {
          Thread.sleep(1);
        }
      },
      "int64",
      [],
    ),
  );
  Interceptor.replace(
    libanon.add(0x3c83c),
    new NativeCallback(
      function () {
        console.log("0x3C83C : bypassed");
        while (true) {
          Thread.sleep(1);
        }
      },
      "int64",
      [],
    ),
  );
  Interceptor.replace(
    libanon.add(0x4f38c),
    new NativeCallback(
      function () {
        console.log("0x4F38C : bypassed");
        while (true) {
          Thread.sleep(1);
        }
      },
      "int64",
      [],
    ),
  );
  Interceptor.replace(
    libanon.add(0x70004),
    new NativeCallback(
      function () {
        console.log("0x70004 : bypassed");
        while (true) {
          Thread.sleep(1);
        }
      },
      "int64",
      [],
    ),
  );
  Interceptor.replace(
    libanon.add(0x758a4),
    new NativeCallback(
      function () {
        console.log("0x758A4 : bypassed");
        while (true) {
          Thread.sleep(1);
        }
      },
      "int64",
      [],
    ),
  );
  Interceptor.replace(
    libanon.add(0x76404),
    new NativeCallback(
      function () {
        console.log("0x76404 : bypassed");
        while (true) {
          Thread.sleep(1);
        }
      },
      "int64",
      [],
    ),
  );
  Interceptor.replace(
    libanon.add(0x92c14),
    new NativeCallback(
      function () {
        console.log("0x92C14 : bypassed");
        while (true) {
          Thread.sleep(1);
        }
      },
      "int64",
      [],
    ),
  );
}
function hook_process_startup_info_collection(libanon) {
  Interceptor.attach(libanon.add(0x28e74), {
    onEnter(args) {
      this.lr = this.context.lr;
      console.log("onenter 0x28E74 ===============");
    },
    onLeave(ret) {
      const argv = libanon.add(0xa2f28).readPointer();
      const envp = libanon.add(0xa2f30).readPointer();
      const auxv = libanon.add(0xa2f38).readPointer();
      console.log("onleave 0x28E74, ret =", ret);
      console.log("A2F28 =", argv);
      console.log("A2F30 =", envp);
      console.log("A2F38 =", auxv);
      console.log("========== argv ==========");
      for (let i = 0; i < 20; i++) {
        let p = argv.add(i * Process.pointerSize).readPointer();
        if (p.isNull()) {
          console.log("argv[" + i + "] = NULL");
          break;
        }
        console.log("argv[" + i + "] =", p.readCString());
      }
      console.log("========== envp ==========");
      for (let i = 0; i < 100; i++) {
        let p = envp.add(i * Process.pointerSize).readPointer();
        if (p.isNull()) {
          console.log("envp[" + i + "] = NULL");
          break;
        }
        console.log("envp[" + i + "] =", p.readCString());
      }
      console.log("========== auxv ==========");
      for (let i = 0; i < 50; i++) {
        let entry = auxv.add(i * 16);
        let type = entry.readU64();
        let value = entry.add(8).readU64();
        console.log("auxv[" + i + "] type =", type, "value =", value);
        if (type.equals(0)) break;
      }
    },
  });
}
function hook_dynamic_exec_code(libanon) {
  Interceptor.attach(libanon.add(0x95420), {
    onEnter(args) {
      const code = args[0];
      console.log("\n========== dynamic code ==========");
      console.log("address : " + code);
      console.log("size    : 0x38");
      console.log("\n--- HEX ---");
      console.log(
        hexdump(code, {
          offset: 0,
          length: 0x38,
          header: true,
          ansi: false,
        }),
      );
      console.log("\n--- ARM64 ---");
      let p = code;
      const end = code.add(0x38);
      while (p.compare(end) < 0) {
        const insn = Instruction.parse(p);
        console.log(p + "  " + insn.mnemonic + " " + insn.opStr);
        p = p.add(insn.size);
      }
      console.log("==================================\n");
    },
  });
  Interceptor.attach(libanon.add(0x583c0), {
    onEnter(args) {
      this.lr = this.context.lr;
      console.log("onenter 0x583C0, lr is : ", this.lr.sub(libanon));
      const code = libanon.add(0x57810);
      const size = 0xd4;
      console.log("\n========== 0x57810 dynamic code ==========");
      console.log("address : " + code);
      console.log("size    : " + size);
      console.log("\n--- HEX ---");
      console.log(
        hexdump(code, {
          offset: 0,
          length: size,
          header: true,
          ansi: false,
        }),
      );
      console.log("\n--- ARM64 ---");
      let p = code;
      const end = code.add(size);
      while (p.compare(end) < 0) {
        const insn = Instruction.parse(p);
        console.log(p + "  " + insn.mnemonic + " " + insn.opStr);
        p = p.add(insn.size);
      }
      console.log("==================================\n");
    },
    onLeave(ret) {
      console.log("onleave 0x583C0, ret is : ", ret.toString());
    },
  });
}
function dump_dynamic_so(libanon, libanon_size) {
  console.log("start dump so");
  var sodata = Memory.readByteArray(libanon, libanon_size);
  var f = new File("/data/data/com.Hyatt.hyt/libanon.so", "wb");
  f.write(sodata);
  f.close();
  console.log("done dump so");
}
function dump_dex_file(dex_ptr, dex_size) {
  console.log("start dump dex file");
  var dexdata = Memory.readByteArray(dex_ptr, dex_size);
  var f = new File("/data/data/com.Hyatt.hyt/dex_file.dex", "wb");
  f.write(dexdata);
  f.close();
  console.log("done dump dex file");
}
var g_clean_libanon_snapshot = null;
var g_clean_libanon_snapshot_size = 0;
function save_clean_libanon_snapshot(libanon, libanon_size) {
  if (!libanon || libanon.isNull() || !libanon_size) {
    console.log("[snapshot] invalid args");
    return null;
  }
  if (g_clean_libanon_snapshot) {
    console.log(
      "[snapshot] already saved at",
      g_clean_libanon_snapshot,
      "size =",
      g_clean_libanon_snapshot_size,
    );
    return g_clean_libanon_snapshot;
  }
  var shadow = Memory.alloc(libanon_size);
  Memory.copy(shadow, libanon, libanon_size);
  g_clean_libanon_snapshot = shadow;
  g_clean_libanon_snapshot_size = libanon_size;
  console.log("[snapshot] saved clean libanon at", shadow, "size =", libanon_size);
  return shadow;
}
function hook_error_handler(libanon) {
  Interceptor.attach(libanon.add(0x380f8), {
    onEnter: function (args) {
      console.log("error_handler onenter, error code is : ", args[1].toInt32());
    },
  });
}
function bypass_lib_c_inline_hook_detection(libanon) {
  Interceptor.attach(libanon.add(0x6fe14), {
    onLeave: function (ret) {
      ret.replace(0);
    },
  });
}
function ror32(x, n) {
  x = x >>> 0;
  return ((x >>> n) | (x << (32 - n))) >>> 0;
}
function bytesToPrintable(u8) {
  let s = "";
  for (let i = 0; i < u8.length; i++) {
    const b = u8[i];
    if (b >= 0x20 && b <= 0x7e) s += String.fromCharCode(b);
    else s += "\\x" + ("0" + b.toString(16)).slice(-2);
  }
  return s;
}
/**
 * @param {NativePointer} a1Ptr  指向 blob: [u32 v4][u32 v3][cipher...]
 * @param {number} length        解密长度(调用点第三参)
 * @param {NativePointer} keySch qword_A32D0[7] 指向的 u32 key schedule
 */
function decodeDynString(a1Ptr, length, keySch) {
  let v4 = a1Ptr.readU32() >>> 0; // a1[0]
  let v3 = a1Ptr.add(4).readU32() >>> 0; // a1[1]
  const ct = a1Ptr.add(8); // a1 + 2 dwords
  const out = new Uint8Array(length);
  const ks = new Uint8Array(8);
  const dv = new DataView(ks.buffer);
  for (let idx = 0; idx < length; idx++) {
    if ((idx & 7) === 0) {
      for (let off = 0; off < 108; off += 4) {
        // 27 rounds
        const k = keySch.add(off).readU32() >>> 0;
        v3 = (k ^ ((ror32(v3, 8) + v4) >>> 0)) >>> 0;
        v4 = (v3 ^ ror32(v4, 29)) >>> 0;
      }
      dv.setUint32(0, v4, true); // little-endian
      dv.setUint32(4, v3, true);
    }
    out[idx] = ct.add(idx).readU8() ^ ks[idx & 7];
  }
  return out;
}
const g_str_targets = [[0x4923, 65]];
// 在拿到 libanon 后调用
function dumpUnreachedDynStrings(libanon) {
  const qwordA32D0 = libanon.add(0xa32d0);
  const keySch = qwordA32D0.add(7 * 8).readPointer();
  g_str_targets.forEach(([off, len]) => {
    const buf = decodeDynString(libanon.add(off), len, keySch);
    console.log(
      `[dynstr] off=0x${off.toString(16)} len=${len} -> ${bytesToPrintable(buf)}`,
    );
  });
}
function bypass_memory_hash_check(libanon) {
  Interceptor.attach(libanon.add(0x94ed4), {
    onEnter: function (args) {
      this.lr = this.context.lr.sub(libanon);
      if (this.lr.equals(0x37d68)) {
        // sip_hash(qword_9D830, size, v44, ...), v44 在栈上紧跟 dex_key 之后
        this.dex_key = args[2].sub(0x20);
      }
    },
    onLeave: function (ret) {
      if (this.lr.equals(0x37d68)) {
        var expected = libanon.add(0xa32c8).readU64();
        ret.replace(expected);
        //console.log(
        //  "0x94ED4 patched ret, dex_key is:\n",
        //  hexdump(this.dex_key, { length: 32 }),
        //);
      }
    },
  });
}
var g_has_dump = false;
var g_dex_key = null;
function hook_hmac_sign_in_target(libanon) {
  // Implementation for hooking HMAC sign in the target library
  Interceptor.attach(libanon.add(0x7059C), {
    onEnter: function (args) {
      console.log(" hmac_sign_in_target called, lr is : ", this.context.lr.sub(libanon), "; target is : ", args[0].sub(libanon));
      if (args[0].sub(libanon).equals(g_dex_key)) {
        console.log(" dex_key has been changed by hmac_sign_in_target");
      }
    }
  });
}
function hook_exec_hmac_sign_algo9_and_get_dex_key(libanon) {
  Interceptor.attach(libanon.add(0x70500), {
    onEnter: function (args) {
      this.lr = this.context.lr.sub(libanon);
      g_dex_key = args[4].sub(libanon);
    }
  });
}
function hook_verify_checksum(libanon) {
  // Implementation for hooking the verify checksum function in the target library
  Interceptor.attach(libanon.add(0x5C4DC), { // Replace 0x70600 with the actual offset of the verify checksum function
    onEnter: function (args) {
      this.lr = this.context.lr.sub(libanon);
    },
    onLeave: function (ret) {
      console.log("verify_checksum returned, ret is : ", ret, "; lr is :" , this.lr);
    }
  });
}
function hook_find_mem_block_target_addr(libanon) {
  // 找到 find_mem_block_and_hmac_sign 最终命中的内存块地址
  // 这里其实hook的是 sha256()的地址
  Interceptor.attach(libanon.add(0x55ED0), {
    onEnter: function (args) {
      this.lr = this.context.lr.sub(libanon);
      // 来自 find_mem_block_and_hmac_sign 内部的 sha256(v5, len, ...)
      if (this.lr.equals(0x12160)) {
        var addr = args[0];
        var len = args[1].toUInt32();
        console.log("[find_mem_block] matched block addr =", addr, "off =", addr.sub(libanon), "len =", len);
        console.log("[find_mem_block] start replace ....");
        args[0] = g_clean_libanon_snapshot;
        console.log("[find_mem_block] replaced with clean snapshot");
      }
    },
  });
  Interceptor.attach(libanon.add(0x120C4), {
    onEnter: function (args) {
      console.log("[find_mem_block] enter, dex_key =", args[0].sub(libanon));
    },
  });
}
function hook_decompress_dex(libanon) {
  // decompress_dex
              Interceptor.attach(libanon.add(0x8BF94), { // replace 0x123456 with the actual offset
                onEnter: function (args) {
                  this.lr = this.context.lr.sub(libanon);
                },
                onLeave: function (ret) {
                  console.log("Interceptor detached at 0x8BF94, ret = ", ret, " lr = ", this.lr);
                },
              });
}
let g_cls_timeline_installed = false;
function hook_art_dexclassloader_timeline(libanon) {
  if (g_cls_timeline_installed) return;
  g_cls_timeline_installed = true;
  const ts = () => Date.now();
  // ---- Java 层:DexClassLoader / ClassLoader ----
  Java.perform(function () {
    const Exception = Java.use("java.lang.Exception");
    const Log = Java.use("android.util.Log");
    const DexClassLoader = Java.use("dalvik.system.DexClassLoader");
    const PathClassLoader = Java.use("dalvik.system.PathClassLoader");
    const BaseDexClassLoader = Java.use("dalvik.system.BaseDexClassLoader");
    const ClassLoader = Java.use("java.lang.ClassLoader");
    const DexFile = Java.use("dalvik.system.DexFile");
    const watch = (n) =>
      n && (n.indexOf("Hyatt") >= 0 || n.indexOf("Application") >= 0 || n.indexOf("Splash") >= 0);
    const bt = () => Log.getStackTraceString(Exception.$new());
    const dclInit = DexClassLoader.$init.overload(
      "java.lang.String", "java.lang.String", "java.lang.String", "java.lang.ClassLoader"
    );
    dclInit.implementation = function (dexPath, odexPath, libPath, parent) {
      console.log(`[${ts()}][DCL.$init] dexPath=${dexPath} odexPath=${odexPath} libPath=${libPath} parent=${parent}`);
      console.log(bt());
      return dclInit.call(this, dexPath, odexPath, libPath, parent);
    };
    const pclInit1 = PathClassLoader.$init.overload("java.lang.String", "java.lang.ClassLoader");
    pclInit1.implementation = function (path, parent) {
      console.log(`[${ts()}][PCL.$init-2] path=${path} parent=${parent}`);
      return pclInit1.call(this, path, parent);
    };
    const pclInit2 = PathClassLoader.$init.overload("java.lang.String", "java.lang.String", "java.lang.ClassLoader");
    pclInit2.implementation = function (dexPath, libPath, parent) {
      console.log(`[${ts()}][PCL.$init-3] dexPath=${dexPath} libPath=${libPath} parent=${parent}`);
      return pclInit2.call(this, dexPath, libPath, parent);
    };
    const findClass = BaseDexClassLoader.findClass.overload("java.lang.String");
    findClass.implementation = function (name) {
      if (watch(name)) console.log(`[${ts()}][BaseDexClassLoader.findClass] ${name}`);
      return findClass.call(this, name);
    };
    const loadClass1 = ClassLoader.loadClass.overload("java.lang.String");
    loadClass1.implementation = function (name) {
      if (watch(name)) console.log(`[${ts()}][ClassLoader.loadClass] ${name} loader=${this}`);
      return loadClass1.call(this, name);
    };
    const loadDex = DexFile.loadDex.overload("java.lang.String", "java.lang.String", "int");
    loadDex.implementation = function (src, out, flags) {
      console.log(`[${ts()}][DexFile.loadDex] src=${src} out=${out} flags=${flags}`);
      return loadDex.call(this, src, out, flags);
    };
  });
  // ---- ART 层:ClassLinker::DefineClass / FindClass ----
  const art = Process.findModuleByName("libart.so");
  if (!art) return;
  for (const s of art.enumerateSymbols()) {
    if (!/ClassLinker.*(DefineClass|FindClass)/.test(s.name)) continue;
    if (s.name.indexOf("CheckJNI") >= 0) continue;
    Interceptor.attach(s.address, {
      onEnter(args) {
        let desc = "";
        for (let i = 0; i < 4; i++) {
          try {
            const t = Memory.readCString(args[i]);
            if (t && t.length > 2 && t[0] === "L") { desc = t; break; }
          } catch (_) {}
        }
        this.desc = desc;
        console.log(`[${ts()}][ART] ${s.name} enter desc=${desc || "<na>"}`);
      },
      onLeave(ret) {
        console.log(`[${ts()}][ART] leave ret=${ret}`);
      }
    });
  }
}
function hook_calc_xxh64_like_fingerprint(libanon) {
  Interceptor.attach(libanon.add(0x8A998), {
    onEnter: function (args) {
      console.log("calc_xxh64_like_fingerprint onEnter");
      this.lr = this.context.lr.sub(libanon);
      if (this.lr.equals(0x3D4C8) || this.lr.equals(0x5AF84)) {
        console.log("calc_xxh64_like_fingerprint condition met");
        args[0] = g_clean_libanon_snapshot;
      }
    },
    onLeave: function (ret) {
      console.log("calc_xxh64_like_fingerprint onLeave ret =", ret);
    },
  });
}
function hook_sub_82560(libanon) {
  const f = libanon.add(0x82560);
  console.log("xxxxxxxxxxxxxxxxx", f);
  Interceptor.attach(f, {
    onEnter(args) {
      console.log("sub_82560 onEnter");
      this.env = args[0];
      this.a2 = args[1].toUInt32();
      this.a3 = args[2];
      this.a4 = args[3].toInt32() & 0xff;
      this.a5 = args[4]; // char** output
      let inStr = "<null>";
      try {
        if (!this.a3.isNull()) inStr = Memory.readCString(this.a3);
      } catch (_) {
        inStr = "<bad-cstr>";
      }
      console.log(
        `[sub_82560] enter a2=${this.a2} a3="${inStr}" a4=${this.a4} a5=${this.a5}`
      );
    },
    onLeave(retval) {
      const ret = retval.toInt32();
      let outPtr = ptr(0);
      let outStr = "<null>";
      try {
        if (!this.a5.isNull()) {
          outPtr = this.a5.readPointer(); // *a5
          if (!outPtr.isNull()) outStr = Memory.readCString(outPtr);
        }
      } catch (_) {
        outStr = "<bad-out>";
      }
      console.log(
        `[sub_82560] leave ret=${ret} out_ptr=${outPtr} out_str="${outStr}"`
      );
    },
  });
  console.log("[sub_82560] hooked @ 0x82560");
}
function hook_unpack_result(libanon) {
  Interceptor.attach(libanon.add(0x81890), { // Replace 0x12345 with the actual offset
    onEnter: function (args) {
      console.log("unpack_result onEnter");
      this.lr = this.context.lr.sub(libanon);
      this.out = args[0];
    },
    onLeave: function (ret) {
      console.log("unpack_result lr is :", this.lr, "; size is :", ret, "; out is : \n", hexdump(this.out, { length: 16 }));
      if (this.lr.equals(0x5aed8)) {
        //dump_dex_file(this.out, ret.toUInt32());
      }
    },
  });
}
function hook_dlopen() {
  console.log("-------------Current PID:", Process.id);
  var loader_android_dlopen_ext = Module.findExportByName(
    null,
    "__loader_android_dlopen_ext",
  );
  console.log(
    "__loader_android_dlopen_ext address is => ",
    loader_android_dlopen_ext,
  );
  Interceptor.attach(loader_android_dlopen_ext, {
    onEnter: function (args) {
      var pathptr = args[0];
      var path = pathptr.readCString();
      console.log("path is => ", path);
      if (path && path.indexOf("libdexprotector.so") >= 0) {
        this.match = true;
      }
       Thread.sleep(1);
    },
    onLeave: function (ret) {
      if (this.match) {
        var libdexprotector = Process.findModuleByName("libdexprotector.so");
        if (libdexprotector) {
          Interceptor.attach(libdexprotector.findExportByName("JNI_OnLoad"), {
            onEnter: function (args) {
              console.log("dexprotector jni_onload onenter");
              this.internal_jni_onload_p = libdexprotector.base
                .add(0xb238)
                .readPointer();
              var libanon = Process.findRangeByAddress(
                this.internal_jni_onload_p,
              ).base;
              var libanon_size = Process.findRangeByAddress(
                this.internal_jni_onload_p,
              ).size;
              this.libanon = libanon;
              this.libanon_size = libanon_size;
              // 这里用一块内存把干净的libanon保存起来
              save_clean_libanon_snapshot(libanon, libanon_size);
              console.log(
                "libanon base is =========================> ",
                libanon,
              );
              console.log(
                "libanon size is =========================> ",
                libanon_size,
              );
              console.log(
                "internal jni onload location is =========================> ",
                this.internal_jni_onload_p.sub(libanon),
              );
              //dumpUnreachedDynStrings(libanon);
              //hook_decode_string_and_hook_myself(libanon);
              hook_decode_xor_string(libanon);
              //handle_plt_functions(libanon);
              bypass_pthread_detect(libanon);
              //hook_process_startup_info_collection(libanon);
              // dump动态函数
              //hook_dynamic_exec_code(libanon);
              hook_error_handler(libanon);
              bypass_lib_c_inline_hook_detection(libanon);
       
              bypass_memory_hash_check(libanon);
              // 看dex_key 是否被加盐
              //hook_hmac_sign_in_target(libanon);
              // 给 dex_key 指针赋值
              hook_exec_hmac_sign_algo9_and_get_dex_key(libanon);
              // hook verify checksum function
              //hook_verify_checksum(libanon);
              // hook find_mem_block_and_hmac_sign matched address
              hook_find_mem_block_target_addr(libanon);
              //hook_decompress_dex(libanon);
              hook_calc_xxh64_like_fingerprint(libanon);
              //hook_unpack_result(libanon);
              hook_sub_82560(libanon);
             
            },
            onLeave: function (ret) {
              console.log("dexprotector jni_onload onleave, ret: ", ret);
              //dump_dynamic_so(this.libanon, this.libanon_size);      
              Thread.sleep(3);
            },
          });
        }
      }
    },
  });
}
function main() {
  hook_dlopen();
}
setImmediate(main);










冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。

收藏
免费 4
打赏
分享
最新回复 (1)
雪    币: 2430
活跃值: (3637)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
2
xx
1小时前
0
游客
登录 | 注册 方可回帖
返回