一、介绍
Appshark 是一个静态污点分析工具,用于扫描 Android 应用程序中的漏洞。我做了一点改造,增加了应用批处理能力。
工具能力
:自动扫描apk漏洞,筛选出符合条件的应用集合,再对这部分应用有针对性的进行人工详细分析。主要价值在于对厂商海量的系统应用能减少人力筛查成本;工具特点
:支持漏洞规则自定义、多漏洞规则并行筛查、支持文件批处理;工具局限性
:分析的是污点在变量之间的传递关系,所以无论是source、sink还是sanitizer描述的具体粒度都是变量(但是也已满足绝大多数场景)。
改造后项目仓库地址:晚点补链接//todo
开源项目地址:https://github.com/bytedance/appshark
官方文档:https://github.com/bytedance/appshark/blob/main/doc/zh/overview.md
二、使用
2.1 工具运行及结果分析
环境
:>= JDK-11
配置
:config/config.json
运行
:$ python3 appShark.py -s
输入
:
① config配置:主要设置文件路径及选择使用何种漏洞分析规则来处理文件
② rules: 漏洞分析规则 (后面会介绍规则编写)
输出
:
① results.json文件(这里对其他非必要文件做了精简)
② 发现匹配漏洞规则的应用,且至少有1个导出组件的情况下,会在包名加上_marked后缀。
results.json文件说明:
{
"AppInfo": { // App信息
"AppName": "NFC Service",
"PackageName": "com.android.nfc",
"min_sdk": 34,
"target_sdk": 34,
"versionCode": 34,
"versionName": "14",
"classCount": 1254,
"methodCount": 8466,
"appsharkTakeTime": 8571
},
"ManifestRisk": { // AndroidManifest安全信息
"debuggable": false, // 是否允许调试
"allowBackup": false, // 是否允许对应用数据的备份和恢复
"usesCleartextTraffic": false //是否使用明文流量(非加密的Http)
},
"SecurityInfo": { // 匹配自定义静态扫描规则生成的安全结果,这里以全局扫描是否有setWifiEnabled调用为例
"camille": {
"setWifiEnabled": {
"category": "camille",
"detail": "setWifiEnabled",
"name": "setWifiEnabled",
"vulners": [
{
"details": {
"url": "/Users/XX/appshark-main/out/vulnerability/0-setWifiEnabled.html",
"position": "<com.android.nfc.ConfirmConnectToWifiNetworkActivity: void onClick(android.view.View)>",
"target": [ // 调用链
"<com.android.nfc.ConfirmConnectToWifiNetworkActivity: void onClick(android.view.View)>",
"virtualinvoke $r3.<android.net.wifi.WifiManager: boolean setWifiEnabled(boolean)>(1)"
]
},
"hash": "eb84e25e4fd117ff421ff2a2c81ef92671f087c7",
"old_hash": "68064390a20df5d492e9525b7c43acf8096e8c83"
},
...
],
"deobfApk": ""
}
}
},
"DeepLinkInfo": {
},
"HTTP_API": [
],
"JsBridgeInfo": [
],
"BasicInfo": { // 组件基本信息,这里主要是呈现组件是否导出及其相关信息
"ComponentsInfo": {
"exportedReceivers": {
"com.android.nfc.NfcReaderDetector$1": {
"exported": true,
"DynamicBroadcastReceiver": "com.android.nfc.NfcReaderDetector$1",
"RegisteredMethod": "<com.android.nfc.NfcReaderDetector: void <init>(android.content.Context)>",
"RegisteredStmt": "virtualinvoke $r1_1.<android.content.Context: android.content.Intent registerReceiver(android.content.BroadcastReceiver,android.content.IntentFilter,int)>($r3, $r7, 2)"
},
"com.android.nfc.NfcBootCompletedReceiver": {
"exported": true,
"<receiver exported=true name=com.android.nfc.NfcBootCompletedReceiver>": [
{
"<intent-filter>": [
{
"content": "<action name=android.intent.action.BOOT_COMPLETED>",
"isString": true
}
]
}
]
},
...
},
"unExportedActivities": {
"com.android.nfc.TechListChooserActivity": {
"exported": false,
"<activity process=:com.android.nfc.chooser finishOnCloseSystemDialogs=true name=com.android.nfc.TechListChooserActivity launchMode=3 multiprocess=false theme=16974850 excludeFromRecents=true>": [
]
},
"com.android.nfc.cardemulation.AppChooserActivity": {
"exported": false,
"<activity clearTaskOnLaunch=true finishOnCloseSystemDialogs=true name=com.android.nfc.cardemulation.AppChooserActivity multiprocess=true theme=2131886364 excludeFromRecents=true>": [
]
},
...
},
"unExportedProviders": {
"androidx.startup.InitializationProvider": {
"exported": false,
"<provider exported=false name=androidx.startup.InitializationProvider authorities=com.android.nfc.androidx-startup>": [
{
"<meta-data name=androidx.lifecycle.ProcessLifecycleInitializer value=androidx.startup>": [
]
}
]
},
...
},
"unExportedServices": {
"com.android.nfc.handover.PeripheralHandoverService": {
"exported": false,
"<service name=com.android.nfc.handover.PeripheralHandoverService>": [
]
},
...
},
"unExportedReceivers": {
"androidx.profileinstaller.ProfileInstallReceiver": {
"exported": false,
"<receiver exported=true name=androidx.profileinstaller.ProfileInstallReceiver permission=android.permission.DUMP enabled=true directBootAware=false>": [
{
"<intent-filter>": [
{
"content": "<action name=androidx.profileinstaller.action.INSTALL_PROFILE>",
"isString": true
}
]
},
...
]
}
},
"exportedActivities": {
"com.android.nfc.BeamShareActivity": {
"exported": true,
"<activity exported=true noHistory=true finishOnCloseSystemDialogs=true icon=2131230958 name=com.android.nfc.BeamShareActivity theme=16973839 excludeFromRecents=true label=2131820586>": [
{
"<intent-filter>": [
{
"content": "<action name=android.intent.action.SEND>",
"isString": true
},
{
"content": "<category name=android.intent.category.DEFAULT>",
"isString": true
},
{
"content": "<data mimeType=*/*>",
"isString": true
}
]
},
...
]
}
}
},
"JSNativeInterface": [
]
},
"UsePermissions": [ // 申请的权限
"android.permission.BLUETOOTH_PRIVILEGED",
...
],
"DefinePermissions": { // 自定义权限
"com.miui.nfc.permission.SEND_HCI_EVENT": "signatureOrSystem",
...
},
"Profile": "/Users/XX/appshark-main/out/vulnerability/2-profiler.json"
}
2.2 配置文件设置
config.json5主要的设置项:
"apkPath"
: // apk文件路径,必要参数
"out"
: "", // 结果输出路径
"maxThread"
: 1, // 控制内部进行指针分析等操作时的并行度,默认数量为2
"rules"
: "wifiEnable.json", // 自定义规则配置
"rulePath"
: "config/rules", //specifies the rule's parent directory, default is ./config/rules
"logLevel"
: 1, //debug 0;info 1;warn 2;error 3
"javaSource"
: true, //是否在最终的漏洞详情中展示源码. 该源码是通过jadx反编译得到.
"supportFragment"
:true , //是否对处理Fragment的lifeCycle函数. 类似于处理Activity的onCreate等函数
"wholeProcessMode"
: //是否进行全程序分析,默认为false. 全行程分析主要是影响分析的范围和性能表现
2.3 自定义规则撰写
- 规则模块介绍:
entry
: 分析入口,一般是个函数
source
:污染源
sink
:污染利用点
sanitizer
: 过滤source到sink的无效链路,消除误报。
注:appshark分析的是污点在变量之间的传递关系,所以无论是source,还是sink,还是sanitizer描述的具体粒度都是变量。
- 各模块规则撰写实现:
规则JSON编写框架
{
"unZipSlip": {
"SliceMode": true, // 1 分析模式入口
"desc": {
"category": "FileRisk", // 2 分类
},
"entry": { // 3 分析入口
},
"source": { // 4 污染源定义
},
"sanitizer": { // 6 过滤无效链路
},
"sink": { // 5 污染利用点
}
}
}
① 分析入口模式(mode)
DirectMode
: 固定分析入口。需要明确指明分析的入口,即 : entry;
SliceMode
: 不固定分析入口。和DirectMode的区别是它的分析入口不是固定的,而是根据具体的source,sink计算得到的;
ConstStringMode
: 以常量字符串所在的函数作为分析入口。不受traceDepth的约束;
ConstNumberMode
: 以常量数值所在的函数作为分析入口。不受traceDepth的约束;
APIMode
: APIMode和前面的几种mode都不一样,他并不是一个数据流分析的规则,而是一个简单的查找指定api的规则。
② 分类(category)
这里就是封装安全信息的key,这里key已经优化了配置:保持名称与分类一致即可
"setWifiEnabled": {
"desc": {
"category": "setWifiEnabled",
}
}
③ 分析入口(entry)
"entry": {
"methods": [ "<net.bytedance.security.app.ruleprocessor.testdata.ZipSlip: void UnZipFolder(java.lang.String,java.lang.String)>" ]
}
分析入口一般是一个函数。按jimple规则:<类名>:<返回值> <函数> (<函数参数>...) 为模板设置,entry只有在DirectMode下需要明确指定,其他三个模式下,都无需明确指明分析入口。
④ 污染源定义(source)
常量字符串
函数返回值
某对象的field
某个函数的参数
某个对象的创建
I 常量字符串
"source": {
"ConstString": ["path1"]
}
对应:
String s="path1";
f(12,"path1");
s将成为source. 函数f的参数1将成为source
II 函数返回值
"source": {
"Return": ["<java.util.zip.ZipEntry: java.lang.String getName()>" ]
}
也就是getName的返回值将会是source, 那么:
ZipEntry e=getEntry();
String name=e.getName();
name将成为source点。
III 某对象的field
"source": {
"Field": [ "<android.provider.CalendarContract: android.net.Uri CONTENT_URI>", ]
}
Uri uri=CalendarContract.CONTENT_URI;
uri将会成为source点. 注意不区分该field是静态field还是非静态field。
IV 某个函数的参数
"source": {
"Param": {
"<android.webkit.WebViewClient: android.webkit.WebResourceResponse shouldInterceptRequest(android.webkit.WebView, android.webkit.WebResourceRequest)>": [ "p1" ]
}
}
p0是第一个参数,p1是第二个参数,这里p1对应的是WebResourceRequest,它才是source。
V 某个对象的创建
"source": {
"NewInstance": ["android.content.Intent"]
}
那么:
android.content.Intent i=new android.content.Intent();
这时候变量i将成为source点。
⑤ 污染源利用(sink)
key:
- LibraryOnly 默认值为false,如果设置为true,那么就要求匹配到的函数签名必须是EngineConfig.json5中指定的Library
- TaintParamType 参数类型限制
- TaintCheck 检查规则
限制条件(目前sink点只能是函数的周边)
- this指针 @this "TaintCheck": [ "@this"]
- 函数的某个参数 p0,p1,p2,所有参数p* "TaintCheck": [ "p*" ]
- 函数的返回值 return "TaintCheck": [ "return" ]
多条件设置写法:"TaintCheck": [ "@this","return" ]
举例:
"sink": {
"<*: * startActivit*(*)>": {
"LibraryOnly": true,
"TaintParamType": [
"android.content.Intent",
"android.content.Intent[]"
],
"TaintCheck": ["p*"]
}
}
这里定startActivit相关泛函数的入参.Intent/.Intent[]为漏洞利用点,即sink,appshark会检查能否找到从source到这些变量的一个污点传播路径。
⑥ 过滤无效链路规则(sanitizer)
sanitizer目的是消除误报. 发现了一条从source到sink的完整传播路径, 该路径经由sanitizer规则过滤,如果满足条件就删掉这条路径,否则保留。
以上面unZipSlip举例:
"rule1": {
"<java.io.File: java.lang.String getCanonicalPath()>": {
"TaintCheck": ["@this" ]
}
},
"containsDotDot": {
"<java.lang.String: boolean contains(java.lang.CharSequence)>": {
"TaintCheck": [ "@this"],
"p0": ["..*"]
}
},
"indexDotDot": {
"<java.lang.String: boolean indexOf(java.lang.String)>": {
"TaintCheck": ["@this"],
"p0": [ "..*"]
}
}
顶层规则是或的关系:也就是说,你可以自定义多个一级key,这里就是指rule1、containsDotDot、indexDotDot,过滤规则满足他们3个中的1个就过滤掉;
二层规则间是与的关系:containsDotDot下的TaintCheck和p0规则是与的关系,即:
if(path.contains("../")){
return false
}
"TaintCheck": ["@this"]:从source出发,传播到的所有变量中,是否污染到了<java.lang.String: boolean contains(java.lang.CharSequence)>这个函数的this指针,即path
"p0": [ ".."] :常量字符串..污染到contains的参数0
因此这个规则意思就是看代码里是否有"../"相关的判断语句,如果有就认为是对这种漏洞有预防,就不用统计了。
更详细撰写规则说明参考官方文档:https://github.com/bytedance/appshark/blob/main/doc/zh/how_to_write_rules.md
了解到这,结合官方详细文档,自己就可以编写简单规则试试看了,项目中也提供了一些写好的规则模板,可以借鉴学习。
三、实现原理分析
① 配置文件解析 :Json解析config.json;
② apk解析:使用jadx对apk反编译成java; 解析manifest和resource文件,提权清单文件信息,包括:应用、组件信息、权限信息等;
③ 代码预处理:通过soot框架构建call graph, 即各函数的可达路径图;
④ 用户自定义规则解析:解析规则,查找source、sink;
⑤ 数据流分析:基于call graph和漏洞规则,寻找source到sink调用链;
⑥ 输出报告:封装数据,输出报告文件。