某密企业版加固反调试与VMP分析
前言:
如有侵权,联系删除
本来想的是来分析一下这些加固方案来对frida和lsp进行魔改优化的,但是分析到后面感觉可以学习的东西还是蛮多的,就将爱加密企业版加固继续分析了一下,目前的进度是基本上将vmp给还原出来啦,但是还没有找到被保护的方法名到opcode之间是怎么进行映射的,如果有对这个爱加密企业版分析的大佬,欢迎交流一下一下,教教弟弟
混淆去除
我们要分析的基本上就是libexec.so和libexecmain.so,然后经过我的分析这个libexec.so应该是主要的加固逻辑,比如vmp,frida检测,fart检测,lsp检测等等,这个libexecmain.so呢主要作用就是配合libexec.so的vmp保护来使用的,就我发现的两个点:第一个就是java层函数转换成native函数是在libexecmain.so中进行注册的;第二个呢就是vmp有一层opcode的转换,这个转换就是在libexecmain.so中进行的
So dump
我们先看libexec.so就行,这个so需要我们从内存中dump下来,然后才可以分析,要不然都没法看
两种dump方法,应该是第二种可以dump下来,不过这些dump so的方法都有很多,网上的教程代码也很多,直接网上找一下就行。另外我有看到有师傅会说遇到中间不可读的内存怎么dump,我之前也遇到一次,那个时候直接用gg修改器dump的内存,这个没看到出现有内存权限的问题,有兴趣的大佬可以去看看
function dump_so(so_name) {
Java.perform(function () {
let currentApplication = Java.use('android.app.ActivityThread').currentApplication()
let dir = currentApplication.getApplicationContext().getFilesDir().getPath()
let libso = Process.getModuleByName(so_name)
console.log('[name]:', libso.name)
console.log('[base]:', libso.base)
console.log('[size]:', ptr(libso.size))
console.log('[path]:', libso.path)
let file_path = dir + '/' + libso.name + '_' + libso.base + '_' + ptr(libso.size) + '.so'
let file_handle = new File(file_path, 'wb')
if (file_handle && file_handle != null) {
Memory.protect(ptr(libso.base), libso.size, 'rwx')
let libso_buffer = ptr(libso.base).readByteArray(libso.size)
file_handle.write(libso_buffer)
file_handle.flush()
file_handle.close()
console.log('[dump]:', file_path)
}
})
}
function my_hook_dlopen(soName) {
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) {
dump_so(soName);
}
}
}
);
}
function dump_so(so_name) {
var libso = Process.getModuleByName(so_name);
console.log("[name]:", libso.name);
console.log("[base]:", libso.base);
console.log("[size]:", ptr(libso.size));
console.log("[path]:", libso.path);
var file_path = "/data/data/com.****/" + libso.name + "_" + libso.base + "_" + ptr(libso.size) + "android_dlopen_ext.so";
//其中,com.***为包名
var file_handle = new File(file_path, "wb");
if (file_handle && file_handle != null) {
Memory.protect(ptr(libso.base), libso.size, 'rwx');
var libso_buffer = ptr(libso.base).readByteArray(libso.size);
file_handle.write(libso_buffer);
file_handle.flush();
file_handle.close();
console.log("[dump]:", file_path);
}
}
setImmediate(my_hook_dlopen("libexec.so"));//libexec.so为要进行内存dump的so文件名称
接下来就是用Sofixer进行修复了,这个修复好就可以进行详细的分析了
sofixer -s soruce.so -o fix.so -m 0x0 -d
-s 待修复的so路径
-o 修复后输出的路径
-m 内存dump的基地址(16位)
-d 输出调试debug信息
br混淆&字符串混淆
大家在分析这个程序的时候可能会遇到两个难题:
1、字符串被混淆了
2、br混淆,ollvm混淆导致很难分析
举个例子,我随便拿一个函数
可以看到有一个这样的混淆,另外就是可以看看字符串,可以看到一些具有价值的字符串,但是会缺少一些检测的字符串,例如:frida,fart,lsp等等
这就导致了我们会遇到很多这样的难点,然后我写了两个idapython的代码来处理这个问题,第一个就是br混淆的以及字符串混淆的地方,后面会给大家说怎么找到这个字符串加密的,先把这个代码给大家,我放到一块了
import idaapi
import ida_auto
import idc
import idautils
import ida_bytes
import ida_kernwin
import ida_nalt
import ida_name
import ida_ua
import struct
# ================= 配置区域 =================
# 如果自动获取失败,请手动填入 str_crypto 的地址 (例如: 0xA2050)
TARGET_FUNC_ADDR = None
'''
str_decrypt:
decrypt1:
0x9F25E948B92D4A45, 0x386578A85687F0E4, 0x4B51520439262E88,
0x7B87C8EA63F76E60, 0xE6A06E6C31795C9A, 0x1A4A3820BF5626CE,
0x65BC7CFCD3143D04, 0xFAD0B56FC5CE72DA
KEY_SLICE_QWORD_INDEX = 6
KEY_LEN = 16
decrypt2:
0xAB8C3B2E50169861,0x224C6E3AB4882054
KEY_SLICE_QWORD_INDEX = 0
KEY_LEN = 16
decrypt3:
0xABAFB26A6BF6D2F1, 0xA5A16AC47B85D18A
KEY_SLICE_QWORD_INDEX = 2
KEY_LEN = 16
decrypt4:
0xDE6E703D98CAAD0E, 0xFD1F48E9397A550E
KEY_SLICE_QWORD_INDEX = 2
KEY_LEN = 16
decrypt5:
0xF8A00174E273EADA, 0x405B88BD533D60C5
KEY_SLICE_QWORD_INDEX = 2
KEY_LEN = 16
decrypt6:
0xEA7A86455C89A9B2, 0xD5967F18810D335C
KEY_SLICE_QWORD_INDEX = 2
KEY_LEN = 16
decrypt7:
0x11A9087F8E50DFF4, 0xE91E02407E6021C2, 0x15C1FE34AAC73F2E,
0x26D03B2922ABD4B6, 0
KEY_SLICE_QWORD_INDEX = 2
KEY_LEN = 16
dec
'''
QWORD_KEY = [
0x7247D3FB49D1B6BA, 0x23DD5CD6F2334842, 0x6356908C726F6CD8,
0x6EBC8BD9E70C902
]
KEY_SLICE_QWORD_INDEX = 0
KEY_LEN = 16
# ===========================================
def get_key():
out = bytearray()
for v in QWORD_KEY:
if isinstance(v, str):
v = int(v, 0)
out += struct.pack('<Q', int(v) & 0xFFFFFFFFFFFFFFFF)
start = int(KEY_SLICE_QWORD_INDEX) * 8
end = start + int(KEY_LEN)
key = bytes(out[start:end])
if not key:
raise ValueError("key slice empty")
return key
def decrypt_logic(data, key):
"""核心异或解密逻辑"""
key_len = len(key)
result = bytearray(len(data))
for i in range(len(data)):
result[i] = data[i] ^ key[i % key_len]
return bytes(result)
def get_default_strtype():
try:
return ida_nalt.get_default_str_type()
except Exception:
pass
try:
return idc.get_inf_attr(idc.INF_STRTYPE)
except Exception:
pass
try:
inf = idaapi.get_inf_structure()
if hasattr(inf, "strtype"):
return inf.strtype
except Exception:
pass
if hasattr(idc, "STRTYPE_C"):
return idc.STRTYPE_C
return 0
def create_strlit_compat(ea, length, strtype):
try:
return ida_bytes.create_strlit(ea, length, strtype)
except Exception:
pass
try:
return ida_bytes.create_strlit(ea, ea + length)
except Exception:
pass
try:
return idc.create_strlit(ea, ea + length)
except Exception:
return False
def find_nul_pos(bs):
for i in range(len(bs)):
x = bs[i] if isinstance(bs[i], int) else ord(bs[i])
if x == 0:
return i
return -1
def printable_prefix_len(bs, max_len=256):
n = 0
lim = min(len(bs), max_len)
for i in range(lim):
x = bs[i] if isinstance(bs[i], int) else ord(bs[i])
if 32 <= x <= 126:
n += 1
else:
break
return n
def make_strlit_any(ea, dec, min_len=4):
z = find_nul_pos(dec)
if z >= min_len:
strlen = z + 1
ida_bytes.del_items(ea, 0, strlen)
return create_strlit_compat(ea, strlen, get_default_strtype())
pref = printable_prefix_len(dec)
if pref >= min_len:
ida_bytes.del_items(ea, 0, pref)
return create_strlit_compat(ea, pref, get_default_strtype())
return False
def mnem(ea):
try:
return idc.print_insn_mnem(ea).lower()
except Exception:
return idaapi.print_insn_mnem(ea).lower()
def opnd(ea, i):
try:
return idc.print_operand(ea, i).lower()
except Exception:
return idaapi.print_operand(ea, i).lower()
def optype(ea, i):
try:
return idc.get_operand_type(ea, i)
except Exception:
return idaapi.get_operand_type(ea, i)
def opval(ea, i):
try:
return idc.get_operand_value(ea, i)
except Exception:
return idaapi.get_operand_value(ea, i)
def prev_insn(ea):
try:
p = idc.prev_head(ea)
return p if p != idc.BADADDR else None
except Exception:
p = idaapi.prev_head(ea, 0)
return p if p != idaapi.BADADDR else None
def is_bl_to(ea, callee_ea):
if mnem(ea) != "bl":
return False
try:
tgt = opval(ea, 0)
except Exception:
return False
return tgt == callee_ea
def try_adrp_add_x0(add_ea):
if mnem(add_ea) != "add":
return None
if opnd(add_ea, 0) != "x0" or opnd(add_ea, 1) != "x0":
return None
if optype(add_ea, 2) != idc.o_imm and optype(add_ea, 2) != idaapi.o_imm:
return None
off = opval(add_ea, 2)
adrp_ea = prev_insn(add_ea)
if adrp_ea is None or mnem(adrp_ea) != "adrp" or opnd(adrp_ea, 0) != "x0":
return None
base = opval(adrp_ea, 1) & ~0xFFF
return base + off
def recover_ptr_x0(call_ea, lookback=30):
ea = call_ea
for _ in range(lookback):
ea = prev_insn(ea)
if ea is None:
break
mm = mnem(ea)
if mm == "add":
v = try_adrp_add_x0(ea)
if v is not None and idaapi.getseg(v):
return v
if mm == "adr" and opnd(ea, 0) == "x0":
v = opval(ea, 1)
if idaapi.getseg(v):
return v
if mm == "ldr" and opnd(ea, 0) == "x0":
v = opval(ea, 1)
if idaapi.getseg(v):
return v
if mm == "mov" and opnd(ea, 0) == "x0":
if optype(ea, 1) == idc.o_imm or optype(ea, 1) == idaapi.o_imm:
v = opval(ea, 1)
if idaapi.getseg(v):
return v
return None
def recover_len_w1(call_ea, lookback=30):
ea = call_ea
for _ in range(lookback):
ea = prev_insn(ea)
if ea is None:
break
if mnem(ea) == "mov":
if opnd(ea, 0) in ("w1", "x1"):
if optype(ea, 1) == idc.o_imm or optype(ea, 1) == idaapi.o_imm:
return opval(ea, 1) & 0xFFFFFFFF
if opnd(ea, 1) in ("wzr", "xzr"):
return 0
return None
def find_str_crypto_ea():
try:
ea = ida_name.get_name_ea(idaapi.BADADDR, "str_crypto")
return ea if ea != idaapi.BADADDR else None
except Exception:
ea = idc.get_name_ea_simple("str_crypto")
return ea if ea != idc.BADADDR else None
def auto_decrypt_scan_bl(str_crypto_ea=None, rename_strings=True, silent=False, min_len=1, max_len=0x4000):
if str_crypto_ea is None:
str_crypto_ea = find_str_crypto_ea()
if str_crypto_ea is None:
ida_kernwin.msg("[!] str_crypto not found.\n")
return
seg = idaapi.get_first_seg()
call_sites = []
while seg:
if seg.perm & idaapi.SEGPERM_EXEC:
ea = seg.start_ea
while ea < seg.end_ea:
if ida_bytes.is_code(ida_bytes.get_full_flags(ea)) and is_bl_to(ea, str_crypto_ea):
call_sites.append(ea)
ea = idaapi.next_head(ea, seg.end_ea)
seg = idaapi.get_next_seg(seg.start_ea)
if not silent:
ida_kernwin.msg(" str_crypto=0x%X BL_sites=%d\n" % (str_crypto_ea, len(call_sites)))
key = get_key()
seen = set()
ok = 0
fail = 0
patched = 0
made_str = 0
for call_ea in call_sites:
ptr = recover_ptr_x0(call_ea)
ln = recover_len_w1(call_ea)
if ptr is None or ln is None:
fail += 1
continue
if ln < min_len or ln > max_len:
fail += 1
continue
if not idaapi.getseg(ptr):
fail += 1
continue
uniq = (ptr, ln)
if uniq in seen:
continue
seen.add(uniq)
raw = ida_bytes.get_bytes(ptr, ln)
if raw is None:
fail += 1
continue
dec = decrypt_logic(raw, key)
ida_bytes.patch_bytes(ptr, dec)
patched += ln
ok += 1
if make_strlit_any(ptr, dec, min_len=4):
made_str += 1
if rename_strings:
try:
nm = ida_name.get_name(ptr)
except Exception:
nm = idc.get_name(ptr)
if (not nm) or (not nm.startswith("a")):
try:
ida_name.set_name(ptr, "a_%X" % ptr, ida_name.SN_CHECK)
except Exception:
idc.set_name(ptr, "a_%X" % ptr, idc.SN_CHECK)
if not silent:
ida_kernwin.msg(" done ok=%d fail=%d unique=%d patched=%d bytes strings=%d\n" % (ok, fail, len(seen), patched, made_str))
def find_arg_length(call_addr):
"""
向上回溯寻找 W1/X1 寄存器的赋值 (长度)
"""
curr = call_addr
# 向上回溯最多 15 条指令
for _ in range(15):
curr = idc.prev_head(curr)
mnem = idc.print_insn_mnem(curr)
# 匹配 MOV W1, #imm 或 MOV X1, #imm
# ARM64 中 W1/X1 是第二个参数
if mnem.startswith("MOV") and (idc.print_operand(curr, 0) == "W1" or idc.print_operand(curr, 0) == "X1"):
if idc.get_operand_type(curr, 1) == idc.o_imm:
return idc.get_operand_value(curr, 1)
return None
def find_arg_address(call_addr):
"""
向上回溯寻找 X0 寄存器的赋值 (数据地址)
处理 ADR X0, addr 和 ADRP+ADD 模式
"""
curr = call_addr
# 向上回溯最多 15 条指令
for _ in range(15):
curr = idc.prev_head(curr)
mnem = idc.print_insn_mnem(curr)
op0 = idc.print_operand(curr, 0)
# 目标必须是 X0 (第一个参数)
if op0 != "X0":
continue
# 情况1: ADR X0, label (直接地址)
if mnem == "ADR":
# 获取操作数1的引用地址
refs = list(idautils.DataRefsFrom(curr))
if refs:
return refs[0]
# 备用方案:尝试直接获取值
val = idc.get_operand_value(curr, 1)
if val != -1:
return val
# 情况2: ADD X0, X?, #offset (通常配合上面的 ADRP)
# 这种情况下,IDA 通常会把计算后的最终地址作为数据引用(dref)附加在指令上
elif mnem == "ADD":
refs = list(idautils.DataRefsFrom(curr))
if refs:
return refs[0]
return None
def process_call_site(call_addr):
"""处理单个调用点"""
print(f" 分析调用点: {hex(call_addr)}")
length = find_arg_length(call_addr)
data_addr = find_arg_address(call_addr)
if length is None:
print(f" [-] 未找到长度参数 (W1/X1)")
return
if data_addr is None:
print(f" [-] 未找到数据地址参数 (X0)")
return
# 安全检查
if length <= 0 or length > 4096:
print(f" [!] 长度异常 ({length}),跳过以防破坏数据库")
return
print(f" [+] 目标地址: {hex(data_addr)}, 长度: {hex(length)}")
# 读取密文
encrypted_bytes = ida_bytes.get_bytes(data_addr, length)
if not encrypted_bytes:
print(" [-] 读取内存失败")
return
# 解密
key = get_key()
decrypted_bytes = decrypt_logic(encrypted_bytes, key)
# 写入 IDA 数据库 (Patch)
ida_bytes.patch_bytes(data_addr, decrypted_bytes)
ida_bytes.del_items(data_addr, 0, length)
strtype = get_default_strtype()
ida_bytes.create_strlit(data_addr, length, strtype)
idaapi.plan_and_wait(data_addr, data_addr + length)
ida_auto.auto_wait()
ida_kernwin.refresh_idaview_anyway()
# 打印预览
preview = decrypted_bytes.decode('utf-8', errors='ignore').replace('\n', ' ')
print(f" [√] 解密完成: \"{preview}\"")
# 在调用点添加注释
idc.set_cmt(call_addr, f"Decrypted: {preview[:20]}...", 0)
def main():
print("=== 开始批量解密 str_crypto ===")
# 1. 确定函数地址
func_addr = TARGET_FUNC_ADDR
if func_addr is None:
func_addr = idc.get_screen_ea() # 获取光标所在位置
func_name = idc.get_func_name(func_addr)
print(f" 目标函数: {func_name} @ {hex(func_addr)}")
# 2. 遍历所有调用者 (Xrefs)
# CodeRefsTo(func_addr, 0) 获取所有调用这个函数的地址
refs = list(idautils.CodeRefsTo(func_addr, 0))
if not refs:
print("[-] 没有找到调用者,请确认光标是否在函数头,或函数已被分析。")
return
print(f" 找到 {len(refs)} 处调用")
for ref in refs:
process_call_site(ref)
print("=== 批量处理结束,请刷新反汇编视图 (F5) ===")
if __name__ == "__main__":
main()
#把鼠标点在该函数的第一行(或者在 BL str_crypto 的地方)运行
这个我就是为了简单的分析,实现能看的效果,不过我测试了一下感觉还不错,可以给大家看看效果,大家也可以在这个上面进行优化
反调试分析 libexec.so
线程追踪
这里先关注一下frida的反调试,爱加密企业版的反frida应该有许多大佬都有写过,但是我尝试了一下大佬们的代码,发现确实和我找的线程是一样的,但是不知道为什么就是不能绕过,就很纳闷。(下面我的代码可能某些部分是没复制全的,只复制了部分关键代码,可能需要大佬们在验证的时候自己补一下)
现在的反调试肯定是调用pthread_create这个函数
#include <pthread.h>
int pthread_create(pthread_t *thread, // 保存新创建线程的线程标识符tid
const pthread_attr_t *attr, // 线程属性对象的指针
void *(*start_routine)(void *), // 线程入口函数地址
void *arg); // 线程函数的参数
所以我们用这个来查看哪里调用了就行
function hook_dlopen() {
var dlopen = Module.findExportByName(null, "android_dlopen_ext");
if (dlopen) {
Interceptor.attach(dlopen, {
onEnter: function (args) {
this.path = args[0].isNull() ? "" : args[0].readCString();
var soName = Memory.readCString(args[0]);
console.log(`dlopen== soName=${soName}`);
if (soName.indexOf(soname1) >= 0) {
this.flag = true;
}
}, onLeave: function (retval) {
if (this.flag) {
hook_phread();
}
}
});
}
}
function hook_phread() {
const pthreadCreate = Module.findExportByName(null, "pthread_create");
if (!pthreadCreate) {
console.log("Failed to find pthread_create");
return;
}
Interceptor.attach(pthreadCreate, {
onEnter: function (args) {
const callSite = ptr(this.returnAddress);
const callSiteModule = Process.findModuleByAddress(callSite);
let callSiteOffset = null;
let callSiteModuleName = null;
if (callSiteModule) {
callSiteOffset = callSite.sub(callSiteModule.base);
callSiteModuleName = callSiteModule.name;
console.log("[pthread_create] called from", callSiteModuleName, "offset=" + callSiteOffset.toString());
} else {
console.log("[pthread_create] called from", callSite);
}
const startRoutine = args[2];
const startModule = Process.findModuleByAddress(startRoutine);
if (startModule) {
const startOffset = ptr(startRoutine).sub(startModule.base);
console.log("[pthread_create] start_routine in", startModule.name, "offset=" + startOffset.toString());
} else {
console.log("[pthread_create] start_routine at", startRoutine);
}
}
})
}
这样大家就可以找到是哪里调用的了,从而去一点点找到关键函数信息
可以发现是这样的
通过hook也可以指导这里的返回值是0,可以进去sub_42F90看看
这里就是我上面说的字符串混淆的地方,就可以使用上面的脚本了,其实就是一个异或,大家进去可以发现有异或,找到key解密就行了
我写了一份注释,大家可以参照看一下
__int64 sub_43364()
{
void ***v0; // x23
const char *v1; // x0
const char *v2; // x1
unsigned int v3; // w19
void **v4; // x8
const char *v5; // x20
__int64 v6; // x0
char *v7; // x19
__int64 v8; // x0
void **v9; // x8
__int64 v10; // x19
void **v11; // x8
__int64 v12; // x19
int v13; // w8
void **v14; // x9
char *v15; // x19
_BYTE *v16; // x20
char v17; // w24
int v18; // w25
unsigned int v19; // w8
char *v20; // x0
char *v21; // x21
void **v22; // x8
__int64 v23; // x19
__int64 (__fastcall *v24)(__int64, char *, _QWORD); // x20
unsigned int v25; // w9
char v26; // w0
const char *v28; // x20
unsigned int v29; // w9
const char *v30; // x20
unsigned int v31; // w9
char *v32; // [xsp+8h] [xbp-3F8h] BYREF
__int64 v33; // [xsp+10h] [xbp-3F0h] BYREF
void *ptr; // [xsp+18h] [xbp-3E8h] BYREF
int v35; // [xsp+24h] [xbp-3DCh] BYREF
__int64 v36; // [xsp+28h] [xbp-3D8h] BYREF
void *v37; // [xsp+30h] [xbp-3D0h] BYREF
void *v38; // [xsp+38h] [xbp-3C8h] BYREF
__int128 v39[2]; // [xsp+40h] [xbp-3C0h] BYREF
char v40; // [xsp+60h] [xbp-3A0h]
char v41[32]; // [xsp+70h] [xbp-390h] BYREF
char v42; // [xsp+90h] [xbp-370h]
int v43; // [xsp+A0h] [xbp-360h] BYREF
char v44; // [xsp+A4h] [xbp-35Ch]
char v45; // [xsp+A5h] [xbp-35Bh]
char v46; // [xsp+A6h] [xbp-35Ah]
__int16 v47; // [xsp+A7h] [xbp-359h]
char v48; // [xsp+A9h] [xbp-357h]
char v49; // [xsp+AAh] [xbp-356h]
char v50; // [xsp+ABh] [xbp-355h]
char v51; // [xsp+ACh] [xbp-354h]
char v52; // [xsp+ADh] [xbp-353h]
char v53; // [xsp+AEh] [xbp-352h]
char v54; // [xsp+AFh] [xbp-351h]
__int128 v55; // [xsp+B0h] [xbp-350h] BYREF
__int128 v56; // [xsp+C0h] [xbp-340h]
__int128 v57; // [xsp+D0h] [xbp-330h]
__int128 v58; // [xsp+E0h] [xbp-320h]
__int128 v59; // [xsp+F0h] [xbp-310h]
__int128 v60; // [xsp+100h] [xbp-300h]
__int128 v61; // [xsp+110h] [xbp-2F0h]
__int128 v62; // [xsp+120h] [xbp-2E0h]
__int128 v63; // [xsp+130h] [xbp-2D0h]
__int128 v64; // [xsp+140h] [xbp-2C0h]
__int128 v65; // [xsp+150h] [xbp-2B0h]
__int128 v66; // [xsp+160h] [xbp-2A0h]
__int128 v67; // [xsp+170h] [xbp-290h]
__int128 v68; // [xsp+180h] [xbp-280h]
__int128 v69; // [xsp+190h] [xbp-270h]
__int128 v70; // [xsp+1A0h] [xbp-260h]
char s[48]; // [xsp+1B0h] [xbp-250h] BYREF
char v72[520]; // [xsp+1E0h] [xbp-220h] BYREF
__int64 v73; // [xsp+3E8h] [xbp-18h]
v73 = *(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
v0 = off_E3290;
v1 = *&byte_24C[*off_E3290 + 116];
if ( !v1 || (v2 = *(&qword_90 + *off_E3290)) == 0LL || strcmp_0(v1, v2) )
{
memset(v72, 0, 512);
sub_42A28(v72);
if ( self_strstr(v72, ":") )
return 1;
}
v4 = *v0;
if ( !*(&qword_1D0 + *v0 + 7) )
{
v23 = *(&qword_88 + v4);
v24 = *(*(&qword_48 + v4) + 104);
v25 = atomic_load(&dword_F1098);
if ( !v25 && !dword_F1098 )
{
atomic_store(1u, &dword_F1098);
str_decrypt1(aGqu, 0xCu);
}
v26 = v24(v23, aGqu, 0LL);
v3 = 0;
if ( v26 )
return v3;
v4 = *v0;
}
memset(s, 0, 33);
v5 = *(&qword_90 + v4);
v3 = 0;
if ( !(loc_43B7C)() ) // 这里有三个svc的调用,openat,read,close这三个调用,就是打开某个文件,大概率是maps文件
{
v6 = strlen(v5);
(**(&word_38 + *v0))(v5, v6, s);
v7 = strdup(s);
v8 = strlen(v7);
(**(&word_38 + *v0))(v7, v8, s);
free(v7);
v9 = *v0;
v69 = 0u;
v70 = 0u;
v67 = 0u;
v68 = 0u;
v65 = 0u;
v66 = 0u;
v63 = 0u;
v64 = 0u;
v61 = 0u;
v62 = 0u;
v59 = 0u;
v60 = 0u;
v57 = 0u;
v58 = 0u;
v55 = 0u;
v56 = 0u;
v10 = *(&word_38 + *&byte_24C[v9 + 124]);
LODWORD(v9) = atomic_load(&dword_F10B8);
if ( !v9 && !dword_F10B8 )
{
atomic_store(1u, &dword_F10B8);
str_decrypt1(aEng, 9u);
}
sub_432C0(&v55, 256LL, aEng, v10);
if ( !(*(*(&qword_48 + *v0) + 112))(*(&qword_88 + *v0), &v55, &v38, v39) || !v38 || !*&v39[0] )
return 0;
v41[10] = s[14] ^ s[13];
v41[11] = s[15] ^ s[14];
v41[0] = s[4] ^ s[3];
v41[1] = s[5] ^ s[4];
v41[2] = s[6] ^ s[5];
v41[3] = s[7] ^ s[6];
v41[4] = s[8] ^ s[7];
v41[5] = s[9] ^ s[8];
v41[6] = s[10] ^ s[9];
v41[7] = s[11] ^ s[10];
v41[8] = s[12] ^ s[11];
v41[9] = s[13] ^ s[12];
v41[12] = s[16] ^ s[15];
v41[13] = s[17] ^ s[16];
v41[14] = s[18] ^ s[17];
v41[15] = s[19] ^ s[18];
sub_40004(v72);
v43 = 0;
sub_403E0(v72, LODWORD(v39[0]), v38, v38, &v43);
v11 = *v0;
v69 = 0u;
v70 = 0u;
v67 = 0u;
v68 = 0u;
v65 = 0u;
v66 = 0u;
v63 = 0u;
v64 = 0u;
v61 = 0u;
v62 = 0u;
v59 = 0u;
v60 = 0u;
v57 = 0u;
v58 = 0u;
v55 = 0u;
v56 = 0u;
v12 = *(&dword_40 + *&byte_24C[v11 + 124]);
LODWORD(v11) = atomic_load(&dword_F10B8);
if ( !v11 && !dword_F10B8 )
{
atomic_store(1u, &dword_F10B8);
str_decrypt1(aEng, 9u);
}
sub_432C0(&v55, 256LL, aEng, v12);
if ( (*(*(&qword_48 + *v0) + 112))(*(&qword_88 + *v0), &v55, &v37, &v36) )
{
v3 = 0;
v13 = 27;
if ( v37 && v36 )
{
v49 = s[19] ^ s[18];
v50 = s[20] ^ s[19];
v43 = *&s[9] ^ *&s[8];
v44 = s[13] ^ s[12];
v45 = s[14] ^ s[13];
v46 = s[15] ^ s[14];
v47 = *&s[16] ^ *&s[15];
v48 = s[18] ^ s[17];
v51 = s[21] ^ s[20];
v52 = s[22] ^ s[21];
v53 = s[23] ^ s[22];
v54 = s[24] ^ s[23];
sub_40004(v72);
v35 = 0;
sub_403E0(v72, v36, v37, v37, &v35);
v14 = *v0;
v32 = 0LL;
v15 = v38;
v16 = *(&dword_B0 + v14);
LODWORD(v14) = atomic_load(&dword_F10C8);
if ( !v14 && !dword_F10C8 )
{
atomic_store(1u, &dword_F10C8);
str_decrypt1(aVivoY75, 8u);
}
if ( self_strstr(v16, aVivoY75) )
goto LABEL_24;
v28 = *(&dword_B0 + *v0);
v29 = atomic_load(&dword_F10B0);
if ( !v29 && !dword_F10B0 )
{
atomic_store(1u, &dword_F10B0);
str_decrypt1(aVivoY35a, 9u);
}
if ( !strcmp_0(v28, aVivoY35a) ) // 这里竟然是设备信息还检测到了我的两个手机的设备信息???一个pixel,一个vivo
goto LABEL_24;
v30 = *(&dword_B0 + *v0);
v31 = atomic_load(&dword_F10B4);
if ( !v31 && !dword_F10B4 )
{
atomic_store(1u, &dword_F10B4);
str_decrypt1(aCoolpadY8039, 0xEu);
}
if ( !strcmp_0(v30, aCoolpadY8039) || *(&qword_1D0 + *v0 + 6) )// 这里查看了assert
// 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
// 74244e9c74 43 6f 6f 6c 70 61 64 20 59 38 30 33 2d 39 00 00 Coolpad Y803-9..
// 74244e9c84 61 73 73 65 74 73 2f 25 73 00 00 00 2c 14 42 00 assets/%s...,.B.
{
LABEL_24:
sub_33808(0x1388u); // 这里有一个svc的sleep函数
v17 = 0;
}
else
{
v17 = 1;
}
v18 = 0;
while ( 1 )
{
v19 = atomic_load(&dword_F10C4);
if ( !v19 && !dword_F10C4 )
{
atomic_store(1u, &dword_F10C4);
str_decrypt1("\t7", 2u);
}
v20 = strtok_r_0(v15, "\t7", &v32);
if ( !v20 )
break;
v21 = v20;
sub_761B8(v20);
if ( *v21 )
{
ptr = 0LL;
if ( (v17 & 1) == 0 )
sub_33808(0xAu);
if ( !(*(*(&qword_48 + *v0) + 24))(*(&qword_88 + *v0), v21, &ptr, &v33) )
{
LABEL_48:
v3 = 0;
v13 = 1;
goto LABEL_49;
}
if ( ptr && v33 )
{
v22 = *v0;
v42 = 0;
memset(v41, 0, sizeof(v41));
v40 = 0;
memset(v39, 0, sizeof(v39));
(*(*(&word_38 + v22) + 8LL))();
sub_75D3C(v39, v37 + (32 * v18), 32LL);
if ( TssSDK::strncmp_x(v39, v41, 0x20u) )// 环境检测没到达这里
goto LABEL_48;
++v18;
free(ptr);
v15 = 0LL;
}
}
else
{
v15 = 0LL;
}
}
if ( v37 )
free(v37);
v13 = 0;
v3 = 1;
}
}
else
{
v3 = 0;
v13 = 27;
}
LABEL_49:
if ( v13 > 26 || !v13 )
{
if ( v38 )
free(v38);
}
else
{
return 0;
}
}
return v3;
}
回溯调用:
其实可以发现没有什么特别重要的,这里我们交叉引用往上回溯看看吧
可以发现是一个数组,分析一下43364的函数,再交叉引用看看E5BB0是谁调用的
发现43364就是刚才看到的,那就只能交叉引用了
继续交叉引用
那就只能看看这些函数了,同时我们要尽可能去恢复所有的字符串,然后有可能大家在恢复的时候就看到frida了,我当时看的时候就看到frida的字符串了
大家在看上面这些表的时候可能会注意到DCQ sub_53CBC这个地方
看起来是和我们开始的时候看到的那个线程的结构是一样的,看一下off_E7DB0
也是一个数组,那我们可以肯定这里面肯定会存在重要函数
看第一个出现了一个qemu的
继续看第二个
又遇到一个这样的,那就恢复吧,下面我给一下带有注释的
__int64 sub_55EB8()
{
unsigned int v0; // w9
unsigned __int64 v1; // x0
unsigned int v2; // w9
unsigned __int64 v3; // x0
void ***v4; // x24
__int64 v5; // x19
__int64 (__fastcall *v6)(__int64, char *); // x20
unsigned int v7; // w9
__int64 v8; // x19
unsigned int v9; // w9
unsigned int v11; // w9
unsigned __int64 v12; // x0
unsigned int v13; // w9
unsigned __int64 v14; // x0
unsigned int v15; // w9
unsigned __int64 v16; // x0
__int64 v17; // x19
__int64 (__fastcall *v18)(__int64, char *); // x20
unsigned int v19; // w9
__int64 v20; // x19
unsigned int v21; // w9
unsigned __int64 v22; // x0
void **v23; // x8
__int64 v24; // x19
__int64 (__fastcall *v25)(__int64, char *); // x20
unsigned int v26; // w9
__int64 v27; // x19
__int64 v28; // x20
void (__fastcall *v29)(__int64, __int64 *, char *, char *, char *, __int64); // x21
unsigned int v30; // w9
unsigned int v31; // w9
unsigned int v32; // w9
unsigned int v33; // w9
unsigned int v34; // w8
void **v35; // x8
int v36; // w22
int v37; // w9
unsigned __int64 v38; // t2
char *v39; // x0
char *v40; // x19
char *v41; // x0
int v42; // w14
int v43; // w16
char *v44; // x20
int v45; // w0
int v46; // w1
unsigned int v47; // w14
int v48; // w15
unsigned int v49; // w12
unsigned int v50; // w14
int v51; // w0
unsigned int v52; // w14
unsigned int v53; // w16
int v54; // w0
unsigned int v55; // w3
int v56; // w12
int v57; // w13
int v58; // w5
unsigned int v59; // w16
int v60; // w2
unsigned int v61; // w14
int v62; // w6
int v63; // w1
int v64; // w13
unsigned int v65; // w2
int v66; // w14
int v67; // w3
unsigned int v68; // w5
int v69; // w16
int v70; // w2
signed int v71; // w2
int v72; // w12
int v73; // w13
unsigned int v74; // w13
int v75; // w26
unsigned int v76; // w8
unsigned int v77; // w9
int v78; // w23
unsigned int v79; // w8
_BYTE *v80; // x0
unsigned int v81; // w8
unsigned int v82; // w8
unsigned int v83; // w8
unsigned int v84; // w8
_BYTE *v85; // x0
unsigned __int8 *v86; // x25
unsigned int v87; // w8
unsigned __int8 *v88; // x8
unsigned __int8 *v89; // x11
__int64 v90; // x12
int v91; // w13
unsigned int v93; // w8
unsigned int v94; // w8
_BYTE *v95; // x0
unsigned int v96; // w8
_BOOL4 v97; // w21
int v98; // w9
unsigned int v99; // w8
unsigned __int64 v100; // t2
void **v101; // x20
int v102; // w9
unsigned int v103; // w8
unsigned __int64 v104; // t2
__int64 (__fastcall *v105)(char *, char *); // x19
unsigned int v106; // w8
unsigned int v107; // w8
unsigned __int8 v108; // w19
__int64 (__fastcall *v109)(char *, char *); // x26
unsigned int v110; // w8
unsigned int v111; // w8
unsigned __int8 v112; // w0
int v113; // w9
unsigned int v114; // w8
unsigned __int64 v115; // t2
void (__fastcall *v116)(char *, char *); // x19
unsigned int v117; // w8
unsigned int v118; // w8
void (__fastcall *v119)(char *, char *); // x19
unsigned int v120; // w8
unsigned int v121; // w8
__int64 (__fastcall *v122)(char *, char *, _QWORD); // x20
unsigned int v123; // w8
unsigned int v124; // w9
unsigned __int8 (*v125)(void); // x8
__int64 (__fastcall *v126)(char *, char *, _QWORD); // x20
unsigned int v127; // w8
unsigned int v128; // w9
unsigned int v129; // w9
int v130; // w0
int v131; // w9
int v132; // w9
unsigned int v133; // w9
unsigned int v134; // w9
unsigned __int64 v135; // x0
unsigned __int64 v136; // [xsp+8h] [xbp-E8h]
int v137; // [xsp+10h] [xbp-E0h]
int v138; // [xsp+14h] [xbp-DCh]
int v139; // [xsp+18h] [xbp-D8h]
int v140; // [xsp+1Ch] [xbp-D4h]
int v141; // [xsp+20h] [xbp-D0h]
int v142; // [xsp+24h] [xbp-CCh]
__int64 v143; // [xsp+28h] [xbp-C8h] BYREF
unsigned __int64 v144; // [xsp+30h] [xbp-C0h] BYREF
unsigned __int8 *v145; // [xsp+38h] [xbp-B8h] BYREF
char *v146; // [xsp+40h] [xbp-B0h] BYREF
char v147[8]; // [xsp+48h] [xbp-A8h] BYREF
__int128 v148; // [xsp+50h] [xbp-A0h] BYREF
__int128 v149; // [xsp+60h] [xbp-90h]
__int128 v150; // [xsp+70h] [xbp-80h]
__int128 v151; // [xsp+80h] [xbp-70h]
__int128 v152; // [xsp+90h] [xbp-60h]
__int128 v153; // [xsp+A0h] [xbp-50h]
__int128 v154; // [xsp+B0h] [xbp-40h]
__int128 v155; // [xsp+C0h] [xbp-30h]
__int64 v156; // [xsp+D8h] [xbp-18h]
v156 = *(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
v0 = atomic_load(&dword_F1678);
if ( !v0 && !dword_F1678 )
{
atomic_store(1u, &dword_F1678);
str_decrypt4(aDataDexname, 0xDu);
}
v1 = linux_eabi_syscall(__NR_faccessat, -100, aDataDexname, 0, 0);// "/data/dexname"检测了这个玩意
if ( v1 < 0xFFFFFFFFFFFFF001LL )
{
if ( v1 != -1 )
return 1LL;
}
else
{
*_errno(v1) = -v1;
}
v2 = atomic_load(&dword_F167C);
if ( !v2 && !dword_F167C )
{
atomic_store(1u, &dword_F167C);
str_decrypt4(aSystemLib64Lib_0, 0x20u);
}
v3 = linux_eabi_syscall(__NR_faccessat, -100, aSystemLib64Lib_0, 0, 0);// "/system/lib64/libxiaojianbang.so"检测这个so
if ( v3 < 0xFFFFFFFFFFFFF001LL )
{
if ( v3 != -1 )
return 1LL;
}
else
{
*_errno(v3) = -v3;
}
v4 = off_E3290;
v5 = *(&word_10 + *off_E3290);
v6 = *(*v5 + 48LL);
v7 = atomic_load(&dword_F16C4);
if ( !v7 && !dword_F16C4 )
{
atomic_store(1u, &dword_F16C4);
str_decrypt4(aCnYoulorUnpack, 0x12u);
}
v8 = v6(v5, aCnYoulorUnpack);
(*(*(&qword_20 + *v4) + 208))(*(&word_10 + *v4));// cn/youlor/Unpacker"调用查看这个
if ( v8 )
return 1LL;
v9 = atomic_load(&dword_F16C8);
if ( !v9 && !dword_F16C8 )
{
atomic_store(1u, &dword_F16C8);
str_decrypt4(aLibsotweakSo, 0xDu);
}
if ( dlopen(aLibsotweakSo, 0) ) // libsotweak.so
return 1LL;
v11 = atomic_load(&dword_F16CC);
if ( !v11 && !dword_F16CC )
{
atomic_store(1u, &dword_F16CC);
str_decrypt4(aDataLocalTmpUn, 0x1Fu);
}
v12 = linux_eabi_syscall(__NR_faccessat, -100, aDataLocalTmpUn, 0, 0);// "/data/local/tmp/unpacker.config"
if ( v12 < 0xFFFFFFFFFFFFF001LL )
{
if ( v12 != -1 )
return 1LL;
}
else
{
*_errno(v12) = -v12;
}
v13 = atomic_load(&dword_F16EC);
if ( !v13 && !dword_F16EC )
{
atomic_store(1u, &dword_F16EC);
str_decrypt4(aDataLocalTmpAu, 0x1Bu);
}
v14 = linux_eabi_syscall(__NR_faccessat, -100, aDataLocalTmpAu, 0, 0);// /data/local/tmp/aupk.config检测这个
if ( v14 < 0xFFFFFFFFFFFFF001LL )
{
if ( v14 != -1 )
return 1LL;
}
else
{
*_errno(v14) = -v14;
}
v15 = atomic_load(&dword_F16F0);
if ( !v15 && !dword_F16F0 )
{
atomic_store(1u, &dword_F16F0);
str_decrypt4(aDataFart, 0xAu);
}
v16 = linux_eabi_syscall(__NR_faccessat, -100, aDataFart, 0, 0);// 检测fart脱壳机,/data/fart
if ( v16 < 0xFFFFFFFFFFFFF001LL )
{
if ( v16 != -1 )
return 1LL;
}
else
{
*_errno(v16) = -v16;
}
v17 = *(&word_10 + *v4);
v18 = *(*v17 + 48LL);
v19 = atomic_load(&dword_F16F4);
if ( !v19 && !dword_F16F4 )
{
atomic_store(1u, &dword_F16F4);
str_decrypt4(aCnMikFartext, 0xEu);
}
v20 = v18(v17, aCnMikFartext); // "cn/mik/Fartext"依旧fart
(*(*(&qword_20 + *v4) + 208))(*(&word_10 + *v4));
if ( v20 )
return 1LL;
v21 = atomic_load(&dword_F16F8);
if ( !v21 && !dword_F16F8 )
{
atomic_store(1u, &dword_F16F8);
str_decrypt4(aDataSystemMikC, 0x15u);
}
v22 = linux_eabi_syscall(__NR_faccessat, -100, aDataSystemMikC, 0, 0);// /data/system/mik.conf检测
if ( v22 < 0xFFFFFFFFFFFFF001LL )
{
if ( v22 != -1 )
return 1LL;
}
else
{
*_errno(v22) = -v22;
}
v23 = *v4;
v143 = 0LL;
v24 = *(&word_10 + v23);
v25 = *(*v24 + 1336LL);
v26 = atomic_load(&dword_F16FC);
if ( !v26 && !dword_F16FC )
{
atomic_store(1u, &dword_F16FC);
str_decrypt4(aMikrom, 6u);
}
v27 = v25(v24, aMikrom);
v28 = *(&word_10 + *v4);
v29 = *(*(&qword_20 + *v4) + 128);
v30 = atomic_load(&dword_F1700);
if ( !v30 && !dword_F1700 )
{
atomic_store(1u, &dword_F1700);
str_decrypt4(aAndroidOsServi, 0x19u);
}
v31 = atomic_load(&dword_F1704);
if ( !v31 && !dword_F1704 )
{
atomic_store(1u, &dword_F1704);
str_decrypt4(aLjavaLangStrin_8, 0x28u);
}
v32 = atomic_load(&dword_F1708);
if ( !v32 && !dword_F1708 )
{
atomic_store(1u, &dword_F1708);
str_decrypt4(aGetservice, 0xAu);
}
v29(v28, &v143, aAndroidOsServi, aLjavaLangStrin_8, aGetservice, v27);
(*(**(&word_10 + *v4) + 184LL))(*(&word_10 + *v4), v27);
if ( v143 )
return 1LL;
v33 = atomic_load(&dword_F170C);
if ( !v33 && !dword_F170C )
{
atomic_store(1u, &dword_F170C);
str_decrypt4(aDataLocalTmpRe, 0x1Fu);
}
v34 = atomic_load(&dword_F1710);
if ( !v34 && !dword_F1710 )
{
atomic_store(1u, &dword_F1710);
str_decrypt4(aReFridaServer, 0xFu);
}
if ( !strstr(aDataLocalTmpRe, aReFridaServer) )// /data/local/tmp/re.frida.server "re.frida.server检测frida
return 1LL;
v35 = *v4;
v36 = *(&qword_D8 + *v4 + 4);
if ( v36 < 23 )
goto LABEL_208;
v37 = v36 + 926;
if ( v36 - 97 >= 0 )
v37 = v36 - 97;
HIDWORD(v38) = -1431655765
* (v36 - 97 - (v37 & 0xFFFFFC00) + 1)
* (v36 - 97 - (v37 & 0xFFFFFC00))
* ((2 * (v36 - 97 - (v37 & 0xFFFFFC00))) | 1)
+ 715827882;
LODWORD(v38) = HIDWORD(v38);
if ( (v38 >> 1) > 0x2AAAAAAA )
{
sub_57594(v147);
v146 = 0LL;
}
do
{
v39 = sub_57594(v147); // 打开maps文件
v146 = 0LL;
}
while ( ((((v36 - 13) % 0x7FFF + 1) * ((v36 - 13) % 0x7FFF)) & 1) != 0 );
v40 = v39;
if ( !v39 )
{
v97 = 0;
goto LABEL_172;
}
v41 = strtok_r_0(v39, "\n", &v146);
if ( !v41 )
{
LABEL_161:
v97 = 0;
goto LABEL_167;
}
v42 = v36 + 1002;
v43 = v36 + 52;
v44 = v41;
v45 = v36 + 83;
if ( v36 - 21 >= 0 )
v42 = v36 - 21;
v46 = v36 - 46;
v47 = v42 & 0xFFFFFC00;
if ( v43 >= 0 )
v45 = v36 + 52;
v48 = v36 + 14;
v49 = v36 - 21 - v47;
v50 = v45 & 0xFFFFFFE0;
if ( v46 >= 0 )
v51 = v36 - 46;
else
v51 = v36 - 15;
v52 = v43 - v50;
v53 = v51 & 0xFFFFFFE0;
if ( v48 >= 0 )
v54 = v36 + 14;
else
v54 = v36 + 525;
v55 = v52 + 2;
v56 = (v49 + 1) * v49 * ((2 * v49) | 1);
v57 = v52 + 2 + v52 + 1;
v58 = v36 - 55;
v59 = v46 - v53;
v60 = (v52 + 1) * (v52 + 2);
v61 = v57 + v52;
v62 = v36 + 968;
if ( v36 + 39 >= 0 )
v63 = v36 + 39;
else
v63 = v36 + 550;
v64 = v60 * v57;
v65 = v59 + 2;
v66 = v61 * v55;
if ( v58 >= 0 )
v62 = v36 - 55;
v67 = (v59 + 1) * v65 * (2 * v59 + 3);
v68 = v58 - (v62 & 0xFFFFFC00);
v69 = (3 * v59 + 3) * v65;
if ( v36 - 52 >= 0 )
v70 = v36 - 52;
else
v70 = v36 + 971;
v141 = v56 % 6;
v71 = (v36 - 52 - (v70 & 0xFFFFFC00) + 1)
* (v36 - 52 - (v70 & 0xFFFFFC00))
* ((2 * (v36 - 52 - (v70 & 0xFFFFFC00))) | 1);
v72 = v64 * (v66 - 1);
if ( v36 - 17 >= 0 )
v73 = v36 - 17;
else
v73 = v36 - 2;
v138 = ((v68 + 1) * v68 * (v68 + 2)) % 6;
v74 = v36 - 17 - (v73 & 0xFFFFFFF0);
v140 = v71 % 6;
v75 = v72 % 30;
v139 = v67 * (v69 - 1) % 30;
v142 = ((v74 + 1) * v74 * (v74 + 2) * (v74 + 3) * (v74 + 4) * (v74 + 5) * (v74 + 6)) % 30;
v76 = (v48 - (v54 & 0xFFFFFE00) + 1) * (v48 - (v54 & 0xFFFFFE00));
v77 = (v36 + 39 - (v63 & 0xFFFFFE00) + 1) * (v36 + 39 - (v63 & 0xFFFFFE00));
v78 = (v76 * v76) & 3;
v137 = (v77 * v77) & 3;
while ( 1 )
{
if ( v141 )
goto LABEL_107;
while ( 1 )
{
v79 = atomic_load(&dword_F172C);
if ( !v79 && !dword_F172C )
{
atomic_store(1u, &dword_F172C);
str_decrypt4(aH_1, 0x11u);
}
v80 = self_strstr(v44, aH_1); // 这个是检测frida的,参数是frida-agent-64.so
if ( !v75 )
break;
LABEL_107:
v81 = atomic_load(&dword_F172C);
if ( !v81 && !dword_F172C )
{
atomic_store(1u, &dword_F172C);
str_decrypt4(aH_1, 0x11u);
}
self_strstr(v44, aH_1); // frida-agent-64.so同样的检测
}
if ( v80 )
goto LABEL_166;
v82 = atomic_load(&dword_F1710);
if ( !v82 && !dword_F1710 )
{
atomic_store(1u, &dword_F1710);
str_decrypt4(aReFridaServer, 0xFu);
}
if ( self_strstr(v44, aReFridaServer) )
goto LABEL_166;
v83 = atomic_load(&dword_F1730);
if ( !v83 && !dword_F1730 )
{
atomic_store(1u, &dword_F1730);
str_decrypt4(asc_E8414, 7u);
}
if ( self_strstr(v44, asc_E8414) ) // /memfd:
{
v154 = 0u;
v155 = 0u;
v152 = 0u;
v153 = 0u;
v150 = 0u;
v151 = 0u;
v148 = 0u;
v149 = 0u;
v84 = atomic_load(&dword_F1734);
if ( !v84 && !dword_F1734 )
{
atomic_store(1u, &dword_F1734);
str_decrypt4(asc_E841C, 0xAu); // %lx-%lx %s
}
sscanf(v44, asc_E841C, &v145, &v144, &v148);
if ( (v144 - v145) > 0x200000 )
{
if ( v139 )
self_strstr(&v148, "r");
do
v85 = self_strstr(&v148, "r");
while ( v78 );
if ( v85 && self_strstr(&v148, "p") )
break;
}
}
LABEL_146:
if ( *(&dword_1CC + *v4 + 3) )
{
v93 = atomic_load(&dword_F173C);
if ( !v93 && !dword_F173C )
{
atomic_store(1u, &dword_F173C);
str_decrypt4(aL_1, 0xCu);
}
if ( self_strstr(v44, aL_1) )
goto LABEL_166;
if ( v140 )
{
LABEL_156:
v96 = atomic_load(&dword_F1744);
if ( !v96 && !dword_F1744 )
{
atomic_store(1u, &dword_F1744);
str_decrypt4(aDataAdb, 0xAu);
}
self_strstr(v44, aDataAdb); // "/data/adb/
}
v94 = atomic_load(&dword_F1744);
if ( !v94 && !dword_F1744 )
{
atomic_store(1u, &dword_F1744);
str_decrypt4(aDataAdb, 0xAu);
}
v95 = self_strstr(v44, aDataAdb);
if ( v142 )
goto LABEL_156;
if ( v95 )
goto LABEL_166;
}
v44 = strtok_r_0(0LL, "\n", &v146);
if ( !v44 )
goto LABEL_161;
}
v86 = v145;
if ( *v145 != 127 || v145[1] != 69 )
goto LABEL_145;
v136 = v144;
v87 = atomic_load(&dword_F1740);
if ( !v87 && !dword_F1740 )
{
atomic_store(1u, &dword_F1740);
str_decrypt4(aFridaagentstop, 0x14u);
}
v88 = v86 + 0x200000;
if ( (v86 + 0x200000) >= v136 )
v88 = v136;
if ( v88 - 20 <= v86 )
{
LABEL_145:
if ( v138 )
goto LABEL_165;
goto LABEL_146;
}
v89 = v86;
while ( 1 )
{
if ( *v89 == aFridaagentstop[0] ) // FridaAgentStopReason
{
v90 = &dword_0 + 1;
do
{
v91 = v90 + 1;
if ( v89[v90] != aFridaagentstop[v90] )
break;
}
while ( v88 > &v86[v90++] );
if ( (v91 - 1) >= 0x14 )
break;
}
++v89;
++v86;
if ( v88 - 20 <= v89 )
goto LABEL_145;
}
if ( v137 )
{
while ( 1 )
;
}
if ( v138 )
{
while ( 1 )
LABEL_165:
;
}
LABEL_166:
v97 = 1;
LABEL_167:
if ( ((((v36 - 10) % 0x7FFF + 1) * ((v36 - 10) % 0x7FFF)) & 1) != 0 )
goto LABEL_171;
while ( 1 )
{
free(v40);
v98 = v36 + 81;
if ( v36 + 50 >= 0 )
v98 = v36 + 50;
v99 = v36 + 50 - (v98 & 0xFFFFFFE0);
HIDWORD(v100) = -286331153
* (v99 + 1)
* (v99 + 2)
* (v99 + 2 + v99 + 1)
* ((v99 + 2 + v99 + 1 + v99) * (v99 + 2) - 1)
+ 143165576;
LODWORD(v100) = HIDWORD(v100);
if ( (v100 >> 1) <= 0x8888888 )
break;
LABEL_171:
free(v40);
}
LABEL_172:
if ( !v97 )
{
v101 = *v4;
if ( *(&dword_1C8 + *v4 + 1) )
{
v102 = v36 - 51;
if ( v36 - 66 >= 0 )
v102 = v36 - 66;
v103 = v36 - 66 - (v102 & 0xFFFFFFF0);
HIDWORD(v104) = -286331153 * (v103 + 1) * v103 * (v103 + 2) * (v103 + 3) * (v103 + 4) * (v103 + 5) * (v103 + 6)
+ 143165576;
LODWORD(v104) = HIDWORD(v104);
if ( (v104 >> 1) > 0x8888888 )
goto LABEL_192;
while ( 1 )
{
v105 = *(*v101 + 5);
v106 = atomic_load(&dword_F16B0);
if ( !v106 && !dword_F16B0 )
{
atomic_store(1u, &dword_F16B0);
str_decrypt4(aLibcSo_3, 7u);
}
v107 = atomic_load(&dword_F1728);
if ( !v107 && !dword_F1728 )
{
atomic_store(1u, &dword_F1728);
str_decrypt4(aKg, 4u);
}
v108 = v105(aLibcSo_3, aKg); // 这里是exit
v109 = *(**v4 + 5);
v110 = atomic_load(&dword_F16B0);
if ( !v110 && !dword_F16B0 )
{
atomic_store(1u, &dword_F16B0);
str_decrypt4(aLibcSo_3, 7u);
}
v111 = atomic_load(&dword_F1748);
if ( !v111 && !dword_F1748 )
{
atomic_store(1u, &dword_F1748);
str_decrypt4(str_pthread_create, 0xEu);
}
v112 = v109(aLibcSo_3, str_pthread_create);// pthread_create这里调用的就是线程创建的函数
v113 = v36 + 50;
if ( v36 + 19 >= 0 )
v113 = v36 + 19;
v114 = v36 + 19 - (v113 & 0xFFFFFFE0);
HIDWORD(v115) = -858993459 * (v114 + 1) * v114 * (v114 + 2) * (v114 + 3) * (v114 + 4) * (v114 + 5) + 429496728;
LODWORD(v115) = HIDWORD(v115);
if ( (v115 >> 2) <= 0xCCCCCCC )
break;
LABEL_192:
v116 = *(*v101 + 5);
v117 = atomic_load(&dword_F16B0);
if ( !v117 && !dword_F16B0 )
{
atomic_store(1u, &dword_F16B0);
str_decrypt4(aLibcSo_3, 7u);
}
v118 = atomic_load(&dword_F1728);
if ( !v118 && !dword_F1728 )
{
atomic_store(1u, &dword_F1728);
str_decrypt4(aKg, 4u);
}
v116(aLibcSo_3, aKg);
v119 = *(**v4 + 5);
v120 = atomic_load(&dword_F16B0);
if ( !v120 && !dword_F16B0 )
{
atomic_store(1u, &dword_F16B0);
str_decrypt4(aLibcSo_3, 7u);
}
v121 = atomic_load(&dword_F1748);
if ( !v121 && !dword_F1748 )
{
atomic_store(1u, &dword_F1748);
str_decrypt4(str_pthread_create, 0xEu);
}
v119(aLibcSo_3, str_pthread_create); // pthread_create
}
v97 = (v112 | v108) != 0;
}
}
if ( v97 )
return 1LL;
v35 = *v4;
LABEL_208:
if ( *(&dword_1CC + v35 + 3) )
{
v122 = *(*v35 + 3);
v123 = atomic_load(&dword_F1750);
if ( !v123 && !dword_F1750 )
{
atomic_store(1u, &dword_F1750);
str_decrypt4(str_libnativebridge_so, 0x12u);
}
v124 = atomic_load(&dword_F1754);
if ( !v124 && !dword_F1754 )
{
atomic_store(1u, &dword_F1754);
str_decrypt4(aNativebridgeer, 0x11u);
}
v125 = v122(str_libnativebridge_so, aNativebridgeer, 0LL);
if ( !v125 )
{
v126 = *(**v4 + 3);
v127 = atomic_load(&dword_F1750);
if ( !v127 && !dword_F1750 )
{
atomic_store(1u, &dword_F1750);
str_decrypt4(str_libnativebridge_so, 0x12u);
}
v128 = atomic_load(&dword_F1758);
if ( !v128 && !dword_F1758 )
{
atomic_store(1u, &dword_F1758);
str_decrypt4(aZn7android17na, 0x20u);
}
v125 = v126(str_libnativebridge_so, aZn7android17na, 0LL);// libnativebridge.so
if ( !v125 )
goto LABEL_244;
}
if ( !v125() )
{
LABEL_244:
v154 = 0u;
v155 = 0u;
v152 = 0u;
v153 = 0u;
v150 = 0u;
v151 = 0u;
v148 = 0u;
v149 = 0u;
v129 = atomic_load(&dword_F1714);
if ( !v129 && !dword_F1714 )
{
atomic_store(1u, &dword_F1714);
str_decrypt4(aRoDalvikVmNati, 0x1Au);
}
v130 = sub_55B90(aRoDalvikVmNati, &v148);
if ( v130 <= 0 )
{
v131 = v130 + 160;
if ( v130 + 33 >= 0 )
v131 = v130 + 33;
while ( (((v130 + 33 - (v131 & 0xFFFFFF80) + 1)
* (v130 + 33 - (v131 & 0xFFFFFF80))
* (v130 + 33 - (v131 & 0xFFFFFF80) + 2)
* (v130 + 33 - (v131 & 0xFFFFFF80) + 3)) & 7) != 0 )
;
}
v132 = v130 + 497;
if ( v130 - 14 >= 0 )
v132 = v130 - 14;
while ( (((v130 - 14 - (v132 & 0xFFFFFE00) + 1)
* (v130 - 14 - (v132 & 0xFFFFFE00))
* (v130 - 14 - (v132 & 0xFFFFFE00) + 1)
* (v130 - 14 - (v132 & 0xFFFFFE00))) & 3) != 0 )
;
if ( v130 < 1 )
goto LABEL_245;
v133 = atomic_load(&dword_F1718);
if ( !v133 && !dword_F1718 )
{
atomic_store(1u, &dword_F1718);
str_decrypt4(aBi_0, 0x10u);
}
if ( !self_strstr(&v148, aBi_0) )
{
v134 = atomic_load(&dword_F1724);
if ( !v134 && !dword_F1724 )
{
atomic_store(1u, &dword_F1724);
str_decrypt4(aSystemLibLibri, 0x1Cu);
}
v135 = linux_eabi_syscall(__NR_faccessat, -100, aSystemLibLibri, 0, 0);// 检测root
// /system/lib/libriruloader.so 是著名的 Android Root 框架 Riru 的核心组件之一。
if ( v135 >= 0xFFFFFFFFFFFFF001LL )
{
*_errno(v135) = -v135;
goto LABEL_245;
}
if ( v135 == -1 )
{
LABEL_245:
(**(&stru_2F8.st_value + *v4))(v147, 0LL, sub_572C8, 0LL);// 这里开了一个线程进行检测
v35 = *v4;
goto LABEL_246;
}
}
}
return 1LL;
}
LABEL_246:
(**(&stru_2F8.st_value + v35))(v147, 0LL, &loc_57FA8, 0LL);// 模拟器检测
return 0LL;
}
然后我们分析一下这个代码就知道,如果检测没通过就是返回1,使用frida进行修改试试呗,修改成0
function hook_test() {
var offset = 0x55EB8;
var module = Process.findModuleByName(soname1);
if (!module) {
module = Process.findModuleByName(soname2);
}
if (!module) {
console.log("[hook_test] target module not found for", soname1, "or", soname2);
return;
}
var cmp_addr = module.base.add(offset);
Interceptor.attach(cmp_addr, {
onEnter: function (args) {
console.log("=======")
},
onLeave: function (retval) {
console.log(retval);
console.log("=======检测函数的返回值修改=======")
retval.replace(0);
}
});
}
完美绕过
也就到达了另外一个检测的so
反调试小结
一、 基础文件特征检测 (File Artifacts)
原理:App 通过检查特定的文件路径是否存在,来判断当前环境是否被修改过。 核心手段:使用 linux_eabi_syscall(__NR_faccessat, ...)。 学习点:为什么要用 syscall?
普通开发者用 File.exists() (Java) 或 access()/open() (C)。
逆向者通常会 Hook libc 中的 open 函数来隐藏文件(文件重定向)。
直接调用 syscall (系统调用) 可以绕过 PLT/GOT 表的 Hook,让基于 Frida/Xposed 的常规文件隐藏手段失效。需要使用 Kernel 级的 Hook (如 KernelSU) 或 Seccomp 才能拦截。
具体检测项:
/data/dexname
作用:检测残留文件。某些简易的脱壳脚本会将 Dump 下来的 dex 文件临时保存在这里。
/system/lib64/libxiaojianbang.so
作用:检测定制 ROM 或 特定修改版工具。
背景:“小健邦”是大佬,某些定制的逆向环境(ROM)为了致敬或标识,会包含这个文件。这是典型的“黑名单”检测。
/data/local/tmp/unpacker.config 和 /data/local/tmp/aupk.config
作用:检测自动化脱壳工具的配置文件。很多脱壳机(Unpacker)在运行时需要读取配置文件,通常放在 /data/local/tmp/ 这个可写目录下。
/data/fart
作用:检测 FART (Fox Android Runtime Trace) 脱壳机。FART 是一种主动调用的脱壳方案,这个路径是它存放脱壳配置或结果的特征目录。
/data/system/mik.conf
作用:检测 MikRom。MikRom 是一个集成了 FART 和各种逆向工具的定制安卓系统。这是它的系统配置文件路径。
二、 内存与类加载检测 (Class & Library Loading)
原理:检测特定的 Java 类是否被加载,或者特定的 Native 库是否在内存中。
cn/youlor/Unpacker
代码:通过 JNI 反射获取类。
作用:检测 Youlor (Hluwa) 相关的脱壳工具(如 FRIDA-DEXDump 的早期变种或类似的 Xposed 插件)。
libsotweak.so
代码:dlopen(aLibsotweakSo, 0)
作用:尝试加载这个库。libsotweak.so 常用于 Cydia Substrate 框架或某些越狱/Root 插件。如果能加载成功,说明环境不纯净。
cn/mik/Fartext
作用:检测 FART 在 MikRom 中的特有辅助类。
三、 内存映射扫描 (Memory Maps Scanning)
原理:读取 /proc/self/maps 文件。这个文件记录了当前进程加载了哪些 so 库、dex 文件以及它们的内存地址。这是检测 Frida 最强力的方式。 学习点:代码中并没有直接写 fopen("/proc/self/maps"),而是通过控制流平坦化(那个复杂的 while 循环和数学计算)隐藏了读取逻辑,防止被静态分析一眼看穿。
具体检测项:
frida-agent-64.so (代码变量 aH_1)
作用:这是 Frida 注入到目标进程后的核心 so 文件名。
对抗:现在的 Frida 通常会随机化这个名字,或者逆向者会修改它。
/data/adb/ (代码变量 aDataAdb)
作用:检测 Magisk/Riru/Zygisk 注入。
原理:Magisk 模块(如 Riru)注入的 so 文件通常位于 /data/adb/modules/...。如果在 maps 里看到有代码段 (r-xp) 来自 /data/adb/ 目录,说明进程被注入了。
FridaAgentStopReason (代码变量 aFridaagentstop)
作用:字符串特征扫描。即使你把 frida 的 so 改名了,它内存里还有特定的字符串。代码会扫描内存段,寻找 Frida 特有的字符串。
大内存段检测
代码:if ((v144 - v145) > 0x200000)
作用:检测未命名的、可执行的大内存块。Frida 注入后通常会申请一段较大的内存来存放某些逻辑,且这段内存可能没有对应的文件路径(Anonymous Memory)。
四、 运行环境与服务检测 (Environment & Service)
re.frida.server
作用:检测 Frida 服务端。通常是在 /data/local/tmp 下寻找名为 re.frida.server 的套接字文件或临时文件。
android.os.ServiceManager.getService (获取 aMikrom 等)
作用:检测系统服务。定制的脱壳 ROM(如 MikRom)可能会向系统注册一些特殊的服务来辅助脱壳。普通手机里获取不到这些服务。
/system/lib/libriruloader.so
作用:检测 Riru 框架。
原理:Riru 是老牌的 Magisk 注入框架。这个文件是它的加载器,存在于系统目录说明你安装了 Riru 模块。
五、 Inline Hook 完整性检测
原理:检测关键的系统函数(如 pthread_create)是否被修改(Hook)。
代码逻辑:
dlopen("libc.so") 并 dlsym 找到 pthread_create 的地址。
读取该地址开头的几个字节(指令)。
计算 CRC 校验值或检查是否包含跳转指令(如 ARM64 的 B 指令或 LDR PC, ...)。
作用:Frida 或 Xposed 在 Hook 一个 Native 函数时,必须修改该函数的前几条指令,以此跳转到 Hook 函数。这种检测就是看函数头有没有被改动。
六、 模拟器检测 (Anti-Emulator)
libnativebridge.so
作用:检测 Native Bridge (Houdini)。
原理:模拟器(如 BlueStacks、雷电)通常是在 x86 电脑上运行。为了运行 ARM 架构的 App,它们必须使用 Native Bridge 技术进行指令转译。真机(ARM 架构)通常不会用到这个转译库。
ro.dalvik.vm.native.bridge
代码:sub_55B90 读取属性。
作用:检查系统属性开关,看是否开启了指令转译。
这里也就可以来根据这些特征我们稍微修改一下frida,当然crc这个我没修改,没想好怎么实现crc的绕过,不过可以先简单对frida这些硬编码的检测进行修改掉,我分析的这个APP其实还有一个加固保护,感觉也挺不错的,分享给大佬们来看看
反调试分析 libmsaoaidsec.so
这个遗留下来的也就是上面我们看到的这个
这个大家去搜这个so,可以搜到很多对于这个so的过检测,但是我想去看看他是怎么进行检测的,感觉还是可以学到很多东西的
先看第一个0x1d308 其实是0x1c544,因为这个是他注册的函数
检测一 :0x1c544
先大体看一下吧,其实就是这样的
代码贴一下:
void __fastcall __noreturn detect_str(__int64 a1)
{
size_t v2; // w0
unsigned __int64 v3; // x8
unsigned int v4; // w11
char *v5; // x12
unsigned int v6; // w13
unsigned __int64 v7; // x16
int v8; // w17
char *v9; // x10
unsigned __int64 v10; // x9
int v11; // w13
size_t v12; // w0
unsigned __int64 v13; // x8
unsigned int v14; // w11
char *v15; // x12
unsigned int v16; // w13
unsigned __int64 v17; // x16
int v18; // w17
char *v19; // x10
unsigned __int64 v20; // x9
int v21; // w13
size_t v22; // w0
unsigned __int64 v23; // x8
unsigned int v24; // w11
char *v25; // x12
unsigned int v26; // w13
unsigned __int64 v27; // x16
int v28; // w17
char *v29; // x10
unsigned __int64 v30; // x9
int v31; // w13
size_t v32; // w0
unsigned __int64 v33; // x8
unsigned int v34; // w11
char *v35; // x12
unsigned int v36; // w13
unsigned __int64 v37; // x16
int v38; // w17
char *v39; // x10
unsigned __int64 v40; // x9
int v41; // w13
size_t v42; // w0
unsigned __int64 v43; // x8
unsigned int v44; // w11
char *v45; // x12
unsigned int v46; // w13
unsigned __int64 v47; // x16
int v48; // w17
char *v49; // x10
unsigned __int64 v50; // x9
int v51; // w13
size_t v52; // w0
unsigned __int64 v53; // x8
unsigned int v54; // w11
char *v55; // x12
unsigned int v56; // w13
unsigned __int64 v57; // x16
int v58; // w17
char *v59; // x10
unsigned __int64 v60; // x9
int v61; // w13
size_t v62; // w0
unsigned __int64 v63; // x8
unsigned int v64; // w11
char *v65; // x12
unsigned int v66; // w13
unsigned __int64 v67; // x16
int v68; // w17
char *v69; // x10
unsigned __int64 v70; // x9
int v71; // w13
size_t v72; // w0
unsigned __int64 v73; // x8
unsigned int v74; // w11
char *v75; // x12
unsigned int v76; // w13
unsigned __int64 v77; // x16
int v78; // w17
char *v79; // x10
unsigned __int64 v80; // x9
int v81; // w13
size_t v82; // w0
unsigned __int64 v83; // x8
unsigned int v84; // w11
char *v85; // x12
unsigned int v86; // w13
unsigned __int64 v87; // x16
int v88; // w17
char *v89; // x10
unsigned __int64 v90; // x9
int v91; // w13
size_t v92; // w0
unsigned __int64 v93; // x8
unsigned int v94; // w11
char *v95; // x12
unsigned int v96; // w13
unsigned __int64 v97; // x16
char v98; // w1
int v99; // w17
char *v100; // x10
unsigned __int64 v101; // x9
int v102; // w13
__int64 v103; // x0
__int64 v104; // x0
v2 = _strlen_chk(byte_4904A, 0xCu);
if ( v2 >= 1 )
{
if ( v2 < 2uLL )
{
v3 = 0LL;
LABEL_7:
v9 = &byte_4904A[v3];
v10 = v2 - v3;
do
{
v11 = *(qword_30670 + v3 % 3);
--v10;
LODWORD(v3) = v3 + 1;
*v9++ ^= v11;
}
while ( v10 );
goto LABEL_9;
}
v3 = v2 - (v2 & 1);
v4 = 0;
v5 = &byte_4904B;
v6 = 1;
v7 = v3;
do
{
v8 = *(qword_30670 + v6 % 3);
v7 -= 2LL;
v6 += 2;
LOBYTE(v8) = *v5 ^ v8;
*(v5 - 1) ^= *(qword_30670 + 4 * (v4 % 3));
*v5 = v8;
v5 += 2;
v4 += 2;
}
while ( v7 );
if ( (v2 & 1) != 0 )
goto LABEL_7;
}
LABEL_9:
v12 = _strlen_chk(byte_49056, 6u);
if ( v12 < 1 )
goto LABEL_17;
if ( v12 >= 2uLL )
{
v13 = v12 - (v12 & 1);
v14 = 0;
v15 = &byte_49057;
v16 = 1;
v17 = v13;
do
{
v18 = *(qword_30670 + v16 % 3);
v17 -= 2LL;
v16 += 2;
LOBYTE(v18) = *v15 ^ v18;
*(v15 - 1) ^= *(qword_30670 + 4 * (v14 % 3));
*v15 = v18;
v15 += 2;
v14 += 2;
}
while ( v17 );
if ( (v12 & 1) == 0 )
{
LABEL_17:
v22 = _strlen_chk(byte_4906A, 0xAu);
if ( v22 < 1 )
goto LABEL_25;
if ( v22 >= 2uLL )
{
v23 = v22 - (v22 & 1);
v24 = 0;
v25 = &byte_4906B;
v26 = 1;
v27 = v23;
do
{
v28 = *(qword_30670 + v26 % 3);
v27 -= 2LL;
v26 += 2;
LOBYTE(v28) = *v25 ^ v28;
*(v25 - 1) ^= *(qword_30670 + 4 * (v24 % 3));
*v25 = v28;
v25 += 2;
v24 += 2;
}
while ( v27 );
if ( (v22 & 1) == 0 )
{
LABEL_25:
v32 = _strlen_chk(byte_49030, 0x1Au);
if ( v32 < 1 )
goto LABEL_33;
if ( v32 >= 2uLL )
{
v33 = v32 - (v32 & 1);
v34 = 0;
v35 = &byte_49031;
v36 = 1;
v37 = v33;
do
{
v38 = *(qword_30670 + v36 % 3);
v37 -= 2LL;
v36 += 2;
LOBYTE(v38) = *v35 ^ v38;
*(v35 - 1) ^= *(qword_30670 + 4 * (v34 % 3));
*v35 = v38;
v35 += 2;
v34 += 2;
}
while ( v37 );
if ( (v32 & 1) == 0 )
{
LABEL_33:
v42 = _strlen_chk(byte_4905C, 0xEu);
if ( v42 < 1 )
goto LABEL_41;
if ( v42 >= 2uLL )
{
v43 = v42 - (v42 & 1);
v44 = 0;
v45 = &byte_4905D;
v46 = 1;
v47 = v43;
do
{
v48 = *(qword_30670 + v46 % 3);
v47 -= 2LL;
v46 += 2;
LOBYTE(v48) = *v45 ^ v48;
*(v45 - 1) ^= *(qword_30670 + 4 * (v44 % 3));
*v45 = v48;
v45 += 2;
v44 += 2;
}
while ( v47 );
if ( (v42 & 1) == 0 )
{
LABEL_41:
v52 = _strlen_chk(byte_49020, 0x10u);
if ( v52 < 1 )
goto LABEL_49;
if ( v52 >= 2uLL )
{
v53 = v52 - (v52 & 1);
v54 = 0;
v55 = &byte_49021;
v56 = 1;
v57 = v53;
do
{
v58 = *(qword_30670 + v56 % 3);
v57 -= 2LL;
v56 += 2;
LOBYTE(v58) = *v55 ^ v58;
*(v55 - 1) ^= *(qword_30670 + 4 * (v54 % 3));
*v55 = v58;
v55 += 2;
v54 += 2;
}
while ( v57 );
if ( (v52 & 1) == 0 )
{
LABEL_49:
v62 = _strlen_chk(byte_49074, 0x10u);
if ( v62 < 1 )
goto LABEL_57;
if ( v62 >= 2uLL )
{
v63 = v62 - (v62 & 1);
v64 = 0;
v65 = &byte_49075;
v66 = 1;
v67 = v63;
do
{
v68 = *(qword_30670 + v66 % 3);
v67 -= 2LL;
v66 += 2;
LOBYTE(v68) = *v65 ^ v68;
*(v65 - 1) ^= *(qword_30670 + 4 * (v64 % 3));
*v65 = v68;
v65 += 2;
v64 += 2;
}
while ( v67 );
if ( (v62 & 1) == 0 )
{
LABEL_57:
v72 = _strlen_chk(byte_49084, 0x10u);
if ( v72 < 1 )
goto LABEL_65;
if ( v72 >= 2uLL )
{
v73 = v72 - (v72 & 1);
v74 = 0;
v75 = &byte_49085;
v76 = 1;
v77 = v73;
do
{
v78 = *(qword_30670 + v76 % 3);
v77 -= 2LL;
v76 += 2;
LOBYTE(v78) = *v75 ^ v78;
*(v75 - 1) ^= *(qword_30670 + 4 * (v74 % 3));
*v75 = v78;
v75 += 2;
v74 += 2;
}
while ( v77 );
if ( (v72 & 1) == 0 )
{
LABEL_65:
v82 = _strlen_chk(byte_49094, 0xBu);
if ( v82 < 1 )
goto LABEL_73;
if ( v82 >= 2uLL )
{
v83 = v82 - (v82 & 1);
v84 = 0;
v85 = &byte_49095;
v86 = 1;
v87 = v83;
do
{
v88 = *(qword_30670 + v86 % 3);
v87 -= 2LL;
v86 += 2;
LOBYTE(v88) = *v85 ^ v88;
*(v85 - 1) ^= *(qword_30670 + 4 * (v84 % 3));
*v85 = v88;
v85 += 2;
v84 += 2;
}
while ( v87 );
if ( (v82 & 1) == 0 )
{
LABEL_73:
v92 = _strlen_chk(byte_4909F, 0xCu);
if ( v92 < 1 )
goto LABEL_81;
if ( v92 >= 2uLL )
{
v93 = v92 - (v92 & 1);
v94 = 0;
v95 = &byte_490A0;
v96 = 1;
v97 = v93;
do
{
v98 = *v95;
v99 = *(qword_30670 + v96 % 3);
v97 -= 2LL;
v96 += 2;
*(v95 - 1) ^= *(qword_30670 + 4 * (v94 % 3));
*v95 = v98 ^ v99;
v95 += 2;
v94 += 2;
}
while ( v97 );
if ( (v92 & 1) == 0 )
{
while ( 1 )
{
LABEL_81:
v103 = sub_1BFAC();
v104 = sub_1C158(v103);
sub_1C26C(v104);
sub_26334(a1);
sleep(4u);
}
}
}
else
{
v93 = 0LL;
}
v100 = &byte_4909F[v93];
v101 = v92 - v93;
do
{
v102 = *(qword_30670 + v93 % 3);
--v101;
LODWORD(v93) = v93 + 1;
*v100++ ^= v102;
}
while ( v101 );
goto LABEL_81;
}
}
else
{
v83 = 0LL;
}
v89 = &byte_49094[v83];
v90 = v82 - v83;
do
{
v91 = *(qword_30670 + v83 % 3);
--v90;
LODWORD(v83) = v83 + 1;
*v89++ ^= v91;
}
while ( v90 );
goto LABEL_73;
}
}
else
{
v73 = 0LL;
}
v79 = &byte_49084[v73];
v80 = v72 - v73;
do
{
v81 = *(qword_30670 + v73 % 3);
--v80;
LODWORD(v73) = v73 + 1;
*v79++ ^= v81;
}
while ( v80 );
goto LABEL_65;
}
}
else
{
v63 = 0LL;
}
v69 = &byte_49074[v63];
v70 = v62 - v63;
do
{
v71 = *(qword_30670 + v63 % 3);
--v70;
LODWORD(v63) = v63 + 1;
*v69++ ^= v71;
}
while ( v70 );
goto LABEL_57;
}
}
else
{
v53 = 0LL;
}
v59 = &byte_49020[v53];
v60 = v52 - v53;
do
{
v61 = *(qword_30670 + v53 % 3);
--v60;
LODWORD(v53) = v53 + 1;
*v59++ ^= v61;
}
while ( v60 );
goto LABEL_49;
}
}
else
{
v43 = 0LL;
}
v49 = &byte_4905C[v43];
v50 = v42 - v43;
do
{
v51 = *(qword_30670 + v43 % 3);
--v50;
LODWORD(v43) = v43 + 1;
*v49++ ^= v51;
}
while ( v50 );
goto LABEL_41;
}
}
else
{
v33 = 0LL;
}
v39 = &byte_49030[v33];
v40 = v32 - v33;
do
{
v41 = *(qword_30670 + v33 % 3);
--v40;
LODWORD(v33) = v33 + 1;
*v39++ ^= v41;
}
while ( v40 );
goto LABEL_33;
}
}
else
{
v23 = 0LL;
}
v29 = &byte_4906A[v23];
v30 = v22 - v23;
do
{
v31 = *(qword_30670 + v23 % 3);
--v30;
LODWORD(v23) = v23 + 1;
*v29++ ^= v31;
}
while ( v30 );
goto LABEL_25;
}
}
else
{
v13 = 0LL;
}
v19 = &byte_49056[v13];
v20 = v12 - v13;
do
{
v21 = *(qword_30670 + v13 % 3);
--v20;
LODWORD(v13) = v13 + 1;
*v19++ ^= v21;
}
while ( v20 );
goto LABEL_17;
}
可以发现这个就是一个加密,我给大佬们解密出来了,总之就是下面这个
import idc
# 根据 DCQ 提取的精确 Key
KEYS = [0x99, 0xA7, 0xA9]
targets = [
(0x4904A, 0xC), # 可能是某个进程名
(0x49056, 0x6), # ".main." 干扰项
(0x4906A, 0xA), # 可能是 "su" 相关
(0x49030, 0x1A), # 关键路径
(0x4905C, 0xE), # /proc/self/fd
(0x49020, 0x10), # 关键路径
(0x49074, 0x10), # /data/local/tmp
(0x49084, 0x10), # /data/xxxx
(0x49094, 0xB), # 关键路径
(0x4909F, 0xC) # 关键路径
]
def clean_decrypt():
print("--- Refined IJiami Decryption ---")
for addr, length in targets:
decrypted_str = ""
for i in range(length):
# 关键:每个字符串块起始,i 都是 0,对应 KEYS[0]
key = KEYS[i % 3]
orig_byte = idc.get_wide_byte(addr + i)
dec_byte = orig_byte ^ key
# Patch 到 IDA 数据库中,方便 F5 查看
idc.patch_byte(addr + i, dec_byte)
if 32 <= dec_byte <= 126:
decrypted_str += chr(dec_byte)
else:
decrypted_str += "." # 不可打印字符
print(f"[+] {hex(addr)} (len {length}): {decrypted_str}")
print("--- Done. Please refresh F5 view ---")
if __name__ == "__main__":
clean_decrypt()
'''
这个函数单纯就是来解密字符串的
[+] 0x4904a (len 12): gum-js-loop.
[+] 0x49056 (len 6): gmain.
[+] 0x4906a (len 10): linjector.
[+] 0x49030 (len 26): /proc/self/task/%s/status.
[+] 0x4905c (len 14): /proc/self/fd.
[+] 0x49020 (len 16): /proc/self/task.
[+] 0x49074 (len 16): /proc/self/maps.
[+] 0x49084 (len 16): /data/local/tmp.
[+] 0x49094 (len 11): _AGENT_1.0.
[+] 0x4909f (len 12): frida-agent.
'''
检测二 :0x1b8d4
这里存在着一个混淆的,有些地方会爆红,那是因为他识别不出来,就像这个
这个ptr_etpid是我手动改的,要不然就会爆红
可以看一下这个检测
主要也就是三个检测,可以分开来看一下,我都重新命名了,根据这个名字也可以看出来
检测1:tracepid
第一个检测,检查tracepid,也就是调试器标志
__int64 detect_tracepid()
{
size_t v0; // w0
unsigned __int64 v1; // x8
unsigned int v2; // w11
unsigned int v3; // w12
_BYTE *v4; // x13
unsigned __int64 v5; // x16
int v6; // w17
unsigned __int64 v7; // x9
char *v8; // x11
int v9; // w13
FILE *v10; // x0
FILE *v11; // x19
size_t v12; // w0
unsigned __int64 v13; // x8
unsigned int v14; // w20
unsigned int v15; // w11
unsigned int v16; // w12
_BYTE *v17; // x13
unsigned __int64 v18; // x16
int v19; // w17
unsigned __int64 v20; // x9
char *v21; // x11
int v22; // w13
char *v23; // x0
int v24; // w0
unsigned __int64 v26; // [xsp+0h] [xbp-860h] BYREF
__int16 v27; // [xsp+8h] [xbp-858h]
char v28; // [xsp+Ah] [xbp-856h]
char v29[1024]; // [xsp+10h] [xbp-850h] BYREF
__int128 v30; // [xsp+410h] [xbp-450h] BYREF
char v31[1024]; // [xsp+428h] [xbp-438h] BYREF
__int64 v32; // [xsp+828h] [xbp-38h]
v32 = *(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
memset(v31, 0, sizeof(v31));
v30 = xmmword_304DF; // 解密出来应该是/proc/%d/status
v0 = _strlen_chk(&v30, 0x10u);
if ( v0 >= 1 )
{
if ( v0 < 2uLL )
{
v1 = 0LL;
LABEL_7:
v7 = v0 - v1;
v8 = &v31[v1 - 8];
do
{
v9 = dword_305BC[v1 % 3];
--v7;
LODWORD(v1) = v1 + 1;
*v8++ ^= v9;
}
while ( v7 );
goto LABEL_9;
}
v1 = v0 - (v0 & 1);
v2 = 0;
v3 = 1;
v4 = &v30 + 1;
v5 = v1;
do
{
v6 = dword_305BC[v3 % 3];
v5 -= 2LL;
v3 += 2;
LOBYTE(v6) = *v4 ^ v6;
*(v4 - 1) ^= LOBYTE(dword_305BC[v2 % 3]);
*v4 = v6;
v4 += 2;
v2 += 2;
}
while ( v5 );
if ( (v0 & 1) != 0 )
goto LABEL_7;
}
LABEL_9:
_sprintf_chk(v31, 0LL, 1024LL, &v30, PID); // 就是这个数字应该是pid
v10 = ptr_open(v31, "r");
if ( v10 )
{
v11 = v10;
memset(v29, 0, sizeof(v29));
v28 = 0;
v27 = -23603;
v26 = 0xCEC9DBC2FAC8D5CDLL;
v12 = _strlen_chk(&v26, 0xBu); // 解密出来是TracerPid
if ( v12 < 1 )
goto LABEL_19;
if ( v12 < 2uLL )
{
v13 = 0LL;
LABEL_17:
v20 = v12 - v13;
v21 = &v26 + v13;
do
{
v22 = dword_305BC[v13 % 3];
--v20;
LODWORD(v13) = v13 + 1;
*v21++ ^= v22;
}
while ( v20 );
goto LABEL_19;
}
v13 = v12 - (v12 & 1);
v15 = 0;
v16 = 1;
v17 = &v26 + 1;
v18 = v13;
do
{
v19 = dword_305BC[v16 % 3];
v18 -= 2LL;
v16 += 2;
LOBYTE(v19) = *v17 ^ v19;
*(v17 - 1) ^= LOBYTE(dword_305BC[v15 % 3]);
*v17 = v19;
v17 += 2;
v15 += 2;
}
while ( v18 );
if ( (v12 & 1) != 0 )
goto LABEL_17;
LABEL_19:
while ( fgets(v29, 1024, v11) )
{
v23 = strstr(v29, &v26);
if ( v23 )
{
v24 = ptr_atoi(v23 + 10);
if ( v24 )
{
v14 = v24;
fclose(v11);
return v14;
}
break;
}
}
fclose(v11);
return 0;
}
else
{
return -1;
}
}
检测2: ppid
FILE *__fastcall sub_1AB54(unsigned int a1)
{
size_t v2; // w0
unsigned __int64 v3; // x8
unsigned int v4; // w11
unsigned int v5; // w12
_BYTE *v6; // x13
unsigned __int64 v7; // x16
int v8; // w17
unsigned __int64 v9; // x9
char *v10; // x11
int v11; // w13
FILE *result; // x0
FILE *v13; // x19
size_t v14; // w0
unsigned __int64 v15; // x8
unsigned int v16; // w11
unsigned int v17; // w12
_BYTE *v18; // x13
unsigned __int64 v19; // x16
int v20; // w17
unsigned __int64 v21; // x9
char *v22; // x11
int v23; // w13
char *v24; // x0
int v25; // [xsp+8h] [xbp-848h] BYREF
__int16 v26; // [xsp+Ch] [xbp-844h]
char v27[1024]; // [xsp+10h] [xbp-840h] BYREF
__int128 v28; // [xsp+410h] [xbp-440h] BYREF
char v29[1024]; // [xsp+428h] [xbp-428h] BYREF
__int64 v30; // [xsp+828h] [xbp-28h]
v30 = *(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
memset(v29, 0, sizeof(v29));
v28 = xmmword_304DF; // 解密出来依旧/proc/%d/status
if ( a1 == -1 )
return 0LL;
v2 = _strlen_chk(&v28, 0x10u);
if ( v2 >= 1 )
{
if ( v2 >= 2uLL )
{
v3 = v2 - (v2 & 1);
v4 = 0;
v5 = 1;
v6 = &v28 + 1;
v7 = v3;
do
{
v8 = dword_305BC[v5 % 3];
v7 -= 2LL;
v5 += 2;
LOBYTE(v8) = *v6 ^ v8;
*(v6 - 1) ^= LOBYTE(dword_305BC[v4 % 3]);
*v6 = v8;
v6 += 2;
v4 += 2;
}
while ( v7 );
if ( (v2 & 1) == 0 )
goto LABEL_10;
}
else
{
v3 = 0LL;
}
v9 = v2 - v3;
v10 = &v29[v3 - 8];
do
{
v11 = dword_305BC[v3 % 3];
--v9;
LODWORD(v3) = v3 + 1;
*v10++ ^= v11;
}
while ( v9 );
}
LABEL_10:
_sprintf_chk(v29, 0LL, 1024LL, &v28, a1);
result = ptr_open(v29, "r");
if ( !result )
return result;
v13 = result;
memset(v27, 0, sizeof(v27));
v26 = 157;
v25 = -37685303; // PPid:
v14 = _strlen_chk(&v25, 6u);
if ( v14 < 1 )
goto LABEL_19;
if ( v14 < 2uLL )
{
v15 = 0LL;
LABEL_17:
v21 = v14 - v15;
v22 = &v25 + v15;
do
{
v23 = dword_305BC[v15 % 3];
--v21;
LODWORD(v15) = v15 + 1;
*v22++ ^= v23;
}
while ( v21 );
goto LABEL_19;
}
v15 = v14 - (v14 & 1);
v16 = 0;
v17 = 1;
v18 = &v25 + 1;
v19 = v15;
do
{
v20 = dword_305BC[v17 % 3];
v19 -= 2LL;
v17 += 2;
LOBYTE(v20) = *v18 ^ v20;
*(v18 - 1) ^= LOBYTE(dword_305BC[v16 % 3]);
*v18 = v20;
v18 += 2;
v16 += 2;
}
while ( v19 );
if ( (v14 & 1) != 0 )
goto LABEL_17;
do
{
LABEL_19:
if ( !fgets(v27, 1024, v13) )
goto LABEL_22;
v24 = strstr(v27, &v25);
}
while ( !v24 );
if ( ptr_atoi(v24 + 5) == PID )
{
LABEL_22:
fclose(v13);
return (&dword_0 + 1);
}
fclose(v13);
return 0LL;
}
检测3:stat
__int64 detect_stat()
{
unsigned int v0; // w0
DIR *v1; // x0
DIR *v2; // x19
struct dirent *v3; // x0
unsigned __int8 v4; // w9
const char *d_name; // x21
char *v6; // x8
int v7; // t1
unsigned int v8; // w0
__int64 result; // x0
int v10; // w21
__int64 v11; // x24
int v12; // w8
_BYTE v13[1024]; // [xsp+8h] [xbp-A48h] BYREF
char v14[1024]; // [xsp+408h] [xbp-648h] BYREF
char v15[512]; // [xsp+808h] [xbp-248h] BYREF
__int64 v16; // [xsp+A08h] [xbp-48h]
v16 = *(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
memset(v15, 0, sizeof(v15));
memset(v14, 0, sizeof(v14));
memset(v13, 0, sizeof(v13));
v0 = ptr_getpid();
_sprintf_chk(v14, 0LL, 1024LL, "/proc/%d/task", v0);
v1 = ptr_opendir(v14);
if ( !v1 )
return 0xFFFFFFFFLL;
v2 = v1;
v3 = readdir(v1);
if ( v3 )
{
while ( 1 )
{
d_name = v3->d_name;
v4 = v3->d_name[0];
if ( v4 )
{
v6 = &v3->d_name[1];
while ( v4 - 48 > 9 )
{
v7 = *v6++;
v4 = v7;
if ( !v7 )
goto LABEL_14;
}
v8 = ptr_getpid();
_sprintf_chk(v15, 0LL, 512LL, "/proc/%d/task/%s/stat", v8, d_name);
result = open(v15, 0);
if ( result == -1 )
return result;
v10 = result;
result = _read_chk(result, v13, 1024LL, 1024LL);
if ( result == -1 )
return result;
v11 = 0LL;
do
v12 = v13[v11++];
while ( v12 != 41 );
ptr_close(v10);
if ( (v13[(v11 + 1)] | 0x20) == 't' && v13[(v11 + 2)] == ' ' )
return 777LL;
}
LABEL_14:
v3 = readdir(v2);
if ( !v3 )
goto LABEL_15;
}
}
else
{
LABEL_15:
closedir(v2);
return 0LL;
}
}
#混淆主要也就是需要plt表的混淆,直接手动重新命名就行
'''
这个线程其实存在着三个检测:
1、1AE48:这个函数的作用很明显,就是打开/proc/%d/status然后搜索TracerPid的字段
2、1B730:扫描这个"/proc/%d/task/%s/stat,得到所有的线程pid,然后查看他的属性是不是在调试状态,也就是看是不是T或者t
在 Linux 进程状态中,'T' 或 't' 代表 TASK_TRACED(正在被跟踪/调试)或 TASK_STOPPED(停止运行)
3、1AB54:这个函数的作用是检测PPID:检测这个进程是不是由zygote来产生的,正常应该是1,
如果不是1,说明这个进程是被其他进程fork出来的,那么就说明这个进程是被调试的
'''
检测三 :0x26e5c
这个检测有点像重打包的签名校验,有对去签感兴趣的可以关注一下这个的手法
__int64 detect_signature()
{
unsigned int v0; // w0
int v1; // w8
unsigned int v3; // [xsp+10h] [xbp-70h]
bool v4; // [xsp+16h] [xbp-6Ah]
bool v5; // [xsp+17h] [xbp-69h]
_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2));
v0 = sub_C930();
v1 = -1782206741;
v3 = v0;
while ( 1 )
{
while ( 1 )
{
while ( v1 > -1137393265 )
{
if ( v1 > -499842569 )
{
if ( v1 == -499842568 )
{
if ( (check_signature() & 1) != 0 )
v1 = 0xBB729CCC;
else
v1 = 0x93174C3C;
}
else
{
v5 = v3 != 0;
v1 = -1137393264;
}
}
else if ( v1 == -1137393264 )
{
if ( v4 && v5 )
v1 = -499842568;
else
v1 = 0xBB729CCC;
}
else
{
v1 = -1372371036;
}
}
if ( v1 > -1372371037 )
break;
if ( v1 == 0x93174C3C )
{
call_svc_exit(0LL);
v1 = -1150116660;
}
else
{
v1 = -779687515;
}
}
if ( v1 != -1372371036 )
break;
v4 = v3 < 0xD;
v1 = 629336195;
}
return 0LL;
}
那个子函数的代码太长了,我就不全部复制了,复制一下关键点
'''
去混淆过检测.libmsaoaidsec_26e5c 的 Docstring
这个线程检测的内容有点高级,有两个函数
第一个函数:
第一点是他自己写了一个zip解析的函数来手动解析apk文件,有可能会对crc值进行校验
第二点是他写了一个函数来检测是否存在/split_config.arm64_v8a.apk,
目前app有base.apk split_config.arm64_v8a.apk split_config.xxhdpi.apk split_config.zh.apk等
大概率是为了防止重打包和合并的
第三点就是他检测了META-INF/这个下的签名,就是来进行重打包检测的,而且预设了签名值,进行对比
第二个函数:
写了一段shellcode,这个会调用svc强制杀死进程的
解密代码看下面
'''
import struct
# 1. 原始加密数据 (从 xmmword_30760 和后续内存提取)
# Index 0 is 0x91, but code says v11[0]=8.
# 91 ^ 99 = 08, so it matches.
encrypted_bytes = [
0x91, 0xA7, 0x29, 0x4B, 0xA6, 0xA9, 0x99, 0x73,
0x69, 0x9A, 0xF8, 0x7F, 0x86, 0x87, 0xAA, 0x4C,
0xB8, 0x89, 0x9A, 0x72, 0xB6, 0xB9, 0xA4, 0x7C
]
# 2. 解密 Key
KEYS = [0x99, 0xA7, 0xA9]
def decrypt_shellcode():
decrypted = []
print("--- 原始解密结果 ---")
for i, byte_val in enumerate(encrypted_bytes):
key = KEYS[i % 3]
dec_val = byte_val ^ key
decrypted.append(dec_val)
# 打印前 12 个字节的十六进制
print("Hex: " + " ".join([f"{b:02X}" for b in decrypted[:12]]))
# 3. 模拟代码中的 "运行时修改" (*v11 += 3008)
# v11[0] 是一个 int (4字节),代码对其加了 3008 (0xBC0)
# 原始前4字节 (Little Endian): 08 00 80 D2 (即 0xD2800008)
instruction = struct.unpack('<I', bytes(decrypted[:4]))[0]
modified_instr = instruction + 3008
# 替换回去
decrypted_modified = list(decrypted)
new_bytes = struct.pack('<I', modified_instr)
for i in range(4):
decrypted_modified[i] = new_bytes[i]
print("\n--- 运行时 Patch 后 (真实指令) ---")
print("Hex: " + " ".join([f"{b:02X}" for b in decrypted_modified[:12]]))
return decrypted_modified
shellcode = decrypt_shellcode()
检测也就是这么多
反调试小结
libmsaoaidsec.so 核心反调试/防篡改分析报告
一、 总体架构
该 SO 库采用“多线程协同监控”架构,启动后会创建至少三个常驻线程,分别负责特征扫描、状态巡逻和深度取证。只要任意一个线程检测到异常,就会触发底层的 SVC #0 自毁逻辑。
二、 三大检测线程详解
- 线程一:环境特征扫描 (The Scanner)
函数地址: sub_1C544
运行机制: while(1) { ... sleep(4); } (每 4 秒运行一次)。
混淆手段:
栈字符串混淆 (Stack Strings): 字符串并非明文存储,而是通过 XOR 算法动态解密。
Key: 0x99, 0xA7, 0xA9 (3 字节循环异或)。
检测目标:
文件路径: /proc/self/status (状态), /proc/net/tcp (端口扫描), /system/bin/su (Root)。
注入特征: frida-agent.so, libxposed.so, linjector。
线程名称: gum-js-loop, gmain (Frida 专属线程名)。
目录: /data/local/tmp (常用工具存放地)。
- 线程二:进程状态巡逻 (The Patrolman)
函数地址: sub_1B8D4
运行机制: while(1) { ... usleep(2000000); } (每 2 秒运行一次)。
核心逻辑: 依次执行三个子函数,若返回异常值则触发自毁。
检测点 A (sub_1AE48):
行为: 读取 /proc/self/status。
目标: 检查 TracerPid 字段。
异常: 返回值 > 0 (表示被调试)。
检测点 B (sub_1AB54):
行为: 读取 /proc/self/status。
目标: 检查 PPid (父进程 ID)。
异常: 返回值 == 0 (父进程非 Zygote,可能由 gdbserver 启动)。
检测点 C (sub_1B730):
行为: 遍历 /proc/self/task/[tid]/stat。
目标: 检查所有子线程的状态字符。
异常: 返回值 == 777。这意味着发现某个线程状态为 t (tracing) 或 T (stopped),通常是因为断点命中或被挂起。
- 线程三:深度完整性取证 (The Forensics Expert)
函数地址: sub_1678C
运行机制: 最复杂的逻辑,包含暴力枚举和内存映射解析。
核心逻辑:
暴力搜查: 循环拼接 1-10 后缀,检查 /data/local/tmp/ 下是否存在临时文件(针对 Frida Server 的随机名)。
内存映射分析: 解析 /proc/self/maps,定位当前加载的 APK 路径(包括 base.apk 和 split_config.arm64_v8a.apk)。
防篡改 (Anti-Tamper):
手动 ZIP 解析: 不使用系统 API,直接读取 APK 二进制流,搜索 EOCD (PK\x05\x06) 和中央目录。
CRC32 校验: 解压 classes.dex, META-INF/CERT.RSA 等核心文件,计算 CRC32。
签名指纹比对: 将提取的证书转为 Hex 字符串,与内置的正确签名指纹比对。
异常: 只要上述任一环节发现不匹配(如 APK 被重打包、签名不一致、Split APK 缺失),函数返回异常值,触发闪退。
三、 处决机制 (The Executioner)
函数地址: sub_269AC
触发条件: 上述任一线程发现异常。
手段:
运行时解密 28 字节 Shellcode。
通过 mmap 申请可执行内存。
执行 MOV X8, #94 (exit_group) -> SVC #0。
特点: 绕过 libc.so 的 exit/kill Hook,直接调用内核杀进程,且执行完立即 munmap 销毁证据。
把过掉这个检测的代码给大家
const soname1="libexecmain.so"
const soname2="libexec.so"
let hooksInstalled = false;
let pthreadHookInstalled = false;
let flagss=false;
let msaoaidsecHandled = false;
const killedStartRoutine = new NativeCallback(function (arg) {
return ptr(0);
}, 'pointer', ['pointer']);
function hook_dlopen(){
var dlopen = Module.findExportByName(null, "android_dlopen_ext");
console.log(`dlopen=${dlopen}`);
if(dlopen){
Interceptor.attach(dlopen, {
onEnter: function (args) {
var soName = Memory.readCString(args[0]);
console.log(`dlopen soName=${soName}`);
if(soName.indexOf(soname1)>=0 || soName.indexOf(soname2)>=0){
this.flag=true;
}
if(soName.indexOf("libmsaoaidsec.so")>=0){
console.log("=====libmsaoaidsec.so调用=====");
this.flag2=true;
flagss=true;
}
},onLeave:function(retval){
if(this.flag && !hooksInstalled){
hooksInstalled = true;
hook_phread();
anti_check()
}
if(this.flag2 && !msaoaidsecHandled){
flagss=false;
console.log("=====bypass_detect_func=====");
msaoaidsecHandled = bypass_detect_func() || msaoaidsecHandled;
}
}
});
}
}
function hook_phread(){
if (pthreadHookInstalled) return;
pthreadHookInstalled = true;
const pthreadCreate = Module.findExportByName(null, "pthread_create");
if (!pthreadCreate) {
console.log("Failed to find pthread_create");
return;
}
Interceptor.attach(pthreadCreate, {
onEnter: function (args) {
const callSite = ptr(this.returnAddress);
const callSiteModule = Process.findModuleByAddress(callSite);
let callSiteOffset = null;
let callSiteModuleName = null;
if (callSiteModule) {
callSiteOffset = callSite.sub(callSiteModule.base);
callSiteModuleName = callSiteModule.name;
console.log("[pthread_create] called from", callSiteModuleName, "offset=" + callSiteOffset.toString());
} else {
console.log("[pthread_create] called from", callSite);
}
const startRoutine = args[2];
const startModule = Process.findModuleByAddress(startRoutine);
if (startModule) {
const startOffset = ptr(startRoutine).sub(startModule.base);
console.log("[pthread_create] start_routine in", startModule.name, "offset=" + startOffset.toString());
if (startModule.name === "libmsaoaidsec.so") {
if (!msaoaidsecHandled) {
msaoaidsecHandled = bypass_detect_func() || msaoaidsecHandled;
}
const off = startOffset.toUInt32();
if (off === 0x1c544 || off === 0x1b8d4 || off === 0x26e5c) {
args[2] = killedStartRoutine;
}
}
} else {
console.log("[pthread_create] start_routine at", startRoutine);
}
}
})
}
function anti_check(){
var offset = 0x55EB8;
var module = Process.findModuleByName(soname1);
if (!module) {
module = Process.findModuleByName(soname2);
}
if (!module) {
console.log("[hook_test] target module not found for", soname1, "or", soname2);
return;
}
var cmp_addr = module.base.add(offset);
Interceptor.attach(cmp_addr, {
onEnter: function (args) {
},
onLeave: function(retval){
console.log("=======检测函数的返回值修改=======")
retval.replace(0);
}
});
}
function nopFunc(addr) {
const pageStart = addr.and(ptr(-Process.pageSize));
Memory.protect(pageStart, Process.pageSize, 'rwx');
var writer = new Arm64Writer(addr);
writer.putRet(); // 直接将函数首条指令设置为ret指令
writer.flush(); // 写入操作刷新到目标内存,使得写入的指令生效
writer.dispose(); // 释放 Arm64Writer 使用的资源
console.log("nop " + addr + " success");
}
function bypass_detect_func() {
var base = Module.findBaseAddress("libmsaoaidsec.so")
if (base.isNull()) return false;
// jxbank
nopFunc(base.add(0x1c544));
nopFunc(base.add(0x1b8d4));
nopFunc(base.add(0x26e5c));
return true;
}
setImmediate(() => {
hook_dlopen();
});
vmp分析
看完上面的反调试就到了大头了,也就是爱加密的VMP分析,因为我也在分析,只能说把自己的想法给大佬们讲讲,先说一下这个vmp吧,我分析着这个vmp是对dalvik字节码进行了vmp保护,因为我观察到他有256个指令数,而且分析了几个正好是对应的dalvik的字节码,猜测应该是了,我目前通过利用AI以及一些调试找到了生成opcode的地方,也凑巧找到了dex脱壳的地方,也是偶然发现,我目前卡在了对受保护的java层函数到opcode之间的对应关系上,希望有研究过的大佬教教弟弟,难坏我了😭😭😭
感觉爱加密企业版的主要的特色就是通过壳的一个函数来实现懒加载,就是不通过jnionload来实现初始化native的函数,而是用这个C.i来实现懒加载,让C.i这个函数通过查表的方式来实现查找类,查找函数名这种进行加载,所以如果要实现脱壳修复的话,我们就需要自己去将类中的native函数实现加载
再详细一点介绍的话,那就是java层的每一个具有native方法的类,都有一个对应static的C.i函数,然后他会传递一个id,就是这个id去查找对应的类和对应的方法名,然后进行RegisterNatives注册native方法,不过这个native函数确实将它调用到了libexecmain.so上面去,具体的执行,应该是libexecmain.so这个文件再进行后续的分发,然后把他们分发到具体执行的so上面
难点分析:
1、定位vmp的地方
2、定位opcode产生的地方
3、将保护函数和opcode关联起来
这三个难点的难度是逐渐增加的,下面给大佬们说一下我的想法和思路
vmp定位:
这个vmp函数的定位还是挺简单的,我们前面不是已经绕过对frida的检测了嘛,我们对下面这个函数hook就行
function hook_RegisterNatives() {
var RegisterNatives_addr = null;
var symbols = Process.findModuleByName("libart.so").enumerateSymbols();
for (var i = 0; i < symbols.length; i++) {
var symbol = symbols[i].name;
if ((symbol.indexOf("CheckJNI") == -1) && (symbol.indexOf("JNI") >= 0)) {
if (symbol.indexOf("RegisterNatives") >= 0) {
RegisterNatives_addr = symbols[i].address;
console.log("RegisterNatives_addr: ", RegisterNatives_addr);
}
}
}
Interceptor.attach(RegisterNatives_addr, {
onEnter: function (args) {
var env = args[0];
var jclass = args[1];
var class_name = Java.vm.tryGetEnv().getClassName(jclass);
var methods_ptr = ptr(args[2]);
var method_count = args[3].toInt32();
console.log("RegisterNatives method counts: ", method_count);
for (var i = 0; i < method_count; i++) {
var name = methods_ptr.add(i * Process.pointerSize * 3).readPointer().readCString();
var sig = methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize).readPointer().readCString();
var fnPtr_ptr = methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2).readPointer();
var find_module = Process.findModuleByAddress(fnPtr_ptr);
console.log("类: ", class_name, "方法: ", name, "签名: ", sig, "函数地址: ", fnPtr_ptr, "模块名: ", find_module.name, "函数偏移: ", ptr(fnPtr_ptr).sub(find_module.base));
}
},
onLeave: function (retval) { }
});
}
可以得到下面这些(片段)
[Native] C.i called, x = 67108975
RegisterNatives method counts: 1
类: com.huawei.hms.aaid.InitProvider 方法: delete 签名: (Landroid/net/Uri;Ljava/lang/String;[Ljava/lang/String;)I 函数地址: 0x7583c66f84 模块名: libexecmain.so 函数偏移: 0xc6f84
RegisterNatives method counts: 1
类: com.huawei.hms.aaid.InitProvider 方法: getType 签名: (Landroid/net/Uri;)Ljava/lang/String; 函数地址: 0x7583c6715c 模块名: libexecmain.so 函数偏移: 0xc715c
RegisterNatives method counts: 1
类: com.huawei.hms.aaid.InitProvider 方法: insert 签名: (Landroid/net/Uri;Landroid/content/ContentValues;)Landroid/net/Uri; 函数地址: 0x7583c67214 模块名: libexecmain.so 函数偏移: 0xc7214
RegisterNatives method counts: 1
类: com.huawei.hms.aaid.InitProvider 方法: onCreate 签名: ()Z 函数地址: 0x7583c673fc 模块名: libexecmain.so 函数偏移: 0xc73fc
RegisterNatives method counts: 1
类: com.huawei.hms.aaid.InitProvider 方法: query 签名: (Landroid/net/Uri;[Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)Landroid/database/Cursor; 函数地址: 0x7583c674b4 模块名: libexecmain.so 函数偏移: 0xc74b4
RegisterNatives method counts: 1
类: com.huawei.hms.aaid.InitProvider 方法: update 签名: (Landroid/net/Uri;Landroid/content/ContentValues;Ljava/lang/String;[Ljava/lang/String;)I 函数地址: 0x7583c6769c 模块名: libexecmain.so 函数偏移: 0xc769c
调试定位:
然后我写了一个基于unicorn的对JNI函数和SVC函数进行转发真机的一个小调试器,因为我最开始想要用这个来trace的,发现效率不如qdbi就放弃了,直接改成调试了,可能bug有点多,大佬们别喷😭
https://github.com/XiaoWaaay/VixlDebugger
代码给各位大佬,我没整理,有点乱了
const soname1 = "libexecmain.so"
const soname2 = "libexec.so"
let hooksInstalled = false;
let pthreadHookInstalled = false;
let flagss = false;
let msaoaidsecHandled = false;
const killedStartRoutine = new NativeCallback(function (arg) {
return ptr(0);
}, 'pointer', ['pointer']);
function hook_dlopen() {
var dlopen = Module.findExportByName(null, "android_dlopen_ext");
console.log(`dlopen=${dlopen}`);
if (dlopen) {
Interceptor.attach(dlopen, {
onEnter: function (args) {
this.path = args[0].isNull() ? "" : args[0].readCString();
var soName = Memory.readCString(args[0]);
console.log(`dlopen== soName=${soName}`);
if (soName.indexOf(soname1) >= 0 || soName.indexOf(soname2) >= 0) {
this.flag = true;
}
if (soName.indexOf("libmsaoaidsec.so") >= 0) {
console.log("=====libmsaoaidsec.so调用=====");
this.flag2 = true;
flagss = true;
}
}, onLeave: function (retval) {
if (startState === 0 && this.path.indexOf(TARGET_SO_NAME) !== -1) {
//console.log("[+] dlopen detected: " + this.path);
startDebug();
}
if (this.flag && !hooksInstalled) {
hooksInstalled = true;
//hook_xxdlopen();
startDebug();
hook_phread();
//hook_dlopenso();
//hook_RegisterNatives();
//hook_strdecrypt()
//hook_cmp();
hook_test()
//find_addr();
//check_package();
}
if (this.flag2 && !msaoaidsecHandled) {
console.log("====包hook====")
hook_test2()
flagss = false;
console.log("=====bypass_detect_func=====");
msaoaidsecHandled = bypass_detect_func() || msaoaidsecHandled;
}
}
});
}
}
function hook_phread() {
if (pthreadHookInstalled) return;
pthreadHookInstalled = true;
const pthreadCreate = Module.findExportByName(null, "pthread_create");
if (!pthreadCreate) {
console.log("Failed to find pthread_create");
return;
}
Interceptor.attach(pthreadCreate, {
onEnter: function (args) {
const callSite = ptr(this.returnAddress);
const callSiteModule = Process.findModuleByAddress(callSite);
let callSiteOffset = null;
let callSiteModuleName = null;
if (callSiteModule) {
callSiteOffset = callSite.sub(callSiteModule.base);
callSiteModuleName = callSiteModule.name;
console.log("[pthread_create] called from", callSiteModuleName, "offset=" + callSiteOffset.toString());
} else {
console.log("[pthread_create] called from", callSite);
}
const startRoutine = args[2];
const startModule = Process.findModuleByAddress(startRoutine);
if (startModule) {
const startOffset = ptr(startRoutine).sub(startModule.base);
console.log("[pthread_create] start_routine in", startModule.name, "offset=" + startOffset.toString());
if (startModule.name === "libmsaoaidsec.so") {
if (!msaoaidsecHandled) {
msaoaidsecHandled = bypass_detect_func() || msaoaidsecHandled;
}
const off = startOffset.toUInt32();
if (off === 0x1c544 || off === 0x1b8d4 || off === 0x26e5c) {
args[2] = killedStartRoutine;
}
}
} else {
console.log("[pthread_create] start_routine at", startRoutine);
}
}
})
}
function hook_cmp() {
var offset = 0x55EB8;
var module = Process.findModuleByName(soname1);
if (!module) {
module = Process.findModuleByName(soname2);
}
if (!module) {
console.log("[hook_cmp] target module not found for", soname1, "or", soname2);
return;
}
var cmp_addr = module.base.add(offset);
console.log("[hook_cmp] module=", module.name, "base=", module.base, "cmp_addr=", cmp_addr);
Interceptor.attach(cmp_addr, {
onEnter: function (args) {
console.log("[cmp] called with", this.context.x0);
var p0 = this.context.x0;
console.log(hexdump(ptr(p0)), 0x30);
console.log("=========");
console.log("[cmp] called with", this.context.x1);
var p1 = this.context.x1;
console.log(hexdump(ptr(p1)), 0x30);
},
});
}
function hook_strdecrypt() {
var offset = 0x75BB4;
var module = Process.findModuleByName(soname1);
if (!module) {
module = Process.findModuleByName(soname2);
}
if (!module) {
console.log("[hook_strdecrypt] target module not found for", soname1, "or", soname2);
return;
}
var cmp_addr = module.base.add(offset);
console.log("[hook_strdecrypt] module=", module.name, "base=", module.base, "addr=", cmp_addr);
Interceptor.attach(cmp_addr, {
onEnter: function (args) {
this.arg0 = args[0];
},
onLeave: function (retval) {
try {
var p0 = ptr(this.arg0);
console.log("[strdecrypt] arg0=", p0);
try {
var s = Memory.readCString(p0);
console.log("[strdecrypt] arg0_str=", s);
} catch (e) {
console.log("[strdecrypt] arg0_str_read_failed=", e.message);
}
try {
console.log(hexdump(p0, { length: 0x80 }));
} catch (e) {
console.log("[strdecrypt] arg0_hexdump_failed=", e.message);
}
var frames = Thread.backtrace(this.context, Backtracer.FUZZY).slice(0, 24);
var bt = frames.map(function (a) {
try {
return DebugSymbol.fromAddress(a).toString();
} catch (e) {
return a.toString();
}
}).join("\n");
console.log("[test] backtrace:\n" + bt);
} catch (e) {
console.log("[strdecrypt] onLeave_error=", e.message);
}
}
});
}
function hook_test() {
var offset = 0x55EB8;
var module = Process.findModuleByName(soname1);
if (!module) {
module = Process.findModuleByName(soname2);
}
if (!module) {
console.log("[hook_test] target module not found for", soname1, "or", soname2);
return;
}
var cmp_addr = module.base.add(offset);
Interceptor.attach(cmp_addr, {
onEnter: function (args) {
console.log("=======")
// var p8 = this.context.x0;
// console.log("p0=",p8);
// console.log("p8=",p8.sub(module.base));
// console.log("[test] called with", this.context.x0);
// var p23 = this.context.x0;
// console.log(hexdump(ptr(p23), { length: 0x40 }));
// console.log("=========");
// console.log("[cmp] called with", this.context.x1);
// var p1 = this.context.x1;
// console.log(hexdump(ptr(p1), { length: 0x30 }));
},
onLeave: function (retval) {
console.log(retval);
console.log("=======检测函数的返回值修改=======")
retval.replace(0);
}
});
}
function nopFunc(addr) {
const pageStart = addr.and(ptr(-Process.pageSize));
Memory.protect(pageStart, Process.pageSize, 'rwx');
var writer = new Arm64Writer(addr);
writer.putRet(); // 直接将函数首条指令设置为ret指令
writer.flush(); // 写入操作刷新到目标内存,使得写入的指令生效
writer.dispose(); // 释放 Arm64Writer 使用的资源
console.log("nop " + addr + " success");
}
function bypass_detect_func() {
var base = Module.findBaseAddress("libmsaoaidsec.so")
if (base.isNull()) return false;
// jxbank
nopFunc(base.add(0x1c544));
nopFunc(base.add(0x1b8d4));
nopFunc(base.add(0x26e5c));
return true;
}
function dump_target_memory() {
// 你的 x9 指向的地址
var targetAddr = ptr("0x000000753d8e9a88");
console.log(" 正在分析地址: " + targetAddr);
// 自动寻找包含这个地址的内存段
var range = Process.findRangeByAddress(targetAddr);
if (range) {
console.log("---------------------------------------------");
console.log("[+] 命中目标内存段 (Ghost Region)!");
console.log(" Base: " + range.base);
console.log(" Size: " + range.size);
console.log(" Prot: " + range.protection); // 应该是 r-xp
console.log(" File: " + range.file); // 应该是 null/undefined
console.log("---------------------------------------------");
// 1. 检查是不是 ELF
try {
var magic = range.base.readU32();
if (magic === 0x464C457F) {
console.log("[+] 头部检测通过: 这是一个标准的 ELF 文件 (SO)!");
} else {
console.log("[!] 头部检测警告: 魔数是 " + magic.toString(16));
console.log(" (可能是被抹头了,或者是纯 Shellcode/Dex)");
}
} catch(e) {
console.log("[-] 内存不可读");
}
// 2. Dump 到文件
var filename = "/data/data/com.intsig.camscanner/dump_memory.so";
var file = new File(filename, "wb");
if (file) {
var data = range.base.readByteArray(range.size);
file.write(data);
file.flush();
file.close();
console.log("[+] 成功 Dump 到: " + filename);
console.log(" 请执行: adb pull " + filename);
}
// 3. 计算函数偏移
var offset = targetAddr.sub(range.base);
console.log(" 你的 x9 函数位于 Dump 文件的偏移: " + offset);
console.log(" (IDA 打开后按 G 跳转到这个偏移即可看到代码)");
} else {
console.log("[-] 未找到对应的内存段,地址可能已变动 (ASLR)。");
console.log(" 请重新获取最新的 x9 地址。");
}
}
function find_addr() {
var offset = 0x5FB9C;
console.log("[find_addr] ======");
var module = Process.findModuleByName("libexec.so");
if (!module) {
console.log("[find_addr] target module not found for", "libexec.so");
return;
}
var addr = module.base.add(offset);
Interceptor.attach(addr, {
onEnter: function (args) {
var p19 = this.context.x8;
console.log("[find_addr]== ==", p19.sub(module.base));
}
})
}
function check_package() {
var offset = 0x33040;
var module = Process.findModuleByName("libexec.so");
if (!module) {
console.log("[check_package] target module not found for", "libexec.so");
return;
}
var addr = module.base.add(offset);
Interceptor.attach(addr, {
onEnter: function (args) {
this.arg0 = args[0];
//console.log("args[0]=",hexdump(ptr(args[0]), { length: 0x30 }));
// console.log("args[1]=",args[1]);
// console.log("args[2]=",args[2]);
// console.log("args[3]=",args[3]);
// console.log("str0=",Memory.readCString(args[0]));
// console.log("str2=",Memory.readCString(args[2]));
// console.log("str3=",Memory.readCString(args[3]));
//var p19 = this.context.x0;
//console.log("[check_package]== ==",p19.sub(module.base));
}, onLeave: function (retval) {
console.log("check_package", retval);
//console.log("args[0]=",hexdump(ptr(this.arg0), { length: 0x100 }));
}
})
}
function hook_test2() {
var offset = 0x18FE4;
var module = Process.findModuleByName("libmsaoaidsec.so");
if (!module) {
console.log("[hook_test] target module not found for", soname1, "or", soname2);
return;
}
console.log("====包检测====")
var cmp_addr = module.base.add(offset);
Interceptor.attach(cmp_addr, {
onEnter: function (args) {
console.log("====包检测====")
},
onLeave: function (retval) {
console.log(retval);
console.log("=======检测函数的返回值修改=======")
retval.replace(0);
}
});
}
function hook_dlopenso() {
var offset = 0x870E8;
var module = Process.findModuleByName("libexec.so");
Interceptor.attach(module.base.add(offset), {
onEnter: function (args) {
var env = this.context.x0;
var jclass = this.context.x1;
var class_name = Java.vm.tryGetEnv().getClassName(jclass);
var methods_ptr = ptr(this.context.x2);
var method_count = this.context.x3.toInt32();
console.log("RegisterNatives method counts: ", method_count);
for (var i = 0; i < method_count; i++) {
var name = methods_ptr.add(i * Process.pointerSize * 3).readPointer().readCString();
var sig = methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize).readPointer().readCString();
var fnPtr_ptr = methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2).readPointer();
var find_module = Process.findModuleByAddress(fnPtr_ptr);
console.log("类: ", class_name, "方法: ", name, "签名: ", sig, "函数地址: ", fnPtr_ptr, "模块名: ", find_module.name, "函数偏移: ", ptr(fnPtr_ptr).sub(find_module.base));
}
// console.log("dlopen_ext called with", this.context.x2);
// var p2 = this.context.x2;
// console.log("p2=",p2);
// console.log(hexdump(ptr(p2), { length: 0x30 }));
// console.log("dlopen_ext called with", this.context.x3);
// var p3 = this.context.x3;
// console.log("p3=",p3);
// console.log(hexdump(ptr(p3), { length: 0x30 }));
}
})
}
function hook_RegisterNatives() {
var RegisterNatives_addr = null;
var symbols = Process.findModuleByName("libart.so").enumerateSymbols();
for (var i = 0; i < symbols.length; i++) {
var symbol = symbols[i].name;
if ((symbol.indexOf("CheckJNI") == -1) && (symbol.indexOf("JNI") >= 0)) {
if (symbol.indexOf("RegisterNatives") >= 0) {
RegisterNatives_addr = symbols[i].address;
console.log("RegisterNatives_addr: ", RegisterNatives_addr);
}
}
}
Interceptor.attach(RegisterNatives_addr, {
onEnter: function (args) {
var env = args[0];
var jclass = args[1];
var class_name = Java.vm.tryGetEnv().getClassName(jclass);
var methods_ptr = ptr(args[2]);
var method_count = args[3].toInt32();
console.log("RegisterNatives method counts: ", method_count);
for (var i = 0; i < method_count; i++) {
var name = methods_ptr.add(i * Process.pointerSize * 3).readPointer().readCString();
var sig = methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize).readPointer().readCString();
var fnPtr_ptr = methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2).readPointer();
var find_module = Process.findModuleByAddress(fnPtr_ptr);
console.log("类: ", class_name, "方法: ", name, "签名: ", sig, "函数地址: ", fnPtr_ptr, "模块名: ", find_module.name, "函数偏移: ", ptr(fnPtr_ptr).sub(find_module.base));
}
},
onLeave: function (retval) { }
});
}
function hook_xxdlopen(){
var offset=0xDD800;
var module = Process.findModuleByName("libexec.so");
Interceptor.attach(module.base.add(offset), {
onEnter: function (args) {
console.log("打开的so",args[0]);
console.log("====xx====")
},
onLeave: function (retval) {
}
});
}
const VIXLTRACE_SO_PATH = "/data/local/tmp/libvixl_trace.so";
const TARGET_SO_NAME = "libexec.so";
//必须下在函数入口处,然后再在调试的时候下其他的断点
const TARGET_OFFSET_HEX = "0x87214";
const soPath = VIXLTRACE_SO_PATH;
const mod = Module.load(soPath);
console.log("[+] loaded:", mod.name, "base=", mod.base);
function nf(name, ret, args) {
const p = Module.findExportByName(mod.name, name) || Module.findExportByName(null, name);
if (!p) throw new Error("export not found: " + name);
return new NativeFunction(p, ret, args);
}
const GetOrCreateTramp = nf("VixlTrace_GetOrCreateTrampoline", "pointer", ["pointer", "uint64", "pointer"]);
const GetOrCreateTrampByAddr = nf("VixlTrace_GetOrCreateTrampolineByAddr", "pointer", ["uint64"]);
const SetTargetRange = nf("VixlTrace_SetTargetModuleRange", "int", ["pointer", "uint64", "uint64", "uint64"]);
const DebugEnable = nf("VixlTrace_DebugEnable", "void", ["int"]);
const DebugAddBreakpoint = nf("VixlTrace_DebugAddBreakpoint", "void", ["uint64"]);
const DebugStartServer = nf("VixlTrace_DebugStartServer", "int", ["pointer"]);
const DebugHoldUntilClient = nf("VixlTrace_DebugHoldUntilClient", "void", ["int"]);
const HookByAddr = nf("VixlTrace_HookByAddr", "int", ["uint64"]);
let startState = 0;
let startTries = 0;
function asU64(v) {
if (v instanceof UInt64) return v;
if (v instanceof NativePointer) return new UInt64(v.toString());
return new UInt64(v);
}
function computeTarget() {
const base = Module.findBaseAddress(TARGET_SO_NAME);
if (base === null) return null;
const offsetPtr = ptr(TARGET_OFFSET_HEX);
const target = base.add(offsetPtr);
if (!target.and(ptr("0x3")).isNull()) {
throw new Error("target address is not 4-byte aligned: " + target);
}
const offset = new UInt64(TARGET_OFFSET_HEX);
return { base, target, offset };
}
function startDebug() {
if (startState !== 0) return;
startState = 1;
startTries++;
let info;
try {
info = computeTarget();
} catch (e) {
console.error("[!] computeTarget failed:", e);
startState = 0;
if (startTries < 5) setTimeout(startDebug, 200);
return;
}
if (!info) {
startState = 0;
setTimeout(startDebug, 50);
return;
}
const { base, target, offset } = info;
console.log("[+] target base=", base, "offset(hex)=", ptr(TARGET_OFFSET_HEX), "target=", target);
try {
console.log("[+] DebugEnable(1)");
DebugEnable(1);
console.log("[+] DebugHoldUntilClient(0)");
DebugHoldUntilClient(0);
console.log("[+] DebugStartServer(vixltrace_dbg)");
const ssRet = DebugStartServer(Memory.allocUtf8String("vixltrace_dbg"));
console.log("[+] DebugStartServer ret=", ssRet);
const m = Process.getModuleByName(TARGET_SO_NAME);
const mStart = asU64(m.base);
const mEnd = asU64(m.base.add(m.size));
const mBase = asU64(m.base);
const ok = SetTargetRange(Memory.allocUtf8String(TARGET_SO_NAME), mStart, mEnd, mBase);
console.log("[+] SetTargetRange ret=", ok, " range=", m.base, "-", m.base.add(m.size));
const hookOk = HookByAddr(asU64(target));
console.log("[+] HookByAddr ret=", hookOk, " target=", target);
if (hookOk === 0) throw new Error("HookByAddr failed");
} catch (e) {
console.error("[!] native call failed:", e);
startState = 0;
if (startTries < 5) setTimeout(startDebug, 200);
return;
}
startState = 2;
console.log("[+] debugger armed: bp@", target, " server=localabstract:vixltrace_dbg");
}
setImmediate(() => {
hook_dlopen();
});
大家改上面代码的so就行,去调试这个就行
模块名: libexecmain.so 函数偏移: 0xc73fc
然后一点一点进去看br,bl跳转就行,也可以直接点击c,他会把跳转出去的偏移打印出来,大家也就能找到类似于这个的地方
trace定位:
当然,大家直接用qdbi直接去trace这些函数更快,也更佳方便,只不过我是想看看他的opcode的结构的时候才去一点点分析内存特征的
里面就是那200多个指令集的地方,只不过存在一些指令替换,有点难看,不过用ida-mcp应该也很好解决,我的版本太低就没用
定位opcode:
这个定位opcode我最开始是无意中碰巧发现的,给大家说一下思路吧,因为我知道他肯定会调用assert下的那个ijiami.dat这个文件,大家只要对这个文件调用的地方回溯就可以知道他哪里调用的了,但是又回到那个难点了,他的字符串是加密的,所以他有三个方式去解密
1、字符串解密
这个方法比较笨,那就是大家去看哪些字符串没有被解密,然后交叉引用,找到key,然后用我上面那个的脚本去批量解密,这样肯定可以得到这个ijiami.dat的字符串
2、搜索0x464C457F
这个也就是我的方法,这个确实可以找到,因为我最开始猜测他是自定义linker,然后调试+trace+静态分析+hook验证,确实找到了位置
3、memem函数
我们知道这个memem函数一般是在一个内存块中查找另一个子内存块首次出现的位置
void *memmem(const void *haystack, size_t haystacklen,
const void *needle, size_t needlelen);
haystack: 要扫描的“干草堆”(主内存区域)。
haystacklen: 主内存区域的大小(字节)。
needle: 要寻找的“针”(子内存块)。
needlelen: 子内存块的大小(字节)。
返回值: 如果找到了,返回指向子块起始位置的指针;如果没找到,返回 NULL
我们hook这个查看字符串,也许可以找到关键位置呢
这几个方法差不多都可以找到opcode的位置以及dump dex的位置,然后会发现他其实都是从asseert下可以直接解密得到的数据,跟我使用56.al得到的结果是一样的
保护函数与opcode的关联
这个是真有点难到我了,我目前发现他的类的规律了,先给大家看一下上面得到的opcode吧,如果大家用我上面的调试器去调试看了一下,就可以在下面这段代码里面发现出现的opcode
我目前找到的关系呢就是从java层的那个C.i(参数)这个到类名的关联,但是再到opcode我还没有实现关联
上面我们hook了注册函数,可以得到这个
RegisterNatives method counts: 1
类: s.h.e.l.l.C 方法: i 签名: (I)V 函数地址: 0x758668c214 模块名: libexec.so 函数偏移: 0x87214
dlopen== soName=/data/user/0/com.intsig.camscanner/files/libexecmain.so
RegisterNatives method counts: 1
类: android.util.Log 方法: println_native 签名: (IILjava/lang/String;Ljava/lang/String;)I 函数地址: 0x75866417e8 模块名: libexec.so 函数偏移: 0x3c7e8
代码
// 调用 C.i(117440919);
JNIEnv *__fastcall java2native(JNIEnv *result, __int64 a2, int a3)
{
int v3; // w20
void ***v4; // x25
void **v5; // x8
_QWORD *v6; // x9
__int64 v7; // x24
const char *v8; // x22
int v9; // w9
int v10; // w10
unsigned __int64 v11; // t2
JNIEnv *v12; // x19
int v13; // w9
unsigned __int64 v14; // t2
JNIEnv *v15; // x21
jclass v16; // x0
void **v17; // x8
jclass v18; // x22
__int64 v19; // x22
void (__fastcall *v20)(JNIEnv *, void **, char *, __int64, char *, char *, JNIEnv *); // x25
unsigned int v21; // w9
unsigned int v22; // w9
unsigned int v23; // w9
int v24; // w9
unsigned __int64 v25; // t2
int v26; // w9
int v27; // w8
int v28; // w22
void *v29; // x20
int v30; // w9
int v31; // w8
unsigned __int64 v32; // t2
void *v33[2]; // [xsp+0h] [xbp-10h] BYREF
v3 = a3 & 0xFFFFFF;
v33[1] = *(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
v4 = off_E3290;
v5 = *off_E3290;
v6 = *&byte_2E0[*off_E3290 + 16]; // 偏移表
v7 = *(*v6 + 4LL * (a3 & 0xFFFFFF)); // 获取index
v8 = *(v6[1] + 8 * v7); // 这个是根据id查找的类名
if ( v8 )
{
v10 = v7 + 0x1C;
if ( v7 - 3 >= 0 )
v10 = v7 - 3;
v9 = v7 - 3;
HIDWORD(v11) = -286331153
* (v9 - (v10 & 0xFFFFFFE0) + 2)
* (v9 - (v10 & 0xFFFFFFE0) + 1)
* ((2 * (v9 - (v10 & 0xFFFFFFE0) + 1)) | 1)
* (3 * (v9 - (v10 & 0xFFFFFFE0) + 2) * (v9 - (v10 & 0xFFFFFFE0) + 1) - 1)
+ 143165576;
LODWORD(v11) = HIDWORD(v11);
v12 = result;
if ( (v11 >> 1) > 0x8888888 )
goto LABEL_9;
while ( 1 )
{
result = (*(*(&qword_20 + v5) + 184))(v12, v8);// 跳转到字符串转换的地方,不重要
v13 = v7 + 124;
if ( v7 + 61 >= 0 )
v13 = v7 + 61;
HIDWORD(v14) = -858993459
* (v7 + 61 - (v13 & 0xFFFFFFC0) + 1)
* (v7 + 61 - (v13 & 0xFFFFFFC0))
* (v7 + 61 - (v13 & 0xFFFFFFC0) + 2)
* (v7 + 61 - (v13 & 0xFFFFFFC0) + 3)
* (v7 + 61 - (v13 & 0xFFFFFFC0) + 4)
+ 429496728;
LODWORD(v14) = HIDWORD(v14);
if ( (v14 >> 1) < 0x19999999 )
break;
v5 = *v4;
LABEL_9:
(*(*(&qword_20 + v5) + 184))(v12, v8);
v5 = *v4;
}
v15 = result;
if ( result )
{
v16 = (*v12)->FindClass(v12, v8);
v17 = *v4;
v33[0] = v16;
v18 = v16;
(*(*(&qword_20 + v17) + 208))(v12); // 跳转到3cc84
if ( !v18 )
{
v19 = *(&qword_68 + *v4);
v20 = *(*(&qword_20 + *v4) + 136);
v21 = atomic_load(&dword_F2584);
if ( !v21 && !dword_F2584 )
{
atomic_store(1u, &dword_F2584);
str_decrypt6(aJavaLangClassl, 0x15u);
}
v22 = atomic_load(&dword_F2588);
if ( !v22 && !dword_F2588 )
{
atomic_store(1u, &dword_F2588);
str_decrypt6(aLjavaLangStrin_10, 0x25u);
}
v23 = atomic_load(&dword_F258C);
if ( !v23 && !dword_F258C )
{
atomic_store(1u, &dword_F258C);
str_decrypt6(aFindclass, 9u);
}
v20(v12, v33, aJavaLangClassl, v19, aLjavaLangStrin_10, aFindclass, v15);
}
v24 = v7 + 13;
if ( v7 - 50 >= 0 )
v24 = v7 - 50;
HIDWORD(v25) = -858993459
* (v7 - 50 - (v24 & 0xFFFFFFC0) + 1)
* (v7 - 50 - (v24 & 0xFFFFFFC0))
* (v7 - 50 - (v24 & 0xFFFFFFC0) + 2)
* (v7 - 50 - (v24 & 0xFFFFFFC0) + 3)
* (v7 - 50 - (v24 & 0xFFFFFFC0) + 4)
+ 429496728;
LODWORD(v25) = HIDWORD(v25);
if ( (v25 >> 1) > 0x19999998 )
{
(*v12)->DeleteLocalRef(v12, v15);
sub_86F40(v12, v33[0], v3);
}
v26 = v7 - 8;
if ( v7 - 39 >= 0 )
v26 = v7 - 39;
v27 = v7 - 39 - (v26 & 0xFFFFFFE0);
v28 = (v27 * v27 * v27 * v27 * (6 * v27 + 2 * v27 * v27 + 5) - v27 * v27) % 12;
do
{
(*v12)->DeleteLocalRef(v12, v15);
result = sub_86F40(v12, v33[0], v3);
}
while ( v28 );
v29 = v33[0];
if ( v33[0] )
{
v30 = v7 + 11;
if ( v7 - 20 >= 0 )
v30 = v7 - 20;
v31 = v7 - 20 - (v30 & 0xFFFFFFE0);
HIDWORD(v32) = -1431655765 * (v31 * v31 * v31 * v31 * (6 * v31 + 2 * v31 * v31 + 5) - v31 * v31) + 715827880;
LODWORD(v32) = HIDWORD(v32);
if ( (v32 >> 2) > 0x15555554 )
goto LABEL_35;
while ( 1 )
{
result = ((*v12)->DeleteLocalRef)(v12, v29);
if ( ((((v7 - 15) % 0x7FFF + 1) * ((v7 - 15) % 0x7FFF)) & 1) == 0 )
break;
LABEL_35:
(*v12)->DeleteLocalRef(v12, v29);
}
}
}
}
return result;
}
这个可以找到参数到类的关系,另外就是libexec mai n.so,这个可以发现他是不断嵌套的
__int64 __fastcall sub_BA4E4(
__int64 a1,
__int64 a2,
__int64 a3,
__int64 a4,
__int64 a5,
__int64 a6,
__int64 a7,
__int64 a8,
__int64 a9)
{
unsigned __int64 StatusReg; // x19
__int64 (__fastcall *v10)(__int64, __int64, __int64, __int128 *); // x9
__int64 result; // x0
__int64 v12; // [xsp+8h] [xbp-118h]
__int64 v13[6]; // [xsp+90h] [xbp-90h] BYREF
__int128 v14[2]; // [xsp+C0h] [xbp-60h] BYREF
__int128 v15; // [xsp+E8h] [xbp-38h]
__int128 v16; // [xsp+F8h] [xbp-28h]
v13[4] = a7;
v13[5] = a8;
v13[2] = a5;
v13[3] = a6;
v13[0] = a3;
v13[1] = a4;
StatusReg = _ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2));
v12 = *(StatusReg + 40);
*(&v16 + 1) = 0xFFFFFF80FFFFFFD0LL;
*(&v15 + 1) = v14;
*&v16 = v13;
*&v15 = &a9;
v10 = *(*off_217CD0 + 24LL);
v14[0] = v15;
v14[1] = v16;
result = v10(a1, 50332294LL, a2, v14);
if ( *(StatusReg + 40) != v12 )
return sub_BA59C(result);
return result;
}
__int64 __fastcall sub_BA59C(__int64 a1, __int64 a2)
{
unsigned __int64 StatusReg; // x21
int *v3; // x22
int *v4; // x23
_BOOL4 v7; // w8
_BOOL4 v8; // w9
__int64 result; // x0
int v10; // w8
int v11; // w9
__int64 v12; // [xsp+B8h] [xbp-38h]
StatusReg = _ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2));
v3 = off_21A010;
v4 = off_218BD8[0];
v12 = *(StatusReg + 40);
v7 = 8 * *off_218BD8[0] > 323;
v8 = (*off_218BD8[0] ^ *off_21A010) < 189;
if ( *off_218BD8[0] >= 10 && (((*off_21A010 - 1) * *off_21A010) & 1) != 0 && ((v7 ^ v8) & 1) == 0 && (v7 || v8) )
goto LABEL_7;
while ( 1 )
{
result = (*(*off_217CD0 + 24LL))(a1, 50332295LL, a2);
v10 = (*v4 > 9) & ((*v3 - 1) * *v3);
v11 = (*v4 ^ *v3) < 78 && 4 * *v4 > 478;
if ( v11 != v10 || !(v11 | v10) )
break;
LABEL_7:
(*(*off_217CD0 + 24LL))(a1, 50332295LL, a2);
}
if ( *(StatusReg + 40) != v12 )
return sub_BA788(result);
return result;
}
这个libexecmain就是保护的函数注册到的地方,也就是上面说的那个,再然后就不知道怎么办了,希望有研究过的大佬教教怎么搞的,想学习一下