Android 源码分析实战 - 授权时拦截 QQ 用户名和密码

1. 说在前面

本文内容其实是不适合发出来的,希望大家切勿用作商业用途,也切勿将功能发布到线上环境。技术一定是为生活服务的,是为了大家共同的美好生活。判断一件事是否值得做,一定是利人利己,错误的事可能舒服自己而痛苦了别人,损人利己的事千万不可为。

2. 需求背景

在测试功能时,我们可能会遇到一些偶现崩溃的情况,往往难以复现。在修改代码时,有时往往改了这个 Bug ,在某个其他地方又引发了另一个 Bug,或者又是不经意间修改了 UI 样式界面。当然以上问题,我们可以看日志跟踪,也可以多写单元测试。基于等等场景,我们部门共建了一个 sdk ,我也是其中的一员。

其中重要的一个环节,复现 bug 时需要还原当时现场。也就是说要还原当时测试同学的操作步骤,需要还原当时请求的网络数据,需要还原数据库,需要还原 SharedPreferences ,如果这些现场都能还原,就很有可能复现并解决这个奔溃。而这里还涉及到账号还原,也就是说要还原当时测试同学登录的账号,我这里说的是在另一台手机上自动切换登录。

同时基于以上功能,我们就可以事先录制很多正常操作路径,也可以把当时的每个界面布局录制下来。这样每次发布之前,都在云平台上跑一遍,就能测试发现很多问题。如何还原网络数据、还原数据库等现场本文暂时不讲,本文主要来分析如何还原账号信息。温馨提示,只要对源码足够熟悉,这些都不是事。

3. 需求分析

PCG 部门的所有产品都是比较成熟的产品,任何一个产品都有亿量级的用户,我们写的 sdk 是无法侵入业务代码的。也就是说业务开发的同学在 Application 中配置一个入口,以上这些功能就都要能实现。现在回到账号还原上来,如果要还原账号现场,那么必定会有账号拦截与自动切换登录,而这整个过程,业务上层是不会给我们适配代码的。因为腾讯视频、应用宝与腾讯新闻等等,整个 PCG 的应用都需要集成我们的 sdk。我们在写代码的时候一定要考虑通用性、适配性与集成成本等等。只是该功能只在测试环境集成。

PCG 应用业务侧登录都是用的 QQ 与微信第三方登录,我们规定测试同学只能用 QQ 授权登录,方便自动拦截切换登录实现。那么接下来第一步就是如何拦截登录的账号信息,我之前考虑过只拦截 QQ 授权时的信息,但后面发现授权信息的 token 会有过期时间,后面就果断放弃了。所以要实现该功能且要做到所有应用都通用,就只能想办法拦截到 QQ 授权时的用户名和密码。

4. 需求实现

怎么在不侵入业务逻辑代码情况下,拦截到 QQ 授权时的用户名和密码呢?估计大部分人都会认为无法实现,但其实只要对源码够熟悉,分析实现起来还是挺简单的。业务侧普通的授权方式是没有输入用户名和密码的过程,我们想要拦截这些信息,势必需要用户有这个主动的操作过程,基于这点我们就需要引导用户跳转到 H5 的授权界面。那这还不简单,我们在业务逻辑中直接打开 QQ 的 H5 授权不就可以了?但问题是我们不能改业务逻辑代码,而且该 sdk 也不会上线,因此我们只能在 Application 初始化 sdk 里面中去做处理。

那么怎么才能在不该动原有业务逻辑情况下,点击 QQ 授权是自动跳转到 H5 授权界面呢?我们势必要去翻一下 QQ 登录提供的 sdk 源码,发现其中会判断有没有安装 QQ 应用,如果没有则是提示下载 QQ ,如果有安装 QQ 则会跳转到 AgentActivity 进行授权。因此我们只要想办法欺骗 QQ 的授权 sdk 就行,当调方法问有没有安装 QQ 时,我们返回安装了;当启动 AgentActivity 授权时,我们偷偷的将其引导到 H5 授权界面去输入用户名和密码,只有这样我们才有机会拦截到用户名和密码。那么如何才能欺骗呢?这就取决于我们对源码的熟悉程度了。

    // PMS
    private HandlerInvokeCallback mPMSCallback = new HandlerInvokeCallback() {
        @Override
        public void beforeInvoke(Method method, Object[] args) {
            try {
                // 一般上层业务逻辑中会有判断 QQ 有没有安装
                if (CommonUtils.equals("getPackageInfo", method.getName())) {
                    if (CommonUtils.equals(args[0], QQ_PACKAGE_NAME)) {
                        // 替换成当前应用的包名,无论是否安装 QQ 返回都是安装
                        args[0] = mApplication.getPackageName();
                    }
                }
                // SDK 中会查询 QQ_OAUTH_ACTIVITY ,默认返回 QQLoginH5AuthorizeActivity
                if (CommonUtils.equals("queryIntentActivities", method.getName())) {
                    Intent queryIntent = (Intent) args[0];
                    ComponentName componentName = queryIntent.getComponent();
                    if (componentName == null) {
                        return;
                    }
                    String queryClassName = componentName.getClassName();
                    if (CommonUtils.equals(queryClassName, QQ_OAUTH_ACTIVITY)) {
                        args[0] = new Intent(mApplication, QQLoginH5AuthorizeActivity.class);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
                LogUtil.w(TAG, "pms beforeInvoke exception: " + e.getMessage());
            }
        }

        @Override
        public Object afterInvoke(Method method, Object[] args, Object returnObj) {
            return returnObj;
        }
    };

    // AMS
    private HandlerInvokeCallback mAMSCallback = new HandlerInvokeCallback() {
        @Override
        public void beforeInvoke(Method method, Object[] args) {
            try {
                // 把跳转到 QQ 原生的页面,都替换跳转到 QQLoginH5AuthorizeActivity
                if (CommonUtils.equals("startActivity", method.getName())) {
                    Intent intent = (Intent) args[2];
                    ComponentName componentName = intent.getComponent();
                    if (componentName == null) {
                        return;
                    }
                    if (CommonUtils.equals(componentName.getClassName(), QQ_OAUTH_ACTIVITY)) {
                        intent.setComponent(new ComponentName(mApplication, QQLoginH5AuthorizeActivity.class));
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
                LogUtil.w(TAG, "ams beforeInvoke exception: " + e.getMessage());
            }
        }

        @Override
        public Object afterInvoke(Method method, Object[] args, Object returnObj) {
            return returnObj;
        }
    };

    // Application 中初始化入口
    @Override
    public void init(Application application) {
        this.mApplication = application;
        SharedPreferences accountSp = mApplication.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
        mAccountSpEditor = accountSp.edit();
        // 读取之前存的 QQ 用户名和密码
        mQQAccount = accountSp.getString(SP_QQ_ACCOUNT_KEY, "");
        mQQPwd = accountSp.getString(SP_QQ_PWD_KEY, "");
        mAppId = accountSp.getString(SP_QQ_APP_ID_KEY, "");
        mAuthLoginType = accountSp.getInt(SP_QQ_AUTH_TYPE_KEY, 0);
        // 手Q应用适配另一套
        if (CommonUtils.equals(application.getPackageName(), QQ_PACKAGE_NAME)) {
            adapterInterceptQQAccount();
        } else {
            hookPMSAndAMS();
        }
        LogUtil.i(TAG, "init read account info: " + mQQAccount + ", " + mQQPwd + ", " + mAppId + ", " + mAuthLoginType);
    }

只要跳转到了 H5 的授权页面,拦截用户名和密码就很容易了。其实开发中我们看似很多实现不了的功能,只要我们静下心来去分析,还是能够实现的。这其实还是一个比较简单的功能,再扩展一些像还原网络现场,势必需要拦截监控用户的网络,而微视用的 wns 、手Q用的是 msf、腾讯体育是自己修改的 OkHttp 千奇百怪。

其实生活中总会有些事,不管我们有多少困难,有多少委屈,有多少艰苦,我们做下去,我们最终必然会感觉到骄傲。

视频地址:https://pan.baidu.com/s/1jdUJtbYdf2HW101MR9Ut5w
视频密码:ffo1

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,366评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,521评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,689评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,925评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,942评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,727评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,447评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,349评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,820评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,990评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,127评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,812评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,471评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,017评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,142评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,388评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,066评论 2 355