XX下载器分析过程

来源:互联网 发布:laravel mac 环境 编辑:程序博客网 时间:2024/05/22 10:28

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 下载器发送的第二个请求为:

http://www.onlinedown.net/api/index.php?action=pc.pconline.soft&webid=1&softid=20355&token=9b89c16eeafa0faf120b0f9b78294677

其返回的内容为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应该是个参数有效性验证的信息。

现在的问题归结为下载器如何生成softidtoken信息。

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_57FA04push    dword ptr [eax]push    offset aSoftid_0 ; "&softid="mov     eax, off_57F550push    dword ptr [eax]push    offset aToken_0 ; "&token="mov     eax, off_57FA04push    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:021794BC043EFDDC  0056AE38  .text:aToken_0043EFDE0  0214DF1C  debug031:0214DF1C043EFDE4  0056AE18  .text:aSoftid_0043EFDE8  0212B1C4  debug031:0212B1C4043EFDEC  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]所指位置的指针发生改变,从指针指向的结果字符串可得如下结论:

  1. [ebp+var_20]所存为指向字符串的指针。
  2. .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_20350208951C  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…譬如:腾讯QQ_1@20355.exe中的20355。

为了排除文件名对内存搜索的影响,复制一份二进制文件,并修改文件名,去除掉其中的20355,另开一个IDA,调试运行此二进制文件。
结果发现:无论怎么运行,该二进制文件都显示无法获取下载信息的错误,费解… 使用改名前的二进制文件运行,无任何问题… 此时开始怀疑猜测下载器是从文件名中获取参数信息的。

为验证此猜测,对文件名进行修改,并运行查看是否能获取下载信息,测试结果如下:

FileName Run Result 腾讯QQ_2@20355.exe Fail 腾讯QQ_1@20356.exe Succ Other_1@20355.exe Succ

从以上结果可见,仅当下载器的文件名构成为

描述信息 + “_” + webid + “@” + softid + “.exe”

且各参数有效时,下载器才能正常工作,这说明猜测是合理的:下载器从自己的文件名中获取下载参数信息

5. 总结:

现在重新梳理下问题与结论:

Question
XX下载器是如何获取下载连接地址的?

Answer
使用类似如下链接的HTTP请求获取:
http://www.onlinedown.net/api/index.php?action=pc.pconline.soft&webid=1&softid=20355&token=9b89c16eeafa0faf120b0f9b78294677
其中webidsoftid参数包含在下载器的文件名中,所处位置为:xxx_webid@softid.exe。
token参数为字符串 "ONLINEDOWNE_"加上 softid 后进行MD5散列运算后的值。

.

Question
如何在不下载下载器的情况下获取一个软件的SoftID。

Answer
XX软件站每个软件下载页面的URL中已包含相应的SoftID。

附录

附录A

通过SoftID获取下载链接的Python代码:

# -*- coding: utf-8 -*- import urllib2from cookielib import CookieJarimport md5import reimport syssoftid = 20355m = 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
1 0