首页 > 逆向调试 > QQScLauncher逆向分析

QQScLauncher逆向分析

QQScLauncher逆向分析

逆向版本:QQ2011正式版(2425)
逆向目的:只是感兴趣,没有任何其他意图
版权声明:本文档由代码疯子整理,在保留本文档版权声明和原始出处的前提下欢迎转载!
需要工具:IDA Pro、Ollydbg
正文内容
什么是QQScLauncher?在QQ最新版(QQ2011正式版)中加入了不少新功能,如手写、视频群聊、语音输入等,另外还有一个功能是可以把好友拖放到桌面上,点击就可以直接与之进行聊天,处于好奇,本人对他进行了一下逆向,于是便有本文。

先去准备两个QQ小号(842342202、2390318912,不是必须的,这里显示,所以用小号)

先随便把一个好友拖放到桌面上,会产生一个快捷方式,查看这个快捷方式的属性,得到目标内容,会发现一个叫做QQScLauncher的程序:

"C:\Program Files\Tencent\QQ\Bin\QQScLauncher.exe" /uin:842342202 /quicklunch:
1D0BEC54CBEE33C09ED6F8089A7528E12EA3F4892C20DE8B0E8EF1144D765CD46176A599C09696DF

可以看到,这里是通过一个叫做QQScLauncher.exe的程序来启动的,uin参数指定自己的QQ号码,quicklaunch肯定就是用于鉴别好友用的了。

用PEiD对QQScLauncher.exe进行查壳,结果什么也没查到,估计是我的PEiD数据库太久了,用File Format Identifier查出来是Visual C++ 2005 Release -> Microsoft。其实是什么不要紧,拿起IDA逆一逆就没什么了。
QQScLauncher逆向分析

下面使用IDA对其进行简单的分析,可以看到一个获取命令行参数/uin和/quicklunch的过程:

.text:00401000 sub     esp, 270h
.text:00401006 mov     eax, dword_403000
.text:0040100B xor     eax, esp
.text:0040100D mov     [esp+270h+var_4], eax
.text:00401014 mov     eax, ds:__argc        ; 命令行参数个数argc的地址
.text:00401019 push    ebx
.text:0040101A push    ebp
.text:0040101B push    esi	
.text:0040101C push    edi
.text:0040101D xor     edi, edi              ; edi清零
.text:0040101F xor     ebp, ebp              ; ebp清零
.text:00401021 cmp     [eax], edi            ; 看看是不是正常启动(三个参数)
.text:00401023 mov     [esp+280h+var_270], edi	; 存放QQ号的位置
.text:00401027 mov     [esp+280h+var_26C], edi	; 0/1暗示QQ号是否找到
.text:0040102B mov     [esp+280h+var_268], edi	; 0/1暗示quicklunch是否找到
.text:0040102F jle     loc_40112A            ; 不是正常启动则跳转
.text:00401035 mov     ebx, ds:wcsncmp       ; 存储wcsncmp函数的地址,后面会经常用到
.text:0040103B jmp     short loc_401040
.text:0040103B ; ---------------------------------------------------------------------------
.text:0040103D align 10h
.text:00401040
.text:00401040 loc_401040:                   ; CODE XREF: wWinMain(x,x,x,x)+3Bj
.text:00401040                               ; wWinMain(x,x,x,x)+93j
.text:00401040 mov     ecx, ds:__wargv       ; 命令行参数的地址
.text:00401046 mov     edx, [ecx]            ; 取得第一个字符串的地址
.text:00401048 mov     esi, [edx+edi*4]      ; 取得第一个字符串的字符
.text:00401048 				; 第一次将取到C:\Program Files\Tencent\QQ\Bin\QQScLauncher.exe
.text:00401048                               ;
.text:0040104B push    5                     ; MaxCount
.text:0040104D push    offset Str2           ; "/uin:"
.text:00401052 push    esi                   ; Str1
.text:00401053 call    ebx ; wcsncmp         ; 调用wcsncmp比较字符串,看前五个字符是否是/uin:
.text:00401055 add     esp, 0Ch              ; 平衡wcsncmp堆栈
.text:00401058 test    eax, eax              ; 找到时返回0,否则返回非0值
.text:0040105A jnz     short loc_40106D      ; 没有找到"/uin:"时跳转
.text:0040105C add     esi, 0Ah              ; 0xA=10,跳过前十个字节,长度正好是/uin:的Unicode长度
.text:0040105F mov     [esp+280h+var_270], esi ; 把QQ号码存下来
.text:00401063 mov     [esp+280h+var_26C], 1 ; 表示QQ号码已经找到
.text:0040106B jmp     short loc_401089
.text:0040106D ; ---------------------------------------------------------------------------
.text:0040106D
.text:0040106D loc_40106D:                   ; CODE XREF: wWinMain(x,x,x,x)+5Aj
.text:0040106D push    0Ch                   ; MaxCount
.text:0040106F push    offset aQuicklunch    ; "/quicklunch:"
.text:00401074 push    esi                   ; Str1
.text:00401075 call    ebx ; wcsncmp         ; 调用wcsncmp比较字符串,看前12个字符是否是/quicklunch:
.text:00401075                               ; 看到这我石化了,quicklunch?快餐么?
.text:00401077 add     esp, 0Ch
.text:0040107A test    eax, eax              ; 看是否找到quicklunch
.text:0040107C jnz     short loc_401089      ; 没有找到则跳转
.text:0040107E lea     ebp, [esi+18h]        ; quicklunch值的地址由ebp保存
.text:00401081 mov     [esp+280h+var_268], 1 ; 表示quicklunch已经找到
.text:00401089
.text:00401089 loc_401089:                   ; CODE XREF: wWinMain(x,x,x,x)+6Bj
.text:00401089                               ; wWinMain(x,x,x,x)+7Cj
.text:00401089 mov     eax, ds:__argc        ; 命令行参数个数argc的地址
.text:0040108E add     edi, 1                ; 准备取得下一个命令行参数
.text:00401091 cmp     edi, [eax]            ; 看看命令行参数是否已经取完
.text:00401093 jl      short loc_401040      ; 没有取完则调回去继续取
.text:00401095 xor     ebx, ebx

对于上面的代码中蓝色的行,我们是如何知道他的意义的呢?看下面的几行代码就知道了:(这里贴代码工具看不到蓝色,读者可以下载文章末尾的word文档)

.text:00401095 xor     ebx, ebx              ; ebx寄存器清零
.text:00401097 cmp     [esp+280h+var_268], ebx ; 从这里的比较可以知道这几个字段的意义
.text:0040109B jz      loc_40112A
.text:004010A1 cmp     [esp+280h+var_26C], ebx
.text:004010A5 jz      loc_40112A

可以看到,这里判断quicklunch和QQ号是否找到,没找到就跳转;跳转到的地址和上面命令行参数个数不是3是一样的(绿色的代码行)。
接下来,程序生成一个特定的字符串,格式为”qqexchangewnd_shortcut_prefix_842342202″,后面那几位就是QQ号码了。然后通过FindWindow来查找窗口,调用代码如下:

004010E4	PUSH ECX		; /Title = "qqexchangewnd_shortcut_prefix_842342202"
004010E5	PUSH 00402198	;|5b3838f5-0c81-46d9-a4c0-6ea28ca3e942
004010EA	CALL DWORD PTR DS:[<&USER32.FindWindowW>]       ; \FindWindowW

如果FindWindow返回非零值,则接下来的代码逻辑是计算出quicklunch的长度,代码如下:

.text:004010F4 mov     ecx, ebp              ; 让ecx指向quicklunch
.text:004010F6 mov     [esp+280h+ProcessInformation], ebx
.text:004010FA lea     esi, [ecx+2]          ; esi略过quicklunch第一个字符
.text:004010FD lea     ecx, [ecx+0]
.text:00401100
.text:00401100 loc_401100:                   ; CODE XREF: wWinMain(x,x,x,x)+109j
.text:00401100 mov     dx, [ecx]             ; 找到ECX中第一个'\0'
.text:00401100                               ; 这里其实就是求字符串长度吧
.text:00401103 add     ecx, 2
.text:00401106 cmp     dx, bx
.text:00401109 jnz     short loc_401100
.text:0040110B sub     ecx, esi              ; 计算出字符串的字节数
.text:0040110D sar     ecx, 1                ; 因为是Unicode,所以字节数除以2

然后,程序会调用SendMessage发送WM_COPYDATA消息:

.text:00401117 push    ecx                   ; lParam(指向COPYDATASTRUCT结构的指针)
.text:00401118 push    ebx                   ; wParam(这里是0)
.text:00401119 push    4Ah                   ; Msg(就是WM_COPYDATA)
.text:0040111B push    eax                   ; hWnd(接收方窗口句柄,上面FindWindow的返回值)
.text:0040111C mov     [esp+290h+hObject], edx ; 填充COPYDATASTRUCT.cbData
.text:00401120 mov     [esp+290h+var_25C], ebp ; 填充COPYDATASTRUCT.lpData
.text:00401124 call    ds:SendMessageW       ; 调用SendMessage

可以看到上面SendMessage时wParam用了0,这里本来应该是发送方的窗口句柄值(我猜wParam的字段是为了通知接收方消息是从哪里传来的,这里接收方不会反过来和发送方通信,所以填0也不影响。有兴趣自己测试吧,这里懒得验证了)。
之后,QQ就可以弹出聊天窗口了(这里的逻辑就由QQ负责了)。
如果QQ没有开启,那么上面的FindWindow调用就返回NULL,那么将进入另一个分支。来到如下代码:

.text:0040114A loc_40114A:                   ; CODE XREF: wWinMain(x,x,x,x)+F2j
.text:0040114A call    GetQQInstallPath      ; 如果QQ没有开启,则跳转到这里

这里的函数名字是我自己改的,看代码逻辑可以知道他是用来获取QQ安装目录的:

.text:004012E0 GetQQInstallPath proc near    ; CODE XREF: wWinMain(x,x,x,x):loc_40114Ap
.text:004012E0 push    esi                   ; quicklunch
.text:004012E1 push    20Ah                  ; unsigned int
.text:004012E6 call    ??2@YAPAXI@Z          ; operator new(0x020A)分配空间
.text:004012EB add     esp, 4
.text:004012EE mov     esi, eax
.text:004012F0 push    104h                  ; nSize
.text:004012F5 push    esi                   ; lpFilename
.text:004012F6 push    0                     ; hModule
.text:004012F8 mov     word ptr [esi], 0
.text:004012FD call    ds:GetModuleFileNameW ; 得到当前进程的路径
.text:00401303 push    5Ch                   ; 字符'\\'
.text:00401305 push    esi                   ; Str
.text:00401306 call    ds:wcsrchr            ; wcsrchr返回字符在字符串中最后一次出现的位置
.text:0040130C add     esp, 8
.text:0040130F test    eax, eax
.text:00401311 jz      short loc_401338      ; 没有找到'\\'时跳转
.text:00401313 add     eax, 2                ; eax指向下一个字符
.text:00401316 mov     ecx, eax
.text:00401318 sub     ecx, esi              ; 前面一段字符的字节数
.text:0040131A push    offset aQq_exe        ; "QQ.exe"
.text:0040131F sar     ecx, 1                ; 算术右移一位,得到长度
.text:00401321 mov     edx, 104h
.text:00401326 push    offset aS             ; "%s"
.text:0040132B sub     edx, ecx
.text:0040132D push    edx                   ; SizeInWords
.text:0040132E push    eax                   ; Dst(替换QQScLauncher.exe)
.text:0040132F call    ds:swprintf_s         ; 调用swprintf_s
.text:00401335 add     esp, 10h
.text:00401338
.text:00401338 loc_401338:                   ; CODE XREF: GetQQInstallPath+31j
.text:00401338 mov     eax, esi
.text:0040133A pop     esi
.text:0040133B retn                          ; 返回
.text:0040133B GetQQInstallPath endp

上面先通过new来分配一段空间,然后调用GetModuleFileName来获取当前进程的路径,也就是:

C:\Program Files\Tencent\QQ\Bin\QQScLauncher.exe

然后通过wcsrchr来查找’\’字符,从而eax指向\ QQScLauncher.exe,eax加上2(一个Unicode字符长度),于是eax指向QQScLauncher.exe,后面调用swprintf_s时第一个参数直接填入eax即可。

随后程序会调用一个函数来生成一些CreateProcess需要的一些参数,这个过程和前面分析的过程差不多,所以不再赘述。之后就是通过CreateProcess来创建一个QQ.exe进程了。

012829C8  |ModuleFileName = "C:\Program Files\Tencent\QQ\Bin\QQ.exe"
01282BE0  |CommandLine = "/uin:842342202 
/quicklunch:1D0BEC54CBEE33C09ED6F8089A7528E12EA3F4892C20DE8B0E8EF1144D765CD46176A599C09696DF"
00000000  |pProcessSecurity = NULL
00000000  |pThreadSecurity = NULL
00000000  |InheritHandles = FALSE
00000000  |CreationFlags = 0
00000000  |pEnvironment = NULL
00000000  |CurrentDir = NULL
0012FCA4  |pStartupInfo = 0012FCA4
0012FC94  \pProcessInfo = 0012FC94

可以看到这里把QQ号和好友的识别符作为命令行参数传给了QQ.exe。如果启动QQ.exe失败则会继续尝试使用CreateProcess来启动QQ,直到启动成功,之后程序的逻辑也就结束了。

分析到这里,可以看出QQScLauncher.exe的内部逻辑已经很清晰了,这个程序没有经过特殊处理,很适合像我这样的新手分析,有兴趣的话还可以自己写一个QQScLauncher.exe。

有意思的是命令行参数竟然写成了quicklunch……至于QQ是如何通过那一串16进制来找到QQ好友的,暂时没有找到地方,有兴趣的朋友可以试着看一下。
(本文由代码疯子原创,原文发表于看雪论坛,http://bbs.pediy.com/showthread.php?t=141688
本文PDF文档和IDB文件下载:QQScLauncher逆向分析(解压密码:www.programlife.net)


觉得文章还不错?点击此处对作者进行打赏!


本文地址: 程序人生 >> QQScLauncher逆向分析
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!


更多



  1. 2011年10月21日17:13 | #1

    估计是想写quicklaunch吧

    [回复]

    代码疯子 回复:

    @JayXon, 应该是的,测试人员竟然也没发现这一点

    [回复]

    JayXon 回复:

    @代码疯子, 腾讯的程序员可能英语不太好,我在QQ的别的文件中也发现了类似的拼写错误

    [回复]

  2. 2011年10月22日23:39 | #2

    腾讯的东西都可以破解,强悍啊

    [回复]

    代码疯子 回复:

    @C瓜哥, 额,这个就不算破解了,随便找个没加密的程序看一下而已

    [回复]

    C瓜哥 回复:

    @代码疯子, 好吧,我没用对关键字
    不过,用这个来做坏事应该不难吧 [em012]

    [回复]

    代码疯子 回复:

    @C瓜哥, 一般做去广告之类的,目前我还没有搞过这方面的东西

    [回复]

  3. 2011年10月22日23:40 | #3

    腾讯的东西都可以破解,强悍啊
    你以后能去的公司,绝对超过TX之辈了

    [回复]

  4. 2011年10月23日13:46 | #4

    看得我头大。。 [em013]

    [回复]

    代码疯子 回复:

    @袋鼠, 因为你不搞这个嘛

    [回复]

  5. 2011年10月23日17:18 | #5

    厉害的网站,支持!!!

    [回复]

    代码疯子 回复:

    @DuUsT, 谢谢光临 [em010]

    [回复]

  6. 2012年2月22日17:12 | #6

    我表示看不懂 [em021]

    [回复]

  7. 乱码时代
    2012年8月20日14:56 | #7

    乱码时代

    [回复]

  8. 乱码时代
    2012年8月20日14:57 | #8

    [em016] [em002] [em006]

    [回复]

  9. 小哥
    2013年6月1日22:36 | #9

    [em022] [em021] 给力哥~

    [回复]