Skip to content

Commit 6d14648

Browse files
committed
攻防 FART 脱壳:特征检测识别 + 对抗绕过全解析
1 parent e2b142f commit 6d14648

File tree

5 files changed

+525
-1
lines changed

5 files changed

+525
-1
lines changed

.idea/deploymentTargetSelector.xml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/src/main/cpp/CMakeLists.txt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,3 +373,31 @@ target_link_libraries(
373373
# 链接 shadowhook
374374
shadowhook::shadowhook
375375
)
376+
377+
378+
## FART ##########################################################################################
379+
380+
add_library( # 设置库的名称
381+
fart
382+
383+
# 设置库的类型
384+
SHARED
385+
386+
# 设置源文件路径
387+
fart.cpp
388+
)
389+
390+
# 为 fart 动态库启用字符串加密
391+
target_compile_options(
392+
fart
393+
PRIVATE
394+
-mllvm -sobf)
395+
396+
# 抹除符号
397+
set_target_properties(aes PROPERTIES LINK_FLAGS "-Wl,--version-script=${CMAKE_SOURCE_DIR}/hide.map")
398+
399+
target_link_libraries(
400+
fart
401+
# 链接 log 库
402+
${log-lib}
403+
)

app/src/main/cpp/fart.cpp

Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
#include <jni.h>
2+
#include <unistd.h>
3+
#include <string>
4+
#include <android/log.h>
5+
#include <sstream>
6+
#include <signal.h>
7+
#include <set>
8+
#include <vector>
9+
#include <fstream>
10+
#include <dlfcn.h>
11+
#include <regex>
12+
13+
#define LOG_TAG "AntiFART"
14+
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
15+
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
16+
17+
18+
extern "C"
19+
JNIEXPORT jboolean JNICALL
20+
Java_com_cyrus_example_fart_AntiFART_detectFARTByReflection(JNIEnv *env, jclass clazz) {
21+
const char *targetClass = "android/app/ActivityThread";
22+
jclass target = env->FindClass(targetClass);
23+
if (target == nullptr) {
24+
LOGE("Failed to find ActivityThread");
25+
return JNI_FALSE;
26+
}
27+
28+
struct {
29+
const char *name;
30+
const char *sig;
31+
} suspiciousMethods[] = {
32+
{"getClassField", "(Ljava/lang/ClassLoader;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/reflect/Field;"},
33+
{"getClassFieldObject", "(Ljava/lang/ClassLoader;Ljava/lang/String;Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;"},
34+
{"invokeStaticMethod", "(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Class;[Ljava/lang/Object;)Ljava/lang/Object;"},
35+
{"getFieldOjbect", "(Ljava/lang/String;Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;"},
36+
{"getClassloader", "()Ljava/lang/ClassLoader;"},
37+
{"loadClassAndInvoke", "(Ljava/lang/ClassLoader;Ljava/lang/String;Ljava/lang/reflect/Method;)V"},
38+
{"fart", "()V"},
39+
{"fartwithClassloader", "(Ljava/lang/ClassLoader;)V"},
40+
{"fartthread", "()V"},
41+
};
42+
43+
for (const auto &method: suspiciousMethods) {
44+
jmethodID mid = env->GetStaticMethodID(target, method.name, method.sig);
45+
if (env->ExceptionCheck()) {
46+
env->ExceptionClear(); // 避免崩溃
47+
continue;
48+
}
49+
if (mid != nullptr) {
50+
LOGE("Detected FART method: %s", method.name);
51+
kill(getpid(), SIGKILL);
52+
return JNI_TRUE;
53+
}
54+
}
55+
56+
return JNI_FALSE;
57+
}
58+
59+
60+
extern "C"
61+
JNIEXPORT jstring JNICALL
62+
Java_com_cyrus_example_fart_AntiFART_dumpMethods(JNIEnv *env, jclass, jstring className) {
63+
const char *clsName = env->GetStringUTFChars(className, nullptr);
64+
65+
// 替换 . 为 /,Java 类名用点,JNI 中要用斜杠
66+
std::string classPath(clsName);
67+
std::replace(classPath.begin(), classPath.end(), '.', '/');
68+
69+
jclass clazz = env->FindClass(classPath.c_str());
70+
env->ReleaseStringUTFChars(className, clsName);
71+
72+
if (clazz == nullptr) {
73+
return env->NewStringUTF("Class not found");
74+
}
75+
76+
jmethodID getDeclaredMethods = env->GetMethodID(
77+
env->FindClass("java/lang/Class"),
78+
"getDeclaredMethods",
79+
"()[Ljava/lang/reflect/Method;"
80+
);
81+
if (getDeclaredMethods == nullptr) {
82+
return env->NewStringUTF("getDeclaredMethods not found");
83+
}
84+
85+
jobjectArray methodArray = (jobjectArray) env->CallObjectMethod(clazz, getDeclaredMethods);
86+
if (methodArray == nullptr) {
87+
return env->NewStringUTF("No methods");
88+
}
89+
90+
jsize len = env->GetArrayLength(methodArray);
91+
jclass methodClass = env->FindClass("java/lang/reflect/Method");
92+
jmethodID toStringMid = env->GetMethodID(methodClass, "toString", "()Ljava/lang/String;");
93+
94+
std::ostringstream oss;
95+
96+
for (jsize i = 0; i < len; ++i) {
97+
jobject methodObj = env->GetObjectArrayElement(methodArray, i);
98+
jstring methodStr = (jstring) env->CallObjectMethod(methodObj, toStringMid);
99+
const char *cstr = env->GetStringUTFChars(methodStr, nullptr);
100+
101+
oss << cstr << "\n";
102+
103+
env->ReleaseStringUTFChars(methodStr, cstr);
104+
env->DeleteLocalRef(methodStr);
105+
env->DeleteLocalRef(methodObj);
106+
}
107+
108+
std::string result = oss.str();
109+
return env->NewStringUTF(result.c_str());
110+
}
111+
112+
113+
extern "C"
114+
JNIEXPORT jobjectArray JNICALL
115+
Java_com_cyrus_example_fart_AntiFART_listLoadedFiles(JNIEnv *env, jclass) {
116+
std::ifstream maps("/proc/self/maps");
117+
std::string line;
118+
std::vector<std::string> paths;
119+
120+
while (std::getline(maps, line)) {
121+
std::size_t pathPos = line.find('/');
122+
if (pathPos != std::string::npos) {
123+
std::string path = line.substr(pathPos);
124+
if (std::find(paths.begin(), paths.end(), path) == paths.end()) {
125+
paths.push_back(path);
126+
}
127+
}
128+
}
129+
130+
jclass stringClass = env->FindClass("java/lang/String");
131+
jobjectArray result = env->NewObjectArray(paths.size(), stringClass, nullptr);
132+
for (size_t i = 0; i < paths.size(); ++i) {
133+
env->SetObjectArrayElement(result, i, env->NewStringUTF(paths[i].c_str()));
134+
}
135+
136+
return result;
137+
}
138+
139+
140+
void *lookup_symbol(const char *libraryname, const char *symbolname) {
141+
void *imagehandle = dlopen(libraryname, RTLD_GLOBAL | RTLD_NOW);
142+
if (imagehandle != nullptr) {
143+
void *sym = dlsym(imagehandle, symbolname);
144+
if (sym != nullptr) {
145+
return sym;
146+
}
147+
}
148+
return nullptr;
149+
}
150+
151+
extern "C"
152+
JNIEXPORT jboolean JNICALL
153+
Java_com_cyrus_example_fart_AntiFART_hasSymbolInSO(JNIEnv *env, jclass, jstring soName, jstring symbolName) {
154+
const char *so = env->GetStringUTFChars(soName, nullptr);
155+
const char *symbol = env->GetStringUTFChars(symbolName, nullptr);
156+
157+
void *sym = lookup_symbol(so, symbol);
158+
jboolean result = sym != nullptr ? JNI_TRUE : JNI_FALSE;
159+
160+
LOGI("lookup_symbol: so=%s, symbol=%s, result=%s", so, symbol, result == JNI_TRUE ? "FOUND" : "NOT FOUND");
161+
162+
env->ReleaseStringUTFChars(soName, so);
163+
env->ReleaseStringUTFChars(symbolName, symbol);
164+
165+
return result;
166+
}
167+
168+
// so 黑名单函数特征
169+
std::vector<std::string> so_symbols_blacklist = {
170+
"dumpDexFileByExecute",
171+
"dumpArtMethod",
172+
"myfartInvoke",
173+
"DexFile_dumpMethodCode"
174+
};
175+
176+
// 读取 /proc/self/maps 获取加载的 .so 路径
177+
std::set<std::string> get_loaded_so_paths() {
178+
std::set<std::string> so_paths;
179+
std::ifstream maps("/proc/self/maps");
180+
std::string line;
181+
std::regex so_regex(".+\\.so(\\s|$)");
182+
183+
while (std::getline(maps, line)) {
184+
std::size_t path_pos = line.find('/');
185+
if (path_pos != std::string::npos) {
186+
std::string path = line.substr(path_pos);
187+
if (std::regex_search(path, so_regex)) {
188+
so_paths.insert(path);
189+
}
190+
}
191+
}
192+
return so_paths;
193+
}
194+
195+
// 读取文件内容为字符串
196+
std::string read_file_content(const std::string &path) {
197+
FILE *file = fopen(path.c_str(), "rb");
198+
if (!file) {
199+
LOGI("Failed to open: %s", path.c_str());
200+
return "";
201+
}
202+
203+
fseek(file, 0, SEEK_END);
204+
long size = ftell(file);
205+
rewind(file);
206+
207+
std::string buffer(size, 0);
208+
fread(&buffer[0], 1, size, file);
209+
fclose(file);
210+
211+
return buffer;
212+
}
213+
214+
// 返回匹配到的特征列表
215+
std::vector<std::string> get_matched_signatures(const std::string &content, const std::vector<std::string> &patterns) {
216+
std::vector<std::string> matched;
217+
for (const auto &pattern: patterns) {
218+
if (content.find(pattern) != std::string::npos) {
219+
matched.push_back(pattern);
220+
}
221+
}
222+
return matched;
223+
}
224+
225+
226+
// JNI 方法:检测已加载 .so 中是否包含黑名单符号
227+
extern "C"
228+
JNIEXPORT jobjectArray JNICALL
229+
Java_com_cyrus_example_fart_AntiFART_detectFartInLoadedSO(JNIEnv *env, jclass clazz) {
230+
std::vector<std::string> detected_logs;
231+
auto so_paths = get_loaded_so_paths();
232+
233+
for (const auto &path: so_paths) {
234+
std::string content = read_file_content(path);
235+
if (!content.empty()) {
236+
std::vector<std::string> matched = get_matched_signatures(content, so_symbols_blacklist);
237+
if (!matched.empty()) {
238+
std::ostringstream oss;
239+
oss << "[FART DETECTED] " << path << " => ";
240+
for (size_t i = 0; i < matched.size(); ++i) {
241+
oss << matched[i];
242+
if (i != matched.size() - 1) oss << ", ";
243+
}
244+
LOGI("%s", oss.str().c_str());
245+
detected_logs.push_back(oss.str());
246+
}
247+
}
248+
}
249+
250+
jclass stringClass = env->FindClass("java/lang/String");
251+
jobjectArray result = env->NewObjectArray(detected_logs.size(), stringClass, nullptr);
252+
for (int i = 0; i < detected_logs.size(); ++i) {
253+
env->SetObjectArrayElement(result, i, env->NewStringUTF(detected_logs[i].c_str()));
254+
}
255+
256+
return result;
257+
}
258+
259+
260+
// dex 黑名单函数特征
261+
const std::vector<std::string> dex_method_blacklist = {
262+
"loadClassAndInvoke",
263+
"fart",
264+
"fartwithClassloader",
265+
"fartthread"
266+
};
267+
268+
269+
// 读取 /proc/self/maps 获取加载的 dex 或 dex 相关文件路径
270+
std::set<std::string> get_loaded_dex_paths() {
271+
std::set<std::string> dex_paths;
272+
std::ifstream maps("/proc/self/maps");
273+
std::string line;
274+
275+
// 匹配 dex、odex、vdex、art、apk、jar 文件
276+
std::regex dex_regex(R"((\.dex|\.odex|\.vdex|\.art|\.apk|\.jar)(\s|$))");
277+
278+
while (std::getline(maps, line)) {
279+
std::size_t path_pos = line.find('/');
280+
if (path_pos != std::string::npos) {
281+
std::string path = line.substr(path_pos);
282+
if (std::regex_search(path, dex_regex)) {
283+
dex_paths.insert(path);
284+
}
285+
}
286+
}
287+
return dex_paths;
288+
}
289+
290+
291+
// JNI 方法:检测已加载 dex 中是否包含黑名单符号
292+
extern "C"
293+
JNIEXPORT jobjectArray JNICALL
294+
Java_com_cyrus_example_fart_AntiFART_detectFartInLoadedDex(JNIEnv *env, jclass clazz) {
295+
std::vector<std::string> detected_logs;
296+
auto dex_paths = get_loaded_dex_paths();
297+
298+
for (const auto &path: dex_paths) {
299+
std::string content = read_file_content(path);
300+
if (!content.empty()) {
301+
std::vector<std::string> matched = get_matched_signatures(content, dex_method_blacklist);
302+
if (!matched.empty()) {
303+
std::ostringstream oss;
304+
oss << "[FART DETECTED] " << path << " => ";
305+
for (size_t i = 0; i < matched.size(); ++i) {
306+
oss << matched[i];
307+
if (i != matched.size() - 1) oss << ", ";
308+
}
309+
LOGI("%s", oss.str().c_str());
310+
detected_logs.push_back(oss.str());
311+
}
312+
}
313+
}
314+
315+
jclass stringClass = env->FindClass("java/lang/String");
316+
jobjectArray result = env->NewObjectArray(detected_logs.size(), stringClass, nullptr);
317+
for (int i = 0; i < detected_logs.size(); ++i) {
318+
env->SetObjectArrayElement(result, i, env->NewStringUTF(detected_logs[i].c_str()));
319+
}
320+
321+
return result;
322+
}

0 commit comments

Comments
 (0)