Android 第三方應(yīng)用廣告攔截實(shí)現(xiàn)


前年在的上一家公司,制造的機(jī)器里應(yīng)用裝有不良廣告,嚴(yán)重影響了兒童客戶使用者的思想健康,導(dǎo)致被人投訴。于是乎,就有了想研發(fā)一款類似于360廣告屏蔽的應(yīng)用的念頭。嗯,事情就是這樣,現(xiàn)在切入主題。

目前市場(chǎng)上有很多安全軟件,它們攔截第三方應(yīng)用廣告的方式都不一樣,比如說有 以so 注入方式來攔截彈出廣告。
現(xiàn)在我們來看下這種方式的詳細(xì)情況:

要做到攔截,首先我們得知道廣告是怎么出來的,原來第三方應(yīng)用大部分是以加入廣告jar形式加入廣告插件,然后在AndroidManifest中聲明廣告service或者在程序中執(zhí)行廣告Api,廣告插件再通過Http請(qǐng)求去加載廣告。在java中,有四種訪問網(wǎng)絡(luò)的接口,如apache的http庫(kù)(如下介紹),這幾種方式首先都會(huì)通過getaddrinfo函數(shù)獲取域名地址,然后通過connect函數(shù)連接到服務(wù)器讀取廣告信息。

  1. WebView(源碼文件在frameworks/base/core/java/android/webkit/WebView.java)。通過WebView類的void loadUrl(String url)、void postUrl(String url, byte[] postData)、void loadData(String data, String mimeType, String encoding)、void loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding, String historyUrl)、void evaluateJavascript(String script, ValueCallback resultCallback)等加載網(wǎng)頁(yè)。
  2. apache-http(源碼目錄在external/apache-http/ , HttpGet 和 HttpPost類)。通過external/apache-http/src/org/apache/http/impl/client/DefaultRequestDirector.java中的DefaultRequestDirector類的HttpResponse execute(HttpHost target, HttpRequest request, HttpContext context)方法執(zhí)行訪問的網(wǎng)絡(luò)的動(dòng)作。
  3. okhttp(源碼目錄在external/okhttp/)。通過external/okhttp/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java中的HttpEngine類的private void connect(Request request) throws IOException方法連接網(wǎng)絡(luò)。
  4. URL(源碼在libcore/luni/src/main/java/java/net/URL.java)。通過libcore/luni/src/main/java/java/net/URL.java中的URL類的URLConnection openConnection() throws IOException方法和URLConnection openConnection(Proxy proxy) throws IOException方法連接網(wǎng)絡(luò)。

然后再來說說動(dòng)態(tài)庫(kù)注入,具體什么是動(dòng)態(tài)庫(kù)注入,以及如何注入,網(wǎng)上有很多文章,這里就不介紹。動(dòng)態(tài)庫(kù)注入攔截呢,主要是攔截getaddrinfo,根據(jù)條件返回錯(cuò)誤URL(源碼在libcore/luni/src/main/java/java/net/URL.java)。通過libcore/luni/src/main/java/java/net/URL.java中的URL類的URLConnection openConnection() throws IOException方法和URLConnection openConnection(Proxy proxy) throws IOException方法連接網(wǎng)絡(luò)。攔截網(wǎng)絡(luò)請(qǐng)求,達(dá)到攔截作用。不過需要注意一點(diǎn)就是攔截之前要確定你所攔截的動(dòng)態(tài)庫(kù)是否是你需要攔截的庫(kù)?例如A程序調(diào)用了動(dòng)態(tài)庫(kù)BO和CO,而BO和CO都調(diào)用了connect函數(shù),此時(shí)需要攔截BO的請(qǐng)求,需要注入到BO動(dòng)態(tài)庫(kù)并修改GOT表,而不是注入到CO中。

攔截HTTP方式廣告在多數(shù)廣告包中,應(yīng)用程序首先會(huì)通過apache的http庫(kù)或JDK中的http方法先將廣告數(shù)據(jù)下載過來,然后通過WebView顯示。這種方式通過注入攔截進(jìn)程的/system/lib/libjavacore.so可實(shí)現(xiàn)廣告地址攔截。
攔截WebView方式廣告廣告插件也可以直接通過WebView加載URL,通過分析WebView加載流程可知它的網(wǎng)絡(luò)處理過程均交給libchromium_net庫(kù)來完成。因此通過注入libjavacore.so是無法實(shí)現(xiàn)攔截,而是需要注入到/system/lib/libchromium_net.so。

通過這種方式已經(jīng)完全能夠攔截掉第三方APP廣告,但存在一些問題:
1.廣告商可以通過JNI方式調(diào)用系統(tǒng)getaddrinfo與connect實(shí)現(xiàn)自己的解析與連接過程的動(dòng)態(tài)庫(kù),從而跳過libjavacore.so導(dǎo)致攔截?zé)o效。 2.攔截WebView方式廣告雖然能夠不顯示廣告,但通常仍然會(huì)有浮動(dòng)框顯示”網(wǎng)頁(yè)無法打開”,從而影響美觀。 3.最重要的是我們機(jī)器是沒有root權(quán)限的??!
第三個(gè)問題直接導(dǎo)致了放棄了這種注入做法。 來來去去一段時(shí)間后,目前是采用android 系統(tǒng)本地掃描第三方應(yīng)用廣告形式。具體怎么做,請(qǐng)往下看!

如果對(duì)這種方式不了解的話,建議先看下這篇 Android系統(tǒng)掃描帶廣告應(yīng)用的做法。

所以具體廣告插件掃描方案是匹配包名+類名形式的: 1.掃描本地所有第三方應(yīng)用,列出一個(gè)應(yīng)用中的所有類,將包名+類名方式與廣告插件特征庫(kù)進(jìn)行匹配; 2.將匹配出來的應(yīng)用所帶廣告特征,通過系統(tǒng)提供傳入接口,將這些規(guī)則設(shè)置進(jìn)去。(當(dāng)然,系統(tǒng)代碼是需要改的,做了一些處理,主要是在上面介紹中的幾種訪問網(wǎng)絡(luò)方式上做了判斷處理)。
這種方案的關(guān)鍵在于廣告特征庫(kù)的完善,廣告插件特征庫(kù)收集越全,掃描出來的廣告插件就可以越準(zhǔn)確。所幸,公司有幾位大神,做過類似的事情,所以工作簡(jiǎn)單了多些。
獲取第三方應(yīng)用:

/** * 查詢機(jī)器內(nèi)非本公司應(yīng)用 */ 
public List<PackageInfo> getAllLocalInstalledApps() { 
      List<PackageInfo> apps = new ArrayList<PackageInfo>(); 
      if(pManager == null){ 
            return apps; 
      }
      //獲取所有應(yīng)用  
      List<PackageInfo> paklist = pManager.getInstalledPackages(0);
      for (int i = 0; i < paklist.size(); i++) { 
           PackageInfo pak = (PackageInfo) paklist.get(i); 
           //屏蔽掉公司內(nèi)部應(yīng)用
           //...
           //判斷是否為非系統(tǒng)預(yù)裝的應(yīng)用程序 
           if ((pak.applicationInfo.flags & pak.applicationInfo.FLAG_SYSTEM) <= 0) { 
               // customs applications  apps.add(pak); 
         } 
     } 
     return apps; 
}

============================
獲取某個(gè)應(yīng)用的廣告特征

public static List<String> getClassNameByDex(Context context, String packageName) { 
          List<String> datalist = new ArrayList<String>(); 
          String path = null; 
          try { 
               path = context.getPackageManager().getApplicationInfo(packageName, 0).sourceDir;
          // 獲得某個(gè)程序的APK路徑 
          } catch (NameNotFoundException e) { 
                e.printStackTrace(); 
          }
          try { 
               if(TextUtils.isEmpty(path)){ 
                    return datalist;
               } 
               DexFile dexFile = new DexFile(path);// get dex file of APK  
               Enumeration<String> entries = dexFile.entries(); 
               while (entries.hasMoreElements()) {// travel all classes 
                     String className = (String) entries.nextElement(); 
                     String totalname = packageName + "."+className;          
                     datalist.add(totalname); 
               } 
           } catch (IOException e) { 
                 e.printStackTrace(); 
           } 
           return datalist; 
 }

=======================================
將應(yīng)用中的所有類名與特征庫(kù)進(jìn)行匹配:


for (PackageInfo info : infolsit) { 
       if (info == null) { 
             continue; 
       } 
       data = getClassNameByDex(context,info.packageName);
       if(data == null){ 
            Log.d(TAG,"getAdFlagForLocalApp() 類名解析出錯(cuò)"+info.packageName); 
            continue; 
       } 
       sgPgmap = new HashMap<String, String>(); 
       for (String clsname : data) { 
            for (ADSInfo adinfo : flaglist) { 
                   String flag = adinfo.getAdFlag(); //廣告樣本庫(kù)的某一標(biāo)識(shí)  
                   String adpg = adinfo.getAdName(); //廣告樣本庫(kù)的某一包名 
                   if (clsname.contains(adpg)) { //匹配類名與廣告特征庫(kù)里的匹配符,看是否包含關(guān)系 
                       sgPgmap.put(flag,info.packageName); 
                   } 
            } 
       } 
       if(sgPgmap.size() > 0){ //AdsPgInfo 一個(gè)對(duì)應(yīng)應(yīng)用里包含了多少個(gè)標(biāo)識(shí) 
             adspginfo = new AdsPgInfo(info.packageName, sgPgmap);      
             pglist.add(adspginfo); 
       } 
}

ps: 在匹配時(shí),有一個(gè)很注意的點(diǎn),有時(shí)候單單類名匹配不準(zhǔn),或者會(huì)漏掉某些廣告,所以應(yīng)該加上包名,再去匹配特征庫(kù)里的匹配符,這樣才能百無一漏。
在此舉例一個(gè)指智廣告的特征(特征顯示形式可自定義,只要符合自己的解析策略即可):

ads.banner.zhidian#指智廣告#com/adzhidian/#ad.zhidian3g.cn

ads.banner.zhidian 為該類型廣告標(biāo)識(shí),主要是為了匹配時(shí)應(yīng)用對(duì)應(yīng)標(biāo)識(shí)的簡(jiǎn)潔性,不用直接跟著一群特征到處跑。。
指智廣告 該廣告名稱
com/adzhidian/ 該廣告用來匹配應(yīng)用中類名的匹配符,當(dāng)應(yīng)用中某一(包名+類名)包含該匹配符時(shí),說明了該應(yīng)用包含該廣告
ad.zhidian3g.cn 需要傳給系統(tǒng)的一個(gè)規(guī)則特征。

匹配出所有應(yīng)用的所屬規(guī)則特征后,接下來需要傳給系統(tǒng)了,系統(tǒng)將滿足需求的幾個(gè)接口提供出來。這邊涉及到修改系統(tǒng)層代碼,我就主要講下實(shí)現(xiàn)思路,會(huì)貼出關(guān)鍵的幾個(gè)代碼。 實(shí)現(xiàn)思路:系統(tǒng)根據(jù)應(yīng)用層傳入的應(yīng)用包名以及規(guī)則,將其緩存,在webview或http處請(qǐng)求時(shí),對(duì)其進(jìn)行判斷處理。
添加某應(yīng)用規(guī)則接

/** * add Adblock url of package pkgName */ 
private boolean addAdblockUrlInner(String pkgName, String url) {
       synchronized (mAdblockEntries) { 
              HashMap<String, UrlEntry> pkgEntry = mAdblockEntries.get(pkgName); 
              if (pkgEntry == null) { 
                    pkgEntry = new HashMap<String, UrlEntry>(); 
                    if (pkgEntry == null) { 
                         Slog.e(TAG, "addAdblockUrl():new HashMap<String, UrlEntry>() fail!");
                         return false; 
                    } 
                    mAdblockEntries.put(pkgName, pkgEntry); 
               } 
               UrlEntry entry = pkgEntry.get(url); 
               if (entry == null) { 
                    pkgEntry.put(url, new UrlEntry(0, false)); 
               } else { 
                    entry.deleted = false; 
               } 
          } 
          return true;
  }

==============================
WebView類postUrl處判斷處理

/** 
 * Loads the given URL. * * @param url the URL of the resource to load 
 */
public void loadUrl(String url) { 
       checkThread(); 
       if (!isAddressable(url)) { 
              return; 
       } 
       if (DebugFlags.TRACE_API) 
              Log.d(LOGTAG, "loadUrl=" + url); 
       if(!isChromium && url.startsWith("file://")){ 
              Log.e("WebView.java", "loadurl setLocalSWFMode"); 
       mProvider.setLocalSWFMode(); 
 } 
/** 
 * Returns true if the url is not included by adblock service 
 */ 
private boolean isAddressable(String url) { 
     boolean addressable = true; 
     AdblockManager adblockManager = AdblockManager.getInstance(); 
     if (adblockManager != null) { 
          String adblockUrl = adblockManager.containedAdblockUrl(ActivityThread.currentPackageName(), url); 
          if (adblockUrl != null) { 
                 addressable = false;        
                 adblockManager.increaseNumberOfTimes(ActivityThread.currentPackageName(), adblockUrl); 
          } 
      } 
      return addressable;
}

由于系統(tǒng)代碼這部分的改動(dòng)并非是我改的,更深細(xì)節(jié)處的理論就不清楚了。 應(yīng)用層的廣告特征庫(kù)為了可以持續(xù)更新,建議可以做成網(wǎng)絡(luò)更新方式。 據(jù)此,廣告攔截功能實(shí)現(xiàn)就完成了,可能會(huì)有瑕疵,不過持續(xù)優(yōu)化中。

如果覺得此文不錯(cuò),麻煩幫我點(diǎn)下“喜歡”。么么噠!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 鏈接:http://www.itdecent.cn/p/fd61e8f4049e 一、簡(jiǎn)介 這部分主要介紹下 W...
    柒黍閱讀 1,970評(píng)論 0 4
  • 這篇博客主要來介紹 WebView 的相關(guān)使用方法,常見的幾個(gè)漏洞,開發(fā)中可能遇到的坑和最后解決相應(yīng)漏洞的源碼,以...
    Shawn_Dut閱讀 7,533評(píng)論 3 55
  • AndroidWebView 一、簡(jiǎn)介 WebView在Android平臺(tái)上是一個(gè)特殊的View, 基于webki...
    斌林誠(chéng)上閱讀 2,013評(píng)論 0 5
  • 測(cè)試APP權(quán)限overview靜態(tài)分析Android 權(quán)限定制權(quán)限動(dòng)態(tài)分析測(cè)試自定義url靜態(tài)分析動(dòng)態(tài)分析測(cè)試通過...
    Ireliaaa閱讀 998評(píng)論 0 1
  • 郭老板聞言立刻極為熱情的說,“杜老板您說多少就是多少,我絕不還口!” “您太抬舉我了……還是看看這梳子和我的朋友有...
    花妖兒閱讀 370評(píng)論 0 1

友情鏈接更多精彩內(nèi)容