简介:这份资源面向计算机、信息安全、人工智能等专业的在校学生与教师,提供一套完整的安卓恶意软件检测机器学习项目,可用于本科毕设、课程设计或大作业。项目基于Android专家知识提取敏感API与权限特征,并采用OpCode N-gram特征构建分类模型,经多种算法与交叉验证,最佳准确率可达98%;数据集包含从第三方市场获取的698个正常APK与VirusShare的756个恶意APK。压缩包共18个文件,以12个Python脚本为核心,辅以2个CSV数据文件、3张PNG结果图和1份Markdown说明文档,整体约652KB,结构清晰便于按模块查阅。目前已有114人学习。读者可获得完整可运行的源码、训练数据与项目文档,理解特征提取、反编译与模型评估的完整流程,并在此基础上二次开发或撰写论文。
1. 本科毕设选安卓恶意软件检测:为什么机器学习方案比特征码扫描更值得做
每年到了毕设选题季,安卓恶意软件检测这个方向总会被翻出来。原因很直接:Android 应用数量庞大,恶意样本变种快,传统基于签名(特征码)的杀毒方式对加壳、混淆、动态加载几乎无能为力。而机器学习方案的核心思路是——不依赖已知病毒库,而是从 APK 里提取权限、API 调用、Intent 等静态特征,训练分类模型,让模型自己去学“什么样的应用像恶意软件”。这套思路在本科毕设里落地性很强:数据集公开、Python 工具链成熟、模型可解释性够写论文,而且整个流程从反编译到特征工程到模型评估都能自己跑通。
这篇文章面向正在做或准备做这个选题的本科生,也适合想快速搭一个恶意软件检测原型的工程师。我会按“数据怎么来 → 特征怎么提 → 模型怎么训 → 坑在哪 → 怎么把结果做得能写进论文”的顺序讲清楚。你不需要有安全背景,但需要会基本的 Python 和命令行操作。读完你应该能独立跑出一个可复现的检测流程,并且知道哪些参数值得调、哪些环节最容易翻车。
2. 数据与特征工程:从 APK 到模型能吃的数值矩阵
2.1 公开数据集怎么选,别一上来就自己爬样本
本科毕设最怕在数据获取上卡住。自己爬 APK 不仅慢,还涉及法律和伦理边界,不建议作为主要数据来源。常见做法是使用公开的恶意软件数据集,比如 Drebin、CICMalDroid、AndroZoo 等。Drebin 提供了约 5500 个恶意应用和大量良性应用,每个样本附带权限、API 调用、Activity 等静态特征,非常适合做特征工程练习。CICMalDroid 更新一些,包含多类恶意家族,适合做多分类。
我一般会建议:先用 Drebin 或类似结构化数据集跑通全流程,确认模型能出结果,再考虑用 AndroZoo 的原始 APK 做扩展实验。AndroZoo 只提供 APK 文件,需要自己反编译提取特征,工作量大但论文里可以写“自建特征提取管线”,加分。
数据划分上,不要随机打乱后按 8:2 分。恶意软件家族之间相似度高,随机划分会导致训练集和测试集里出现同家族样本,准确率虚高。正确做法是按家族或按时间划分:用较早的样本训练,较新的样本测试,这样更接近真实检测场景。如果数据集本身没标时间,至少按家族做分组划分(GroupShuffleSplit)。
2.2 用 Androguard 提取权限与 API 特征
如果你手上有原始 APK,提取静态特征的标准工具是 Androguard。下面这段代码演示如何从一个 APK 文件里提取权限列表和敏感 API 调用,输出成字典,后续可以转成向量。
from androguard.misc import AnalyzeAPK import re # 敏感 API 关键词,可按需扩充 SENSITIVE_APIS = [ "sendTextMessage", "getDeviceId", "getSubscriberId", "openConnection", "exec", "Runtime.exec", "getLastKnownLocation", "Camera.open", "MediaRecorder" ] def extract_features(apk_path): try: a, d, dx = AnalyzeAPK(apk_path) except Exception as e: print(f"解析失败: {apk_path}, 原因: {e}") return None # 1. 权限特征 permissions = a.get_permissions() perm_dict = {p: 1 for p in permissions} # 2. API 调用特征 api_dict = {api: 0 for api in SENSITIVE_APIS} for method in dx.get_methods(): method_str = str(method.get_method()) for api in SENSITIVE_APIS: if api in method_str: api_dict[api] = 1 # 3. 合并 features = {} features.update(perm_dict) features.update(api_dict) features["apk_name"] = apk_path.split("/")[-1] return features这段代码的逻辑是:AnalyzeAPK返回三个对象,a是 APK 对象,d是 Dalvik 字节码列表,dx是交叉引用分析结果。权限直接从a.get_permissions()拿,API 调用则遍历所有方法,用字符串匹配判断是否出现敏感 API 名。参数上,SENSITIVE_APIS列表决定了特征维度,你可以根据论文需要扩充到 50 个甚至上百个。注意AnalyzeAPK对加壳或损坏的 APK 会抛异常,必须用 try-except 包住,否则批量处理会中断。
提取完所有样本后,用 pandas 对齐特征列,缺失值填 0,得到形如(n_samples, n_features)的矩阵。标签列单独存。这个矩阵就是机器学习模型的输入。
2.3 特征向量化与归一化:别让权限数量主导距离
权限特征是二值的(有/无),API 特征也是二值的,但如果你还提取了“权限数量”“API 调用次数”这类连续特征,就必须做归一化。常见做法是StandardScaler或MinMaxScaler。对于二值特征,不要用标准化,保持 0/1 即可。混合类型时,用ColumnTransformer分别处理。
另一个容易忽略的点:权限总数在不同应用间差异很大,有的应用申请 50 个权限,有的只申请 5 个。如果直接用原始计数,模型会偏向权限多的样本。我一般会额外构造一个“权限密度”特征:敏感权限数 / 总权限数。这个特征在恶意软件检测里区分度不错,因为恶意应用往往申请大量敏感权限但总权限数不一定高。
特征选择上,可以用卡方检验或随机森林的 feature_importances_ 做初步筛选,把维度从几百降到几十,既加快训练也降低过拟合。但注意:特征选择必须在训练集上做,然后应用到测试集,不能在全量数据上选完再划分,否则数据泄露。
3. 模型训练与评估:从逻辑回归到 LightGBM 的实操路径
3.1 先跑逻辑回归建立基线,别直接上深度学习
很多同学一上来就想用 CNN 或 LSTM,但表格型特征上树模型往往更强,而且训练快、可解释。我的建议是:先跑逻辑回归,得到一个基线准确率和 AUC,然后换随机森林,再换 LightGBM。每一步都记录指标,论文里可以画对比图。
下面是一个完整的训练与评估脚本,用随机森林做示例:
import pandas as pd import numpy as np from sklearn.model_selection import GroupShuffleSplit from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report, roc_auc_score, confusion_matrix # 假设 df 是特征矩阵,label 列是标签,family 列是家族名 X = df.drop(columns=["label", "family", "apk_name"]) y = df["label"] groups = df["family"] # 按家族分组划分,避免同家族样本同时出现在训练和测试集 gss = GroupShuffleSplit(n_splits=1, test_size=0.3, random_state=42) train_idx, test_idx = next(gss.split(X, y, groups)) X_train, X_test = X.iloc[train_idx], X.iloc[test_idx] y_train, y_test = y.iloc[train_idx], y.iloc[test_idx] # 随机森林,参数按经验设置 clf = RandomForestClassifier( n_estimators=200, # 树的数量,200 在速度和效果间比较平衡 max_depth=20, # 限制深度,防止过拟合 min_samples_leaf=2, # 叶子最小样本数,小数据集可设 1 class_weight="balanced", # 类别不平衡时自动加权 random_state=42, n_jobs=-1 ) clf.fit(X_train, y_train) y_pred = clf.predict(X_test) y_prob = clf.predict_proba(X_test)[:, 1] print(classification_report(y_test, y_pred)) print("AUC:", roc_auc_score(y_test, y_prob)) print("混淆矩阵:\n", confusion_matrix(y_test, y_pred))关键参数说明:n_estimators不是越大越好,200 到 500 之间通常够用,再大收益递减且训练变慢。max_depth控制模型复杂度,恶意软件数据集样本量通常几千到几万,深度 15 到 25 比较合适。class_weight="balanced"在恶意样本少于良性样本时很重要,否则模型会偏向多数类。GroupShuffleSplit的groups参数传入家族列,确保同一家族的样本不会跨训练集和测试集。
3.2 评估指标别只看准确率,AUC 和召回率才是重点
恶意软件检测里,准确率(Accuracy)很容易虚高。如果良性样本占 90%,模型全预测为良性也能拿 90% 准确率,但一个恶意软件都抓不到。所以必须看召回率(Recall)和 AUC。召回率衡量“恶意软件里有多少被检出”,AUC 衡量模型整体排序能力。本科毕设论文里,至少报告 Accuracy、Precision、Recall、F1、AUC 五个指标。
如果召回率偏低,可以调低分类阈值。predict默认阈值 0.5,你可以用y_prob > 0.3作为预测结果,牺牲一些精确率换召回率。这个阈值怎么选,取决于你的应用场景:如果是安全公司初筛,宁可误报也不能漏报,阈值调低;如果是终端用户提示,阈值可以高一些。
另外,混淆矩阵要会看。假阴性(FN)是漏报的恶意软件,假阳性(FP)是误报的良性应用。论文里可以分析 FN 和 FP 的样本特征,比如漏报的恶意软件是不是加了壳、误报的良性应用是不是申请了过多权限。这种分析能体现你对数据的理解,比单纯堆模型加分。
3.3 用 LightGBM 做特征重要性分析,写进论文的讨论章
LightGBM 训练快、内存占用低,而且自带特征重要性输出。下面代码展示如何训练并提取 top 20 重要特征:
import lightgbm as lgb lgb_clf = lgb.LGBMClassifier( n_estimators=300, learning_rate=0.05, max_depth=8, num_leaves=31, subsample=0.8, colsample_bytree=0.8, random_state=42 ) lgb_clf.fit(X_train, y_train) importance = pd.DataFrame({ "feature": X_train.columns, "importance": lgb_clf.feature_importances_ }).sort_values("importance", ascending=False) print(importance.head(20))learning_rate设 0.05 配合 300 棵树,是常见的平衡点。num_leaves控制叶子数,31 是默认值,数据量小可以降到 15。subsample和colsample_bytree做行采样和列采样,防止过拟合。输出的特征重要性可以帮你判断哪些权限或 API 对分类贡献最大,比如SEND_SMS、READ_PHONE_STATE、Runtime.exec往往是 top 特征。这些分析写进论文的“特征分析”章节,比只贴准确率有说服力。
4. 避坑与排查:安卓恶意软件检测里最容易翻车的五个地方
4.1 反编译失败导致样本丢失
现象:批量提取特征时,部分 APK 报错退出,最终样本数比预期少很多。原因:APK 可能被加壳、损坏,或者 Androguard 版本不兼容。解决:用 try-except 捕获异常并记录失败文件名,不要直接跳过。失败样本单独存一个列表,分析是加壳还是文件损坏。如果加壳样本多,可以考虑用动态分析工具补,但本科毕设阶段建议直接剔除并在论文里说明。
4.2 特征维度爆炸但样本量不足
现象:提取了 500 个权限和 API 特征,但样本只有 2000 个,模型过拟合严重,训练集准确率 99%,测试集 70%。原因:特征数远大于样本数,维度灾难。解决:先做特征选择,用卡方检验或 LightGBM 重要性筛到 50 到 100 维。或者用 PCA 降维,但 PCA 会损失可解释性,论文里不好分析。我一般优先用基于树模型的特征选择。
4.3 类别不平衡导致召回率极低
现象:恶意样本只占 10%,模型几乎把所有样本预测为良性,准确率 90% 但召回率接近 0。原因:默认损失函数对多数类友好。解决:设置class_weight="balanced",或者用 SMOTE 过采样。SMOTE 要注意只能在训练集上做,测试集保持原始分布。另外,评估时用 AUC 而不是准确率。
4.4 数据泄露让指标虚高
现象:测试集 AUC 0.99,但换一批新样本后掉到 0.7。原因:特征选择或归一化时用了全量数据,测试集信息泄露到训练过程。解决:所有预处理(归一化、特征选择、SMOTE)都只在训练集上 fit,然后 transform 测试集。用 sklearn 的 Pipeline 可以避免这个错误。
4.5 模型保存与复现问题
现象:论文里写了准确率,但答辩时老师让你现场跑一遍,结果跑不出来。原因:随机种子没固定、依赖库版本不一致、数据路径写死。解决:固定random_state,用joblib.dump保存模型,记录 Python 和主要库的版本号。数据路径用相对路径,或者把数据加载封装成函数。最好写一个requirements.txt,把 androguard、scikit-learn、lightgbm、pandas 的版本都列上。
5. 把毕设做出论文级深度:阈值调优与可解释性分析
5.1 用 PR 曲线找最佳阈值,而不是拍脑袋定 0.5
恶意软件检测里,正类(恶意)通常是少数类,PR 曲线比 ROC 曲线更能反映模型在少数类上的表现。下面代码展示如何画 PR 曲线并找 F1 最大的阈值:
from sklearn.metrics import precision_recall_curve, f1_score import matplotlib.pyplot as plt precision, recall, thresholds = precision_recall_curve(y_test, y_prob) f1_scores = 2 * (precision * recall) / (precision + recall + 1e-8) best_idx = np.argmax(f1_scores) best_threshold = thresholds[best_idx] print(f"最佳阈值: {best_threshold:.3f}, F1: {f1_scores[best_idx]:.3f}") plt.plot(recall, precision) plt.xlabel("Recall") plt.ylabel("Precision") plt.title("PR Curve") plt.show()precision_recall_curve返回的 thresholds 长度比 precision 少 1,所以best_idx要对应好。找到最佳阈值后,用y_prob > best_threshold做最终预测。论文里可以写“通过 PR 曲线将分类阈值从默认 0.5 调整为 X,召回率提升 Y%”。
5.2 用 SHAP 解释单个样本的预测原因
SHAP 能告诉你“为什么这个应用被判定为恶意”。对于毕设答辩,老师很可能问“模型凭什么说它是恶意的”,SHAP 就是你的后悔药。下面代码展示如何对单个样本输出特征贡献:
import shap explainer = shap.TreeExplainer(lgb_clf) shap_values = explainer.shap_values(X_test) # 解释第 i 个样本 i = 0 shap.force_plot(explainer.expected_value[1], shap_values[1][i], X_test.iloc[i])TreeExplainer对 LightGBM 和随机森林都适用。shap_values[1]是正类(恶意)的贡献值。force_plot会生成一个可视化图,红色特征推高恶意概率,蓝色特征拉低。你可以挑一个漏报或误报的样本做分析,写进论文的“案例分析”章节。比如某个良性应用因为申请了SEND_SMS和READ_CONTACTS被误报,你可以解释这是特征相似导致的,并提出后续可以加入行为特征来区分。
5.3 交叉验证与置信区间:让指标更可信
单次划分的测试结果波动可能很大。用 5 折交叉验证,报告平均 AUC 和标准差,论文里写“AUC 为 0.95 ± 0.02”,比单次 0.96 更可信。注意:交叉验证也要按家族分组,用GroupKFold。如果数据量小,5 折够了;数据量大可以用 3 折节省时间。
我自己的习惯是:所有实验跑三遍不同随机种子,取平均。如果三遍结果差异超过 5%,说明模型不稳定,需要检查数据划分或特征质量。这个习惯让我在答辩时被问到“结果可复现吗”时,能直接拿出三组数据。
希望帮到你。
本文还有配套的精品资源,点击获取