tabby原理分析

tabby是一款基于soot的Java静态代码分析工具,用于分析jar包并生成代码属性图,尤其在反序列化链挖掘中有应用。文章详细介绍了tabby的工作流程,包括加载配置、使用soot进行污点分析等步骤,解析了关键类如ClassInfoCollector和SimpleStmtSwitcher的作用,以及如何处理不同类型的方法调用和赋值语句。
摘要由CSDN通过智能技术生成

tabby 是一款基于 soot 实现的java静态代码分析工具,用于分析jar包,生成代码属性图。结合手工可以半自动地完成java反序列化链挖掘工作。

我们从tabby的源代码进行分析,根据源代码的逻辑结构,可以看出其工作流程分成下面几个部分

加载配置文件,设置分析选项
分析类、方法和关联信息
使用soot进行污点分析、生成调用边
保存代码属性图` 

其中使用soot进行污点分析的部分是本文的重点

1.1 加载配置文件,设置分析选项

tabby的入口方法为 tabby.App#run ,代码如下

@Bean
    CommandLineRunner run(){
        return args -> {
            try{
                if(!JavaVersion.isJDK8()){
                    throw new JDKVersionErrorException("Error JDK version. Please using JDK8.");
                }
                loadProperties("config/settings.properties");
                applyOptions();
                analyser.run(props);
                log.info("Done. Bye!");
                System.exit(0);
            }catch (IllegalArgumentException e){
                log.error(e.getMessage() +
                        "\nPlease use java -jar tabby target_directory [--isJDKOnly|--isJDKProcess|--isSaveOnly|--excludeJDK] !" +
                        "\ntarget_directory 为相对路径" +
                        "\n--isJDKOnly出现时,仅处理JDK的内容" +
                        "\n--excludeJDK出现时,不添加当前运行jre环境" +
                        "\n--isJDKProcess出现时,将处理当前运行jre环境的分析" +
                        "\nExample: java -jar tabby cases/jars --isJDKProcess" +
                        "\nOthers: https://github.com/wh1t3p1g/tabby/wiki/Tabby%E9%A3%9F%E7%94%A8%E6%8C%87%E5%8C%97");
            }catch (JDKVersionErrorException e){
                log.error(e.getMessage());
            }
        };
    }` 

这里tabby通过一个 .properties 文件来加载配置,配置选项如下

`# build code property graph
tabby.build.enable                    = true
# jdk settings
tabby.build.isJDKProcess              = true
tabby.build.withAllJDK                = false
tabby.build.excludeJDK                = false
tabby.build.isJDKOnly                 = false

# dealing fatjar
tabby.build.checkFatJar               = true

# default pointed-to analysis
tabby.build.isFullCallGraphCreate     = true

# targets to analyse
tabby.build.target                    = path/to/target
tabby.build.libraries                 = path/to/lib

# load to neo4j
tabby.load.enable                     = false

# debug
tabby.debug.details                   = false
tabby.debug.inner.details             = false` 

这里关注一下 isFullCallGraphCreate 这个选项。如果为 true ,那么分析时使用 FullCallGraphScanner 类,如果为 false 那么使用 CallGraphScanner 类。因为 FullCallGraphScanner 类只是简单的生成调用图,而 CallGraphScanner 类会对输入进行污点分析,所以后续分析时主要针对 CallGraphScanner 类。

加载配置完成以后,代码进入 tabby.core.Analyser#run 方法,先配置soot分析目标所在的 classpath

 `public void run(Properties props) throws IOException {

        if("true".equals(props.getProperty(ArgumentEnum.BUILD_ENABLE.toString(), "false"))){
            Map<String, String> dependencies = getJdkDependencies(
                    props.getProperty(ArgumentEnum.WITH_ALL_JDK.toString(), "false"));
            log.info("Get {} JDK dependencies", dependencies.size());

            Map<String, String> cps = "true".equals(props.getProperty(ArgumentEnum.EXCLUDE_JDK.toString(), "false"))?
                    new HashMap<>():new HashMap<>(dependencies);
            Map<String, String> targets = new HashMap<>();
            // 收集目标
            if("false".equals(props.getProperty(ArgumentEnum.IS_JDK_ONLY.toString(), "false"))){
                String target = props.getProperty(ArgumentEnum.TARGET.toString());
                boolean checkFatJar = "true".equals(props.getProperty(ArgumentEnum.CHECK_FAT_JAR.toString(), "false"));
                Map<String, String> files = FileUtils.getTargetDirectoryJarFiles(target, checkFatJar);
                cps.putAll(files);
                targets.putAll(files);
            }

            if("true".equals(props.getProperty(ArgumentEnum.IS_JDK_ONLY.toString(), "false"))
                    || "true".equals(props.getProperty(ArgumentEnum.IS_JDK_PROCESS.toString(), "false"))){
                targets.putAll(dependencies);
            }

            // 添加必要的依赖,防止信息缺失,比如servlet依赖
            if(FileUtils.fileExists(GlobalConfiguration.LIBS_PATH)){
                Map<String, String> files = FileUtils
                        .getTargetDirectoryJarFiles(GlobalConfiguration.LIBS_PATH, false);
                for(Map.Entry<String, String> entry:files.entrySet()){
                    cps.putIfAbsent(entry.getKey(), entry.getValue());
                }
            }

            runSootAnalysis(targets, new ArrayList<>(cps.values()));
        }` 

然后进入 tabby.core.Analyser#runSootAnalysis 方法

public void runSootAnalysis(Map<String, String> targets, List<String> classpaths){
        try{
            SootConfiguration.initSootOption();
            addBasicClasses();
            // set class paths
            Scene.v().setSootClassPath(String.join(File.pathSeparator, new HashSet<>(classpaths)));
            // get target filepath
            List<String> realTargets = getTargets(targets);
            if(realTargets.isEmpty()){
                log.info("Nothing to analysis!");
                return;
            }
            Main.v().autoSetOptions();

            // 类信息抽取
            classInfoScanner.run(realTargets);
            // 函数调用分析
            if(GlobalConfiguration.IS_FULL_CALL_GRAPH_CONSTRUCT){
                fullCallGraphScanner.run();
            }else{
                callGraphScanner.run();
            }
            rulesContainer.saveStatus();
        }catch (CompilationDeathException e){
            if (e.getStatus() != CompilationDeathException.COMPILATION_SUCCEEDED) {
                throw e;
            }
        }` 

它先调用了 addBasicClasses 方法,内部调用soot提供的API,加载 rules/basicClasses.json 中配置的类文件

`public void addBasicClasses(){
        List<String> basicClasses = rulesContainer.getBasicClasses();
        for(String cls:basicClasses){
            Scene.v().addBasicClass(cls ,HIERARCHY);
        }
    }` 

rules/basicClasses.json 默认的内容如下

`[
  "io.netty.channel.ChannelFutureListener",
  "scala.runtime.java8.JFunction2$mcIII$sp",
  "scala.runtime.java8.JFunction1$mcII$sp",
  "scala.runtime.java8.JFunction0$mcV$sp",
  "scala.runtime.java8.JFunction0$mcZ$sp",
  "scala.runtime.java8.JFunction0$mcJ$sp",
  "scala.runtime.java8.JFunction0$mcI$sp",
  "scala.runtime.java8.JFunction1$mcZJ$sp",
  "scala.runtime.java8.JFunction1$mcZI$sp",
  "scala.runtime.java8.JFunction1$mcVI$sp",
  "scala.runtime.java8.JFunction0$mcD$sp",
  "scala.runtime.java8.JFunction0$mcF$sp",
  "scala.runtime.java8.JFunction0$mcS$sp",
  "scala.runtime.java8.JFunction0$mcB$sp",
  "com.codahale.metrics.Gauge"
]` 

然后调用 Scene.v().setSootClassPath() ,设置soot加载目标文件用到的 classpath ,之后soot加载 target 时,会在这里设置的 classpath 范围内进行搜索

 `public  void  setSootClassPath(String  p)  {
    sootClassPath = p;
    SourceLocator.v().invalidateClassPath();
  }` 

然后调用 getTargets 方法,获取目标类文件的绝对路径,保存到 realTargets 变量中

 `public List<String> getTargets(Map<String, String> targets){
        Set<String> stuff = new HashSet<>();
        List<String> newIgnore = new ArrayList<>();
        targets.forEach((filename,  filepath)  ->  {  if(!rulesContainer.isIgnore(filename)){  stuff.add(filepath);  newIgnore.add(filename);  }  });  rulesContainer.getIgnored().addAll(newIgnore);  log.info("Total analyse {} targets.",  stuff.size());  Options.v().set_process_dir(new  ArrayList<>(stuff));  return  new  ArrayList<>(stuff);  }`

到这里分析需要的soot选项、 classpath 参数和 target 参数就配置好了

完成了上面的配置以后,tabby使用 ClassInfoScanner 来分析类信息和方法信息。

这里定位到 tabby.core.scanner.ClassInfoScanner#run 方法,代码如下,传入的参数为刚才配置的 target ,也就是分析目标类的路径

 `public void run(List<String> paths){
        // 多线程提取基础信息
        Map<String, CompletableFuture<ClassReference>> classes = loadAndExtract(paths);
        transform(classes.values()); // 等待收集结束,并保存classRef
        List<String> runtimeClasses = new ArrayList<>(classes.keySet());
        classes.clear();
        // 单线程提取关联信息
        buildClassEdges(runtimeClasses);
        save();
    }` 

首先看 loadAndExtract 方法,它接收类文件的路径作为参数。里面先调用soot提供的API loadBasicClassesloadDynamicClasses ,加载和项目无关,但是必要的类文件。然后使用 getClassesUnder 方法,根据项目文件的路径获取项目中类文件的路径,并使用 loadClassAndSupport 方法将其加载为 SootClass 类型的对象。然后对于每一个 SootClass 类型的对象,调用 collector.collect 方法,收集保存类、方法信息。

 `public Map<String, CompletableFuture<ClassReference>> loadAndExtract(List<String> targets){
        Map<String, CompletableFuture<ClassReference>> results = new HashMap<>();
        Scene.v().loadBasicClasses();

        Scene.v().loadDynamicClasses();
        int counter = 0;
        log.info("Start to collect {} targets' class information.", targets.size());
        for (final String path : targets) {
            for (String cl : SourceLocator.v().getClassesUnder(path)) {
                try{
                    SootClass theClass = Scene.v().loadClassAndSupport(cl);
                    if (!theClass.isPhantom()) {
                        // 这里存在类数量不一致的情况,是因为存在重复的对象
             
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值