流氓软件微信电话本案例研究



前天公司的同事发现了这款流氓应用。当分析出原理时,小伙伴们都震惊了。

事情是这样的,微信电话本是最近在内测的一款通讯录应用(据说是原QQ通讯录),它最厉害的一点就是可以接管系统的联系人应用。 当你点击系统联系人,短信或者拨号(以下简称CSP)时,将会启动微信通讯录,而不是原有的CSP应用

本文会详细解析这种偷梁换柱的拙劣手段。

相关资源

反编译APK

Apktool获取资源文件

1
apktool d phonebook_218_4.apk

dex2jar反编译

1
2
3
4
5
6
#重命名
mv phonebook_218_4.apk phonebook_218_4.zip
#unzip
unzip phonebook_218_4.zip
#dex2jar
dex2jar.sh classes.dex

最终获得的classes_dex2jar.jar文件需要使用JD-GUI查看。

源码分析

  1. 在应用中可以找到与绑定CSP相关的设置页面

通过“绑定系统拨号”这个string可以找到资源文件。

1
2
3
4
5
6
7
8
9
10
11
grep "绑定系统拨号" . -nrs
#找到该string的资源setting_bind_system_callbt

grep "setting_bind_system_callbt" . -nrs
#接着追setting_bind_system_callbt,可以看到这个string被某个控件引用了

grep "bind_system_callbt_switch" . -nrs
#然后通过控件resId,找到id值

grep "0x7f0c01fc" . -nrs
#就这样顺藤摸瓜追查到了com/tencent/pb/setting/controller/SettingBindActivity

  1. 使用JD-GUI查看SettingBindActivity.java

不难看出点击button时会调用

1
2
3
4
5
6
public void a()
  {
    this.c.toggle();
    io.a().g().b("BIND_SYSTEM_SMS", this.c.isChecked());
    f();
  }

io的方法a() 是一个Singleton 暂且忽略

1
2
3
4
5
6
7
8
9
10
11
12
13
 public static io a()
  {
    if (a == null);
    try
    {
      if (a == null)
        a = new io();
      return a;
    }
    finally
    {
    }
  }

然后注意到所有的回掉最终都会调用f方法,f方法发出了一个 action为 “bind_system_icon_intent” 的广播。

1
2
3
4
5
6
7
8
9
10
11
 private void f()
  {
    sendBroadcast(new Intent("bind_system_icon_intent"));
  }

  public void a()
  {
    this.c.toggle();
    io.a().g().b("BIND_SYSTEM_SMS", this.c.isChecked());
    f();
  }

在AndroidManifest中不难找到其对应的Receiver

1
2
3
4
5
6
<receiver android:name=".remote.ScreenStateReceiver">
    <intent-filter>
        <action android:name="bind_system_icon_intent" />
        <action android:name="unbind_system_icon_intent" />
    </intent-filter>
</receiver>

回头能追到这样一段代码

1
2
3
4
5
6
7
while (!"bind_system_icon_intent".equals(paramIntent.getAction()));
    if ((bool1) || (bool2) || (bool3))
    {
      ail.a(paramContext, bool1, bool2, bool3);
      return;
    }
    ail.a();

显然绑定的逻辑应该在ail中,从ail参数和构造函数就可以看到一些有意思的东西

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class ail extends View
{
  private static ail a = null;
  private static WindowManager b = null;
  private static ActivityManager c = null;
  private static aim d = null;
  private static Context e;
  private HashSet f = new HashSet();
  private HashSet g = new HashSet();
  private HashSet h = new HashSet();
  private HashSet i = new HashSet();
  private boolean j = true;
  private boolean k = true;
  private boolean l = true;
  private boolean m = false;

  private ail(Context paramContext)
  {
    super(paramContext);
    b = (WindowManager)paramContext.getSystemService("window");
    c = (ActivityManager)paramContext.getSystemService("activity");
    e = paramContext;
    e();
    ScreenStateReceiver localScreenStateReceiver = new ScreenStateReceiver();
    localScreenStateReceiver.getClass();
    d = new aim(localScreenStateReceiver, " ");
    d.start();
  }
...
}

ail继承自View,其中有几个值得关注的几个成员,WindowManager b,ActivityManager c,aim d。 分析ail.a()方法,核心代码创建了一个 透明窗口,type = 2010 查看Android源码2010是TYPE_SYSTEM_ERROR(internal system error windows, appear on top of everything they can.),也就是说这个窗口会覆盖在所有用户窗口之上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
  public static void a(Context paramContext, boolean paramBoolean1, boolean paramBoolean2, boolean paramBoolean3)
  {
    if (a == null)
      a = new ail(paramContext);
    if (a != null)
    {
      a.k = paramBoolean1;
      a.j = paramBoolean2;
      a.l = paramBoolean3;
    }
    if ((a == null) || (a.m));
    try
    {
      b.removeView(a);
      a.m = false;
      localail = a;
      localail.setBackgroundColor(0);
      localLayoutParams = new WindowManager.LayoutParams();
      localLayoutParams.width = 1;
      localLayoutParams.height = 1;
      localLayoutParams.gravity = 51;
      localLayoutParams.x = 0;
      localLayoutParams.y = 0;
      localLayoutParams.format = 1;
      localLayoutParams.type = 2010;
      localLayoutParams.flags = 262152;
      if (a.m);
    }
    catch (Exception localException2)
    {
      try
      {
        ail localail;
        WindowManager.LayoutParams localLayoutParams;
        b.addView(localail, localLayoutParams);
        a.m = true;
        return;
        localException2 = localException2;
        Object[] arrayOfObject2 = new Object[2];
        arrayOfObject2[0] = "removeView";
        arrayOfObject2[1] = localException2.getMessage();
        Log.w("gray", arrayOfObject2);
      }
      catch (Exception localException1)
      {
        Object[] arrayOfObject1 = new Object[2];
        arrayOfObject1[0] = "removeView";
        arrayOfObject1[1] = localException1.getMessage();
        Log.w("gray", arrayOfObject1);
      }
    }
  }

同时注意到ail重写了onTouchEvent,在touch事件中会调用d.a()方法。结合上面分析的悬浮窗,大概能推测,微信通讯录启动后会创建一个位于顶层的透明窗口,同时监听所有TouchEvent,检查用户可能启动的任何应用,如果发现是CSP就会替换为微信通讯录的Activity

1
2
3
4
5
  public boolean onTouchEvent(MotionEvent paramMotionEvent)
  {
    d.a();
    return super.onTouchEvent(paramMotionEvent);
  }

接下来,就看看微信通讯录是如何对用户启动的应用做判断的。 首先,在ail构造函数中,调用了e()方法,与之相关的是方法a(),这两方法完成了对系统中所有拨号,联系人相关应用包名的搜索,并将结果保存在对应的HashMap中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
private void a(Intent paramIntent, HashSet paramHashSet, boolean paramBoolean)
  {
    Iterator localIterator = e.getPackageManager().queryIntentActivities(paramIntent, 65536).iterator();
    while (true)
    {
      ResolveInfo localResolveInfo;
      if (localIterator.hasNext())
      {
        localResolveInfo = (ResolveInfo)localIterator.next();
        if (!paramBoolean)
        {
          if ((0x1 & localResolveInfo.activityInfo.applicationInfo.flags) == 0)
            continue;
          paramHashSet.add(localResolveInfo.activityInfo.name);
          if ((localResolveInfo.activityInfo.targetActivity != null) && (localResolveInfo.activityInfo.targetActivity.length() > 1))
            paramHashSet.add(localResolveInfo.activityInfo.targetActivity);
        }
      }
      else
      {
        return;
        paramHashSet.add(localResolveInfo.activityInfo.name);
      }
    }
  }


  private void e()
  {
    Intent localIntent1 = new Intent();
    localIntent1.setAction("android.intent.action.CALL_BUTTON");
    a(localIntent1, this.g, false);
    Intent localIntent2 = new Intent();
    localIntent2.setAction("android.intent.action.DIAL");
    a(localIntent2, this.g, false);
    if (this.g.size() < 1)
    {
      this.g.add("com.android.contacts.activities.DialtactsActivity");
      this.g.add("com.android.contacts.DialtactsActivity");
    }
    Intent localIntent3 = new Intent();
    localIntent3.setAction("android.intent.action.VIEW");
    localIntent3.setType("vnd.android.cursor.dir/contact");
    a(localIntent3, this.f, false);
    if (this.f.size() < 1)
      this.f.add("com.sonyericsson.android.socialphonebook");
    if (this.i.size() < 1)
    {
      this.i.add("com.sonyericsson.conversations");
      this.i.add("com.android.mms");
      this.i.add("com.motorola.blur.conversations");
    }
    Intent localIntent4 = new Intent();
    localIntent4.setAction("android.intent.action.MAIN");
    localIntent4.addCategory("android.intent.category.HOME");
    a(localIntent4, this.h, true);
    if ((IssueSettings.bO) || (IssueSettings.bP))
    {
      this.h.add("com.android.mms.ui.SingleRecipientConversationActivity");
      this.h.add("com.android.mms.ui.NewMessagePopupActivity");
    }
  }

在用户产生触摸事件时,调用方法 aim.a()。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
  public boolean a()
  {
    this.b.removeMessages(2013);
    ail localail = ail.b();
    List localList = ail.c().getRunningTasks(1);
    if ((localList != null) && (localList.size() > 0))
    {
      String str = ((ActivityManager.RunningTaskInfo)localList.get(0)).topActivity.getClassName();
      if (!ail.g(localail).contains(str))
        return false;
      if (a(str))
      {
        this.b.sendEmptyMessageDelayed(2013, 150L);
        this.b.sendEmptyMessageDelayed(2013, 200L);
        this.b.sendEmptyMessageDelayed(2013, 300L);
        this.b.sendEmptyMessageDelayed(2013, 500L);
        return true;
      }
    }
    this.b.sendEmptyMessageDelayed(2013, 50L);
    this.b.sendEmptyMessageDelayed(2013, 100L);
    this.b.sendEmptyMessageDelayed(2013, 200L);
    this.b.sendEmptyMessageDelayed(2013, 500L);
    return true;
  }

看到这一段时,我有理由怀疑该程序员已经进入丧心病狂模式,每当检查到栈顶的Activity是CSP相关应用,就发出四条延时消息,我估计这个延时是为了确保该Activiy完全启动,然后将其替换。

以上分析大部分源自汪文俊大哥,非常感谢。

如何屏蔽同类应用

从原理出发屏蔽应用悬浮窗,目前只有MIUI和Android 4.3具备该功能。

结语

仅从技术角度来说,这段代码还是挺有趣的,但作为一个大厂的应用,随意篡改系统应用,甚至是监听用户行为,这样的做法我认为是欠考虑的。不难预见,微信通讯录仅仅是这场灾难的起始,或许未来的某一天点击微信启动QQ会是一件喜闻乐见的事情。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值