吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 3367|回复: 31
收起左侧

[Android 原创] [广告]静态分析

[复制链接]
我与菜鸟 发表于 2021-3-27 01:36

一:前言

前几天碰到的一个APP,因为不太熟悉android开发所以走了不少的弯路,总觉得是在整理一团乱糟糟的毛线,东扯一截西扯一截,终理不清思绪,在知道其中的核心原理之后,又觉得很简单,之前想的看的那么多不过惘然,甚至都不值得记录下来,奈何费了老大劲分析还是决定有始有终的记录下来。个人觉得这个app应该是被别人破解过,其显示广告的逻辑没有了,应该是修改过。

二:前置

安卓应用代码和网页中javascript的互相调用

1:WebView是什么?

据安卓开发指南中对WebView描述,此类继承自View,算是对其的扩展,用于在安卓应用中进行网页展示,但是仅仅是网页并不包含浏览器中的导航栏和地址栏。

2:如何实现javascript调用android代码?

2.1:先看看开发者指南是如何说的
启用 JavaScript:setJavaScriptEnabled在webview中启用了javascript

    WebView myWebView = (WebView) findViewById(R.id.webview);
    WebSettings webSettings = myWebView.getSettings();
    webSettings.setJavaScriptEnabled(true);

将JavaScript代码绑定到Android代码:addJavascriptInterface()设置了调用接口,以至于js代码可以调用开发者提供的安卓代码

2.2:结合程序来看,开发者自定义了一个继承自WebView的AdView类,并通过post方式回调了如下代码进行设置。

# <ir.adad.client.b>
    public void run() {
        try {
            this.a.getSettings().setJavaScriptEnabled(true); // 在webview中启用javascript
            this.a.h = new r(this.a);
            this.a.addJavascriptInterface(this.a.h, "adad"); // 设置可供js调用的安卓接口,名称是adad(参数二),参数一是接口实例
            if (!(this.a instanceof w)) {
                this.a.a(false, 0, 0, true);
            }
            this.a.loadDataWithBaseURL(o.a().j(), o.a().i(), "text/html", "utf-8", null); //通过HTML字符串加载网址
            m.b("Assigned client to AdView");
        } catch (Exception e) {
            this.a.b = i.NotAssigned;
            this.a.e();
            this.a.j();
            e.printStackTrace();
            m.d("Could not assign downloaded client with error " + e);
        }
    }

3:如何实现android代码调用javascript?


上图中对要执行的javascript进行了拼接,最终其会调用loadUrl来执行

 public void run() {
        d.super.loadUrl(this.a.a); //this.a.a 就是拼接出来要执行的函数
    }

三:开始

1:获取广告逻辑

1.1:先拿到javascript代码
还记得上面加载网页的那个函数吗loadDataWithBaseURL,先看下原型和对应代码

# 原型
loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding, String historyUrl)
# 对应代码
 this.a.loadDataWithBaseURL(o.a().j(), o.a().i(), "text/html", "utf-8", null);

参数一中o.a().j()最终会调用如下代码:

    public String k() {
        String x = aa.x() == null ? "https://js.adad.ir/adad-client/js/" : aa.x();
        if (a) {
            x = c(x);
        }
        try {
            return x + URLEncoder.encode(l(), "utf-8");
        } catch (UnsupportedEncodingException e2) {
            e2.printStackTrace();
            return aa.x();
        }
    }

    public String l() {
        return (((((((((((((((((((BuildConfig.FLAVOR + "?device=" + v.a("DEVICE")) + "&androidid=" + v.a("ANDROID_ID")) + "&token=" + v.a("AdadToken", BuildConfig.FLAVOR, false)) + "&network=" + v.a("NETWORK_CLASS")) + "&data=" + v.a("DATA_CONNECTIVITY")) + "&bazaarversion=" + v.a("BAZAAR_VERSION")) + "&l=" + ae.a().a("content://com.farsitel.bazaar/info/get_uid")) + "&car=" + v.a("NETWORK_OPERATOR")) + "&j=" + y.a().b("content://com.farsitel.bazaar/info/get_jaw")) + "&brand=" + v.a("BRAND")) + "&package=" + v.a("PACKAGE_NAME")) + "&test=" + v.a("AdadTestMode", BuildConfig.FLAVOR, false)) + "&version=" + v.a("VERSION_CODE")) + "&lang=" + v.a("LANGUAGE")) + "&android=" + v.a("ANDROID")) + "&adadversion=" + v.a("ADAD_VERSION")) + "&uid=" + ae.a().b("content://com.farsitel.bazaar/info/get_uid")) + "&model=" + v.a("MODEL")) + "&library=" + v.a("LIBRARY_NAME")) + "&dpi=" + v.a("DPI");
    }
}

我们通过上面的url即可拿到HTML源。
打开之后可以看到满屏令人厌恶的命名方式,这里列入出几个比较重要的类名称,其实从名称我们大概可以猜个八九不离十,只不过代码相对庞大一些。

AdDelivery      //广告分发相关

BannerAd        //横幅式广告
InterstitialAd  //插页式广告 
VideoAd         //视频广告
RichAd          //富媒体

Master          //主人...
Slave           //奴隶...

1.2 AdDelivery内部逻辑
列举一下重要函数:

# 下载
AdDelivery.stopBannerLoop
AdDelivery.startBannerLoop
AdDelivery.getInterstitialAd
AdDelivery.getVideoAd
AdDelivery.getAd
AdDelivery.downloadMedia
AdDelivery.getAdFinished
# 分发
AdDelivery.distributeAd
AdDelivery.giveAdToSlaves

大致捋一下内部逻辑:

简述一下逻辑就是根据不同广告类型发送请求并返回一个JSON对象ad

ad{
    "adtype":  
    "img_url":   
    "base64":  
    "video":  
    "videoFileName":  
    "rich_ad":  
    "richImage":  
    "logo":  
    "richLogo":  
    "adId":  
    "secret":  
    }

这里ad['base64']最终被赋值为图片的base64编码

AdDelivery.downloadMedia = function (ad, onFail) {
    log("(AdDelivery.downloadMedia) Downloading Media for " + ad.adType);
    switch (ad['adType']) {
        case "text":
        case "banner":
        case "interstitial":
            if (ad['img_url'] != undefined) {
                AdDelivery.getBinary(
                    Utils.generateAdadUrl(ad['img_url']),            //这里会根据ad['img_url']的值下载图片
                    function (result) {
                        ad['base64'] = AdDelivery.base64Encode(result); //这里对请求回来的图片进行base64编码并对ad['base64']进行赋值。
                        AdDelivery.getAdFinished(ad);
                    }, function () {
                        onFail("Couldn't Download Ad Media");
                        logEx("user:error", "Network Error: Check your internet connection please.");
                    });
            } else {
                // nothing to download for text ads without logo
                AdDelivery.getAdFinished(ad);
            }

            break;

        case "video":

1.3 回溯
我们以获取插页式广告为例,先查找调用AdDelivery.getInterstitialAd的位置,发现除了自身只有一个地方对其进行了调用。

Master.prepareInterstitial = function () {
    if (Master.state.interstitialState == "downloading") {
        logEx("user:error", "Please don't call prepareInterstitial rapidly!");
    }
    else if (Master.state.interstitialState == "ready") {
        logEx("user:error", "Your interstitial ad is ready. you should call showInterstitial()"); //这里的showInterstitial会在显示时候用到
        AdDelivery.getAdFinished(Master.state.currentInterstitialAd);
    }
    else if (Master.state.interstitialState == "showing") {
        logEx("user:error", "Your interstitial ad is being shown. Wait for it to finish.");
    }
    else {
        Master.state.interstitialState = "downloading";  // 第一次调用应该是走这里,设置状态为下载中

        if (HostCompat.versionCompare("3.2") >= 0)
            adad.createInterstitial();

        log("Starting interstitial loop ");
        AdDelivery.getInterstitialAd();                 //调用位置
    }
};

下面就是看哪里调用Master.prepareInterstitial,会发现并没有js代码对其进行了调用,那其实真正的调用实在App代码中。

# ir.adad.client.aa
    static void a(InterstitialAdListener interstitialAdListener) {
        c.a("Master.prepareInterstitial()");  //这里最后调用loadUrl执行prepareInterstitial,就是前置说的调用机制在起作用
        b = interstitialAdListener;
        q();
    }

到这里我们只需要往上追踪ir.adad.client.aa.a的调用堆栈即可。
最终会发现在类com.doaye.imanaminollah.MainActivity.onCreate中被调用,即有启动这个活动的时候就会启动获取插页广告程序。

# com.doaye.imanaminollah.Splash (入口类)
  public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_splash);
        new Handler().postDelayed(new Runnable() {
            /* class com.doaye.imanaminollah.Splash.AnonymousClass1 */

            public void run() {
                Splash.this.startActivity(new Intent(Splash.this, MainActivity.class)); //这里启动了MainActivity
                Splash.this.finish();
            }
        }, (long) SPLASH_TIME_OUT);
    }

到这里大概的广告是如何获取的逻辑就大致捋顺了。

2:展示广告逻辑

紧接着上述最后的AdDelivery.distributeAd分发广告继续跟踪下去,仍然是以插页式广告为例。
在开始之前需要先按照自己的理解解释一下Master类中的Master.getSlaves,因为Master.getInterstitialId()调用了他。

Master.getSlaves = function (activeOnly) {
    /*
     Result structure:
     {
         count : <int>,
         slaves : {
             id : <long>,
             uiPath : <string>,
             adType : <banner | interstitial>
             clientState:<string>
         }
     }
     */

    var res = activeOnly ?
        JSON.parse(adad.getActiveSlaves()) :    //这里调用了app提供的接口,获取活动的slaves(个人认为是活动的webview视图,每个webview都有自己的id以及对应的类型)
        JSON.parse(adad.getAvailableSlaves());
    var result = {};
    result.slaves = [];

    for (var i = 0; i < res.count; ++i)
        if (res.slaves[i].clientState == "Started") 
            result.slaves.push(res.slaves[i]);

    result.count = result.slaves.length;
    return result;
};
# AdDelivery.distributeAd
...
else if (ad.adType == "interstitial" && AdDelivery.canModifyInterstitial()) {
        Master.state.currentInterstitialAd = ad;
        Master.state.interstitialState = "ready";       //设置插页式广告准备状态
        var interstitialId = Master.getInterstitialId(); //获取类型是”Interstitial“活动webview
        if(interstitialId.count > 0)
            AdDelivery.giveAdToSlaves(Master.getInterstitialId(), ad);
...

紧跟着来看AdDelivery.giveAdToSlaves

AdDelivery.giveAdToSlaves = function (slavesToSend, ad) {
    if (ad == undefined) {
        log("(AdDelivery.giveAdToSlaves) No ad yet. AdDelivery will distribute the ad automatically later");
        AdDelivery.startBannerLoop();
    } else {
        var order = Master.createOrder(Constants.CMD_ASSIGN_AD); //这里奴隶主下达了派发广告的命令
        order.ad = Utils.utf8_to_b64(JSON.stringify(ad));        //参数赋值
        Master.orderSelectedSlaves(slavesToSend, order);
    }
};
# order构成,命令必须是有的,紧跟着变量
*
Order is in this format:
order = {
    command : <string>, (This is mandatory)
    param1 : <var>,
    param2 : <var>,
    ...
}
*/

接下来执行的顺序如下

Master.orderSelectedSlaves ---> (adad.orderSelectedSlaves) ---> 返回来执行Slave.takeOrder系列命令
Slave.takeOrder = function (orderStr) {
    var order = Utils.decodeIncoming(orderStr);

    if (order == undefined)
        log("(Slave.takerOrder) Order is undefined: " + orderStr);
    else if (order.command == undefined)
        log("(Slave.takeOrder) Order's recipient is not defined: " + orderStr);
    else if (order.command == Constants.CMD_ASSIGN_AD) //之前order的命令是CMD_ASSIGN_AD,所以走这里
        Slave.assignAd(order);
    else if (order.command == Constants.CMD_CALL_LISTENER_FAILED) {
        Slave.callListener("onAdFailedToLoad");
        Slave.resetDimensions();
    } else if(order.command == Constants.CMD_SEND_MESSAGE_TO_HOST)
        Slave.sendMessageToHost({"msg" : order.msg});
    else if (order.command == Constants.CMD_ASSIGN_STATUS)
        Slave.assignStatus(order);
    else
        log("(Slave.takeOrder) Order is not known: " + order.command);
};

紧接着看看Slave.assignAd

Slave.assignAd = function (order) {
    adad.onAdLoaded();
    currentAd = JSON.parse(Utils.b64_to_utf8(order.ad)); //这里currentAd很重要,后面显示广告参数全由他提供
    Slave.currentRequestId++;
};

到这里好像已经结束了,并没有在走下去的逻辑,这时候可能得重新看看是否有显示逻辑了。Master.showInterstitial在上述分析中碰到过,其log提示我们一旦插页式广告准备好了应该调用此函数进行显示,其实Master.showInterstitial本应该实在app代码中调用的,但是通过搜索,发现其最终并没有被调用,所以我怀疑这个APP可能是被修改过的,去掉了广告显示逻辑,但是并不影响我们往下查看,所以我们看看Master.showInterstitial调用了什么?

Master.showInterstitial ---> (adad.showInterstitial) ---> 返回来执行Slave.displayAd
Slave.displayAd = function (order) {
    try {
        Slave.updateHostView();

        if(Slave.isRich()) {
            RichAd.displayAd();

        } else {
            if (currentAd['img_url'] != undefined) {
                var image_format = currentAd['img_url'].split('.').pop();
                if (currentAd['contentType'] == 'text')
                    this.setImageData("ad_logo", image_format, currentAd['base64']);
                else if (currentAd['adType'] == 'interstitial')
                    this.setImageData("interstitial_ad_image", image_format, currentAd['base64']); //插页式广告
                else
                    this.setImageData("ad_image", image_format, currentAd['base64']);
            }
        ...
};

当看到上述代码,再看看这个是不是就了然了

四:总结

下载链接.txt

78 Bytes, 下载次数: 3, 下载积分: 吾爱币 -1 CB

免费评分

参与人数 12吾爱币 +14 热心值 +12 收起 理由
qtfreet00 + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
17315044449 + 1 + 1 热心回复!
okay1024 + 1 + 1 收藏=学习
nmy124 + 1 + 1 我很赞同!
24K叶生 + 1 谢谢@Thanks!
poiiop + 1 谢谢@Thanks!
罗焱煜 + 1 热心回复!
无敌小车 + 1 + 1 热心回复!
夏末随风 + 1 我很赞同!
ch1029 + 1 + 1 我很赞同!
pdcba + 1 + 1 我很赞同!
混曌大魔王 + 1 + 1 写的很棒!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| 我与菜鸟 发表于 2021-3-27 19:17
vosdbk 发表于 2021-3-27 17:27
这广告有点复杂了, 一般的APP不会那么闹腾吧

不知道哎,主要是了解调用结构,具体逻辑可以再分析
 楼主| 我与菜鸟 发表于 2021-3-27 10:25
www2455937517 发表于 2021-3-27 09:59
刚学android开发,看了感觉挺简单的,O(∩_∩)O哈哈~

是的,论会正向开发的重要性
深入研究学习 发表于 2021-3-27 03:39
加奈绘 发表于 2021-3-27 06:25
感谢大佬分享,受益良多
吃兔子的肉 发表于 2021-3-27 07:51
是个狠人,学习了学习了
光咣咣咣丶 发表于 2021-3-27 08:29

感谢大佬分享,受益良多
头像被屏蔽
First丶云心 发表于 2021-3-27 08:37
提示: 作者被禁止或删除 内容自动屏蔽
smmhlove 发表于 2021-3-27 08:47
看了很多,还是看不懂
whngomj 发表于 2021-3-27 09:24
谢谢分享,学习学习
pdcba 发表于 2021-3-27 09:33

谢谢分享,学习学习
www2455937517 发表于 2021-3-27 09:59
刚学android开发,看了感觉挺简单的,O(∩_∩)O哈哈~
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-3-28 23:20

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表