前年在的上一家公司纯露,制造的機器里應用裝有不良廣告,嚴重影響了兒童客戶使用者的思想健康代芜,導致被人投訴埠褪。于是乎,就有了想研發(fā)一款類似于360廣告屏蔽的應用的念頭。嗯钞速,事情就是這樣贷掖,現(xiàn)在切入主題。
目前市場上有很多安全軟件玉工,它們攔截第三方應用廣告的方式都不一樣羽资,比如說有 以so 注入方式來攔截彈出廣告。
現(xiàn)在我們來看下這種方式的詳細情況:
要做到攔截遵班,首先我們得知道廣告是怎么出來的屠升,原來第三方應用大部分是以加入廣告jar形式加入廣告插件,然后在AndroidManifest中聲明廣告service或者在程序中執(zhí)行廣告Api狭郑,廣告插件再通過Http請求去加載廣告腹暖。在java中,有四種訪問網(wǎng)絡的接口翰萨,如apache的http庫(如下介紹)脏答,這幾種方式首先都會通過getaddrinfo函數(shù)獲取域名地址,然后通過connect函數(shù)連接到服務器讀取廣告信息亩鬼。
- 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)頁玷过。
- 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)絡的動作辛蚊。
- 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)絡。
- 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)絡初澎。
然后再來說說動態(tài)庫注入,具體什么是動態(tài)庫注入虑凛,以及如何注入碑宴,網(wǎng)上有很多文章,這里就不介紹卧檐。動態(tài)庫注入攔截呢墓懂,主要是攔截getaddrinfo,根據(jù)條件返回錯誤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)絡霉囚。攔截網(wǎng)絡請求,達到攔截作用匕积。不過需要注意一點就是攔截之前要確定你所攔截的動態(tài)庫是否是你需要攔截的庫盈罐?例如A程序調(diào)用了動態(tài)庫BO和CO榜跌,而BO和CO都調(diào)用了connect函數(shù),此時需要攔截BO的請求盅粪,需要注入到BO動態(tài)庫并修改GOT表钓葫,而不是注入到CO中。
攔截HTTP方式廣告在多數(shù)廣告包中票顾,應用程序首先會通過apache的http庫或JDK中的http方法先將廣告數(shù)據(jù)下載過來础浮,然后通過WebView顯示。這種方式通過注入攔截進程的/system/lib/libjavacore.so可實現(xiàn)廣告地址攔截奠骄。
攔截WebView方式廣告廣告插件也可以直接通過WebView加載URL豆同,通過分析WebView加載流程可知它的網(wǎng)絡處理過程均交給libchromium_net庫來完成。因此通過注入libjavacore.so是無法實現(xiàn)攔截含鳞,而是需要注入到/system/lib/libchromium_net.so影锈。
通過這種方式已經(jīng)完全能夠攔截掉第三方APP廣告,但存在一些問題:
1.廣告商可以通過JNI方式調(diào)用系統(tǒng)getaddrinfo與connect實現(xiàn)自己的解析與連接過程的動態(tài)庫蝉绷,從而跳過libjavacore.so導致攔截無效鸭廷。 2.攔截WebView方式廣告雖然能夠不顯示廣告,但通常仍然會有浮動框顯示”網(wǎng)頁無法打開”熔吗,從而影響美觀辆床。 3.最重要的是我們機器是沒有root權限的!磁滚!
第三個問題直接導致了放棄了這種注入做法佛吓。 來來去去一段時間后,目前是采用android 系統(tǒng)本地掃描第三方應用廣告形式垂攘。具體怎么做维雇,請往下看!
如果對這種方式不了解的話晒他,建議先看下這篇 Android系統(tǒng)掃描帶廣告應用的做法吱型。
所以具體廣告插件掃描方案是匹配包名+類名形式的: 1.掃描本地所有第三方應用,列出一個應用中的所有類陨仅,將包名+類名方式與廣告插件特征庫進行匹配津滞; 2.將匹配出來的應用所帶廣告特征,通過系統(tǒng)提供傳入接口灼伤,將這些規(guī)則設置進去触徐。(當然,系統(tǒng)代碼是需要改的狐赡,做了一些處理撞鹉,主要是在上面介紹中的幾種訪問網(wǎng)絡方式上做了判斷處理)。
這種方案的關鍵在于廣告特征庫的完善,廣告插件特征庫收集越全鸟雏,掃描出來的廣告插件就可以越準確享郊。所幸,公司有幾位大神孝鹊,做過類似的事情炊琉,所以工作簡單了多些。
獲取第三方應用:
/** * 查詢機器內(nèi)非本公司應用 */
public List<PackageInfo> getAllLocalInstalledApps() {
List<PackageInfo> apps = new ArrayList<PackageInfo>();
if(pManager == null){
return apps;
}
//獲取所有應用
List<PackageInfo> paklist = pManager.getInstalledPackages(0);
for (int i = 0; i < paklist.size(); i++) {
PackageInfo pak = (PackageInfo) paklist.get(i);
//屏蔽掉公司內(nèi)部應用
//...
//判斷是否為非系統(tǒng)預裝的應用程序
if ((pak.applicationInfo.flags & pak.applicationInfo.FLAG_SYSTEM) <= 0) {
// customs applications apps.add(pak);
}
}
return apps;
}
============================
獲取某個應用的廣告特征:
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;
// 獲得某個程序的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;
}
=======================================
將應用中的所有類名與特征庫進行匹配:
for (PackageInfo info : infolsit) {
if (info == null) {
continue;
}
data = getClassNameByDex(context,info.packageName);
if(data == null){
Log.d(TAG,"getAdFlagForLocalApp() 類名解析出錯"+info.packageName);
continue;
}
sgPgmap = new HashMap<String, String>();
for (String clsname : data) {
for (ADSInfo adinfo : flaglist) {
String flag = adinfo.getAdFlag(); //廣告樣本庫的某一標識
String adpg = adinfo.getAdName(); //廣告樣本庫的某一包名
if (clsname.contains(adpg)) { //匹配類名與廣告特征庫里的匹配符又活,看是否包含關系
sgPgmap.put(flag,info.packageName);
}
}
}
if(sgPgmap.size() > 0){ //AdsPgInfo 一個對應應用里包含了多少個標識
adspginfo = new AdsPgInfo(info.packageName, sgPgmap);
pglist.add(adspginfo);
}
}
ps: 在匹配時苔咪,有一個很注意的點,有時候單單類名匹配不準柳骄,或者會漏掉某些廣告悼泌,所以應該加上包名,再去匹配特征庫里的匹配符夹界,這樣才能百無一漏馆里。
在此舉例一個指智廣告的特征(特征顯示形式可自定義,只要符合自己的解析策略即可):
ads.banner.zhidian#指智廣告#com/adzhidian/#ad.zhidian3g.cn
ads.banner.zhidian 為該類型廣告標識可柿,主要是為了匹配時應用對應標識的簡潔性鸠踪,不用直接跟著一群特征到處跑。复斥。
指智廣告 該廣告名稱
com/adzhidian/ 該廣告用來匹配應用中類名的匹配符营密,當應用中某一(包名+類名)包含該匹配符時,說明了該應用包含該廣告
ad.zhidian3g.cn 需要傳給系統(tǒng)的一個規(guī)則特征目锭。
匹配出所有應用的所屬規(guī)則特征后评汰,接下來需要傳給系統(tǒng)了,系統(tǒng)將滿足需求的幾個接口提供出來痢虹。這邊涉及到修改系統(tǒng)層代碼被去,我就主要講下實現(xiàn)思路,會貼出關鍵的幾個代碼奖唯。 實現(xiàn)思路:系統(tǒng)根據(jù)應用層傳入的應用包名以及規(guī)則惨缆,將其緩存,在webview或http處請求時丰捷,對其進行判斷處理坯墨。
添加某應用規(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)代碼這部分的改動并非是我改的,更深細節(jié)處的理論就不清楚了病往。 應用層的廣告特征庫為了可以持續(xù)更新捣染,建議可以做成網(wǎng)絡更新方式。 據(jù)此停巷,廣告攔截功能實現(xiàn)就完成了耍攘,可能會有瑕疵累贤,不過持續(xù)優(yōu)化中。
如果覺得此文不錯少漆,麻煩幫我點下“喜歡”。么么噠硼被!