Android恶意软件检测实战:函数调用图(FCG)构建与分析指南

1次阅读
没有评论

共计 2692 个字符,预计需要花费 7 分钟才能阅读完成。

image.webp

为什么需要分析函数调用图(FCG)

去年爆发的 FlyTrap 恶意软件家族,通过伪装成合法 VPN 应用窃取银行凭证。分析发现其核心恶意行为分散在 20 多个类中,只有通过函数调用关系才能发现:Runtime.exec()最终会触发隐蔽的短信转发操作。另一个案例是 Triada 木马,它通过动态加载 dex 文件来隐藏真实调用链,传统静态分析完全失效。

Android 恶意软件检测实战:函数调用图 (FCG) 构建与分析指南

这些案例说明,单纯的字符串匹配或权限检测已经不够,必须通过 FCG(Function Call Graph)才能看清恶意代码的完整执行逻辑。

工具链选择:Soot vs Androguard

  • Soot(Java 静态分析框架)
  • 优势:支持 Jimple 中间语言,能处理复杂控制流;提供完整的 Call Graph API
  • 劣势:需要手动处理多 DEX 和动态加载

  • Androguard(Python 工具包)

  • 优势:内置 APK 解析器,开箱即用;适合快速原型开发
  • 劣势:对混淆代码处理能力较弱

推荐组合方案:用 Androguard 做初步 APK 解包,再用 Soot 进行深度分析。

三阶段实战流程

阶段 1:用 Soot 生成控制流图(CFG)

// 初始化 Soot 环境
Options.v().set_src_prec(Options.src_prec_apk);
Options.v().set_process_dir(Collections.singletonList("sample.apk"));
Scene.v().loadNecessaryClasses();

// 选取目标类和方法
SootClass targetClass = Scene.v().getSootClass("com.malware.payload");
SootMethod targetMethod = targetClass.getMethodByName("triggerPayload");

// 生成基本块 (Basic Block) 级别的 CFG
UnitGraph cfg = new BriefUnitGraph(targetMethod.retrieveActiveBody());

// 可视化输出(需 Graphviz 支持)DotGraph dot = new DotGraph("malware_cfg");
for (Unit unit : cfg) {dot.drawNode(unit.toString());
    for (Unit successor : cfg.getSuccsOf(unit)) {dot.drawEdge(unit.toString(), successor.toString());
    }
}
dot.plot("cfg.png");

关键参数说明:
set_src_prec 指定 APK 作为输入源
BriefUnitGraph 生成精简版 CFG(忽略异常处理等边)

阶段 2:构建完整 FCG

跨组件调用处理流程:

[Activity] --Intent--> [BroadcastReceiver]
    ↓                      ↓
[startService]         [sendBroadcast]
    ↓                      ↓
[Service.onStartCommand]  [Receiver.onReceive]

关键代码片段:

// 使用 SparkCallGraphBuilder 构建全量调用图
CallGraph cg = new SparkCallGraphBuilder()
    .setEntryPoint(method)
    .build();

// 处理跨组件调用(需额外分析 AndroidManifest.xml)for (Edge edge : cg) {if (edge.src().getDeclaringClass().isAndroidClass()) {
        // 标记系统 API 调用点
        edge.tag("SystemCall"); 
    }
}

阶段 3:图特征提取

使用 NetworkX 计算图指标:

import networkx as nx

def extract_features(cg):
    G = nx.DiGraph()

    # 添加节点和边
    for node in cg.nodes():
        G.add_node(node.hashCode(), 
                  type=node.getMethod().getReturnType())

    for edge in cg.edges():
        G.add_edge(edge.src().hashCode(),
                  edge.tgt().hashCode(),
                  call_type=edge.kind())

    # 计算关键特征
    features = {'avg_degree': sum(dict(G.degree()).values()) / len(G),
        'clustering': nx.average_clustering(G),
        'assortativity': nx.degree_assortativity_coefficient(G)
    }
    return features

对抗混淆的解决方案

  1. 字符串加密
  2. 动态跟踪:Hook String.equals()方法
  3. 模式匹配:识别 Base64 特征 + 异或运算模式

  4. 反射调用

    // 识别 Class.forName() + getMethod()模式
    if (invokeExpr instanceof StaticInvokeExpr) {if (invokeExpr.getMethod().getName().contains("forName")) {logger.warn("发现反射调用点:" + stmt);
        }
    }

  5. 动态加载

  6. 监控 DexClassLoader 的使用
  7. 提前解压 assets 中的可疑 dex/jar

性能优化技巧

  • 增量分析
    只重新分析修改过的 APK 组件(通过 SHA256 对比)

  • 子图缓存

    def get_subgraph_cache(key):
        if redis.exists(key):
            return pickle.loads(redis.get(key))
        else:
            g = build_subgraph(key)
            redis.setex(key, 3600, pickle.dumps(g))
            return g

实践建议

  1. 数据集准备
  2. MalwareBazaar 的 TOP20 家族样本(建议从 Emotet 开始)
  3. VirusTotal 的 Android 恶意软件数据集(需申请 API 权限)

  4. 完整复现步骤

  5. 使用 apktool 解包样本
  6. 用 Soot 生成初始 CFG
  7. 通过 Spark 构建全程序 FCG
  8. 提取图特征并训练简单分类器

  9. 进阶方向

  10. 结合动态分析的 Hybrid CFG
  11. 使用图神经网络 (GNN) 进行家族分类

通过这个流程,即使是新手也能在 2 - 3 天内搭建出可用的恶意软件检测原型。关键是要理解:FCG 分析不是银弹,必须与其他技术(如权限分析、动态监控)结合使用。

正文完
 0
评论(没有评论)