前言
最近UC浏览器中文版出了一个快速搜索的功能, 在使用其他app的时候, 如果复制了一些内容, 屏幕顶部会弹一个窗口, 提示一些操作, 点击后跳转到UC, 显示这个悬浮窗不需要申请android.permission.SYSTEM_ALERT_WINDOW
权限.
如下图, 截图是在使用Chrome时截的, 但是屏幕顶部却有UC的view浮在屏幕上. 我使用的是小米, 我并没有给UC授悬浮窗权限, 所以我看到这个悬浮窗时是很震惊的.
悬浮窗原理
做过悬浮窗功能的人都知道, 要想显示悬浮窗, 要有一个服务运行在后台, 通过getSystemService(Context.WINDOW_SERVICE)
拿到WindowManager
, 然后向其中addView
, addView
第二个参数是一个WindowManager.LayoutParams
, WindowManager.LayoutParams
中有一个成员type
, 有各种值, 一般设置成TYPE_PHONE
就可以悬浮在很多view的上方了, 但是调用这个方法需要申请android.permission.SYSTEM_ALERT_WINDOW
权限, 在很多机型上, 这个权限的名字叫悬浮窗, 比如小米手机上默认是禁用这个权限的, 有些恶意app会用这个权限弹广告, 而且很难追查是哪个应用弹的. 如果这个权限被禁用, 那么结果就是悬浮窗无法展示, 比如有道词典的复制查词功能, 在小米手机上经常没用, 其实是用户没有授权, 而且应用也没有引导用户给它打开授权.
现在UC能突破这个限制, 我很好奇它是怎么做到的.
研究实现
Android开发有点蛋疼的地方就是太容易被反编译, 但有时这也成为我们研究别人app的一种手段.
反编译
使用apktool可以很轻松的反编译UC.
找代码
逆向别人的app, 比较关键的地方是怎么找代码, 因为代码基本上都是混淆的, 直接看肯定是看不懂的, 只能去找, 突破口一般在字符资源上, 比如我们看到上图中的快速搜索是UC的字符, 那么我们到res/values/strings.xml
去找快速搜索
, 就可以找到下面的内容
1 |
<string name="dark_search_banner_search">快速搜索</string> |
这里我们拿到了快速搜索
对应的名字dark_search_banner_search
, Android在编译时会给每个资源分配一个id, 我们grep一下这个字符资源的名字就能知道id是多少, 一般在R.java
, res/values/public.xml
中有定义, 我直接到public.xml中找到了它的id
1 |
<public type="string" name="dark_search_banner_search" id="0x7f070049" /> |
有了字符资源的id 0x7f070049
, 我们再在代码里面grep一下这个id, 就能知道哪几个文件使用了这个字符资源.
之所以这么确定是在代码里, 是因为UC在我们复制的内容不同时, 悬浮窗标题会不一样, 一定是在代码里控制的, 结果如下
1 |
./com/uc/browser/b/f.smali |
结果可能和大家不一样, 但是一定会找到一个被混淆的smali文件
看代码
这一部应该是最恶心的. smali代码和java代码的关系, 就像汇编代码和C++代码, 但是smali比汇编代码要容易理解的多, 不然也不会有那么多公司故意将代码写在C++层了.
虽然代码都被混淆了, 而且以我们不熟悉的方式出现, 但我们可以根据一些蛛丝马迹来判断代码的执行, 比如Framework的类和API是不能被混淆的, 这也是我们能看懂smali的原因之一, 我们可以结合这些面包屑来还原整个app代码, 当然这需要我们对smali很熟悉, 如果不熟悉smali, 至少要对Android的API熟悉. 因为有时实在看不懂, 我们要靠猜来还原一段代码的逻辑.
首先在代码里面找到0x7f070049
, 发现了如下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
(省略) const v3, 0x7f070049 invoke-virtual {v1, v3}, Landroid/content/res/Resources;->getString(I)Ljava/lang/String; move-result-object v1 iput-object v1, v0, Lcom/uc/browser/b/a;->dpC:Ljava/lang/String; :cond_9 (省略) invoke-virtual {v0, v1}, Lcom/uc/browser/b/a;->o(Landroid/graphics/drawable/Drawable;)V :try_end_2 .catch Ljava/lang/Exception; {:try_start_2 .. :try_end_2} :catch_0 goto/16 :goto_0 (省略) |
这是0x7f070049
出现之后的一部分代码, 一路看下来, 其实都是在取值赋值, 就拿0x7f070049
来说:
1 2 3 4 5 6 |
#使v3寄存器的值为0x7f070049 const v3, 0x7f070049 #v1是Resources实例, 调用它的getString方法, 方法的参数是v3中的值 invoke-virtual {v1, v3}, Landroid/content/res/Resources;->getString(I)Ljava/lang/String; #将结果存入v1寄存器 move-result-object v1 |
其实就是我们常用的getResources().getString
其实如果一直这么看下去, 会发现毫无头绪, 剩下的代码一直在干差不多的事情, 所以我只截取了这部分, 注意最后一行
1 |
goto/16 :goto_0 |
也就是说, 有可能代码转到goto_0
那儿去了, 那么看看goto_0
那里又写了些什么