我想,凡是来这儿的人都有一种潜意识,那就是软件的加密只是被软件作者用于保护他们的利益,和真正的软件的功能没有太大关系;我们解密只不过是让这些和我们作对的保护失去作用。当我们有了一套“有权”使用的软件后,如果它运行出错,我们通常要么将错误报告给作者,要么停止使用它。我们通常不会想到去改进软件,毕竟,我们没有源代码呀。

其实,没有源代码一样也可以改进。我们不妨将软件的保护看成一种对作者有用的Bug,我们可以去掉它,对于运行错误的Bug,我们不是一样可以去掉吗?前不久,有朋友想看以前的聊天记录,可是他忘了QQ密码,怎么也登陆不上,他的唯一选择就是用聊天记录察看器来看了。看着看着,突然程序弹出一个严重错误对话框,说发生严重错误,访问了非法地址。反复试了几遍都是一样。他问我有没有什么办法。我说报告作者吧,他说,作者似乎停止了此软件的开发。我抱着试试看的想法,用IDA 反汇编了这个小程序,然后在IDA中以调试的方式运行它,重复着朋友的操作。果然,错误再次出现,但是被IDA的调试器所捕获,它将我带到如下的函数和地址:

CODE:004855D8 _TQQMsgView_ShowOneMsg proc near        ; CODE XREF: _TQQMsgView_ShowMsgPage+148p
CODE:004855D8                                         ; DATA XREF: CODE:00483BC8o
CODE:004855D8
CODE:004855D8 var_8A4         = dword ptr -8A4h
CODE:004855D8 var_8A0         = dword ptr -8A0h
CODE:004855D8 var_89C         = dword ptr -89Ch
CODE:004855D8 var_898         = dword ptr -898h
CODE:004855D8 var_894         = dword ptr -894h
CODE:004855D8 var_890         = dword ptr -890h
CODE:004855D8 var_886         = byte ptr -886h
CODE:004855D8 var_85          = byte ptr -85h
CODE:004855D8 var_20          = dword ptr -20h
CODE:004855D8 var_1C          = dword ptr -1Ch
CODE:004855D8 var_18          = dword ptr -18h
CODE:004855D8 var_14          = dword ptr -14h
CODE:004855D8 var_10          = dword ptr -10h
CODE:004855D8 var_C           = dword ptr -0Ch
CODE:004855D8 var_8           = dword ptr -8
CODE:004855D8 var_4           = dword ptr -4
CODE:004855D8
CODE:004855D8                 push    ebp
CODE:004855D9                 mov     ebp, esp
CODE:004855DB                 add     esp, -8A4h
CODE:004855E1                 xor     ecx, ecx
CODE:004855E3                 mov     [ebp+var_8A4], ecx
CODE:004855E9                 mov     [ebp+var_8A0], ecx
CODE:004855EF                 mov     [ebp+var_89C], ecx
CODE:004855F5                 mov     [ebp+var_898], ecx
CODE:004855FB                 mov     [ebp+var_894], ecx
CODE:00485601                 mov     [ebp+var_890], ecx
CODE:00485607                 mov     [ebp+var_20], ecx
CODE:0048560A                 mov     [ebp+var_C], ecx
CODE:0048560D                 mov     [ebp+var_10], ecx
CODE:00485610                 mov     [ebp+var_8], edx
CODE:00485613                 mov     [ebp+var_4], eax
CODE:00485616                 xor     eax, eax
CODE:00485618                 push    ebp
CODE:00485619                 push    offset loc_485946
CODE:0048561E                 push    dword ptr fs:[eax]
CODE:00485621                 mov     fs:[eax], esp
CODE:00485624                 lea     eax, [ebp+var_85]
CODE:0048562A                 xor     ecx, ecx
CODE:0048562C                 mov     edx, 65h
CODE:00485631                 call    @Windows@FillMemory$qqrpvuiuc ; Windows::FillMemory(void *,uint,uchar)
CODE:00485636                 lea     eax, [ebp+var_886]
CODE:0048563C                 xor     ecx, ecx
CODE:0048563E                 mov     edx, 65h
CODE:00485643                 call    @Windows@FillMemory$qqrpvuiuc ; Windows::FillMemory(void *,uint,uchar)
CODE:00485648                 lea     ecx, [ebp+var_C]
CODE:0048564B                 lea     edx, [ebp+var_10]
CODE:0048564E                 mov     eax, [ebp+var_8]
CODE:00485651                 mov     eax, [eax]
CODE:00485653                 call    sub_485150
CODE:00485658                 cmp     ds:dword_488850, 0
CODE:0048565F                 jz      short loc_485677
CODE:00485661                 cmp     ds:dword_488850, 1
CODE:00485668                 jz      short loc_485677
CODE:0048566A                 cmp     ds:dword_488850, 4
CODE:00485671                 jnz     loc_48574D
CODE:00485677
CODE:00485677 loc_485677:                             ; CODE XREF: _TQQMsgView_ShowOneMsg+87j
CODE:00485677                                         ; _TQQMsgView_ShowOneMsg+90j
CODE:00485677                 mov     eax, [ebp+var_8]
CODE:0048567A                 add     eax, 5
CODE:0048567D                 mov     [ebp+var_14], eax
CODE:00485680                 mov     eax, [ebp+var_14]
CODE:00485683                 mov     eax, [eax]
CODE:00485685                 mov     [ebp+var_18], eax
CODE:00485688                 add     [ebp+var_14], 4
CODE:0048568C                 lea     eax, [ebp+var_85]
CODE:00485692                 mov     ecx, [ebp+var_18]
CODE:00485695                 mov     edx, [ebp+var_14]
CODE:00485698                 call    sub_407160      <---其实是内存复制函数,将其改成MemCopy
CODE:0048569D                 mov     eax, [ebp+var_18]
CODE:004856A0                 add     [ebp+var_14], eax
CODE:004856A3                 mov     eax, [ebp+var_14]
CODE:004856A6                 mov     eax, [eax]
CODE:004856A8                 mov     [ebp+var_18], eax
CODE:004856AB                 add     [ebp+var_14], 4
CODE:004856AF                 lea     eax, [ebp+var_886]
CODE:004856B5                 mov     ecx, [ebp+var_18]
CODE:004856B8                 mov     edx, [ebp+var_14]
CODE:004856BB                 call    sub_407160       <---其实是内存复制函数,将其改成MemCopy
CODE:004856C0                 mov     eax, [ebp+var_4]       <---在这里得到内存指针
CODE:004856C3                 mov     eax, [eax+318h]        <---在这条指令处出错!!!

程序在4856C3处出错。很显然,在4856C0得到的eax应该是一个内存指针,可是当我在[ebp+var_4]双击时,IDA将我带到的地方竟然是一堆聊天数据,极有可能是存放指针的地方被聊天数据覆盖了。但是如何证明这一点呢?往上看,有两个地方调用了同一个函数sub_407160,让我们进去看看,双击进去一看是这样的:
CODE:00407160 sub_407160      proc near               ; CODE XREF: sub_4596DC+3Cp
CODE:00407160                                         ; sub_4596DC+59p ...
CODE:00407160                 xchg    eax, edx
CODE:00407161                 call    @System@Move$qqrpxvpvi ; System::Move(void *,void *,int)
CODE:00407166                 retn
CODE:00407166 sub_407160      endp

很明显,这是一个内存复制函数,IDA的库函数识别可帮了我的大忙,这正是我一直坚持使用IDA作为调试器的原因之一。为了更直观,我将sub_407160改成MemCopy。(能将函数动态改名,多么酷的功能)。稍加分析,可以看出这个函数是以ecx作为要复制内存大小,而以eax作为复制目的地的。因此在CODE:004856BB处要将数据复制到var_886这个堆栈变量地址,虽然这个变量的大小从堆栈布局可以看出是0x801,2K多一字节,会不会是这个变量还是嫌小呢?我们在即将出错前在CODE:004856B8设断点,监视ecx是否超过0x801。果然,用不了几下,就发现ecx的值为0x8c0。哇,错误找到了--用于接受聊天数据的堆栈缓冲区太小,接下来我们要做的就是如何修改程序从而避免这个错误。

很容易想到,我们必须将var_886这个变量扩大,但是如何做到这一点呢?
在函数开始,有如下指令:
CODE:004855DB                 add     esp, -8A4h
它是用来为函数分配堆栈变量空间的,可以看出局部变量的总大小大约是2K多一点,只要我们在这里修改一下,改成add esp, -28a4h,就可以将函数的堆栈变量大小扩大8K,但仅仅这样改还是于事无补,所扩大的8K不会被函数使用到,var_886能用的空间还是原来那么大。于是我想到了下面的办法:
在CODE:004855DB处将指令改成add esp, -28a4h,从而将函数的堆栈变量大小扩大8K后,将var_886变量地址也向上移8K,这样做既可以保证var_886还是在可使用的堆栈空间内,又保证了它大约有7K大小的空间,比原先的2K大多了。这样改后,函数中所有对var_886的引用也要往上移动8K。这看起来好像不好改,其实用以下的办法大约1到2分钟就可以改好。
1. 用UltraEdit打开要改的可执行文件,从IDA中可以知道要改的指令在文件中的位置,先跳到那个位置。
2. 到了要改的地方后,你往往可以看到指令中出现的立即数,如改堆栈变量大小时,你可以看到5c f7 ff ff,这正是-8A4h。
   在IDA中,按Shift-?,弹出简易计算器,输入-0x28a4,可以知道它的16进制表示为5c d7 ff ff,因此只要将那个f7改成d7就可以了。
3.将所有对var_886的引用向上挪动8K。在IDA中找到对var_886使用的地方,并在UltraEditor中跳到相应的地方,只要将指令处的立即数作类似的修改就可以了。所有这些改动是很快的。

作完这些修改后,程序就正常了。这是我发现,原来有一条很长的聊天记录,长度超过2K。

这篇短文对程序的修改只是个个案,我觉得真正的意义在于说明,对于没有源代码的程序出现崩溃的情况,我们决不是只能望而兴叹,我们可以自己动手。