Argus Static Analysis Framework
Argus-SAF is a static analysis framework that we build in house to do security vetting for Android applications. It integrated two of our previously developed products Argus-Jawa and Argus-Amandroid, and have the capability to perform comprehensive, efficient and highly precise Inter-component Data Flow Analysis.
Argus-Jawa
Argus-Jawa is a general static analysis framework for our home-brewing intermediate representation (IR) language Jawa. Any java-like language (e.g., java, java bytecode, dalvik bytecode) can be analyzed if it have been translated into Jawa.
It provides the ability to:
- Parsing Jawa codes.
- Load information from jar file and class file.
- Build AST for jawa records (classes) and procedures (methods).
- Resolving class hierarchy and class elements overwritten relationship.
- Resolving virtual method invocation.
It can conduct/build:
- Call Graph
- Reaching Definition Analysis
- Points-to Analysis
- Monotonic Data Flow Analysis,
- Reaching Facts Analysis
- Intra-/Inter- procedural Control Flow Graph
- Intra-/Inter- procedural Data Flow Graph
- Data Dependence Analysis
- Taint Analysis
- Side Effect Analysis
Argus-Amandroid
Amandroid meaning
Aman means secure in Indonesian, so Amandroid means secure android.
Overview
Amandroid is a static analysis framework for Android apps.
The Android platform is immensely popular. However, malicious or vulnerable applications have been reported to cause several security problems. Currently there is no effective method that a market operator can use to vet apps entering a market (e.g., Google Play).
Prior works using static analysis to address Android app security problems more focus on specific problems and built specialized tools for them. We observe that a large portion of those security issues can be resolved by addressing one underlying core problem – capturing semantic behaviors of the app such as object points-to and control-/data-flow information. Thus, we designed a new approach to conducting static analysis for vetting Android apps, and built a generic framework, called Amandroid, which builds upon Argus-Jawa and does flow- and context-sensitive data flow analysis in an inter-component way.
Our approach shows that a comprehensive (tracking all objects) static analysis method on Android apps is totally feasible in terms of computation resources, and the Amandroid framework is flexible and easy to be extended for many types of specialized security analyses.
Since Amandroid directly handles Inter-component control and data flows, it can be used to address security problems that result from interactions among multiple components from either the same or different apps. Amandroid analysis is sound in that it can provide assurance of the absence of the specified security problems in an app with well-specified and reasonable assumptions on the Android runtime and its library.
On top of Amandroid we performed certain specific security analyses, for instance,
- Sensitive data flow tracking
- Data injection detection
- API misuse checking
We apply those analyses on hundreds of apps collected from Google Play’s popular apps and a third-party security company, and the results show that it is capable of finding real security issues and efficient enough in terms of analysis time.
Amandroid Workflow
Amandroid take an Android APK x
as the input, then it works as following:
- Extract
x
, then parse*.dex
file toJawa-DeDexer
module and other files (like*.xml
,resource.arsc
) toPreprocess
module. Jawa Dedexer
inDex2Jawa
module decompile the*.dex
file into Jawa format. Parsers inPreprocess
module can provide app’s information toAppInfoCollector
. Developer can specify what kind of information he/she is interested and non-interesting app can be ignored. Finally,Preprocess
module will output meta data ofx
.AndroidEnvironmentGenerator
inEnvironmentBuilder
is getting all sources codes and meta datas from previous step, then building the environment method for each of the component.DataFlowFramework
provide data flow analysis technics to examine data flow problems.AndroidReachingFactsAnalysis
takes environment methods as the entry points and build IDFG.InterProceduralDataDependenceAnalysis
takes IDFG and build DDG.AndroidDataDependentTaintAnalysis
takes DDG and SourceAndSinkManager (provided by the developer) to do taint analysis and output taint result.- Developer specified plugin get all the result, then he/she can do further analysis or visualize it in certain way.
Please note that source codes and environment appeals above are all Jawa format.
Getting Started
This section will help you to start with using Argus-CIT.
Download
Requirement: Java 10
- Go to this link.
- Download argus-saf_***-version-assembly.jar.
Run
To run Argus-SAF, in a terminal command prompt, type:
$ java -jar argus-saf_***-version-assembly.jar
Above command will show you the usage of Argus-SAF:
Available Modes:
a[picheck] Detecting API misuse.
d[ecompile] Decompile Apk file(s).
s[tage] Stage middle results.
t[aint] Perform taint analysis on Apk(s).
There are several modes you can use. Let’s take taint analysis as an example, type:
$ java -jar argus-saf_***-version-assembly.jar t
It will show you the usage and available options:
usage: t[aint] [options] <file_apk/dir>
-d,--debug Output debug information.
-f,--force Force delete previous decompile result. [Default: false]
-i,--ini <path> Set .ini configuration file path.
-mo,--module <name> Taint analysis module to use. [Default: DATA_LEAKAGE, Choices: (COMMUNICATION_LEAKAGE,
OAUTH_TOKEN_TRACKING, PASSWORD_TRACKING, INTENT_INJECTION, DATA_LEAKAGE)]
-o,--output <dir> Set output directory. [Default: .]
Two notable options are -mo,--module
and -i,--ini
.
-mo,--module
allows you to set the module you wanna use in the analysis. By default it set to DATA_LEAKAGE detection, you can switch between different modules by specify this option.-i,--ini
allows you to specify the custom configuration file to use for the analysis, the detailed information will be discussed in Configuration File.
Test
To make sure Argus-SAF running on your environment, you can execute it on our test apks, which you can download from ICC-Bench.
The command to run is:
$ java -jar argus-saf_***-version-assembly.jar t -o /outputPath /path/icc-bench
Install Amandroid Stash
If you are first time use Argus-SAF, above test command will automatically download and install
Amandroid Stash under path ~/.amandroid_stash
. It contains necessary android sdks and configuration files
for Argus-SAF’s analysis.
More test apks you can find from DroidBench.
Working with Argus-SAF
Argus-SAF released two libraries: jawa-core
and amandroid-core
. Both of them are exist in the Maven Central Repo.
As aforementioned, jawa-core
contains all the static analysis apis for analyzing Jawa,
amandroid-core
contains all the android related analyzing apis and tools.
Obtain Argus-SAF as Library
You can obtain Argus-SAF as library for your own project to build new static analysis tools.
Here, we assume your project is a SBT project:
Depend on jawa-core
by editing build.sbt:
libraryDependencies += "com.github.arguslab" %% "jawa-core" % VERSION
Depend on amandroid-core
by editing build.sbt:
libraryDependencies += "com.github.arguslab" %% "amandroid-core" % VERSION
Note that:
1. Depend on amandroid-core
will automatically add jawa-core
as dependency.
2. If your project use Maven or Gradle as the build tool, you should translate it to corresponding format by following format in Maven Central Repo.
3. VERSION
should change to current released version.
Build Project Based on Argus-SAF
Argus-SAF-playground is a project which has the basic setup for a Argus-SAF enhanced project with demo codes of how to perform different kind of analysis. Any project want to leveraging Argus-SAF can just fork from (or just learn the setup from) this repo and based on that to implement your own project.
Configuration File
By default, Argus-SAF will use ~/.amandroid_stash/amandroid/config.ini
as the default configuration file.
However, user could provide their own configuration file as well.
Format is as following:
; General configuration for amandroid
[general]
; Dependence directory for odex resolution.
;dependence_dir = /path
; Output debug information
debug = false
; Java Library jar files
;lib_files = /path/lib1.jar:/path/lib2.jar
; Configuration for data flow analysis
[analysis]
; Handle static initializer
static_init = false
parallel = false
; Context length for k-context sensitive analysis
k_context = 1
; Source and sink list file
;sas_file = /path/sas.txt
; timeout setting for analyzing one component (minutes)
timeout = 10
; Concurrent settings for Amandroid actors
[concurrent]
;actor_conf_file = /path/application.conf
Tutorial: Load APK
Your project could be written in both Java and Scala, in this tutorial we use Scala for demonstration.
Step by Step
First at all, make sure your project has amandroid-core
as dependency.
Then, following steps will decompile an apk file with loading all the classes and resources.
A.
Prepare DecompilerSettings
. It defines the decompile layout, message level, odex dependence files,
and whether force delete the output folder if it’s already exist.
val outputUri = FileUtil.toUri(outputPath)
val layout = DecompileLayout(outputUri)
val settings = DecompilerSettings(
AndroidGlobalConfig.settings.dependence_dir.map(FileUtil.toUri),
dexLog = false, debugMode = false, removeSupportGen = true,
forceDelete = true, Some(new DecompileTimer(5 minutes)), layout)
B.
Decompile the apk.
val apkUri = FileUtil.toUri(apkPath)
val (outUri, srcs, _) = ApkDecompiler.decompile(apkUri, settings)
C.
Initialize ApkGlobal
, load jawa code and collect info. ApkGlobal
is the apk resource manager, class loader and class path manager for our analysis.
val reporter = new PrintReporter(MsgLevel.ERROR)
val apk = new ApkGlobal(ApkModel(apkUri, outUri, srcs), reporter)
srcs foreach {
src =>
val fileUri = FileUtil.toUri(FileUtil.toFilePath(outUri) + File.separator + src)
if(FileUtil.toFile(fileUri).exists()) {
//store the app's jawa code in AmandroidCodeSource which is organized class by class.
apk.load(fileUri, Constants.JAWA_FILE_EXT, AndroidLibraryAPISummary)
}
}
AppInfoCollector.collectInfo(apk, global, outUri)
Load Apk Using ApkYard
ApkYard
is a class which allows loading multiple apks and enables inter-app analysis.
How to do inter-app analysis using ApkYard
you can check tutorial.
val apkUri = FileUtil.toUri(apkPath)
val outputUri = FileUtil.toUri(outputPath)
val reporter = new PrintReporter(MsgLevel.ERROR)
val yard = new ApkYard(reporter)
val layout = DecompileLayout(outputUri)
val settings = DecompilerSettings(AndroidGlobalConfig.settings.dependence_dir, dexLog = false, debugMode = false, removeSupportGen = true, forceDelete = forceDelete, None, layout)
val apk = yard.loadApk(apkUri, settings)
Retrieve Information from Apk
val appName = apk.getAppName
val certificate = apk.getCertificates
val uses_permissions = apk.getUsesPermissions
val component_infos = apk.getComponentInfos // ComponentInfo(compType: [class type], typ: [ACTIVITY, SERVICE, RECEIVER, PROVIDER], exported: Boolean, enabled: Boolean, permission: ISet[String])
val intent_filter = apk.getIntentFilterDB // IntentFilterDB contains intent filter information for each component.
Access Environment Methods
var entryPoints = global.getEntryPoints(AndroidConstants.MAINCOMP_ENV) // Exposed components
if(!public_only)
entryPoints ++= global.getEntryPoints(AndroidConstants.COMP_ENV) // Private components
Full Example
The full example can be found at Argus-SAF-playground:LoadApk.
Tutorial: Load Class, Field, Method
Suppose our apk have following class:
package org.argus.test;
public class Hello {
int i;
public void greeting() {
System.out.println("Hello World!");
}
}
To load the Hello
class and access its attributes:
A.
Load APK follow previous tutorial.
B.
Do following:
val clazz: JawaClass = global.getClassOrResolve(new JawaType("org.argus.test.Hello"))
val method_opt: Option[JawaMethod] = clazz.getDeclaredMethodByName("greeting")
val field_opt: Option[JawaField] = clazz.getDeclaredField("i")
From JawaClass
, JawaMethod
, JawaField
you can access their access flags, qualified name,
overwritten information, etc. The detailed usage you can study from the source code.
Tutorial: Generate Graphs
In the tutorial we show how to generate Control Flow Graph, Reaching Definition Analysis, Call Graph.
Control Flow Graph
Control Flow Graph
can be easily acquired from JawaAlirInfoProvider
with JawaMethod
.
val method: JawaMethod = clazz.getDeclaredMethodByName("greeting").get
Reaching Definition Analysis
Reaching Definition Analysis
can be easily acquired from JawaAlirInfoProvider
with JawaMethod
and Control Flow Graph
.
val method: JawaMethod = clazz.getDeclaredMethodByName("greeting").get
val cfg = JawaAlirInfoProvider.getCfg(method)
val rda = JawaAlirInfoProvider.getRda(method, cfg)
Inter-procedural Data Flow Graph
Inter-procedural Data Flow Graph
(IDFG
) is a combination of Inter-procedural Control Flow Graph
(ICFG
)
and a points-to information map
which denotes that at each program point what are the possible Object types.
We have two points-to analysis algorithm to build IDFG
: InterProceduralSuperSpark
, AndroidReachingFactsAnalysis
.
Most of the time InterProceduralSuperSpark
is just used to build Call Graph efficiently, because
it is more light-weight than AndroidReachingFactsAnalysis
, but still preserves enough precision (flow-,object-,field- sensitive).
We will discuss this in Call Graph section.
In this tutorial we will talk about how to build IDFG
use AndroidReachingFactsAnalysis
:
A.
Configuration. AndroidReachingFactsAnalysisConfig
contains following global variables:
resolve_icc
: control whether find ICC call target and passing points-to facts to target component.resolve_static_init
: control whether handle static init when analyzing. (Recommend to turn this off as it is very time consuming.)parallel
: control whether run analysis in parallel mode. (We don’t suggest to turn this on, as we have more robust Akka Actor solution.)
Whether Resolving ICC
We introduced Component Based Analysis
approach to handle ICC communication in a more scalable way. Thus,
if you are using this approach, you should turn resolve_icc
off.
To set those variables is very simple:
AndroidReachingFactsAnalysisConfig.resolve_icc = false
AndroidReachingFactsAnalysisConfig.resolve_static_init = false
AndroidReachingFactsAnalysisConfig.parallel = false
B.
Load APK follow previous tutorial.
C.
Perform analysis:
implicit val factory = new RFAFactFactory
// ep is the entry point method for the analsis. Most of the time it is the environment method we generated for each component.
val initialfacts = AndroidRFAConfig.getInitialFactsForMainEnvironment(ep)
val timeout = Some(new MyTimeout(AndroidGlobalConfig.settings.timeout minutes))
val idfg = AndroidReachingFactsAnalysis(global, apk, ep, initialfacts, new ClassLoadManager, timeout)
Inter-procedural Data Dependence Graph
val iddResult = InterProceduralDataDependenceAnalysis(global, idfg)
Call Graph
There are few algorithms we can use to build Call Graph
:
InterProceduralSuperSpark
, SignatureBasedCallGraph
, AndroidReachingFactsAnalysisBuilder
, etc.
InterProceduralSuperSpark
is the best option if you want to build a Call Graph
efficiently as well as preserve enough precision.
// methods is the entry point methods you want to start with to build call graph.
val idfg = InterProceduralSuperSpark(global, methods.map(_.getSignature))
val icfg = idfg.icfg
val call_graph = icfg.getCallGraph
Output Graphs in Different Format
Our generated graphs allows three kind of output format: Dot, GraphML, GML.
graph.toDot(writer)
graph.toGraphML(writer)
graph.toGML(writer)
Tutorial: Taint Analysis
Argus-SAF’s taint analysis leverages our Inter-procedural Reaching Fact Analysis
and Inter-procedural Data Dependence Analysis
algorithms, which reported in the Amandroid [pdf] paper.
Step by Step
A.
Perform Inter-procedural Data Flow Analysis and Inter-procedural Data Dependence Analysis
to generate IDFG
and IDDG
.
B.
Provide a Source and Sink Manager
for the taint analysis.
Argus-SAF currently have five build-in managers:
IntentInjectionSourceAndSinkManager
PasswordSourceAndSinkManager
OAuthSourceAndSinkManager
DataLeakageAndroidSourceAndSinkManager
CommunicationSourceAndSinkManager
val ssm = module match {
case INTENT_INJECTION =>
new IntentInjectionSourceAndSinkManager(global, apk, apk.getLayoutControls, apk.getCallbackMethods, AndroidGlobalConfig.settings.sas_file)
case PASSWORD_TRACKING =>
new PasswordSourceAndSinkManager(global, apk, apk.getLayoutControls, apk.getCallbackMethods, AndroidGlobalConfig.settings.sas_file)
case OAUTH_TOKEN_TRACKING =>
new OAuthSourceAndSinkManager(global, apk, apk.getLayoutControls, apk.getCallbackMethods, AndroidGlobalConfig.settings.sas_file)
case DATA_LEAKAGE =>
new DataLeakageAndroidSourceAndSinkManager(global, apk, apk.getLayoutControls, apk.getCallbackMethods, AndroidGlobalConfig.settings.sas_file)
case COMMUNICATION_LEAKAGE =>
new CommunicationSourceAndSinkManager(global, apk, apk.getLayoutControls, apk.getCallbackMethods, AndroidGlobalConfig.settings.sas_file)
}
You can also provide your own Source and Sink Manager
follow tutorial.
C.
Perform taint analysis:
val taint_analysis_result = AndroidDataDependentTaintAnalysis(global, iddResult, idfg.ptaresult, ssm)
Customize Source and Sink Manager
Source and Sink Manager
can specify four kind of Source points and two kind of Sink points.
Source points:
- Api Source: Given api signature will return a tainted data.
- Callback Source: Given callback method will contain tainted data as parameter.
- UI Source: Given ui component contains tainted data.
- ICC Source: Given component environment method is receiving tainted data.
Sink points:
- Api Sink: Given api signature will leak its parameters.
- Icc Sink: Given ICC method will leak Intent.
For both Api Source and Api Sink we can specify it in a Source and Sink File
using following format:
Landroid/telephony/TelephonyManager;.getDeviceId:()Ljava/lang/String; SENSITIVE_INFO -> _SOURCE_
Landroid/content/pm/PackageManager;.queryBroadcastReceivers:(Landroid/content/Intent;I)Ljava/util/List; SENSITIVE_INFO -> _SOURCE_
Landroid/os/Handler;.obtainMessage:(ILjava/lang/Object;)Landroid/os/Message; MESSAGE -> _SOURCE_
Landroid/util/Log;.d:(Ljava/lang/String;Ljava/lang/String;)I -> _SINK_
Ljava/io/Writer;.write:(Ljava/lang/String;II)V -> _SINK_ 1
Ljava/net/URLConnection;.setRequestProperty:(Ljava/lang/String;Ljava/lang/String;)V -> _SINK_ 1|2
Note that, 1|2
in above format means the first and second parameter will leak the data, no number means all parameter.
Your Source and Sink Manager
need extends from SourceAndSinkManager
or AndroidSourceAndSinkManager
or DefaultAndroidSourceAndSinkManager
.
Here we take IntentInjectionSourceAndSinkManager
as an example to discuss:
package org.argus.amandroid.plugin.dataInjection
import org.argus.amandroid.alir.pta.reachingFactsAnalysis.model.InterComponentCommunicationModel
import org.argus.amandroid.alir.taintAnalysis.AndroidSourceAndSinkManager
import org.argus.amandroid.core.Apk
import org.argus.amandroid.core.parser.LayoutControl
import org.argus.jawa.alir.controlFlowGraph.{ICFGInvokeNode, ICFGNode}
import org.argus.jawa.alir.pta.PTAResult
import org.sireum.pilar.ast._
import org.sireum.util._
import org.argus.jawa.core._
/**
* @author Fengguo Wei
* @author Sankardas Roy
*/
class IntentInjectionSourceAndSinkManager(
global: Global,
apk: Apk,
layoutControls: Map[Int, LayoutControl],
callbackSigs: ISet[Signature],
sasFilePath: String)
extends AndroidSourceAndSinkManager(global, apk, layoutControls, callbackSigs, sasFilePath){
// We only interested about icc source, so for api source we just return false
override def isSource(calleeSig: Signature, callerSig: Signature, callerLoc: JumpLocation): Boolean = {
false
}
// We only interested about icc source, so for callback source we just return false
override def isCallbackSource(sig: Signature): Boolean = {
false
}
// We only interested about icc source, so for ui source we just return false
override def isUISource(calleeSig: Signature, callerSig: Signature, callerLoc: JumpLocation): Boolean = {
false
}
// If the given point is environment method's entry node, we consider it as icc source.
override def isIccSource(entNode: ICFGNode, iddgEntNode: ICFGNode): Boolean = {
entNode == iddgEntNode
}
// if the given point is an ICC call, we mark it as icc sink.
override def isIccSink(invNode: ICFGInvokeNode, ptaresult: PTAResult): Boolean = {
var sinkflag = false
val calleeSet = invNode.getCalleeSet
calleeSet.foreach{
callee =>
if(InterComponentCommunicationModel.isIccOperation(callee.callee)){
sinkflag = true
}
}
sinkflag
}
// api source is using the default one, which implemented in AndroidSourceAndSinkManager.
// The basic idea is check whether given api signature is matching with api sinks specified in provided sasFile (Source and Sink File).
}
Tutorial: Inter-app Analysis
val fileUris = apkFiles.map(FileUtil.toUri)
val outputUri = FileUtil.toUri(outputPath)
val reporter = new PrintReporter(MsgLevel.ERROR)
val res = TaintAnalysisTask(TaintAnalysisModules.DATA_LEAKAGE, fileUris, outputUri, forceDelete = true, reporter).run
To customize the inter-app analysis you can check the code at Argus-SAF:TaintAnalysisTask.