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逆一逆就没什么了。
下面使用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./showthread.php?t=141688)
本文PDF文档和IDB文件下载:QQScLauncher逆向分析(解压密码:www.)
本文地址: 程序人生 >> QQScLauncher逆向分析
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!
|