逆向实战之去广告的简单练习

来源:互联网 发布:光子脱毛 知乎 编辑:程序博客网 时间:2024/05/17 23:28

转载自:http://www.52pojie.cn/forum.php?mod=viewthread&tid=632392&extra=page%3D1%26filter%3Dauthor%26orderby%3Ddateline

本人是安卓逆向初学者,以前有断断续续的反编译过几次这个app,主要也只是为了当做学习逆向的第一步,而且也没加壳没混淆,就想试试把它的广告去除,但是因为工作关系经常断断续续的,到头来又忘了自己做过什么,每次都会从头来,特此记录一下完整的逆向去广告过程,也方便自己什么时候忙忘了可以很快重新把这些基础捡起来。 

由于这是一个很良心的app,尽管满屏的广告烦人影响体验,但是作者依靠微薄的广告收入来支撑服务器运营也无可厚非,这个记录我会把一些关键包名或者app名以及任何可能暴露这个app是什么的部分打码,只交流技术和过程,去除广告只是我正好有在使用这个app就顺手拿来练习,本人不希望让任何一个做良心app的开发者寒心。 
首先,我在之前的几次研究过程中,直接反编译再次回编运行会闪退,推断出app做了签名验证,回编闪退这一步不再重现记录,现在重新记录第一步需要去除app的签名验证。 
通过ApkIDE反编译apk,查看清单文件,如果反编译失败,可以尝试更新目录下的apktool。
 
通过清单文件可以发现app打开的第一个启动页面是LoginActivity,我个人比较喜欢从第一个入口开始读代码。
根据com.xxxxxx.xxxx.activities.LoginActivity按照包名找到LoginActivity。
 
smali阅读起来太麻烦,用IDE的功能直接打开Java源码。
 
这样一看就几乎跟自己用Java开发安卓app一样了。
接下来回归工作,要摘除这个app的签名验证,而LoginActivity又是app的第一个入口,所以猜测签名验证的代码或许放在了这里。安卓app里的每个Activity都有自己的生命周期,这里只需要关注onCreate()、onStart()、onResume(),因为执行完这三个回调方法后,Activity就正式启动,界面也会显示出来,但是在回编运行后在界面显示出来之前程序就崩溃退出了,所以开始查看这里是否有重写这三个回调方法。发现只重写了onCreate()方法
 
由于验证签名的目的是要让程序闪退或关闭,就是在判断签名不正确的情况下,调用finish()销毁页面或者是Systeam.exit(0),所以这里的代码可以看出完全没有和这方面相关的内容。
惊了!连第一个页面都没有判断签名,那为什么会闪退?
仔细看上面第一张Java源码的截图就会发现
 
LoginActivity继承了BaseActivity,其实一个比较合格的app,在开发过程中都会为很多Activity先写好一个基类(通常命名也就是BaseActivity),让大部分有相同逻辑的Activity继承它,可以减少很多重复代码冗余。(不得不说这个app比我公司的app代码要规范多了,公司的app历史遗留问题太多,没眼看)开始阅读BaseActivity的源码关键部分
 
 
发现在这里重写了onCreate()和onResume(),但是光这样看调用的方法名好像也没看出来哪里做了签名验证,只能一个个方法点进去看看。
 
这个方法真的就如方法名的字面意思,就是设置主题,忽略。
 
版本号判断和权限申请,忽略。
 
这个UUID的类好像反编译失败了,看不到,但是很明显也不是签名验证,忽略。
 
removeCache()这个方法发现有个判断,而且还调用了finish()销毁页面。
 
再进去看getStringCom()里貌似用到了lib库的方法,处理native我目前不太懂,而且反编译出来的if和for的代码和常规的Java不太一样,只能回头看removeCache()。
结合smail的代码,我试着按照开发者的思路还原了removeCache()的代码。
public static boolean removeCache(Context paramContext){
    if(!MyApplication.getInstance().getStringCom()){
        ((Activity)paramContext).finish();
        return false;
      }
    return true;
    } 
或者 
    public static boolean removeCache(Context paramContext){
        if(MyApplication.getInstance().getStringCom()){
            return true;
        }
        ((Activity)paramContext).finish();
        return false;
    }


结果都是一样的,但是搞不清最原始的代码是哪个。
这样能满足签名错误的情况下保证销毁Activity,但是这写法感觉怪怪的(false大概永远不会被return),也有可能我哪里想的不对,希望有懂的大神解释一下。
继续,既然不懂native,那只能尝试从这里下手了,回到ApkIDE,定位Tools.smail,搜索方法名。
 
接下来很简单,我直接用了一种比较暴力的方法
 
这样这个方法就永远只会返回true了,再次重新编译安装打开app,正常进入了LoginActivity。
但是输入完账号密码登录的时候app又一次崩溃。
回到LoginActivity,这是一个账号登录的界面,看代码没有什么跳转到下一个页面的逻辑,这里可以在onCreate()里看到加载了一个Fragment。
 
进入LoginFragment之后代码一大堆,只找自己想要的登录成功后的代码,这里从布局文件入手会比较方便,只要找到登录按钮的id就行。
 
通过onCreateView()知道这个Fragment加载的布局id为2130968660,用这个id去工程下的R文件寻找对应的布局文件即可。
 
 
所有的布局文件和控件都可以通过这种方式在R文件里拿到对应的id,这里拿到了fragment_login
回到ApkIDE,全局搜索fragment_login或者熟悉安卓的都知道布局一般放在res/layout/里,直接找到对应的xml文件。
 
两个EditText对应输入邮箱账号和密码,还有个Button的id看起来就是登录的按钮,把button_login_login_button复制下来,但是在LoginFragment里的id都是数字(在自己开发的时候,R文件里布局控件对应的都是十六进制的数字,逆向出来的好像是十进制),再复制这个id去R文件里查找对应的数字id。
 
2131689885复制下来,去LoginFragment里查找。
 
 
这里就成功找到了点击登录按钮后的逻辑,查看callLogin()方法。
 
这里貌似完全看不出和签名相关导致app崩溃的内容,由于是点击登录后闪退的,所以推测有可能在封装的网络请求里添加了签名判断。
 
进入RestClient()里发现,还真的有。。。(截图太长,后面的截不上)
 
而且还放把签名放在了请求头里,这就尴尬了。
 
注意str1还有localObject1,签名还用到了UUID,而且str1和localObject1也加到了请求头
 后台肯定会对str1和localObject1以及str2进行匹配
 
 
点进去发现这方面果然又再次调用了native方法,完了,看来不得不去看看so文件了。定位到了这个方法,转换出来了貌似是C++
 
。。。。。。。。。完!全!看!不!懂!!!
因为C++就两年前随便看过一点入门,现在基本上也都忘光了。
有点懵,难道第一次逆向就这样华丽丽的失败了。。。
冷静了一下,决定先搞清楚具体的崩溃原因是什么,用真机安装了有问题的版本,登录,崩溃,捕获到如下日志:
 
看对应报错位置的代码
 
可以得出这样的推论,在传了错误的签名发起网络请求的时候,服务器依旧返回了200,但是没有返回对应的token,导致app这边报npe,是通过这种方式来让app崩溃的。抓了一会头皮,我突然想到,既然是把签名放在了请求头,我岂不是可以安装个正版的app,然后通过抓包获取正确的签名字符串来自己塞进请求头?打开Fiddler,确定手机和电脑在同一局域网后设置代{过}{滤}理连接到电脑上,发现各种抓包失败。
(已经不知道怎么解释了,就是失败,完全看不到请求头的内容)
换个方法,直接在手机上用开发者助手这个app来抓包(不是打广告。。。这真是一个免费好用的app),最开始我没有安装app里的CA证书,抓包失败,安装了之后不知道为啥就抓包成功了。。。(这方面懂的真的少,各位抱歉)
 
最终得到的str1为f1c04xxxxxxxxxxxxxxxxxxxxxx2921c4de,time为1501061966,为签名为7b30e181119f1e780xxxxxxxxxxxxxxxxxxxxxxdbb146907fa8为了不影响其他调用到签名的地方,我决定直接在getStringCon()修改返回值
 
照旧直接把方法删光,直接返回我要的内容。接下来把nonce里的str1还有time也换成和签名对应的内容
 
 
回编运行,登录弹出提示
 
卧槽!惊了!
感觉陷入了绝望。。。
到头来还是得去处理它的so文件。。。
没办法了,只能试试请外援了,找了个大神帮忙研究(此处省略一万字)


讨论的过程没来得及记录,在大神的帮助下,摘除签名验证成功,接下来就可以肆意玩弄这app了(笑
接下来太细节的就不记录了,主要记录去除广告。
到了MainActivity看一遍方法名,有两个很明显的方法。
 
Ads,一看就可以猜测是和广告相关的,点进PopupWebView看看。
 
这个app的广告就是弹出来然后必须看完多少秒才能关闭,可以看到最重要的逻辑就在这。
PopupWebView里的两个show()里面的逻辑完全删掉应该就OK了。
 
这样改了之后发现编译报错。。。
 
看意思是非抽象方法一定要有一个指令,这里我是有点懵逼的,因为在开发的时候方法里什么代码都不写也没问题,因为不知道怎么补充指令,网上也没查到什么相关信息,我决定自己去写个demo来看看空方法的smali是长什么样的。
 (这是我的demo
参照着改成了这样
 
简单粗暴。。。干干净净,回编测试,登录成功后首页的广告不再显示
再往后分析,回到MainActivity,搜索发现popupAdsShow()在页面内没调用,用IDE搜索发现
 
是在BaseFragment里调用了
 
这里可以看到开发者加载了一个网页地址来加载广告,记录了上一次显示广告的时间存到sp里,在时间内广告就不会再显示(突然想到了另一个更环保的去广告方式)。
记录到此结束,也真的是一波三折,因为是新手,所以可能某些地方比较蠢,但是我能用的会用的方法几乎用光了,也算是记录自己逆向的一个过程和思路,最不甘心的还是在so文件的处理上是别人帮我弄好的,望各位逆向大佬指教。 


用到的工具有:APKIDE 3.3.5少月版 Fiddler IDA-Pro 开发者助手(手机app,需要root)