lzs233 发表于 2017-8-5 11:54

记录第一次尝试去广告的过程

本帖最后由 lzs233 于 2017-8-5 12:16 编辑

本人是安卓逆向初学者,以前有断断续续的反编译过几次这个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)

lzs233 发表于 2017-8-7 16:33

litianping 发表于 2017-8-7 15:05
楼主说一下,最后是怎么样去除签名验证的

其实就是通过修改so文件,让native方法直接返回正确的签名内容(要检查签名的话,无论怎么加密始终都会去调用安卓的api去获取当前应用的签名)

www1678 发表于 2017-8-5 12:02

厉害了{:301_1003:}。。。

fq645122 发表于 2017-8-5 12:04

这个过程痛苦而漫长

妲己再美终是妃 发表于 2017-8-5 12:10

过程十分痛苦。
各种验证,十分头疼。

hn0371 发表于 2017-8-5 12:14

感谢分享经验。

步川伊芙 发表于 2017-8-5 12:21

感谢分享……………………

GreyLi 发表于 2017-8-5 14:03

谢谢分享

sleepyhacker 发表于 2017-8-5 14:47

强。第一次挺成功的啊。我要要这样的第一次。

轻描淡写9714 发表于 2017-8-5 16:04

谢谢分享

liwenqiang111 发表于 2017-8-5 16:15

大神360加固的怎么反编译
页: [1] 2 3 4
查看完整版本: 记录第一次尝试去广告的过程