1. 脱壳
查看下载器的PE信息,发现其使用了UPX加壳,所以首先对下载器进行UPX 脱壳。
2. 抓包分析
通过Wireshark查看下载器发送的HTTP请求,发现如下信息:
2.1 下载器发送的第一个请求为:
http://down.72zx.com/xml/web1.xml?winver=6.1
其中如下信息与接下来的HTTP请求相关:
<web>
<id>1</id>
<name>XX软件园</name>
<tag>ONLINEDOWNE</tag>
<xmlurl>
<![CDATA[
http://www.onlinedown.net/api/index.php?action=pc.pconline.soft
]]>
</xmlurl>
<logourl>
<![CDATA[ http://www.onlinedown.net/icon.png ]]>
</logourl>
</web>
2.2 下载器发送的第二个请求为:
其返回的内容为xml格式,其中的downsrc保存了下载的来源:
<downsrc><![CDATA[http://crcfj.onlinedown.net/down/QQ7.4.zip]]></downsrc>
<downsrc><![CDATA[http://fjmcc.newhua.com/down/QQ7.4.zip]]></downsrc>
<downsrc><![CDATA[http://nmas.onlinedown.net/down/QQ7.4.zip]]></downsrc>
<downsrc><![CDATA[http://sdmcc.newhua.com/down/QQ7.4.zip]]></downsrc>
<downsrc><![CDATA[http://jsmcc5.newhua.com/down/QQ7.4.zip]]></downsrc>
<downsrc><![CDATA[http://sdmcc2.newhua.com/down/QQ7.4.zip]]></downsrc>
<downsrc><![CDATA[http://jsmcc2.newhua.com/down/QQ7.4.zip]]></downsrc>
<downsrc><![CDATA[http://cttnb.onlinedown.net/down/QQ7.4.zip]]></downsrc>
<downsrc><![CDATA[http://nm.newhua.com/down/QQ7.4.zip]]></downsrc>
<downsrc><![CDATA[http://sccrc.newhua.com/down/QQ7.4.zip]]></downsrc>
<downsrc><![CDATA[http://nm.onlinedown.net/down/QQ7.4.zip]]></downsrc>
<downsrc><![CDATA[http://amcc.newhua.com/down/QQ7.4.zip]]></downsrc>
<downsrc><![CDATA[http://klrs.onlinedown.net/down/QQ7.4.zip]]></downsrc>
<downsrc><![CDATA[http://sqyd3.newhua.com:82/down/QQ7.4.zip]]></downsrc>
<downsrc><![CDATA[http://sqyd4.newhua.com:82/down/QQ7.4.zip]]></downsrc>
<downsrc><![CDATA[http://zjmcc4.newhua.com/down/QQ7.4.zip]]></downsrc>
<downsrc><![CDATA[http://hlbr.newhua.com/down/QQ7.4.zip]]></downsrc>
<downsrc><![CDATA[http://cttxj.newhua.com/down/QQ7.4.zip]]></downsrc>
<downsrc><![CDATA[http://zjmcc5.newhua.com/down/QQ7.4.zip]]></downsrc>
<downsrc><![CDATA[http://jsmcc4.newhua.com/down/QQ7.4.zip]]></downsrc>
尝试改变HTTP请求中的SoftID来下载其他软件,服务器返回错误结果,因此推断后面的token应该是个参数有效性验证的信息。
现在的问题归结为下载器如何生成softid和token信息。
3. 静态分析
使用IDA对下载器代码进行静态分析。
目前已知信息:
softid为整数: 20355 / 0x00004F83
token是长度为32的十六进制字符串: 9b89c16eeafa0faf120b0f9b78294677
初步静态分析发现:
.text:00569ECC DownloadRealFile proc near ; DATA XREF: .text:00568F5Co
处为下载器启动后与服务器交互并获取下载参数的代码
push offset aWebid_0 ; "webid="
mov eax, off_57FA04
push dword ptr [eax]
push offset aSoftid_0 ; "&softid="
mov eax, off_57F550
push dword ptr [eax]
push offset aToken_0 ; "&token="
mov eax, off_57FA04
push dword ptr [eax+8]
此段代码显示相关信息存放在:
Name | Address |
---|---|
softid | off_57F550 |
token | off_57FA04 |
首先从softid入手:
跟踪地址off_57F550 :
.data:0057F550 off_57F550 dd offset unk_5A569C ; DATA XREF: sub_54C7F4:loc_54C839r
跟踪偏移unk_5A569C :
.bss:005A569C unk_5A569C db ? ; ; DATA XREF: sub_54B2D4+37o
.bss:005A569C ; .data:off_57F550o
.bss:005A569D db ? ;
.bss:005A569E db ? ;
.bss:005A569F db ? ;
在此处设置 read/write 硬件断点。
对token做同样的处理:
跟踪地址off_57FA04 :
.data:0057FA04 off_57FA04 dd offset unk_5A5684 ; DATA XREF: sub_54C7F4+4Br
跟踪偏移unk_5A5684 + 8 -> : .bss : 005A568C :
.bss:005A5684 unk_5A5684 db ? ; ; DATA XREF: sub_54B2D4+27o
.bss:005A5684 ; .data:off_57FA04o
.bss:005A5685 db ? ;
.bss:005A5686 db ? ;
.bss:005A5687 db ? ;
.bss:005A5688 db ? ;
.bss:005A5689 db ? ;
.bss:005A568A db ? ;
.bss:005A568B db ? ;
-------Here------
.bss:005A568C db ? ;
.bss:005A568D db ? ;
.bss:005A568E db ? ;
.bss:005A568F db ? ;
设置好断点后使用IDA Pro调试。
在如下代码处设置断点:
.text:0056A185 push offset aWebid_0 ; "webid="
.text:0056A18A mov eax, off_57FA04
.text:0056A18F push dword ptr [eax]
.text:0056A191 push offset aSoftid_0 ; "&softid="
.text:0056A196 mov eax, off_57F550
.text:0056A19B push dword ptr [eax]
.text:0056A19D push offset aToken_0 ; "&token="
.text:0056A1A2 mov eax, off_57FA04
.text:0056A1A7 push dword ptr [eax+8]
----Set breakpoint on next line ----
.text:0056A1AA push offset dword_56AE54
.text:0056A1AF mov eax, off_57F550
.text:0056A1B4 push dword ptr [eax]
.text:0056A1B6 lea eax, [ebp+var_B8]
.text:0056A1BC mov edx, 3
.text:0056A1C1 call sub_407A4C
中断后栈上的值如下所示:
043EFDD8 021794BC debug031:021794BC
043EFDDC 0056AE38 .text:aToken_0
043EFDE0 0214DF1C debug031:0214DF1C
043EFDE4 0056AE18 .text:aSoftid_0
043EFDE8 0212B1C4 debug031:0212B1C4
043EFDEC 0056ADFC .text:aWebid_0
由此可见:
.text:aSoftid_0的值存储在:debug031:0214DF1C
.text:aToken_0的值存储在:debug031:021794BC
检查对应内存地址的值:
Var | Address | Value |
---|---|---|
Softid | debug031:0214DF1C | [0214DF1C] 32 00 30 00 33 00 35 00 35 00 00 00 00 00 00 00 —- 2.0.3.5.5……. |
Token | debug031:021794BC | [021794BC] 4F 00 4E 00 4C 00 49 00 4E 00 45 00 44 00 4F 00 —- O.N.L.I.N.E.D.O. [021794CC] 57 00 4E 00 45 00 00 00 C0 8C 17 02 B0 04 02 00 —- W.N.E……….. |
从此处看,Softid已经是最后的值20355,而Token还会进一步处理才会变成最后的值。
分析如下代码显示:
.text:0054BE98 ; =============== S U B R O U T I N E =======================================
.text:0054BE98
.text:0054BE98 ; Attributes: bp-based frame
.text:0054BE98
.text:0054BE98 _csis_Send_Http_Request proc near ; CODE XREF: sub_5672F0+184p
.text:0054BE98 ; sub_569ECC+BDp ...
.text:0054BE98
.text:0054BE98 var_18 = dword ptr -18h
.text:0054BE98 var_14 = dword ptr -14h
.text:0054BE98 var_10 = dword ptr -10h
.text:0054BE98 var_C = dword ptr -0Ch
.text:0054BE98 var_8 = dword ptr -8
.text:0054BE98 var_1 = byte ptr -1
.text:0054BE98 arg_0 = dword ptr 8
.text:0054BE98
处的函数 _csis_Send_Http_Request(为分析方便已重命名) 应该是负责发送HTTP请求的。
且寄存器EAX中将存放需要访问的HTTP地址的指针。
在发送获取下载地址的请求前设置断点,验证在call _csis_Send_Http_Request前,EAX中是否包含正确的token。
.text:0056A284 loc_56A284: ; CODE XREF: sub_569ECC+3E0j
.text:0056A284 lea edx, [ebp+var_BC]
.text:0056A28A mov eax, [ebp+var_1C]
.text:0056A28D call sub_412C5C
.text:0056A292 cmp [ebp+var_BC], 0
.text:0056A299 jnz short loc_56A2AB
.text:0056A29B lea eax, [ebp+var_1C]
.text:0056A29E push eax
.text:0056A29F mov cl, 1
.text:0056A2A1 xor edx, edx
.text:0056A2A3 mov eax, [ebp+var_20]
------Set Break Point at next line-------
.text:0056A2A6 call _csis_Send_Http_Request
结果验证了如上的结论
RAX 0000000001ECEA4C debug030:01ECEA4C
debug030:01ECEA4C
位置的内容为:
01ECEA4C 68 00 74 00 74 00 70 00 3A 00 2F 00 2F 00 77 00 h.t.t.p.:././.w.
01ECEA5C 77 00 77 00 2E 00 6F 00 6E 00 6C 00 69 00 6E 00 w.w...o.n.l.i.n.
01ECEA6C 65 00 64 00 6F 00 77 00 6E 00 2E 00 6E 00 65 00 e.d.o.w.n...n.e.
01ECEA7C 74 00 2F 00 61 00 70 00 69 00 2F 00 69 00 6E 00 t./.a.p.i./.i.n.
01ECEA8C 64 00 65 00 78 00 2E 00 70 00 68 00 70 00 3F 00 d.e.x...p.h.p.?.
01ECEA9C 61 00 63 00 74 00 69 00 6F 00 6E 00 3D 00 70 00 a.c.t.i.o.n.=.p.
01ECEAAC 63 00 2E 00 70 00 63 00 6F 00 6E 00 6C 00 69 00 c...p.c.o.n.l.i.
01ECEABC 6E 00 65 00 2E 00 73 00 6F 00 66 00 74 00 26 00 n.e...s.o.f.t.&.
01ECEACC 77 00 65 00 62 00 69 00 64 00 3D 00 31 00 26 00 w.e.b.i.d.=.1.&.
01ECEADC 73 00 6F 00 66 00 74 00 69 00 64 00 3D 00 32 00 s.o.f.t.i.d.=.2.
01ECEAEC 30 00 33 00 35 00 35 00 26 00 74 00 6F 00 6B 00 0.3.5.5.&.t.o.k.
01ECEAFC 65 00 6E 00 3D 00 39 00 62 00 38 00 39 00 63 00 e.n.=.9.b.8.9.c.
01ECEB0C 31 00 36 00 65 00 65 00 61 00 66 00 61 00 30 00 1.6.e.e.a.f.a.0.
01ECEB1C 66 00 61 00 66 00 31 00 32 00 30 00 62 00 30 00 f.a.f.1.2.0.b.0.
01ECEB2C 66 00 39 00 62 00 37 00 38 00 32 00 39 00 34 00 f.9.b.7.8.2.9.4.
01ECEB3C 36 00 37 00 37 00 00 00 01 00 00 00 00 00 00 00 6.7.7...........
从调用前的赋值行为.text:0056A2A3 mov eax, [ebp+var_20]
看,此结果存储在作为上一级函数的参数传入的地址中。
查找当前函数中所有的对[ebp+var_20]
的引用,跟踪到如下位置
.text:0056A22B lea ecx, [ebp+var_20]
------Here-----------------------------^^^^^^^^^^^^^^^^^------
.text:0056A22E mov eax, off_57FA04
.text:0056A233 mov eax, [eax+0Ch]
.text:0056A236 call sub_54B434
.text:0056A23B lea eax, [ebp+var_1C]
.text:0056A23E xor edx, edx
.text:0056A240 call sub_4074D8
.text:0056A245 mov eax, [ebp+var_64]
.text:0056A248 mov dword ptr [eax+48h], 3
.text:0056A24F mov eax, [ebp+var_64]
.text:0056A252 add eax, 40h
.text:0056A255 mov edx, [ebp+var_20]
.text:0056A258 call sub_407484
在
.text:0056A236 call sub_54B434
处设置断点,观察在call前后[ebp+var_20]
指向位置的变化,可以发现,在call之后,[ebp+var_20]
所指位置的指针发生改变,从指针指向的结果字符串可得如下结论:
[ebp+var_20]
所存为指向字符串的指针。.text:0056A236 call sub_54B434
可能是调用了一个类似于字符串format的函数。
如果是format函数,那么用于format的参数应该在call之前通过寄存器或者栈设置好。
往前回溯:
.text:0056A21B mov edx, 6
.text:0056A220 call sub_407A4C
--------------------------------------------------------
.text:0056A225 mov edx, [ebp+var_A4]
.text:0056A22B lea ecx, [ebp+var_20]
.text:0056A22E mov eax, off_57FA04
.text:0056A233 mov eax, [eax+0Ch]
.text:0056A236 call sub_54B434
在 .text:0056A236 call sub_54B434
处设置断点,查看在call之前各寄存器的值。
得到如下结果:
RDX 0000000001FC2B24 debug040:01FC2B24
01FC2B24 77 00 65 00 62 00 69 00 64 00 3D 00 31 00 26 00 w.e.b.i.d.=.1.&.
01FC2B34 73 00 6F 00 66 00 74 00 69 00 64 00 3D 00 32 00 s.o.f.t.i.d.=.2.
01FC2B44 30 00 33 00 35 00 35 00 26 00 74 00 6F 00 6B 00 0.3.5.5.&.t.o.k.
01FC2B54 65 00 6E 00 3D 00 39 00 62 00 38 00 39 00 63 00 e.n.=.9.b.8.9.c.
01FC2B64 31 00 36 00 65 00 65 00 61 00 66 00 61 00 30 00 1.6.e.e.a.f.a.0.
01FC2B74 66 00 61 00 66 00 31 00 32 00 30 00 62 00 30 00 f.a.f.1.2.0.b.0.
01FC2B84 66 00 39 00 62 00 37 00 38 00 32 00 39 00 34 00 f.9.b.7.8.2.9.4.
01FC2B94 36 00 37 00 37 00 00 00 00 00 00 00 00 00 00 00 6.7.7...........
也就是说,在.text:0056A236 call sub_54B434
之前,token已经生成好了,而且其存放的位置为:
[ebp+var_A4]
同上,查找所有对 [ebp+var_A4]
的引用,有如下发现:
.text:0056A215 lea eax, [ebp+var_A4]
-------Here---------------------------------^^^^^^^^^^^^
.text:0056A21B mov edx, 6
.text:0056A220 call sub_407A4C
.text:0056A225 mov edx, [ebp+var_A4]
在 .text:0056A220 call sub_407A4C
设置断点,查看call前后 [ebp+var_A4]
指向的值时候已包含token。
跟踪进入.text:0056A220 call sub_407A4C
的执行过程:
.text:00407AA0 loc_407AA0: ; CODE XREF: sub_407A4C+50j
.text:00407AA0 mov ecx, [ebp+edx*4+4]
--------------------------------------------^^^^^^^^^^^^^-------
.text:00407AA4 test ecx, ecx
.text:00407AA6 jz short loc_407AE7
.text:00407AA8 cmp word ptr [ecx-0Ah], 2
.text:00407AAD jz short loc_407AD3
.text:00407AAF mov esi, eax
.text:00407AB1 mov [ebp+var_4], edx
.text:00407AB4 mov eax, [ebp+edx*4+4]
.text:00407AB8 call sub_40746C
从指令的执行过程看,当指令执行到.text:00407AA0 mov ecx, [ebp+edx*4+4]
时,程序将debug041:00000000015C86C4
赋值给了ECX,而此位置存储的是Token的值。
那么问题来了,debug041:00000000015C86C4
是否是程序的常值?
让我们验证一下,等待程序启动,然后在内存中搜索
39 00 62 00 38 00 39 00 63 00 31 00 36 00 65 00 65 00 61 00 66 00 61 00 30 00 66 00 61 00 66 00
各种结果显示,在如下call之后,Token在内存中被成功生成:
.text:0056A1DC mov eax, [ebp+var_B4]
.text:0056A1E2 lea edx, [ebp+var_B0]
.text:0056A1E8 call _CSIS_Gen_Token
------^^^^^^^^-------------------------^^^^^^^^^^-------
.text:0056A1ED mov edx, [ebp+var_B0]
.text:0056A1F3 lea eax, [ebp+var_AC]
.text:0056A1DC mov eax, [ebp+var_B4]
处将一个内存地址复制给EAX,查看所指向的内容,发现其中内容为:
EAX 000000000208950C debug047:0208950C
0208950C 4F 4E 4C 49 4E 45 44 4F 57 4E 45 5F 32 30 33 35 ONLINEDOWNE_2035
0208951C 35 00 00 00 00 00 00 00 91 96 08 02 B0 04 02 00 5...............
回想Token字符串的组织形式,发觉其与MD5非常类似,因此猜测Token的生成算法可能是MD5,使用MD5对如上的字符串ONLINEDOWNE_20355
做散列运算,得到字符串9b89c16eeafa0faf120b0f9b78294677
,与获取下载地址的HTTP请求中的Token一致!猜测正确!Nice!
4. 结论:
Token生成算法为 MD5(“ONLINEDOWNE_” + SoftID),SoftID为对应的软件ID。
现在剩下的问题为查找SoftID的存储位置。
4.1 静态查找
在IDA字符串窗口中搜索”20355” 的Unicode字符串与Ascii字符串,无果。
使用 20355 的十六进制 0x4F83在二进制文件中搜索 83 4F和4F 83,有结果,但是结果排查发现,其基本与发送请求不相关。
4.2 动态查找
使用IDA调试运行程序,然后在内存中搜索32 00 30 00 33 00 35 00
。
有意外发现:下载的文件名中就包含有SoftID…譬如:腾讯[email protected]
中的20355。
为了排除文件名对内存搜索的影响,复制一份二进制文件,并修改文件名,去除掉其中的20355,另开一个IDA,调试运行此二进制文件。
结果发现:无论怎么运行,该二进制文件都显示无法获取下载信息的错误,费解… 使用改名前的二进制文件运行,无任何问题… 此时开始怀疑猜测下载器是从文件名中获取参数信息的。
为验证此猜测,对文件名进行修改,并运行查看是否能获取下载信息,测试结果如下:
FileName | Run Result |
---|---|
腾讯[email protected] | Fail |
腾讯[email protected] | Succ |
[email protected] | Succ |
从以上结果可见,仅当下载器的文件名构成为
描述信息 + “_” + webid + “@” + softid + “.exe”
且各参数有效时,下载器才能正常工作,这说明猜测是合理的:下载器从自己的文件名中获取下载参数信息。
5. 总结:
现在重新梳理下问题与结论:
Question:
XX下载器是如何获取下载连接地址的?
Answer:
使用类似如下链接的HTTP请求获取:
其中
webid
、softid
参数包含在下载器的文件名中,所处位置为:[email protected]。
token
参数为字符串"ONLINEDOWNE_"
加上 softid 后进行MD5散列运算后的值。
.
Question:
如何在不下载下载器的情况下获取一个软件的SoftID。
Answer:
XX软件站每个软件下载页面的URL中已包含相应的SoftID。
附录
附录A
通过SoftID获取下载链接的Python代码:
# -*- coding: utf-8 -*-
import urllib2
from cookielib import CookieJar
import md5
import re
import sys
softid = 20355
m = md5.new()
m.update("ONLINEDOWNE_%s" % softid)
token = m.hexdigest()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(CookieJar()))
URL = ‘http://www.onlinedown.net/api/index.php?action=pc.pconline.soft&webid=1&softid=%d&token=%s‘ % (softid , token)
try:
response = opener.open(URL)
content = response.read()
except Exception, e:
print "[Error]: Error in reading url content..."
print e
sys.exit()
try:
pattern = re.compile(r‘<downsrc><!\[CDATA\[(.*)\]\]></downsrc>‘)
match = re.match(pattern, content)
DownloadURLs = pattern.findall(content)
except Exception, e:
print "[Error]: Error in match urls..."
print e
sys.exit()
for url in DownloadURLs:
print url
版权声明:本文为博主原创文章,未经博主允许不得转载。