Soot检测Android应用Zip目录遍历漏洞

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010889616/article/details/80955487

Soot简介

Soot是一个Java静态分析框架,它提供了四种中间(representation)表现用于分析与转换Java字节码.Soot既可以作为优化和检查class文件的工具也可以作为一个开发与优化Java字节码的框架。

使用Soot可以对Android应用进行静态分析,Android静态分析指APK不在运行的情况下,根据某些代码特征来分析应用具有哪些行为。

什么是Zip文件遍历攻击以及修复方案

请看这篇博客《Android静态安全检查(二):Zip文件目录遍历攻击

代码实现

工程依赖的jar包,依赖包下载地址:https://github.com/secure-software-engineering/FlowDroid/releases

gradle依赖

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile group: 'com.google.code.gson', name: 'gson', version: '2.8.5'
    compile group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5'

    testCompile group: 'junit', name: 'junit', version: '4.11'
}

定义检查接口ICheck

/**
 * 应用检查接口
 * @author wzj
 * @create 2018-07-01 16:52
 **/
public interface IChecker
{
    void checker();
}

定义基础抽象类,封装公共方法,注意以下两点

  • initSootConfig方法是初始化Soot配置,主要有apk路径和Android SDK的platform文件夹路径
  • 因为有很多Android源码影响分析,定义了excludePackagesList列表,分析的时候,把这些包名过滤掉
import soot.PackManager;
import soot.Scene;
import soot.SootClass;
import soot.options.Options;

import java.util.ArrayList;
import java.util.List;

/**
 * 应用检查的基础类
 * @author wzj
 * @create 2018-07-01 16:35
 **/
public abstract class BasicChecker implements IChecker
{
    /**
     * 检查的时候,要排除的包名
     */
    protected static List<String> excludePackagesList = new ArrayList<String>();

    /**
     * apk路径
     */
    protected String apkPath = "H:\\JAVA\\Soot\\apk\\app-debug.apk";

    /**
     * android jar路径
     */
    protected String jarsPath = "D:\\AndroidSDK\\platforms";

    static
    {
        excludePackagesList.add("java.");
        excludePackagesList.add("android.");
        excludePackagesList.add("javax.");
        excludePackagesList.add("android.support.");
        excludePackagesList.add("sun.");
        excludePackagesList.add("com.google.");
    }

    /**
     * 初始化soot配置
     */
    private void initSootConfig()
    {
        Options.v().set_src_prec(Options.src_prec_apk);
        Options.v().set_output_format(Options.output_format_jimple);
        String androidJarPath = Scene.v().getAndroidJarPath(jarsPath, apkPath);

        List<String> pathList = new ArrayList<String>();
        pathList.add(apkPath);
        pathList.add(androidJarPath);

        Options.v().set_process_dir(pathList);
        Options.v().set_force_android_jar(androidJarPath);
        Options.v().set_keep_line_number(true);
        Options.v().set_process_multiple_dex(true);

        Options.v().set_wrong_staticness(Options.wrong_staticness_ignore);
        Options.v().set_exclude(excludePackagesList);

        Scene.v().loadNecessaryClasses();
        PackManager.v().runPacks();
    }

    /**
     * 是否是例外的包名
     * @param sootClass 当前的类
     * @return 检查结果
     */
    protected boolean isExcludeClass(SootClass sootClass)
    {
        if (sootClass.isPhantom())
        {
            return true;
        }

        String packageName = sootClass.getPackageName();
        for (String exclude : excludePackagesList)
        {
            if (packageName.startsWith(exclude))
            {
                return true;
            }
        }

        return false;
    }

    /**
     * 分析
     */
    public void analyze()
    {
        initSootConfig();
        checker();
    }
}

具体的检测类,遍历每一个类的每一个方法,看是否有ZipEntry的getName()方法调用,如果有方法调用,再判断是否有下面两种调用

  • File类的String getCanonicalPath()方法调用
  • String类的boolean contains(CharSequence s)调用

如果有其中一个,则认为该应用有对路径做处理,则不存在zip目录遍历漏洞;如果没有,则认为有该漏洞。

import com.nii.soot.core.BasicChecker;
import soot.*;
import soot.jimple.InvokeExpr;
import soot.jimple.Stmt;
import soot.jimple.StringConstant;

import java.util.ArrayList;
import java.util.List;

/**
 * zip目录遍历攻击检测
 *
 * @author wzj
 * @create 2018-07-04 21:54
 **/
public class ZipVulnChecker extends BasicChecker
{
    /**
     * ZipEntry 的getName()方法签名
     */
    private static final String ENTRY_GET_NAME_SIGNATURE = "<java.util.zip.ZipEntry: java.lang.String getName()>";

    /**
     * ZipEntry类
     */
    private static final String ENTRY_CLASS = "java.util.zip.ZipEntry";

    /**
     * getCanonicalPath()方法签名
     */
    private static final String GET_CANONICAL_FILE_SIGNATURE = "<java.io.File: java.lang.String getCanonicalPath()>";

    /**
     * contains方法签名
     */
    private static final String CONTAINS_SIGNATURE = "<java.lang.String: boolean contains(java.lang.CharSequence)>";

    /**
     * 具体的检测方法
     */
    public void checker()
    {
        for (SootClass sootClass : Scene.v().getApplicationClasses())
        {
            //判断是否是虚方法,是否是否是接口
            if (sootClass.isPhantom() || sootClass.isInterface() || isExcludeClass(sootClass))
            {
                continue;
            }

            for (SootMethod sootMethod : sootClass.getMethods())
            {
                if (!sootMethod.hasActiveBody())
                {
                    continue;
                }

                //判断是否含有ZipEntry类
                if (!isContainZipEntryClass(sootMethod.getActiveBody()))
                {
                    continue;
                }

                checkZipVuln(sootClass,sootMethod);
            }
        }
    }

    private boolean isContainZipEntryClass(Body body)
    {
        List<Local> localList = new ArrayList<Local>();
        localList.addAll(body.getLocals());
        localList.addAll(body.getParameterLocals());

        for (Local local : localList)
        {
            if (ENTRY_CLASS.equals(local.getType().toString()))
            {
                return true;
            }
        }

        return false;
    }

    /**
     * 检测是否有zip遍历漏洞,判断规则为:
     * 如果调用了getName()方法获取解压文件路径,如果接下来有如下一种判断则认为没有漏洞
     * 1、调用了File的getCanonicalFile()方法获取绝对路径
     * 2、判断该路径是否包含 .. 字符串
     *
     * @param sootClass  sootClass
     * @param sootMethod sootMethod
     */
    private void checkZipVuln(SootClass sootClass, SootMethod sootMethod)
    {
        Body body = sootMethod.getActiveBody();
        Stmt targetStmt = null;

        //是否找到了getName() api
        boolean isFindGetNameApi = false;

        //是否有漏洞
        boolean isZipVul = true;

        for (Unit unit : body.getUnits())
        {
            Stmt stmt = (Stmt) unit;

            //判断是否是一条调用语句
            if (!stmt.containsInvokeExpr())
            {
                continue;
            }

            //获取调用语句的方法签名
            String methodSignature = stmt.getInvokeExpr().getMethod().getSignature();
            if (ENTRY_GET_NAME_SIGNATURE.equals(methodSignature))
            {
                isFindGetNameApi = true;
                targetStmt = stmt;
            }

            if (isFindGetNameApi)
            {
                //判断是否调用File的getCanonicalFile()方法
                if (GET_CANONICAL_FILE_SIGNATURE.equals(methodSignature))
                {
                    isZipVul = false;
                    break;
                }

                //判断是否调用了contains方法,并且第一个参数值为 ..
                if (CONTAINS_SIGNATURE.equals(methodSignature))
                {
                    InvokeExpr invokeExpr = stmt.getInvokeExpr();
                    Value value = invokeExpr.getArg(0);
                    if (value instanceof StringConstant && ((StringConstant) value).value.startsWith(".."))
                    {
                        isZipVul = false;
                        break;
                    }
                }
            }
        }

        if (isFindGetNameApi && isZipVul)
        {
            System.out.println("***************************************");
            System.out.println("This apk has zip vulnerability");
            System.out.println(sootClass.getName());
            System.out.println(sootMethod.getSubSignature());
            System.out.println(targetStmt.getJavaSourceStartLineNumber());
        }
    }

    public static void main(String[] args)
    {
        new ZipVulnChecker().analyze();
    }
}

源码地址

https://github.com/HelloKittyNII/soot-android-static-analysis

阅读更多

没有更多推荐了,返回首页