Skip to content
xiehuc edited this page Apr 20, 2014 · 3 revisions

调试教程

本教程粗略的讲述一下如何使用Chromium来调试Webqq,并且对Webqq的工作流程进行简单 的介绍

首先,打开chromium的 审查元素 面板,快捷键是 Ctrl+Shift+I

graph/inspect-panel.png

其中重要的几个,从右到左分别是:

  • Network: 查看网络通信面板
  • Sources: 查看Js源代码
  • Console: Js交互式控制台

入门篇-使用Network选项卡查看交互

首先切换到 Network 选项卡,然后我们执行WebQQ的登录过程, 同时会在选项卡中记录大量的通信过程.

Webqq就可以看作是所有网络通信的集合. 所以我们通过模仿这种过程, 像服务器发送同样的请求, 服务器无法分辨到底是从浏览器中发出的, 还 是从lwqq中发出的, 它都会返回同样的信息. 所以我们只需要处理这些信 息. 就可以重现WebQQ的所有功能了.

浏览器向服务器发送大量的请求, 其中很多都是无关紧要的. 但是有些则 是重要的通信内容. 一般能够从名称中区分出来: 比如 login2 , get_friend_info2 , get_user_friends2 等等的.

下面我们就以 get_user_friends2 来讲解. 选取之后, 右边会展开详细 内容:

graph/network-panel.png

Headers

Headers 面板中, 从上到下, 分别是请求, 请求头部, 表单数据, 响 应头部.

请求地址就是服务器网页地址, 直接拷贝该地址进源码中, 和服务器通讯 请求方法分为POST和GET, 前者是在HTTP正文中带入参数, 后者是在URL中 带入参数. Cookie是记录状态的数据. 每个请求都必须带正确的Cookie服务器才会给 予正确的响应. Referer: 也是每一个请求都必须带正确的Referer, 服务器才会给出正确 的响应. 在POST方法中, FormData的格式和GET的是一样的. 用key1=val1&key2=val2 的形式. 在写源代码时需要进行简单的转换. 例如在这里就应该写为r={"h":"hello","hash":"CBC5C89A","vfwebqq":"..."} 可以看到,r数值是一个Json字符串. 其中这个hash的算法就经常的发生变动 很是恶心.

Response

切换到 PreviewResponse 面板,这两者的内容都是一样的. Preview可以 对大部分格式进行处理,以比较好的形式显示出来.而Resonpse则是显示的原 始的纯文本内容.

graph/network-response.png

如果hash函数错误了,返回的内容就如同 {"retcode":50} . 可以看到,大部分 服务器的响应都是Json格式. 内容非常的清晰.

接下来只要将各种请求集合起来, 解释其中的参数, 就完成了WebQQ协议的解析 了.非常简单

进阶篇-使用Sources选项卡调试Js

在这里,以调试hash函数为例子,讲解Sources面板的使用方法。

graph/source-panel.png

Sources面板分为三个部分,左边是文件浏览窗口,中间是代码窗口,右边是功能 区域。

通常,hash函数的代码在eqq.all.js里面。于是找到该文件。这里,

  1. 怎么快速定位文件:可以通过Network选项卡的的Initiator列,一般getface是 在 eqq.all.js 中的,所以双击就可以打开
  2. 怎么知道get_user_friends是在eqq.all.js里面的:因为其它大部分的Initiator 都是指向的proxy.html,无法直接定位。所以这里我是用加断点+调用栈的方式找 到的

通常代码都是经过压缩的,可以点击状态栏的 {} 按钮来格式化代码

BreakPoint and CallStack

下面通过2,来讲述callstack的用法

首先,从Network的Initiator中定位到proxy.html中的发送请求的代码。然后在代码区 左边加上断点(行号附近),这里是 httpRequest.send(options.data)

graph/network-initiator.png

然后重新登录WebQQ,这个时候,你会发现,无论什么请求都在触发这个断点,这个时候 就会想到条件断点功能啦。首先寻找条件表达式:将鼠标移动到options上面,可以看到 详细的信息,同时可以在功能区的 Watch Expressions 窗口,点击+号,输入,options. 可以更方便的看到具体的信息。

这里就可以用 options.data.indexOf("hash")!=-1 来设置条件断点。在断点地方右键 Edit Breakpoints ,输入刚才的表达式。然后断点就会变成黄色。点击功能区的播放键 继续执行代码。需要注意,proxy.html有好几个文件。一定要在正确的文件上添加断点 否则是不可能触发的。

graph/source-breakpoint.png

然后在右边的 Call Stack 中可以看到调用栈。从上往下越来越旧。经过几行之后可以找 到eqq.all.js文件。

需要理解为何我们使用CallStack,因为它提供了一种方式,在我们对整个系统都完全没有 任何了解的情况下,调用栈能够提供给我们系统是如何运行的,这样的信息.

graph/source-callstack.png

最上面的一部分是proxy相关的基础调用,所以我们不用管它. 最下面的一部分是进入点 可以认为是回调开始.所以我们也可以不用管它,所以最后的结果就集中在了eqq.all.js 中.最后逐一查看之后可以确定是在start函数中的相关代码中赋值的.其中require函数 的参数是一个数组,分别是GetBuddyList和GetGroupList.也就是说这里是一个并发的思 想,同时发送2个互补冲突的请求,当都满足之后再执行后面的代码!

如果Callstack也不太好看,这个时候可以通过搜索字符串的方式来定位,即 get_user_friends . 因为发送请求的时候url一定要使用字符串形式。而字符串是无法被压缩的。所以可以根据 字符串来定位代码位置。很快的,我们就在eqq.all.js中找到了字符串。需要注意的是字符串 定位的方法准确度非常的低,因为它并不是基于程序的执行过程分析的.所以失败的概率也很大. 主要是它比较接近于人的思维过程,而CallStack更接近程序化的思维过程.

接下来可以看到上面几行就有 b.hash = EQQ.h1 ,于是这里需要找到EQQ.h1是什么时候赋 值的。继续搜索 EQQ.h1 = 很快就在start函数里面找到了代码了:

var b = [alloy.portal.getUin() + "", d.cookie.get("ptwebqq")], b = P(b[0], b[1]);
EQQ.h1 = b;

所以这里hash函数就是P函数。此时如果再搜索是找不到的。这里就在该行再加一个断点。 并重新登录QQ.触发断点之后,将鼠标指向P,然后把滚动条拖到最右边,就可以看到源文本链接:

graph/source-hash.png

点击链接之后。就可以看到hash函数的源文本了,赶快保存下来然后该怎么办怎么办了。