一、介紹
Appshark 是一個靜態(tài)污點分析工具,用于掃描 Android 應(yīng)用程序中的漏洞。我做了一點改造,增加了應(yīng)用批處理能力。
工具能力:自動掃描apk漏洞,篩選出符合條件的應(yīng)用集合,再對這部分應(yīng)用有針對性的進(jìn)行人工詳細(xì)分析。主要價值在于對廠商海量的系統(tǒng)應(yīng)用能減少人力篩查成本;工具特點:支持漏洞規(guī)則自定義、多漏洞規(guī)則并行篩查、支持文件批處理;工具局限性:分析的是污點在變量之間的傳遞關(guān)系,所以無論是source、sink還是sanitizer描述的具體粒度都是變量(但是也已滿足絕大多數(shù)場景)。
改造后項目倉庫地址:晚點補鏈接//todo
開源項目地址:https://github.com/bytedance/appshark
官方文檔:https://github.com/bytedance/appshark/blob/main/doc/zh/overview.md
二、使用
2.1 工具運行及結(jié)果分析
環(huán)境:>= JDK-11
配置:config/config.json
運行:$ python3 appShark.py -s
輸入:
① config配置:主要設(shè)置文件路徑及選擇使用何種漏洞分析規(guī)則來處理文件
② rules: 漏洞分析規(guī)則 (后面會介紹規(guī)則編寫)
輸出:
① results.json文件(這里對其他非必要文件做了精簡)
② 發(fā)現(xiàn)匹配漏洞規(guī)則的應(yīng)用,且至少有1個導(dǎo)出組件的情況下,會在包名加上_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, // 是否允許調(diào)試
"allowBackup": false, // 是否允許對應(yīng)用數(shù)據(jù)的備份和恢復(fù)
"usesCleartextTraffic": false //是否使用明文流量(非加密的Http)
},
"SecurityInfo": { // 匹配自定義靜態(tài)掃描規(guī)則生成的安全結(jié)果,這里以全局掃描是否有setWifiEnabled調(diào)用為例
"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": [ // 調(diào)用鏈
"<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": { // 組件基本信息,這里主要是呈現(xiàn)組件是否導(dǎo)出及其相關(guān)信息
"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": [ // 申請的權(quán)限
"android.permission.BLUETOOTH_PRIVILEGED",
...
],
"DefinePermissions": { // 自定義權(quán)限
"com.miui.nfc.permission.SEND_HCI_EVENT": "signatureOrSystem",
...
},
"Profile": "/Users/XX/appshark-main/out/vulnerability/2-profiler.json"
}
2.2 配置文件設(shè)置
config.json5主要的設(shè)置項:
"apkPath": // apk文件路徑,必要參數(shù)
"out": "", // 結(jié)果輸出路徑
"maxThread": 1, // 控制內(nèi)部進(jìn)行指針分析等操作時的并行度,默認(rèn)數(shù)量為2
"rules": "wifiEnable.json", // 自定義規(guī)則配置
"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函數(shù). 類似于處理Activity的onCreate等函數(shù)
"wholeProcessMode": //是否進(jìn)行全程序分析,默認(rèn)為false. 全行程分析主要是影響分析的范圍和性能表現(xiàn)
2.3 自定義規(guī)則撰寫
- 規(guī)則模塊介紹:
entry: 分析入口,一般是個函數(shù)
source:污染源
sink:污染利用點
sanitizer: 過濾source到sink的無效鏈路,消除誤報。
注:appshark分析的是污點在變量之間的傳遞關(guān)系,所以無論是source,還是sink,還是sanitizer描述的具體粒度都是變量。
- 各模塊規(guī)則撰寫實現(xiàn):
規(guī)則JSON編寫框架
{
"unZipSlip": {
"SliceMode": true, // 1 分析模式入口
"desc": {
"category": "FileRisk", // 2 分類
},
"entry": { // 3 分析入口
},
"source": { // 4 污染源定義
},
"sanitizer": { // 6 過濾無效鏈路
},
"sink": { // 5 污染利用點
}
}
}
① 分析入口模式(mode)
DirectMode: 固定分析入口。需要明確指明分析的入口,即 : entry;
SliceMode: 不固定分析入口。和DirectMode的區(qū)別是它的分析入口不是固定的,而是根據(jù)具體的source,sink計算得到的;
ConstStringMode: 以常量字符串所在的函數(shù)作為分析入口。不受traceDepth的約束;
ConstNumberMode: 以常量數(shù)值所在的函數(shù)作為分析入口。不受traceDepth的約束;
APIMode: APIMode和前面的幾種mode都不一樣,他并不是一個數(shù)據(jù)流分析的規(guī)則,而是一個簡單的查找指定api的規(guī)則。
② 分類(category)
這里就是封裝安全信息的key,這里key已經(jīng)優(yōu)化了配置:保持名稱與分類一致即可
"setWifiEnabled": {
"desc": {
"category": "setWifiEnabled",
}
}
③ 分析入口(entry)
"entry": {
"methods": [ "<net.bytedance.security.app.ruleprocessor.testdata.ZipSlip: void UnZipFolder(java.lang.String,java.lang.String)>" ]
}
分析入口一般是一個函數(shù)。按jimple規(guī)則:<類名>:<返回值> <函數(shù)> (<函數(shù)參數(shù)>...) 為模板設(shè)置,entry只有在DirectMode下需要明確指定,其他三個模式下,都無需明確指明分析入口。
④ 污染源定義(source)
常量字符串函數(shù)返回值某對象的field某個函數(shù)的參數(shù)某個對象的創(chuàng)建
I 常量字符串
"source": {
"ConstString": ["path1"]
}
對應(yīng):
String s="path1";
f(12,"path1");
s將成為source. 函數(shù)f的參數(shù)1將成為source
II 函數(shù)返回值
"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點. 注意不區(qū)分該field是靜態(tài)field還是非靜態(tài)field。
IV 某個函數(shù)的參數(shù)
"source": {
"Param": {
"<android.webkit.WebViewClient: android.webkit.WebResourceResponse shouldInterceptRequest(android.webkit.WebView, android.webkit.WebResourceRequest)>": [ "p1" ]
}
}
p0是第一個參數(shù),p1是第二個參數(shù),這里p1對應(yīng)的是WebResourceRequest,它才是source。
V 某個對象的創(chuàng)建
"source": {
"NewInstance": ["android.content.Intent"]
}
那么:
android.content.Intent i=new android.content.Intent();
這時候變量i將成為source點。
⑤ 污染源利用(sink)
key:
- LibraryOnly 默認(rèn)值為false,如果設(shè)置為true,那么就要求匹配到的函數(shù)簽名必須是EngineConfig.json5中指定的Library
- TaintParamType 參數(shù)類型限制
- TaintCheck 檢查規(guī)則
限制條件(目前sink點只能是函數(shù)的周邊)
- this指針 @this "TaintCheck": [ "@this"]
- 函數(shù)的某個參數(shù) p0,p1,p2,所有參數(shù)p* "TaintCheck": [ "p*" ]
- 函數(shù)的返回值 return "TaintCheck": [ "return" ]
多條件設(shè)置寫法:"TaintCheck": [ "@this","return" ]
舉例:
"sink": {
"<*: * startActivit*(*)>": {
"LibraryOnly": true,
"TaintParamType": [
"android.content.Intent",
"android.content.Intent[]"
],
"TaintCheck": ["p*"]
}
}
這里定startActivit相關(guān)泛函數(shù)的入?yún)?Intent/.Intent[]為漏洞利用點,即sink,appshark會檢查能否找到從source到這些變量的一個污點傳播路徑。
⑥ 過濾無效鏈路規(guī)則(sanitizer)
sanitizer目的是消除誤報. 發(fā)現(xiàn)了一條從source到sink的完整傳播路徑, 該路徑經(jīng)由sanitizer規(guī)則過濾,如果滿足條件就刪掉這條路徑,否則保留。
以上面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": [ "..*"]
}
}
頂層規(guī)則是或的關(guān)系:也就是說,你可以自定義多個一級key,這里就是指rule1、containsDotDot、indexDotDot,過濾規(guī)則滿足他們3個中的1個就過濾掉;
二層規(guī)則間是與的關(guān)系:containsDotDot下的TaintCheck和p0規(guī)則是與的關(guān)系,即:
if(path.contains("../")){
return false
}
"TaintCheck": ["@this"]:從source出發(fā),傳播到的所有變量中,是否污染到了<java.lang.String: boolean contains(java.lang.CharSequence)>這個函數(shù)的this指針,即path
"p0": [ ".."] :常量字符串..污染到contains的參數(shù)0
因此這個規(guī)則意思就是看代碼里是否有"../"相關(guān)的判斷語句,如果有就認(rèn)為是對這種漏洞有預(yù)防,就不用統(tǒng)計了。
更詳細(xì)撰寫規(guī)則說明參考官方文檔:https://github.com/bytedance/appshark/blob/main/doc/zh/how_to_write_rules.md
了解到這,結(jié)合官方詳細(xì)文檔,自己就可以編寫簡單規(guī)則試試看了,項目中也提供了一些寫好的規(guī)則模板,可以借鑒學(xué)習(xí)。
三、實現(xiàn)原理分析

① 配置文件解析 :Json解析config.json;
② apk解析:使用jadx對apk反編譯成java; 解析manifest和resource文件,提權(quán)清單文件信息,包括:應(yīng)用、組件信息、權(quán)限信息等;
③ 代碼預(yù)處理:通過soot框架構(gòu)建call graph, 即各函數(shù)的可達(dá)路徑圖;
④ 用戶自定義規(guī)則解析:解析規(guī)則,查找source、sink;
⑤ 數(shù)據(jù)流分析:基于call graph和漏洞規(guī)則,尋找source到sink調(diào)用鏈;
⑥ 輸出報告:封裝數(shù)據(jù),輸出報告文件。