攻防 FART 脱壳:特征检测识别 + 对抗绕过全解析
版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/
FART 对抗
某视频 app 的壳在启动的时候会检测 FART 特征,日志输出如下:
2025-05-29 02:16:25.612 2557-2557 ActivityThread cn.cntv E go into handleBindApplication
2025-05-29 02:16:25.630 2557-2557 cn.cntv cn.cntv I The ClassLoaderContext is a special shared library.
2025-05-29 02:16:25.807 1512-17245 ActivityManager system_process I Process cn.cntv (pid 2557) has died: fore TOP
2025-05-29 02:16:25.875 1512-1588 ActivityManager system_process I Start proc 2628:cn.cntv/u0a140 for top-activity {cn.cntv/com.cctv.mcctv.ui.activity.SplashActivity}
2025-05-29 02:16:25.932 2628-2628 ActivityThread cn.cntv E go into handleBindApplication
2025-05-29 02:16:25.945 2628-2628 cn.cntv cn.cntv I The ClassLoaderContext is a special shared library.
2025-05-29 02:16:26.113 1512-4110 ActivityManager system_process I Process cn.cntv (pid 2628) has died: fore TOP
2025-05-29 02:16:26.179 1512-1588 ActivityManager system_process I Start proc 2716:cn.cntv/u0a140 for top-activity {cn.cntv/com.cctv.mcctv.ui.activity.SplashActivity}
2025-05-29 02:16:26.233 2716-2716 ActivityThread cn.cntv E go into handleBindApplication
2025-05-29 02:16:26.245 2716-2716 cn.cntv cn.cntv I The ClassLoaderContext is a special shared library.
2025-05-29 02:16:26.291 2716-2716 cn.cntv cn.cntv W type=1400 audit(0.0:126069): avc: granted { execute } for path="/data/data/cn.cntv/files/libexec.so" dev="mmcblk0p64" ino=157243 scontext=u:r:untrusted_app:s0:c140,c256,c512,c768 tcontext=u:object_r:app_data_file:s0:c140,c256,c512,c768 tclass=file app=cn.cntv
2025-05-29 02:16:26.304 2716-2716 cn.cntv cn.cntv W type=1400 audit(0.0:126070): avc: granted { execute } for path="/data/data/cn.cntv/files/libexecmain.so" dev="mmcblk0p64" ino=157244 scontext=u:r:untrusted_app:s0:c140,c256,c512,c768 tcontext=u:object_r:app_data_file:s0:c140,c256,c512,c768 tclass=file app=cn.cntv
2025-05-29 02:16:26.324 2716-2716 cn.cntv cn.cntv W type=1400 audit(0.0:126071): avc: denied { execmod } for path="/apex/com.android.runtime/lib64/libart.so" dev="mmcblk0p61" ino=313 scontext=u:r:untrusted_app:s0:c140,c256,c512,c768 tcontext=u:object_r:system_lib_file:s0 tclass=file permissive=0 app=cn.cntv
2025-05-29 02:16:26.334 2716-2716 cn.cntv cn.cntv W type=1400 audit(0.0:126072): avc: denied { execmod } for path="/system/lib64/liblog.so" dev="mmcblk0p61" ino=3229 scontext=u:r:untrusted_app:s0:c140,c256,c512,c768 tcontext=u:object_r:system_lib_file:s0 tclass=file permissive=0 app=cn.cntv
2025-05-29 02:16:26.385 1512-17245 ActivityManager system_process I Process cn.cntv (pid 2716) has died: fore TOP
2025-05-29 02:16:26.441 1512-1588 ActivityManager system_process I Start proc 2807:cn.cntv/u0a140 for top-activity {cn.cntv/com.cctv.mcctv.ui.activity.SplashActivity}
2025-05-29 02:16:26.491 2807-2807 ActivityThread cn.cntv E go into handleBindApplication
2025-05-29 02:16:26.506 2807-2807 cn.cntv cn.cntv I The ClassLoaderContext is a special shared library.
2025-05-29 02:16:26.682 1512-17245 ActivityManager system_process I Process cn.cntv (pid 2807) has died: fore TOP
2025-05-29 02:16:26.731 1512-1588 ActivityManager system_process I Start proc 2872:cn.cntv/u0a140 for top-activity {cn.cntv/com.cctv.mcctv.ui.activity.SplashActivity}
2025-05-29 02:16:26.783 2872-2872 ActivityThread cn.cntv E go into handleBindApplication
使用的是 ajm 的壳,App 加载 so 文件,主动检测 FART 特征
avc: granted { execute } for path="/data/data/cn.cntv/files/libexec.so"
avc: granted { execute } for path="/data/data/cn.cntv/files/libexecmain.so"
一旦发现异常就触发崩溃(kill)
Process cn.cntv (pid 2628) has died: fore TOP
如何实现类似的功能?
-
首先找到 FART 的特征
-
FART 特征检测识别
-
识别到 FART 特征 kill 进程,没有识别到正常进入 app
FART特征
FART 有什么特征?通过查看 FART 源码可以找到。
FART 开源地址:https://github.com/CYRUS-STUDIO/FART
关于 FART 的详细介绍参考下面的文章:
-
FART 自动化脱壳框架简介与脱壳点的选择
-
FART 主动调用组件设计和源码分析
-
移植 FART 到 Android 10 实现自动化脱壳
-
FART 自动化脱壳框架一些 bug 修复记录
-
使用 Frida 增强 FART:实现更强大的 Android 脱壳能力
ActivityThread
源码:https://github.com/CYRUS-STUDIO/FART/blob/master/fart10/frameworks/base/core/java/android/app/ActivityThread.java
FART 在 ActivityThread 新增了以下方法,这些都可以作为 FART 的特征
public static Field getClassField(ClassLoader classloader, String class_name, String filedName)
public static Object getClassFieldObject(ClassLoader classloader, String class_name, Object obj, String filedName)
public static Object invokeStaticMethod(String class_name, String method_name, Class[] pareTyple, Object[] pareVaules)
public static Object getFieldOjbect(String class_name, Object obj, String filedName)
public static ClassLoader getClassloader()
public static void loadClassAndInvoke(ClassLoader appClassloader, String eachclassname, Method dumpMethodCode_method)
public static void fart()
public static void fartwithClassloader(ClassLoader appClassloader)
public static void fartthread()
DexFile
源码:https://github.com/CYRUS-STUDIO/FART/blob/master/fart10/libcore/dalvik/src/main/java/dalvik/system/DexFile.java
FART 在 DexFile 新增了 dumpMethodCode 方法同样也可以作为 FART 的特征
private static native void dumpMethodCode(Object m);
art_method.cc
FART 在 art/runtime/art_method.cc 中新增以下方法
uint8_t* codeitem_end(const uint8_t **pData)
extern "C" char *base64_encode(char *str,long str_len,long* outlen)
extern "C" void dumpDexFileByExecute(ArtMethod* artmethod)
extern "C" void dumpArtMethod(ArtMethod* artmethod)
extern "C" void myfartInvoke(ArtMethod* artmethod)
dalvik_system_DexFile.cc
FART 在 art/runtime/native/dalvik_system_DexFile.cc 中新增了以下方法
static void DexFile_dumpMethodCode(JNIEnv* env, jclass,jobject method)
java_lang_reflect_Method.cc
FART 在 art/runtime/native/java_lang_reflect_Method.cc 中新增了以下方法
extern "C" ArtMethod* jobject2ArtMethod(JNIEnv* env, jobject javaMethod)
上面这些都可以作为 FART 特征。
FART 特征有的在 native 层,最终编译成 so 文件;有的在 java 层,最终编译成 dex 相关文件。
如何找到这些 so 和 dex 相关文件?
/proc/self/maps
/proc/self/maps 是 Linux(含 Android)系统中一个非常重要的伪文件,它提供了当前进程内存映射(memory mapping)信息,是分析当前进程加载了哪些资源的重要窗口。
包括:
-
加载的 .so 动态库
-
加载的 .dex 文件(包含 ODEX / VDEX)
-
映射的 Java 堆、native 堆、stack 等
-
匿名 mmap 内存区域
-
JIT 编译生成的代码段
-
映射的 /system/, /data/, /apex/, /dev/ashmem 等文件
比如,进入 adb shell ,通过下面命令读取包名 com.cyrus.example 下的 maps 文件
cat /proc/$(pidof com.cyrus.example)/maps
输出结果如下:
12c00000-12c80000 rw-p 00000000 00:00 0 [anon:dalvik-main space (region space)]
12c80000-132c0000 ---p 00000000 00:00 0 [anon:dalvik-main space (region space)]
132c0000-13580000 rw-p 00000000 00:00 0 [anon:dalvik-main space (region space)]
13580000-26280000 ---p 00000000 00:00 0 [anon:dalvik-main space (region space)]
26280000-2a940000 ---p 00000000 00:00 0 [anon:dalvik-main space (region space)]
2a940000-2a980000 ---p 00000000 00:00 0 [anon:dalvik-main space (region space)]
2a980000-2a9c0000 ---p 00000000 00:00 0 [anon:dalvik-main space (region space)]
2a9c0000-2aac0000 ---p 00000000 00:00 0 [anon:dalvik-main space (region space)]
2aac0000-2ab80000 ---p 00000000 00:00 0 [anon:dalvik-main space (region space)]
2ab80000-2abc0000 ---p 00000000 00:00 0 [anon:dalvik-main space (region space)]
2abc0000-2ac00000 rw-p 00000000 00:00 0 [anon:dalvik-main space (region space)]
708d5000-70b5a000 rw-p 00000000 103:1d 1863 /system/framework/arm64/boot.art
70b5a000-70c4a000 rw-p 00000000 103:1d 1833 /system/framework/arm64/boot-core-libart.art
70c4a000-70c80000 rw-p 00000000 103:1d 1848 /system/framework/arm64/boot-okhttp.art
70c80000-70cc1000 rw-p 00000000 103:1d 1830 /system/framework/arm64/boot-bouncycastle.art
70cc1000-70cd1000 rw-p 00000000 103:1d 1827 /system/framework/arm64/boot-apache-xml.art
70cd1000-71595000 rw-p 00000000 103:1d 1839 /system/framework/arm64/boot-framework.art
71595000-715c9000 rw-p 00000000 103:1d 1836 /system/framework/arm64/boot-ext.art
715c9000-716c1000 rw-p 00000000 103:1d 1854 /system/framework/arm64/boot-telephony-common.art
716c1000-716cf000 rw-p 00000000 103:1d 1860 /system/framework/arm64/boot-voip-common.art
716cf000-716e4000 rw-p 00000000 103:1d 1842 /system/framework/arm64/boot-ims-common.art
716e4000-716e7000 rw-p 00000000 103:1d 1824 /system/framework/arm64/boot-android.test.base.art
716e7000-716e9000 rw-p 00000000 103:1d 1851 /system/framework/arm64/boot-org.ifaa.android.manager.art
716e9000-716f0000 rw-p 00000000 103:1d 1845 /system/framework/arm64/boot-ims-ext-common_system.art
716f0000-716f4000 rw-p 00000000 103:1d 1857 /system/framework/arm64/boot-telephony-ext.art
716f4000-716fc000 rw-p 00000000 103:1d 1821 /system/framework/arm64/boot-WfdCommon.art
716fc000-717b2000 r--p 00000000 103:1d 1864 /system/framework/arm64/boot.oat
717b2000-71a4d000 r-xp 000b6000 103:1d 1864 /system/framework/arm64/boot.oat
71a4d000-71a4e000 rw-p 00000000 00:00 0 [anon:.bss]
71a4e000-71a50000 r--s 00000000 103:1d 1882 /system/framework/boot.vdex
71a50000-71a51000 r--p 00351000 103:1d 1864 /system/framework/arm64/boot.oat
71a51000-71a52000 rw-p 00352000 103:1d 1864 /system/framework/arm64/boot.oat
71a52000-71a9b000 r--p 00000000 103:1d 1834 /system/framework/arm64/boot-core-libart.oat
71a9b000-71ba3000 r-xp 00049000 103:1d 1834 /system/framework/arm64/boot-core-libart.oat
...
比如:
71a9b000-71ba3000 r-xp 00049000 103:1d 1834 /system/framework/arm64/boot-core-libart.oat
字段解析如下:
字段 | 示例值 | 含义 |
---|---|---|
71a9b000-71ba3000 | 起始地址 - 结束地址 | 表示这段内存从 0x71a9b000 映射到 0x71ba3000(大约 1MB) |
r-xp | 权限 | r = 可读,x = 可执行,p = 私有 |
00049000 | 文件偏移 | 映射文件时从 offset=0x49000 开始 |
103:1d | 设备编号 | 表示该文件所在设备的主/次设备号 |
1834 | inode 号 | 文件在设备上的 inode 编号 |
/system/framework/arm64/boot-core-libart.oat | 文件路径 | 表示映射的文件路径,来源是系统的 OAT 文件 |
所有我们可以通过读取 /proc/self/maps 得到当前 app 加载的所有资源文件,实现如下:
extern "C"
JNIEXPORT jobjectArray JNICALL
Java_com_cyrus_example_fart_AntiFART_listLoadedFiles(JNIEnv *env, jclass) {std::ifstream maps("/proc/self/maps");std::string line;std::vector<std::string> paths;while (std::getline(maps, line)) {std::size_t pathPos = line.find('/');if (pathPos != std::string::npos) {std::string path = line.substr(pathPos);if (std::find(paths.begin(), paths.end(), path) == paths.end()) {paths.push_back(path);}}}jclass stringClass = env->FindClass("java/lang/String");jobjectArray result = env->NewObjectArray(paths.size(), stringClass, nullptr);for (size_t i = 0; i < paths.size(); ++i) {env->SetObjectArrayElement(result, i, env->NewStringUTF(paths[i].c_str()));}return result;
}
效果如下:
so 文件 FART 特征检测
对于 FART 在 C/C++ 层添加的函数特征码检测。
通过检测 /proc/self/maps下的加载 so库列表得到各个库文件绝对路径
// 读取 /proc/self/maps 获取加载的 .so 路径
std::set<std::string> get_loaded_so_paths() {std::set<std::string> so_paths;std::ifstream maps("/proc/self/maps");std::string line;std::regex so_regex(".+\\.so(\\s|$)");while (std::getline(maps, line)) {std::size_t path_pos = line.find('/');if (path_pos != std::string::npos) {std::string path = line.substr(path_pos);if (std::regex_search(path, so_regex)) {so_paths.insert(path);}}}return so_paths;
}
再通过 fopen 函数将 so 库的内容以16进制读进来放在内存,采用字符串模糊查找来检测是否命中黑名单中的方法特征码。
// so 黑名单函数特征
std::vector<std::string> so_symbols_blacklist = {"dumpDexFileByExecute","dumpArtMethod","myfartInvoke","DexFile_dumpMethodCode"
};// 读取文件内容为字符串
std::string read_file_content(const std::string &path) {FILE *file = fopen(path.c_str(), "rb");if (!file) {LOGI("Failed to open: %s", path.c_str());return "";}fseek(file, 0, SEEK_END);long size = ftell(file);rewind(file);std::string buffer(size, 0);fread(&buffer[0], 1, size, file);fclose(file);return buffer;
}// 单词边界检查
bool is_word_boundary(char ch) {return !std::isalnum(static_cast<unsigned char>(ch)) && ch != '_';
}// 返回匹配到的特征列表
std::vector<std::string> get_matched_signatures(const std::string &content, const std::vector<std::string> &patterns) {std::vector<std::string> matched;for (const auto &pattern : patterns) {size_t pos = content.find(pattern);if (pos != std::string::npos) {// 类似 DexFile_dumpMethodCode 这种,带 _ 的不需要做单词边界检查if (pattern.find('_') != std::string::npos) {matched.push_back(pattern);}else{// 单词边界检查// 这样就不会匹配 farther、himmelfart,但可以匹配像 void fart()、"fart"、 call fart 等形式。char prev = (pos == 0) ? '\0' : content[pos - 1];char next = (pos + pattern.length() < content.size()) ? content[pos + pattern.length()] : '\0';if (is_word_boundary(prev) && is_word_boundary(next)) {matched.push_back(pattern);}}}}return matched;
}// JNI 方法:检测已加载 .so 中是否包含黑名单符号
extern "C"
JNIEXPORT jobjectArray JNICALL
Java_com_cyrus_example_fart_AntiFART_detectFartInLoadedSO(JNIEnv *env, jclass clazz) {std::vector<std::string> detected_logs;auto so_paths = get_loaded_so_paths();for (const auto &path: so_paths) {std::string content = read_file_content(path);if (!content.empty()) {std::vector<std::string> matched = get_matched_signatures(content, so_symbols_blacklist);if (!matched.empty()) {std::ostringstream oss;oss << "[FART DETECTED] " << path << " => ";for (size_t i = 0; i < matched.size(); ++i) {oss << matched[i];if (i != matched.size() - 1) oss << ", ";}LOGI("%s", oss.str().c_str());detected_logs.push_back(oss.str());}}}jclass stringClass = env->FindClass("java/lang/String");jobjectArray result = env->NewObjectArray(detected_logs.size(), stringClass, nullptr);for (int i = 0; i < detected_logs.size(); ++i) {env->SetObjectArrayElement(result, i, env->NewStringUTF(detected_logs[i].c_str()));}return result;
}
可以看到在 libart.so 中命中了多个 FART 特征。
dex 文件 FART 特征检测
对于 FART 在 Java 层添加的方法特征码检测也是类似。
但是 dex 相关文件格式有多种,包括:
-
.dex 文件(原始 dex)
-
.odex(优化过的 dex)
-
.vdex(Verified DEX)
-
.art(预编译的 ART 文件)
-
以及 .jar、.apk 中可能包含 dex 文件的路径
读取 /proc/self/maps 获取加载的 dex 或 dex 相关文件路径
// 读取 /proc/self/maps 获取加载的 dex 或 dex 相关文件路径
std::set<std::string> get_loaded_dex_paths() {std::set<std::string> dex_paths;std::ifstream maps("/proc/self/maps");std::string line;// 匹配 dex、odex、vdex、art、apk、jar 文件std::regex dex_regex(R"((\.dex|\.odex|\.vdex|\.art|\.apk|\.jar)(\s|$))");while (std::getline(maps, line)) {std::size_t path_pos = line.find('/');if (path_pos != std::string::npos) {std::string path = line.substr(path_pos);if (std::regex_search(path, dex_regex)) {dex_paths.insert(path);}}}return dex_paths;
}
再通过 fopen 函数将 dex 相关文件的内容以16进制读进来放在内存,采用字符串模糊查找来检测是否命中黑名单中的方法特征码。
// dex 黑名单函数特征
const std::vector<std::string> dex_method_blacklist = {"loadClassAndInvoke","fart","fartwithClassloader","fartthread"
};// JNI 方法:检测已加载 dex 中是否包含黑名单符号
extern "C"
JNIEXPORT jobjectArray JNICALL
Java_com_cyrus_example_fart_AntiFART_detectFartInLoadedDex(JNIEnv *env, jclass clazz) {std::vector<std::string> detected_logs;auto dex_paths = get_loaded_dex_paths();for (const auto &path: dex_paths) {std::string content = read_file_content(path);if (!content.empty()) {std::vector<std::string> matched = get_matched_signatures(content, dex_method_blacklist);if (!matched.empty()) {std::ostringstream oss;oss << "[FART DETECTED] " << path << " => ";for (size_t i = 0; i < matched.size(); ++i) {oss << matched[i];if (i != matched.size() - 1) oss << ", ";}LOGI("%s", oss.str().c_str());detected_logs.push_back(oss.str());}}}jclass stringClass = env->FindClass("java/lang/String");jobjectArray result = env->NewObjectArray(detected_logs.size(), stringClass, nullptr);for (int i = 0; i < detected_logs.size(); ++i) {env->SetObjectArrayElement(result, i, env->NewStringUTF(detected_logs[i].c_str()));}return result;
}
可以看到在 framework.jar 中检测到了多个 FART 特征。
反 FART 对抗
绕过 FART 对抗只需要定制个性化的 ROM,抹除这些 FART 特征就好了。
抹除 FART 特征
比如把这些 FART 中默认的方法名重命名一下就好了。
public static Field getClassField(ClassLoader classloader, String class_name, String filedName)
public static Object getClassFieldObject(ClassLoader classloader, String class_name, Object obj, String filedName)
public static Object invokeStaticMethod(String class_name, String method_name, Class[] pareTyple, Object[] pareVaules)
public static Object getFieldOjbect(String class_name, Object obj, String filedName)
public static ClassLoader getClassloader()
public static void loadClassAndInvoke(ClassLoader appClassloader, String eachclassname, Method dumpMethodCode_method)
public static void fart()
public static void fartwithClassloader(ClassLoader appClassloader)
public static void fartthread()
private static native void dumpMethodCode(Object m);uint8_t* codeitem_end(const uint8_t **pData)
extern "C" char *base64_encode(char *str,long str_len,long* outlen)
extern "C" void dumpDexFileByExecute(ArtMethod* artmethod)
extern "C" void dumpArtMethod(ArtMethod* artmethod)
extern "C" void myfartInvoke(ArtMethod* artmethod)static void DexFile_dumpMethodCode(JNIEnv* env, jclass,jobject method)extern "C" ArtMethod* jobject2ArtMethod(JNIEnv* env, jobject javaMethod)
假设把函数重命名如下:
Java 层重命名:
原方法名 | 替代方法名 |
---|---|
getClassField | resolveDeclaredField |
getClassFieldObject | extractFieldValue |
invokeStaticMethod | invokeStaticByName |
getFieldOjbect | getInstanceFieldValue |
getClassloader | obtainAppClassLoader |
loadClassAndInvoke | dispatchClassTask |
fart | startCodeInspection |
fartwithClassloader | startCodeInspectionWithCL |
fartthread | launchInspectorThread |
dumpMethodCode | nativeDumpCode |
Native 层函数重命名:
原函数名 | 替代函数名 |
---|---|
codeitem_end | getDexCodeItemEnd |
base64_encode | encodeBase64Buffer |
dumpDexFileByExecute | traceDexExecution |
dumpArtMethod | traceMethodCode |
myfartInvoke | callNativeMethodInspector |
DexFile_dumpMethodCode | DexFile_nativeDumpCode |
jobject2ArtMethod | convertToArtMethodPtr |
记得相关函数调用也要做修改。
自动化脚本
但是一个个修改太麻烦了,写个脚本自动修改(可以自定义 RENAME_MAP 中的值去定制一个只属于自己的 FART ROM,这样就不容易被检测):
import os
import re# 敏感方法名及其替代名映射表
RENAME_MAP = {"getClassField": "resolveDeclaredField","getClassFieldObject": "extractFieldValue","invokeStaticMethod": "invokeStaticByName","getFieldOjbect": "getInstanceFieldValue","getClassloader": "obtainAppClassLoader","loadClassAndInvoke": "dispatchClassTask","fart\\b": "startCodeInspection","fartwithClassloader": "startCodeInspectionWithCL","fartthread": "launchInspectorThread","dumpMethodCode": "nativeDumpCode","codeitem_end": "getDexCodeItemEnd","base64_encode": "encodeBase64Buffer","dumpDexFileByExecute": "traceDexExecution","dumpArtMethod": "traceMethodCode","myfartInvoke": "callNativeMethodInspector","DexFile_dumpMethodCode": "DexFile_nativeDumpCode","jobject2ArtMethod": "convertToArtMethodPtr"
}SOURCE_SUFFIX = (".java", ".kt", ".cc", ".c", ".cpp", ".h", ".js")def replace_in_file(file_path):try:with open(file_path, "r", encoding="utf-8") as f:content = f.read()original_content = contentfor old, new in RENAME_MAP.items():content = re.sub(r'\b' + old + r'\b', new, content)if content != original_content:with open(file_path, "w", encoding="utf-8") as f:f.write(content)print(f"[UPDATED] {file_path}")else:print(f"[SKIPPED] {file_path}")except Exception as e:print(f"[ERROR] {file_path}: {e}")def scan_directory(root_dir):for dirpath, _, filenames in os.walk(root_dir):for file in filenames:if file.endswith(SOURCE_SUFFIX):replace_in_file(os.path.join(dirpath, file))if __name__ == "__main__":import sysif len(sys.argv) < 2:print("Usage: python rename_fart_symbols.py <source_directory>")sys.exit(1)scan_directory(sys.argv[1])input("Press Enter to exit...")
执行脚本:
D:\Projects\FART\rename_fart_symbols.py D:\Projects\FART\fart10
替换完成。
同时也可以用来修改 frida_fart 的 js 源码
D:\Projects\FART\rename_fart_symbols.py D:\Python\anti-app\frida_fart
参考:使用 Frida 增强 FART:实现更强大的 Android 脱壳能力
重新编译系统
把修改后的 FART 代码替换到 Android 系统里面,重新编译。
# 初始化编译环境
source build/envsetup.sh# 设置编译目标
breakfast wayne# 回到 Android 源码树的根目录
croot# 开始编译
brunch wayne
如何编译 FART ROM 参考这篇文章:移植 FART 到 Android 10 实现自动化脱壳
生成 OTA 包
./sign_ota_wayne.sh
编译完成
刷机
由于我这里是在 WSL 中编译,先把 ota 文件 copy 到 windwos 目录下
cp ./signed-ota_update.zip /mnt/e/lineageos/xiaomi6x_wayne_lineageos-17.1_signed-ota_update_fart_cyrus.zip
设备进入 recovery 模式(或者同时按住【音量+】和【开机键】)
adb reboot recovery
【Apply update】【Apply from adb】开启 adb sideload
开始刷机
adb sideload E:\lineageos\xiaomi6x_wayne_lineageos-17.1_signed-ota_update_fart_cyrus.zip
成功刷入后重启手机。
测试
可以看到 so 中已经检测不出 FART 特征
dex 相关文件也没有检测出 FART 特征
使用 frida_fart 发起主动调用
成功脱壳
测试某视频 app 的 ajm 壳成功脱壳,能正常进入 app 没有被 kill 掉
完整源码
开源地址:
-
https://github.com/CYRUS-STUDIO/AndroidExample
-
https://github.com/CYRUS-STUDIO/FART
相关文章:
-
Android Hook技术防范漫谈
-
frida 检测