SharEDITor

教你用反编译劫持微信打造红包自动提醒小工具

大数据 自己动手做聊天机器人 发表于 2018-03-21 18:45:00 阅读612次


每个人都有很多微信群,为防打扰总是屏蔽群消息,然而错过红包就悲催了,经过几个月的苦心钻研,终于成功破解微信,从此再也不用担心错过微信群里的红包啦,这篇文章信息量巨大,请不要错过每一行字。

请尊重原创,转载请注明来源网站www.shareditor.com以及原始链接地址

微信机器人

要想破解微信,先从微信机器人谈起。微信机器人就是通过自动程序实现微信账号的自动回复、自动加好友、自动群管理等功能,一般用来做辅助营销。如今的微信机器人按照技术方案大体分为两种:

1. 一种是基于web协议的微信机器人。他是通过捕捉网页版微信的网络通信数据,找到能直接调用的微信接口,从而写程序实现微信的自动操作,这种方式对技术要求不高,业内基于微信机器人做服务的公司多数都用这种方式,然而这种方式有一个很大的缺陷,那就是经常需要重新扫二维码登陆(这个只能人工,没办法自动),微信团队并不傻,很容易检测出你可能是个机器人,让你重新登录验证一下又如何?另外web版协议容易迭代,变化快,对老版本淘汰更快,所以接口也要经常跟着升级比较麻烦。

2. 另一种是基于xposed框架直接hook手机版微信app,这种相当于直接操作的是手机版微信,这种方法的缺点是对硬件有一定要求同时需要更多的时间成本来深入研究微信内部代码实现破解,另外不同版本的微信app可能要分别破解,耗费双倍的时间,但是最大的优点就是稳定,也就是说一旦破解之后就像一个真人在用手机版微信一样,只要你不升级微信版本,就能一直自动运行下去,除非微信抛弃了这个版本(可能性不大,微信这么大厂子,每个版本服务个一两年不成问题)。下面我们就采取这种方案来实现微信的破解。

 

xposed是什么

xposed是一款android框架,它所做的是替换掉android操作系统内核中的某个部分,从而实现截获android运行的任意一个app的任意一个函数,在这里你可以插装你的代码,想干什么就干什么,比如:偷偷获取到用户输入的账号密码并发送到远程服务器实现盗号、截获微信实现自动抢红包的功能、截获手机的摄像头和麦克风来做偷窥……只有你想不到的,没有做不到的。当然如果这些都能轻易做到那就没有人敢用android手机了,幸运的是xposed只能安装在已经刷了root的android手机上,没root的手机是不能用xposed的,所以不懂的人千万不要在个人手机上随便刷root噢,说不定你的一些小动作都在别人监视范围内呢

 

安装xposed

常见的android手机能够成功安装xposed框架的不多,你的手机是否适合自行百度,我专门试验用的手机是红米4A,有必要的话需要单独购买一款。

安装过程总共分两步:1)申请解锁(地址是http://www.miui.com/unlock/index.html,大概要等两三天时间);2)刷系统(下载个奇兔刷机,找带XP框架的小米4A的ROM包全自动刷就行)

如果你用的不是4A,具体方法就需要自己去百度了,总之刷完之后的手机里会有这么一个app:

 

开发自己的xposed插件

xposed只是一个框架,需要基于这个框架开发插件才能发挥它的功能,网上有很多有意思的插件大家可以按需把玩(http://xposed.appkg.com/category/modules)。开发一款自己的xposed插件网上也有很多教程,但真的没有找到一篇适合直接推荐给大家的,不放心让大家自己耗费生命去找,所以我还是自己手把手来演示一下吧。(注意:以下都是建立在你已经成功刷root并安装好xposed框架的前提下,如果这点没做到,下面的都是白扯)

首先打开你的Android Studio(android的开发工具,请自行下载安装),新建一个Andoird Project,一路下一步、完成即可。

工程创建好后我们要做些配置,首先修改app目录下的build.gradle,在dependencies组配置中增加如下一行:

provided 'de.robv.android.xposed:api:82'

这时Android Studio工具会出现一个提示栏(每当修改这个文件都会出提示),点击“Sync Now”进行同步

新建一个类(比如叫XPosedMain),实现de.robv.android.xposed.IXposedHookLoadPackage接口,并重写handleLoadPackage方法,如下:

import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.callbacks.XC_LoadPackage;

public class XPosedMain implements IXposedHookLoadPackage {
    @Override
    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
        XposedBridge.log("HelloWorld: " + lpparam.packageName);
    }
}

 

其中的XposedBridge.log(lpparam.packageName);是xposed专用的打log方法

之后我们在app/src/main/下创建一个assets目录并创建一个初始化文件xposed_init,内容为你自己的插件主入口的全路径,如:com.zsenselink.myapplication.XPosedMain

(注意:在Android Studio的工程树结构里如果找不到app/src/main就切换到Project树结构)

最后还要修改AndroidManifest.xml文件,在application节点下添加如下设置:

<meta-data
    android:name="xposedmodule"
    android:value="true" />
<meta-data
    android:name="xposeddescription"
    android:value="第一个XPosed模块" />
<meta-data
    android:name="xposedminversion"
    android:value="82" />

现在我们已经实现了我们的第一个插件,它的功能就是当手机的android操作系统每加载一个包的时候就会打印一条log

下面我们就安装到我们的手机上,首先把装好xposed框架的手机用usb线连接到电脑,然后点击Android Studio中的运行(Run app)按钮并选择你的手机设备,点ok安装,这个过程包括了编译、打包、安装到手机并在手机上自动启动这个app,如果这时手机上提示了类似“usb安装是否允许”之类的,请点允许,否则会安装不上

安装好后会看到打开app的界面如下(实际上这个界面对我们的插件功能来说并无卵用):

这时模块的安装还没有结束,打开上面提到过的xposed installer工具,并点击左上角的菜单按钮,并点击“模块”,在这里你会看到你新开发的模块,请勾选你自己的模块,像下面的样子:

 

最后还要重启手机,大功告成!

为了能看到xposed打印的日志,我们在Andoid Studio里打开Logcat,并设置filter为“HelloWorld”,显示如下:

看来我们的插件截获成功啦

 

反编译微信app

下面我们开始破解微信的过程,首先我们要明确我们要破解的微信版本,索性我选择6.5.8这个版本,这个版本你可以随意选,但是一旦选择了以后就尽量不要改了,后面我会告诉你为什么。

上网找到6.5.8的apk文件并下到电脑中,把扩展名.apk改成.zip,并解压,解压后发现目录里有一个classes.dex文件,从网上下载一个dex2jar(https://sourceforge.net/projects/dex2jar/)工具并解压,解压后有一个d2j-dex2jar.sh脚本,然后我们来执行d2j-dex2jar.sh classes.dex,这样会生成一个classes-dex2jar.jar文件,这个就是微信打成jar包的形式,为了能够读取微信的jar包,还需要下载一个jd-gui工具(http://jd.benow.ca/),下载下来之后打开工具,并选择classes-dex2jar.jar文件打开,界面如下:

第一次看到这里有一种蒙圈的感觉,为什么都是a、b、c、d这种,没有带含义的英文单词,这让我怎么分析呢?这是因为人家微信是个大厂子,就怕有人破解他,所以通过混淆编译的方式就为了让你搞不懂(这也是为什么上面说版本确定了就尽量不要改了,因为每一版本混淆后变量名都会变)。不过世上无难事,只要肯攀登,就算我不登也总会有人登,所以我转向百度来看看别人怎么做的,经过不断寻找,我找到了一个很有价值的内容,那就是github上牛人开源的自动抢红包插件(https://github.com/veryyoung/WechatLuckyMoney),直接参考https://github.com/veryyoung/WechatLuckyMoney/blob/master/app/src/main/java/me/veryyoung/wechat/luckymoney/Main.java文件可以确定微信的package名是com.tencent.mm,另外收到消息时会触发的方法是:com.tencent.wcdb.database.SQLiteDatabase类的insert方法,然后我在重新通过jd-gui找到这个方法如下:

这个函数有三个参数,类型分别是String、String、ContentValues

所以我们重新修改一下我们的handleLoadPackage方法如下:

public class XPosedMain implements IXposedHookLoadPackage {
    private final static String WECHAT_PACKAGE_NAME = "com.tencent.mm";

    @Override
    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
        if (!lpparam.packageName.equals(WECHAT_PACKAGE_NAME)) {
            return;
        }

        XposedHelpers.findAndHookMethod("com.tencent.wcdb.database.SQLiteDatabase", lpparam.classLoader, "insert", String.class, String.class, ContentValues.class, new XC_MethodHook() {
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                String args0 = (String) param.args[0];
                String args1 = (String) param.args[1];
                ContentValues contentValues = (ContentValues) param.args[2];
                XposedBridge.log("HelloWorld: " + args0 + " " + args1 + " " + contentValues.toString());
            }
        });
    }
}

改好后重新编译打包安装,然后重启手机

然后打开手机上的微信(确保版本和上面反编译的是同一个版本),Logcat打印日志如下:

……

03-21 15:54:43.103 7104-7145/? I/Xposed: HelloWorld: message msgId bizClientMsgId= msgId=3557 msgSvrId=6629783396598629265 talker=warmheartli content=a flag=0 status=3 msgSeq=608522155 createTime=1521618813000 lvbuffer=[B@f637dd3 isSend=0 type=1 bizChatId=-1 talkerId=41

……

03-21 15:56:22.752 7104-7145/? I/Xposed: HelloWorld: rcontact username domainList= alias= chatroomFlag=0 quanPin= nickname= conRemark= verifyFlag=0 conRemarkPYShort= showHead=75 contactLabelIds= pyInitial= conRemarkPYFull= weiboNickname= lvbuff=[B@768a75a username=kate2ray weiboFlag=0 type=4

……

可以看出我们可以通过args0=="message"来判断是不是有关聊天消息的数据,而从contentValues中能够提取出聊天消息的微信号、消息内容、时间等

至此,豁然开朗!既然已经找到了截获微信消息的方法,那么想做什么都轻而易举了。

 

打造红包提醒小程序

找到了截获微信消息的方法,希望能利用他来给人们提供帮助,因为绝大多数人是没有独立安装xposed模块的条件的,所以我可以贡献出我自己的手机和微信号来为大家提供服务,有需要的可以找我。很多人都为了不被打扰屏蔽了很多微信群,但是一旦群里有红包了又不能及时发现,所以我想到可以利用截获微信消息的方法来给大家发提醒,那么问题来了:1)我怎么监控其他人的群?2)怎么给用户发提醒?当然有问题必然有解决方案,我苦思冥想,第一个问题的解决方案就是让用户把我的微信号拉进群里,这样我就能监控红包消息了,第二个问题最好的方法就是微信服务号的通知消息,那么问题又来了,微信服务号里发通知是要指定用户针对这一服务号的openid的,而插件截获到的消息里是用户的微信id,无法映射上,怎么办?这个问题困扰了我好久,最后我的解决方法是这样的:

1)开发一款微信小程序,在小程序启动时要让用户授权获取用户针对这个小程序的openid和unionid

2)申请一个微信服务号,要让用户关注这个服务号,从而获取该用户针对这个服务号的openid和unionid

3)这个小程序和服务号一定要关联到同一个微信开放平台,这样他们就共享了同一个unionid

4)让用户在他想监控的群里分享一次小程序(开发小程序时要在小程序的分享逻辑里加上一个链接参数,参数值是openid)

5) 把微信插件截获的群消息全都存储到数据库表中

有了以上的基础建设,我们的逻辑就清晰了:首先我们把历史上所有截获到的微信群消息拿出来,统计出每个群下面有哪些人分享过这个小程序,并且把分享时的openid找到并记录下来;然后时刻监控个每一个群消息,一旦有红包出现(红包消息的content是一个特殊的xml)就遍历这个群的openid,并根据openid到unionid再到openid的映射关系转换成服务号的openid,然后利用服务号的开放接口push一条模板消息,告诉他哪个群有红包来了

有关小程序如何申请和开发、公众号如何申请和开发、公众平台如何申请,这些网上有大把大把的文档就不再赘述了,请自行百度,全部方案我已经交代清楚了,后面的工作也都是体力活没有什么技术活了,我也洗洗睡了,最后贴上两段核心代码供大家参考,第一个是小程序里如何把openid带到链接参数上的:

  getSessionInfo: function (js_code) {
    var that = this
    wx.request({
      url: getApp().globalData.getSessionInfoApi,
      data: {
        js_code: js_code
      },
      success: function (res) {
        var openid = res.data.data.openid
        wx.setStorage({
          key: 'openid',
          data: openid,
        })
      }
    })
  },
……
  /**
   * 用户点击右上角分享
   */
  onShareAppMessage: function () {
    var openid = wx.getStorageSync('openid')
    console.log('onShareAppMessage' + openid)
    return {
      title: '这个群里有红包',
      path: '/src/pages/index/index?openid=' + openid,
      imageUrl: '/images/luckymoney.png'
    }
  },

再一个是后台怎么监控消息并给用户push消息的:

def get_chatroom_users():
    chatroom_users = {}
    sql = """
    SELECT r.nickname, m.talker, m.msg FROM wechatbot_message m
    JOIN wechatbot_chatroom r ON m.talker=r.talker
    WHERE m.talker like "%@chatroom" and m.msg like "%wxb704804f2d2a7f77%"
    """
    cur.execute(sql)
    for row in cur.fetchall():
        room_name = row[0]
        talker = row[1]
        msg = row[2]
        match_openid_obj = openid_pattern.search(msg)
        if match_openid_obj:
            openid = match_openid_obj.group(1)
            if openid != 'null':
                if talker in chatroom_users:
                    chatroom_users[talker]['openid_set'].add(openid)
                else:
                    openid_set = set()
                    openid_set.add(openid)
                    chatroom_users[talker] = {'openid_set': openid_set, 'room_name': room_name}

    return chatroom_users

def process_recent_msg(chatroom_users, last_id):
    sql = """
    SELECT id, talker, date_format(create_time, "%%Y-%%m-%%d %%T") FROM wechatbot_message
    WHERE id>%d AND talker like "%%@chatroom" and msg like "%%wxhb_personalreceive%%"
    """ % last_id
    cur.execute(sql)
    max_id = last_id
    for row in cur.fetchall():
        id = row[0]
        if id > max_id:
            max_id = id
        talker = row[1]
        create_time = row[2]
        if talker in chatroom_users:
            openid_set = chatroom_users[talker]['openid_set']
            room_name = chatroom_users[talker]['room_name']
            batch_send_msg(room_name, openid_set, create_time)

    return max_id


def openid2openid(openid):
    """小程序的openid转服务号的openid"""
    sql = """
    SELECT u2.openid FROM wechatbot_luckymoneyuser u1
    JOIN wxpubcallback_user u2 ON u1.unionid=u2.unionid
    WHERE u1.openid="%s"
    """ % openid
    cur.execute(sql)
    row = cur.fetchone()
    if row:
        return row[0]
    else:
        return None


def batch_send_msg(room_name, openid_set, create_time):
    access_token = token.get_token()
    template_id = 'lNShZUopUSNBOG2UZvBTDkbeV7t59mmzfH7bmFYlwio'
    first = '\'' + room_name + u'\'群里有红包!'
    keyword1 = u'微信群'
    keyword2 = u'红包'
    keyword3 = create_time
    remark = u'赶快领取!晚了就没了!'
    url = ''
    for openid in openid_set:
        pub_openid = openid2openid(openid)
        print 'send to', openid, pub_openid
        send_msg(access_token, template_id, pub_openid,
                 first, keyword1, keyword2, keyword3, remark, url)


def load_last_id():
    file_object = open('last_id', 'r')
    line = file_object.read(32)
    line = int(line.strip())
    file_object.close()
    return line


def dump_last_id(last_id):
    file_object = open('last_id', 'w')
    file_object.write(str(last_id))
    file_object.close()


def send_msg(access_token, template_id, openid, first, keyword1, keyword2, keyword3, remark, url):
    content = {}

    content["first"] = {}
    content["first"]["value"] = first
    content["first"]["color"] = "#31b0d5"

    content["keyword1"] = {}
    content["keyword1"]["value"] = keyword1
    content["keyword1"]["color"] = "#31b0d5"

    content["keyword2"] = {}
    content["keyword2"]["value"] = keyword2
    content["keyword2"]["color"] = "#000000"

    content["keyword3"] = {}
    content["keyword3"]["value"] = keyword3
    content["keyword3"]["color"] = "#000000"

    content["remark"] = {}
    content["remark"]["value"] = remark
    content["remark"]["color"] = "#000000"

    api_url = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=%s" % (access_token)

    data = {}
    data["touser"] = openid
    data["template_id"] = template_id
    data["url"] = url
    data["data"] = content
    header = {'Content-Type': 'application/json'}
    req = urllib2.Request(api_url, json.dumps(data), header)
    response = urllib2.urlopen(req, timeout=3)
    res = response.read()
    res_json = json.loads(res)
    print res_json

   if res_json['errcode'] != 0:
        # 重试一次
        token.refresh()
        req = urllib2.Request(api_url, json.dumps(data), header)
        response = urllib2.urlopen(req, timeout=3)
        res = response.read()
        res_json = json.loads(res)
        print res_json
    return res_json


def main():
    last_time = time.time()
    while True:
        last_id = load_last_id()
        now = time.time()
        if now - last_time > 600:
            print datetime.datetime.now(), last_id
            last_time = now
        chatroom_users = get_chatroom_users()
        last_id = process_recent_msg(chatroom_users, last_id)
        dump_last_id(last_id)
        time.sleep(5)

main()