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 excludePackagesList = new ArrayList();/*** 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 pathList = new ArrayList();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 = "";/*** ZipEntry类*/private static final String ENTRY_CLASS = "java.util.zip.ZipEntry";/*** getCanonicalPath()方法签名*/private static final String GET_CANONICAL_FILE_SIGNATURE = "";/*** contains方法签名*/private static final String CONTAINS_SIGNATURE = "";/*** 具体的检测方法*/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 localList = new ArrayList();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() apiboolean 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