作者:y4er
原文鏈接:https://y4er.com/posts/simple-use-of-the-java-static-analysis-framework-tai-e

前言

在做代碼審計的時候,總是遇到一些批量垃圾洞,或者是遇到需要自動化批量找調用鏈驗證的工作,一直想著解決這個問題,后來發現tabby,用了一段時間,總覺得不太舒服,配置不足oom異常加上非人的neo4j的語法,加上太多的toString、equals等無用調用關系,配合上雜亂的neo4j的圖,有點擾亂審計思路。

自己照著tabby抄了一個poop出來,發現自己的問題并沒有解決,只是熟悉了一下soot的基礎用法,會抽取類信息了而已,在此期間狠狠補了一下soot,逐字逐句翻譯啃完了英文的《soot存活指南》 https://www.brics.dk/SootGuide/sootsurvivorsguide.pdf 然后發現soot出了一個新版本的sootup,自己試了試ifds污點分析。

對于指針分析、污點分析還是一知半解,中間嘗試過bytecodedl、doop這種聲明式的分析工具,然后發現suffle語法更變態,鬼畫符,加上沒有詳細的文檔和對應的規則,自己想要進一步拓展過于困難。

思考很久,發現還是自己底子不扎實,于是學了很長一段時間的靜態軟件分析,看了很多的論文(折磨)和視頻,其中包括南京大學譚添、李樾兩位老師的課,北大熊英飛老師的課等等,今天就簡單寫一下譚添、李樾兩位老師開發的tai-e指針分析框架的簡單使用。

防噴:我只是看了課,并不代表我會了,很慚愧的是兩位老師的課我看了第二遍有些地方還是不太理解,但是每看一遍總有新收獲,所以本文有錯實屬正常不過,望讀者賜教。

配置tai-e

GitHub的wiki給了配置教程

git clone https://github.com/pascal-lab/Tai-e
cd Tai-e
git submodule update --init --recursive

idea打開 需要jdk17

gradle也需要jdk17

image.png

然后運行pascal.taie.Main,配置下主類,加一個jvm options Xmx防止oom異常

image.png

輸出

Tai-e starts ...
Writing options to output\options.yml
Usage: Options [-gh] [-ap] [--[no-]native-model] [-pp] [--pre-build-ir] [-cp=<classPath>] [-java=<javaVersion>]
               [-m=<mainClass>] [--options-file=<optionsFile>] [-p=<planFile>] [-scope=<scope>]
               [--world-builder=<worldBuilderClass>] [-a=<String=String>]... [--input-classes=<inputClass>[,
               <inputClass>...]]...
Tai-e options
  -a, --analysis=<String=String>      Analyses to be executed
      -ap, --allow-phantom            Allow Tai-e to process phantom references, i.e., the referenced classes that are
                                        not found in the class paths (default: false)
      -cp, --class-path=<classPath>   Class path. Multiple paths are split by system path separator.
  -g, --gen-plan-file                 Merely generate analysis plan
  -h, --help                          Display this help message
      --input-classes=<inputClass>[,<inputClass>...]
                                      The classes should be included in the World of analyzed program (the classes can
                                        be split by ',')
      -java=<javaVersion>             Java version used by the program being analyzed (default: 6)
  -m, --main-class=<mainClass>        Main class
      --[no-]native-model             Enable native model (default: true)
      --options-file=<optionsFile>    The options file
  -p, --plan-file=<planFile>          The analysis plan file
      -pp, --prepend-JVM              Prepend class path of current JVM to Tai-e's class path (default: false)
      --pre-build-ir                  Build IR for all available methods before starting any analysis (default: false)
      -scope=<scope>                  Scope for method/class analyses (default: APP, valid values: APP, REACHABLE, ALL)
      --world-builder=<worldBuilderClass>
                                      Specify world builder class (default: pascal.taie.frontend.soot.SootWorldBuilder)
--------------------
Version 0.1
--------------------

運行參數

列舉幾個關鍵參數,或者直接看文檔

參數 示例 用途
-ap -ap 允許虛引用,等同于soot的--allow-phantom
-java -java 8 指定java版本為jre8 tai-e會從java-benchmarks/JREs加載對應的jdk lib
-pp -pp 將當前jvm的類路徑添加到分析類路徑中 和-java選項沖突
-m -m com.example.demo.Main 指定主類 表示程序入口 必選參數
--input-classes --input-classes=com.example.demo.controller.TestController,javax.servlet.ServletRequestWrapper 當main函數無法調用到TestController時,可以用這個參數把TestController強制加進來,類似于強制分析?
-cp -cp E:\demo5\target\classes;.\lib\test.jar 類路徑 和soot差不多 支持jar文件或者.java.class文件目錄 在Windows中多個jar以;分隔,unix以:分隔
-g -g 僅生成選項配置文件output/options.yml不執行分析
--options-file --options-file=output/options.yml 解析配置文件作為選項配置
--pre-build-ir --pre-build-ir 分析之前為所有的method構建IR
-scope -scope=APP 指定分析類和方法的分析范圍APP, REACHABLE, ALL
-a -a <id>[=<key>:<value>;...] 指定分析選項,-a可重復指定多個分析,選項會保存在output/tai-e-plan.yml文件中
-p -p output/tai-e-plan.yml 用文件指定分析選項 yaml語法

舉一個污點分析的例子

-cp
E:\tools\code\demo5\target\classes;E:\tools\code\demo5\target\demo5-0.0.1-SNAPSHOT\BOOT-INF\lib\spring-aop-5.3.23.jar;E:\tools\code\demo5\target\demo5-0.0.1-SNAPSHOT\BOOT-INF\lib\spring-beans-5.3.23.jar;E:\tools\code\demo5\target\demo5-0.0.1-SNAPSHOT\BOOT-INF\lib\spring-boot-2.7.4.jar;E:\tools\code\demo5\target\demo5-0.0.1-SNAPSHOT\BOOT-INF\lib\spring-boot-autoconfigure-2.7.4.jar;E:\tools\code\demo5\target\demo5-0.0.1-SNAPSHOT\BOOT-INF\lib\spring-boot-jarmode-layertools-2.7.4.jar;E:\tools\code\demo5\target\demo5-0.0.1-SNAPSHOT\BOOT-INF\lib\spring-context-5.3.23.jar;E:\tools\code\demo5\target\demo5-0.0.1-SNAPSHOT\BOOT-INF\lib\spring-core-5.3.23.jar;E:\tools\code\demo5\target\demo5-0.0.1-SNAPSHOT\BOOT-INF\lib\spring-expression-5.3.23.jar;E:\tools\code\demo5\target\demo5-0.0.1-SNAPSHOT\BOOT-INF\lib\spring-jcl-5.3.23.jar;E:\tools\code\demo5\target\demo5-0.0.1-SNAPSHOT\BOOT-INF\lib\spring-web-5.3.23.jar;E:\tools\code\demo5\target\demo5-0.0.1-SNAPSHOT\BOOT-INF\lib\spring-webmvc-5.3.23.jar;E:\tools\code\demo5\target\demo5-0.0.1-SNAPSHOT\BOOT-INF\lib\tomcat-embed-core-9.0.65.jar;E:\tools\code\demo5\target\demo5-0.0.1-SNAPSHOT\BOOT-INF\lib\tomcat-embed-el-9.0.65.jar;E:\tools\code\demo5\target\demo5-0.0.1-SNAPSHOT\BOOT-INF\lib\tomcat-embed-websocket-9.0.65.jar
--input-classes=com.example.demo.controller.TestController,javax.servlet.ServletRequestWrapper,javax.servlet.ServletResponseWrapper,org.apache.catalina.connector.Request
-java
8
-m
com.example.demo.Main
-ap
-a pta=action:dump;action-file:result.txt;taint-config:src\test\resources\pta\taint\taint-config.yml

直接配置在idea的參數中就行,表示在給定的幾個jar包和class中做pta,指定了taint-config文件表示做p/taint污點分析,強制指定com.example.demo.controller.TestController控制器和幾個引用類,允許虛類,使用的污點配置文件為src\test\resources\pta\taint\taint-config.yml,結果導出到result.txt文件中。

需要重點講一下-a參數,tai-e有三大類參數

  1. Program options 指定程序執行的參數
  2. Analysis options 指定執行代碼分析時的參數
  3. 其他選項 -h這類

-a就是第二種,涉及到代碼分析時需要指定的參數,在src/main/resources/tai-e-analyses.yml中,tai-e作為一個插件式的框架將分析插件模塊化,對應的配置放在了這個文件中。

拿出來一段配置來看

- description: whole-program pointer analysis
  analysisClass: pascal.taie.analysis.pta.PointerAnalysis
  id: pta
  options:
    cs: ci # | k-[obj|type|call][-k'h]
    only-app: false # only analyze application code
    implicit-entries: true # analyze implicit entries
    merge-string-constants: false
    merge-string-objects: true
    merge-string-builders: true
    merge-exception-objects: true
    handle-invokedynamic: false
    advanced: null # specify advanced analysis:
    # zipper | zipper-e | zipper-e=PV
    # scaler | scaler=TST
    # mahjong | collection
    action: null # | dump | compare
    action-file: null # path of file to dump/compare
    reflection-log: null # path to reflection log
    taint-config: null # path to config file of taint analysis,
    # when this file is given, taint analysis will be enabled
    plugins: [ ] # | [ pluginClass, ... ]

- description: call graph construction
  analysisClass: pascal.taie.analysis.graph.callgraph.CallGraphBuilder
  id: cg
  requires: [ pta(algorithm=pta) ]
  options:
    algorithm: pta # | cha
    dump: null # path of file to dump reachable methods and call edges
    dump-methods: null # path of file to dump reachable methods
    dump-call-edges: null # path of file to dump to call edges

id作為插件的唯一標識,當指定-a pta時表明用pascal.taie.analysis.pta.PointerAnalysis類進行分析,options指定了對應類的相關選項,其中id為cg的插件有一個選項為requires: [ pta(algorithm=pta) ]表示調用圖構造需要用到pta的分析結果,相當于依賴。

tai-e實現了很多的分析插件,列一下

  1. whole-program pointer analysis 全程序指針分析
  2. call graph construction 調用圖
  3. identify casts that may fail 識別可能失敗的強制類型轉換
  4. identify polymorphic callsites 識別多態callsites
  5. throw analysis 異常分析
  6. intraprocedural control-flow graph 過程內控制流圖
  7. interprocedural control-flow graph 過程間控制流圖
  8. live variable analysis 存活變量分析
  9. available expression analysis 有效表達分析
  10. reaching definition analysis 可達分析
  11. constant propagation 常量傳播
  12. inter-procedural constant propagation 過程間的常量傳播
  13. dead code detection 死代碼檢查
  14. process results of previously-run analyses 結果分析處理
  15. dump classes and Tai-e IR 導出類和IR
  16. null value analysis 空指針分析
  17. Null pointer and redundant comparison detector 空指針和冗余比較檢查
  18. find clone() related problems clone相關的問題
  19. find the method that may drop or ignore exceptions 找到可能刪除或忽略異常的方法

基本上用到的都在里面了。

使用tai-e分析javase程序

javase程序指的是命令行/桌面程序,javaee指的是web程序。

兩者區別在于程序入口點不同,在javaee中存在多個servlet/controller,需要將多個路由對應的方法加入到靜態軟件分析的入口點entrypoint,而javase只有一個main函數,整個數據流是從main函數進去然后流向其他函數最終到sink點。

tai-e中需要指定-m參數指定程序主類,對于javaee來說需要增加分析入口點。接下來先以一個簡單的javase項目為例學習tai-e的污點分析用法

新建一個maven空項目,創建主類 org.example.Main

package org.example;

import java.io.IOException;

public class Main {
    public static void main(String[] args) throws IOException {
        Main main = new Main();
        String source = main.source(args[0]);
        main.sink(source);
    }

    public String source(String s) {
        return s;
    }

    public String sink(String s) throws IOException {
        Runtime.getRuntime().exec(s);
        return "ok";
    }
}

修改src/test/resources/pta/taint/taint-config.yml污點規則文件

sources:
  - { method: "<org.example.Main: java.lang.String source(java.lang.String)>", type: "java.lang.String" }

sinks:
  - { method: "<java.lang.Runtime: java.lang.Process exec(java.lang.String)>", index: 0 }

transfers:
  - { method: "<java.lang.String: java.lang.String concat(java.lang.String)>", from: base, to: result, type: "java.lang.String" }
  - { method: "<java.lang.String: java.lang.String concat(java.lang.String)>", from: 0, to: result, type: "java.lang.String" }
  - { method: "<java.lang.String: char[] toCharArray()>", from: base, to: result, type: "char[]" }
  - { method: "<java.lang.String: void <init>(char[])>", from: 0, to: base, type: "java.lang.String" }
  - { method: "<java.lang.StringBuffer: java.lang.StringBuffer append(java.lang.String)>", from: 0, to: base, type: "java.lang.StringBuffer" }
  - { method: "<java.lang.StringBuffer: java.lang.StringBuffer append(java.lang.Object)>", from: 0, to: base, type: "java.lang.StringBuffer" }
  - { method: "<java.lang.StringBuffer: java.lang.String toString()>", from: base, to: result, type: "java.lang.String" }
  - { method: "<java.lang.StringBuilder: java.lang.StringBuilder append(java.lang.String)>", from: 0, to: base, type: "java.lang.StringBuilder" }
  - { method: "<java.lang.StringBuilder: java.lang.StringBuilder append(java.lang.Object)>", from: 0, to: base, type: "java.lang.StringBuilder" }
  - { method: "<java.lang.StringBuilder: java.lang.String toString()>", from: base, to: result, type: "java.lang.String" }

運行tai-e給定如下參數

-cp
E:\tools\code\aa\src\main\java
-java
8
-m
org.example.Main
-ap
-a
pta=action:dump;action-file:result.txt;taint-config:src\test\resources\pta\taint\taint-config.yml

指定E:\tools\code\aa\src\main\java為classpath,指定java版本為8,指定主類,允許幻象引用,啟用指針分析和污點分析,并將污點分析結果導出到result.txt文件中。

查看txt文件會發現 tai-e列出了污點的信息流

Detected 1 taint flow(s):
TaintFlow{<org.example.Main: void main(java.lang.String[])>[5@L8] temp$4 = invokevirtual main.<org.example.Main: java.lang.String source(java.lang.String)>(temp$3); -> <org.example.Main: java.lang.String sink(java.lang.String)>[1@L17] invokevirtual temp$0.<java.lang.Runtime: java.lang.Process exec(java.lang.String)>(s);/0}

分析java web

以一個java web tomcat servlet項目為例

存在如下的servlet

package com.example.demo6;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet(name = "helloServlet", value = "/hello-servlet")
public class HelloServlet extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String source = request.getParameter("source");
        Runtime.getRuntime().exec(source);
    }
}

在上面說到分析java web項目需要我們自己定義分析入口點,并且模擬參數對象,所以我們需要修改下污點分析的處理類pascal.taie.analysis.pta.plugin.taint.TaintAnalysis

tai-e實現了插件式編程,將分析拆成小模塊,官方wiki中提到了《如何寫一個分析插件》,除了寫分析插件以外,還可以《創建新的分析》

接下來我們將針對java web修改TaintAnalysis類,使污點分析時識別路由并將其加入到entrypoint中,即增加tai-e分析入口點。

TaintAnalysis類實現了pascal.taie.analysis.pta.plugin.Plugin接口,該接口有幾個生命周期函數

image.png

我們增加程序分析入口點肯定是在onStart函數中,所以在TaintAnalysis類重寫onStart函數

@Override
public void onStart() {
}

那么如何添加entrypoint呢?我搜了issue,發現有人有和我同樣的問題,官方也給出了解決方案

添加entrypoint需要調用pascal.taie.analysis.pta.core.solver.Solver#addEntryPoint函數,該函數需要一個EntryPoint對象,EntryPoint構造函數中需要兩個參數JMethod method, ParamProvider paramProvider,分別對應了入口點函數的JMethod對象,和入口點函數的參數處理器。其中ParamProvider接口有幾個實現類

分別對應不同情況下的參數提供器。其中MainEntryPointParamProvider就是對應的Main函數的參數處理器

其中getParamObjs調用getMainArgs拿到模擬的main函數的參數String[] args,模擬參數用了heapModel.getMockObj()

官方的代碼中ThreadHandler的onStart函數是一個非常好的參數模擬并添加入口點的參考例子

image.png

參考這個我們來照貓畫虎,首先我們需要拿到com.example.demo6.HelloServlet的JMethod對象,很簡單,直接用tai-e的類型系統就行

JClass controller = World.get().getClassHierarchy().getClass("com.example.demo6.HelloServlet");
JMethod method = controller.getDeclaredMethod("doGet");

然后我們需要模擬參數對象,doGet函數有兩個參數HttpServletRequest request, HttpServletResponse response,HttpServletRequest和HttpServletResponse都是一個接口類,我們模擬對象必須是模擬具體的類,所以這里用HttpServletRequest和HttpServletResponse的實現類HttpServletRequestWrapper和HttpServletResponseWrapper,代碼如下

// mock obj
JClass requestWrapper = World.get().getClassHierarchy().getClass("javax.servlet.http.HttpServletRequestWrapper");
JClass responseWrapper = World.get().getClassHierarchy().getClass("javax.servlet.http.HttpServletResponseWrapper");
HeapModel heapModel = solver.getHeapModel();
Obj mockRequest = heapModel.getMockObj("EntryPointObj", "<http-request-wrapper>", requestWrapper.getType(), method);
Obj mockResponse = heapModel.getMockObj("EntryPointObj", "<http-response-wrapper>", responseWrapper.getType(), method);
Obj mockServlet = heapModel.getMockObj("EntryPointObj", "<http-controller>", servlet.getType());

// mock param
SpecifiedParamProvider paramProvider = new SpecifiedParamProvider.Builder(method)
        .addThisObj(mockServlet)
        .addParamObj(0, mockRequest)
        .addParamObj(1, mockResponse)
        .build();
solver.addEntryPoint(new EntryPoint(method, paramProvider));

很簡單,到這我們就把doGet加入到了entrypoint中,然后我們將javax.servlet.ServletRequest#getParameter加入到sink中

// add source request.getParameter(java.lang.String)
JMethod getParameter = requestWrapper.getDeclaredMethod("getParameter");
if (getParameter == null) {
    getParameter = requestWrapper.getSuperClass().getDeclaredMethod("getParameter");
}
sources.put(getParameter, getParameter.getReturnType());

// print sources
sources.forEach((k, v) -> System.out.println(k.getMethodSource() + "\t" + v.getName()));
System.out.println();

運行一下試試,修改idea參數,加上javax.servlet-api-4.0.1.jar的lib包,并且要強制指定--input-classes=com.example.demo6.HelloServlet,javax.servlet.http.HttpServletRequestWrapper,javax.servlet.http.HttpServletResponseWrapper把兩個warpper類引入進來。

-cp E:\tools\code\demo6\target\classes;C:\Users\xxx\.m2\repository\javax\servlet\javax.servlet-api\4.0.1\javax.servlet-api-4.0.1.jar -java 8 --input-classes=com.example.demo6.HelloServlet,javax.servlet.http.HttpServletRequestWrapper,javax.servlet.http.HttpServletResponseWrapper -m com.example.demo6.Main -ap -a pta=action:dump;action-file:result.txt;taint-config:src\test\resources\pta\taint\taint-config.yml

查看result.txt中,發現成功檢測出來一條污點傳播的路徑來

Detected 1 taint flow(s):
TaintFlow{<com.example.demo6.HelloServlet: void doGet(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)>[1@L12] $r1 = invokeinterface request.<javax.servlet.http.HttpServletRequest: java.lang.String getParameter(java.lang.String)>(%stringconst0); -> <com.example.demo6.HelloServlet: void doGet(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)>[3@L13] invokevirtual $r2.<java.lang.Runtime: java.lang.Process exec(java.lang.String)>($r1);/0}

文末

對我而言,靜態軟件分析是一門高深的學問,涉及到的算法以及理論知識都比較晦澀,好在有tai-e這種開箱即用的框架。

回顧來看,tai-e在指針分析的能力和算法設計上毋庸置疑是很優秀的,而且整個框架的設計和插件式編程等設計思維遠超soot這種單例模式一把梭的框架。但是其架構對我們做審計java web自動化并不友好:

  1. 需要指定--input-classes參數強制引入對應的路由
  2. main函數限制
  3. 指定entrypoint入口點和參數mock比較麻煩

參考

  1. 南京大學譚添、李樾兩位老師的課 https://tai-e.pascal-lab.net/
  2. 北大熊英飛老師的課 https://liveclass.org.cn/cloudCourse/#/courseDetail/8mI06L2eRqk8GcsW
  3. tai-e Github開源地址 https://github.com/pascal-lab/Tai-e
  4. 兩位老師的pascal課題組開源的一些指針分析的代碼 https://pascal-group.bitbucket.io/code.html
  5. 《soot存活指南》 https://www.brics.dk/SootGuide/sootsurvivorsguide.pdf
  6. fynch3r師傅的Soot知識點整理
  7. How to analyze java web or spring project with tai-e

文筆垃圾,措辭輕浮,內容淺顯,操作生疏。不足之處歡迎大師傅們指點和糾正,感激不盡。


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/3040/