研究对象:Pocket CHM Pro 5.9
下载地址:自己搜索
软件介绍:这是用于在 CHM 格式创建帮助,电子读物或者手册的工具。袖珍的 CHM 能够直接打开和编辑 CHM 文件,和它能够反编译成批的 CHM。
涉及工具:OD,PEiD,stud_pe,WinHex

一、前言
最近在学习CAD的VBA,在明经通道上看到有在线的《ActiveX 和 VBA 参考》,想要CHM版的还要花30大洋,用OE把在线版的拉下来一看,乐了:居然正是完整的HTM版!自己编译成CHM版的不就OK了么!
但是正是这个编译过程让我郁闷了好半天。先找了个Microsoft HTML Help Workshop汉化版,一编译就出错;再找个国产软件QuickCHM试试看,编译出来的文件很多地方有问题,而且打开工程文件巨慢;最后找到这个PocketCHMPro,用起来感觉不错,但是未注册版只允许编译10个HTM页面。于是打算CRACK之!

二、开始CRACK:开始郁闷
先用PEiD查一下主程序PocketCHMPro.exe:Microsoft Visual C++ 6.0 [Overlay]到底还是2005年的东西,无壳,心中窃喜;进入输入注册码窗口,不输入任何东西直接点“OK”,弹出“User ID can not be empty”,居然还有错误提示,心中狂喜;直接用OD载入,查找字符串参考,没有找到任何有用的东西,字符串加密了?有点郁闷了;下断点“bp MessageBoxA”,没断下来,再下MessageBoxExA,GetDlgItemTextA,GetDlgItemTextExA之类的常用断点,还是没断下来,很郁闷了;直接暂停程序,再切换到程序界面,程序还在正常运行,查看一下任务管理器,居然还是双进程?相当郁闷了;任务管理器上显示新进程路径是“c:\Program Files\Pocket CHM Pro\PocketCHM_Pro.exe”,到目录下找到这个隐藏的主文件,直接运行弹出“程序执行错误”,极其郁闷了居然还是双进程保护!

三、双进程调试:继续郁闷
先用OD分析一下真正的主程序PocketCHM_Pro.exe,果然不出所料,所有关键字符串都已经加密,几乎没有调用标准的API,我所知道的常用断点全部失效,静态分析没有找到有用信息;由于是双进程保护,只好开二个OD,一个调试主程序,另一个附加到PocketCHM_Pro.exe进行调试,却发现动态调试找不到下手的地方。看着程序总是在MFC.dll和pceeuilib.dll中来回转,我却只能一愁莫展,心里这个郁闷呀!
浪费了一二个小时后突然想到:MFC程序处理字符串时都会用到MFC42.#800_CString::~CString,MFC42.#858_CString::operator=等之类的函数,用这个用断点会不会有用呢?于是查找MFC42.#800_CString::~CString参考并在IAT处的JMP直接下断:
0046AB10  jmp     dword ptr [<&MFC42.#800_CString::~CString>]      ;  MFC42.#800_CString::~CString
输入用户ID和注册码后点击“OK”,终于断下来了!
观察堆栈:
0012E9EC   0042E26C  返回到 PocketCH.0042E26C 来自 <jmp.&MFC42.#858_CString::operator=>
在0042E26C处下断,再执行完二个RET后来到注册码处理过程:

代码:
0041FA6F  push    eax
0041FA70  mov     byte ptr [ebp-4], 2
0041FA74  call    <jmp.&MFC42.#3874_CWnd::GetWindowTextA>      ;  获取用户名UN
0041FA79  lea     eax, dword ptr [ebp-18]
0041FA7C  lea     ecx, dword ptr [edi+A8]
0041FA82  push    eax
0041FA83  call    <jmp.&MFC42.#3874_CWnd::GetWindowTextA>      ;  获取用户输入序列号SN
0041FA88  lea     ecx, dword ptr [ebp-18]
0041FA8B  call    <jmp.&MFC42.#6282_CString::TrimLeft>
0041FA90  lea     ecx, dword ptr [ebp-18]
0041FA93  call    <jmp.&MFC42.#6283_CString::TrimRight>
0041FA98  mov     eax, dword ptr [ebp-18]
0041FA9B  cmp     dword ptr [eax-8], ebx                       ;  len(un)=0?
0041FA9E  jnz     0041FB8D
0041FAA4  lea     eax, dword ptr [ebp-30]
0041FAA7  mov     esi, 004AA940
0041FAAC  push    136
0041FAB1  push    eax
0041FAB2  mov     ecx, esi
0041FAB4  call    <DecodeString>                               ;  字符串解密过程
0041FAB9  push    eax                                          ;  "User ID can not be empty"
0041FABA  lea     ecx, dword ptr [ebp-10]
0041FABD  mov     byte ptr [ebp-4], 3
0041FAC1  call    <jmp.&MFC42.#858_CString::operator=>
0041FAC6  lea     ecx, dword ptr [ebp-30]
0041FAC9  mov     byte ptr [ebp-4], 2
0041FACD  call    <jmp.&MFC42.#800_CString::~CString>
0041FAD2  lea     eax, dword ptr [ebp-30]
0041FAD5  lea     ecx, dword ptr [ebp-144]
0041FADB  push    eax
0041FADC  mov     dword ptr [ebp-30], ebx
0041FADF  call    dword ptr [<&fspceelib.CExtBox::CExtBox>]    ;  fspceeli.CExtBox::CExtBox
0041FAE5  lea     ecx, dword ptr [ebp-20]
0041FAE8  mov     byte ptr [ebp-4], 4
0041FAEC  call    <jmp.&MFC42.#540_CString::CString>
0041FAF1  lea     eax, dword ptr [ebp-28]
0041FAF4  push    138
0041FAF9  push    eax
0041FAFA  mov     ecx, esi
0041FAFC  mov     byte ptr [ebp-4], 5
0041FB00  call    <DecodeString>                               ;  字符串解密过程
0041FB05  push    eax                                          ;  "Pocket CHM Pro"
0041FB06  lea     ecx, dword ptr [ebp-20]
0041FB09  mov     byte ptr [ebp-4], 6
0041FB0D  call    <jmp.&MFC42.#858_CString::operator=>
0041FB12  lea     ecx, dword ptr [ebp-28]
0041FB15  mov     byte ptr [ebp-4], 5
0041FB19  call    <jmp.&MFC42.#800_CString::~CString>
0041FB1E  lea     eax, dword ptr [ebp-28]
0041FB21  lea     ecx, dword ptr [ebp-144]
0041FB27  push    eax
0041FB28  lea     eax, dword ptr [ebp-38]
0041FB2B  push    eax
0041FB2C  lea     eax, dword ptr [ebp-34]
0041FB2F  push    eax
0041FB30  lea     eax, dword ptr [ebp-24]
0041FB33  push    eax
0041FB34  lea     eax, dword ptr [ebp-1C]
0041FB37  push    eax
0041FB38  lea     eax, dword ptr [edi+20]
0041FB3B  push    dword ptr [ebp-20]
0041FB3E  mov     dword ptr [ebp-28], ebx
0041FB41  mov     dword ptr [ebp-38], ebx
0041FB44  mov     dword ptr [ebp-34], 104
0041FB4B  push    dword ptr [ebp-10]
0041FB4E  mov     dword ptr [ebp-24], 4
0041FB55  mov     dword ptr [ebp-1C], ebx
0041FB58  push    eax                                          ;  弹出窗口
0041FB59  call    dword ptr [<&fspceelib.CExtBox::Message>]    ;  fspceeli.CExtBox::Message
0041FB5F  push    440
0041FB64  mov     ecx, edi
0041FB66  call    <jmp.&MFC42.#3092_CWnd::GetDlgItem>
0041FB6B  mov     ecx, eax
0041FB6D  call    <jmp.&MFC42.#5981_CWnd::SetFocus>
0041FB72  lea     ecx, dword ptr [ebp-20]
0041FB75  mov     byte ptr [ebp-4], 4
0041FB79  call    <jmp.&MFC42.#800_CString::~CString>
0041FB7E  mov     byte ptr [ebp-4], 2
0041FB82  lea     ecx, dword ptr [ebp-144]
0041FB88  jmp     0041FDA3
0041FB8D  lea     ecx, dword ptr [ebp-14]
0041FB90  call    <jmp.&MFC42.#6282_CString::TrimLeft>
0041FB95  lea     ecx, dword ptr [ebp-14]
0041FB98  call    <jmp.&MFC42.#6283_CString::TrimRight>
0041FB9D  mov     eax, dword ptr [ebp-14]
0041FBA0  cmp     dword ptr [eax-8], ebx                       ;  len(sn)=0?
0041FBA3  jnz     0041FC92
0041FBA9  lea     eax, dword ptr [ebp-1C]
0041FBAC  mov     esi, 004AA940
0041FBB1  push    137
0041FBB6  push    eax
0041FBB7  mov     ecx, esi
0041FBB9  call    <DecodeString>                               ;  字符串解密过程
0041FBBE  push    eax                                          ;  "Serial Number can not be empty"
0041FBBF  lea     ecx, dword ptr [ebp-10]
0041FBC2  mov     byte ptr [ebp-4], 7
0041FBC6  call    <jmp.&MFC42.#858_CString::operator=>
0041FBCB  lea     ecx, dword ptr [ebp-1C]
0041FBCE  mov     byte ptr [ebp-4], 2
0041FBD2  call    <jmp.&MFC42.#800_CString::~CString>
0041FBD7  lea     eax, dword ptr [ebp-1C]
0041FBDA  lea     ecx, dword ptr [ebp-144]
0041FBE0  push    eax
0041FBE1  mov     dword ptr [ebp-1C], ebx
0041FBE4  call    dword ptr [<&fspceelib.CExtBox::CExtBox>]    ;  fspceeli.CExtBox::CExtBox
0041FBEA  lea     ecx, dword ptr [ebp-20]
0041FBED  mov     byte ptr [ebp-4], 8
0041FBF1  call    <jmp.&MFC42.#540_CString::CString>
0041FBF6  lea     eax, dword ptr [ebp-24]
0041FBF9  push    138
0041FBFE  push    eax
0041FBFF  mov     ecx, esi
0041FC01  mov     byte ptr [ebp-4], 9
0041FC05  call    <DecodeString>
0041FC0A  push    eax                                          ;  "Pocket CHM Pro"
0041FC0B  lea     ecx, dword ptr [ebp-20]
0041FC0E  mov     byte ptr [ebp-4], 0A
0041FC12  call    <jmp.&MFC42.#858_CString::operator=>
0041FC17  lea     ecx, dword ptr [ebp-24]
0041FC1A  mov     byte ptr [ebp-4], 9
0041FC1E  call    <jmp.&MFC42.#800_CString::~CString>
0041FC23  lea     eax, dword ptr [ebp-24]
0041FC26  lea     ecx, dword ptr [ebp-144]
0041FC2C  push    eax
0041FC2D  lea     eax, dword ptr [ebp-34]
0041FC30  push    eax
0041FC31  lea     eax, dword ptr [ebp-38]
0041FC34  push    eax
0041FC35  lea     eax, dword ptr [ebp-28]
0041FC38  push    eax
0041FC39  lea     eax, dword ptr [ebp-30]
0041FC3C  push    eax
0041FC3D  lea     eax, dword ptr [edi+20]
0041FC40  push    dword ptr [ebp-20]
0041FC43  mov     dword ptr [ebp-24], ebx
0041FC46  mov     dword ptr [ebp-34], ebx
0041FC49  mov     dword ptr [ebp-38], 104
0041FC50  push    dword ptr [ebp-10]
0041FC53  mov     dword ptr [ebp-28], 4
0041FC5A  mov     dword ptr [ebp-30], ebx
0041FC5D  push    eax                                          ;  弹出窗口
0041FC5E  call    dword ptr [<&fspceelib.CExtBox::Message>]    ;  fspceeli.CExtBox::Message
0041FC64  push    441
0041FC69  mov     ecx, edi
0041FC6B  call    <jmp.&MFC42.#3092_CWnd::GetDlgItem>
0041FC70  mov     ecx, eax
0041FC72  call    <jmp.&MFC42.#5981_CWnd::SetFocus>
0041FC77  lea     ecx, dword ptr [ebp-20]
0041FC7A  mov     byte ptr [ebp-4], 8
0041FC7E  call    <jmp.&MFC42.#800_CString::~CString>
0041FC83  mov     byte ptr [ebp-4], 2
0041FC87  lea     ecx, dword ptr [ebp-144]
0041FC8D  jmp     0041FDA3
0041FC92  push    ecx
0041FC93  lea     eax, dword ptr [ebp-14]
0041FC96  mov     ecx, esp
0041FC98  mov     dword ptr [ebp-3C], esp
0041FC9B  push    eax
0041FC9C  call    <jmp.&MFC42.#535_CString::CString>
0041FCA1  push    ecx
0041FCA2  lea     eax, dword ptr [ebp-18]
0041FCA5  mov     ecx, esp
0041FCA7  mov     dword ptr [ebp-40], esp
0041FCAA  push    eax
0041FCAB  mov     byte ptr [ebp-4], 0B
0041FCAF  call    <jmp.&MFC42.#535_CString::CString>
0041FCB4  mov     esi, 004A9DD8
0041FCB9  mov     byte ptr [ebp-4], 2
0041FCBD  mov     ecx, esi
0041FCBF  call    0045C7DC
0041FCC4  mov     ecx, esi
0041FCC6  call    0045D658                                     ;  加密用户名注册码并保存到注册表
0041FCCB  lea     eax, dword ptr [ebp-1C]
0041FCCE  mov     esi, 004AA940
0041FCD3  push    135
0041FCD8  push    eax
0041FCD9  mov     ecx, esi
0041FCDB  call    <DecodeString>                               ;  字符串解密过程
0041FCE0  push    eax                                          ;  "Thank you!",LF,LF,"Please restart the program, in order to",LF,"activate all functions for you!"
0041FCE1  lea     ecx, dword ptr [ebp-10]
0041FCE4  mov     byte ptr [ebp-4], 0C
0041FCE8  call    <jmp.&MFC42.#858_CString::operator=>
0041FCED  lea     ecx, dword ptr [ebp-1C]
0041FCF0  mov     byte ptr [ebp-4], 2
0041FCF4  call    <jmp.&MFC42.#800_CString::~CString>
0041FCF9  lea     eax, dword ptr [ebp-1C]
0041FCFC  lea     ecx, dword ptr [ebp-23C]
0041FD02  push    eax
0041FD03  mov     dword ptr [ebp-1C], ebx
0041FD06  call    dword ptr [<&fspceelib.CExtBox::CExtBox>]    ;  fspceeli.CExtBox::CExtBox
0041FD0C  lea     ecx, dword ptr [ebp-2C]
0041FD0F  mov     byte ptr [ebp-4], 0D
0041FD13  call    <jmp.&MFC42.#540_CString::CString>
0041FD18  lea     eax, dword ptr [ebp-24]
0041FD1B  push    138
0041FD20  push    eax
0041FD21  mov     ecx, esi
0041FD23  mov     byte ptr [ebp-4], 0E
0041FD27  call    <DecodeString>                               ;  字符串解密过程
0041FD2C  push    eax                                          ;  "Pocket CHM Pro"
0041FD2D  lea     ecx, dword ptr [ebp-2C]
0041FD30  mov     byte ptr [ebp-4], 0F
0041FD34  call    <jmp.&MFC42.#858_CString::operator=>
0041FD39  lea     ecx, dword ptr [ebp-24]
0041FD3C  mov     byte ptr [ebp-4], 0E
0041FD40  call    <jmp.&MFC42.#800_CString::~CString>
0041FD45  lea     eax, dword ptr [ebp-48]
0041FD48  lea     ecx, dword ptr [ebp-23C]
0041FD4E  push    eax
0041FD4F  lea     eax, dword ptr [ebp-4C]
0041FD52  push    eax
0041FD53  lea     eax, dword ptr [ebp-44]
0041FD56  push    eax
0041FD57  lea     eax, dword ptr [ebp-40]
0041FD5A  push    eax
0041FD5B  lea     eax, dword ptr [ebp-3C]
0041FD5E  push    eax
0041FD5F  lea     eax, dword ptr [edi+20]
0041FD62  push    dword ptr [ebp-2C]
0041FD65  mov     dword ptr [ebp-48], ebx
0041FD68  mov     dword ptr [ebp-4C], ebx
0041FD6B  mov     dword ptr [ebp-44], 106
0041FD72  push    dword ptr [ebp-10]
0041FD75  mov     dword ptr [ebp-40], 4
0041FD7C  mov     dword ptr [ebp-3C], ebx
0041FD7F  push    eax                                          ;  弹出窗口
0041FD80  call    dword ptr [<&fspceelib.CExtBox::Message>]    ;  fspceeli.CExtBox::Message
0041FD86  mov     ecx, edi
0041FD88  call    <jmp.&MFC42.#4853_CDialog::OnOK>
0041FD8D  lea     ecx, dword ptr [ebp-2C]
0041FD90  mov     byte ptr [ebp-4], 0D
0041FD94  call    <jmp.&MFC42.#800_CString::~CString>
0041FD99  mov     byte ptr [ebp-4], 2
0041FD9D  lea     ecx, dword ptr [ebp-23C]
0041FDA3  call    dword ptr [<&fspceelib.CExtBox::~CExtBox>]   ;  fspceeli.CExtBox::~CExtBox
0041FDA9  lea     ecx, dword ptr [ebp-14]
0041FDAC  mov     byte ptr [ebp-4], 1
0041FDB0  call    <jmp.&MFC42.#800_CString::~CString>
0041FDB5  lea     ecx, dword ptr [ebp-18]
0041FDB8  mov     byte ptr [ebp-4], bl
0041FDBB  call    <jmp.&MFC42.#800_CString::~CString>
0041FDC0  or      dword ptr [ebp-4], FFFFFFFF
0041FDC4  lea     ecx, dword ptr [ebp-10]
0041FDC7  call    <jmp.&MFC42.#800_CString::~CString>
0041FDCC  mov     ecx, dword ptr [ebp-C]
0041FDCF  pop     edi
0041FDD0  pop     esi
0041FDD1  mov     dword ptr fs:[0], ecx
0041FDD8  pop     ebx
0041FDD9  leave
0041FDDA  retn
其中0041FCC6处的call    0045D658为加密用户名注册码并保存到注册表的过程,分别将长度信息、用户名和密码加密后保存在HKCU\Software\Microsoft\Basic\Library\Utility中的nd、fx和xf中,然后重启验证注册码是否正确。

得知程序的弹出窗口都是用的fspceeli.CExtBox::Message函数来执行的,就可以爆破很多注册限制的地方了。
但是引导程序每次执行时都会重新生成主程序,这样爆破也就无效了,看来还是要从头分析找到引导程序与主程序之间的关系了。

四、分析引导程序:找到眉目

干脆还是从头开始分析引导程序PocketCHMPro.exe吧:
代码:
0042EF18 >push    ebp                                    ; 程序入口
0042EF19  mov     ebp, esp
0042EF1B  push    -1
0042EF1D  push    00401458
0042EF22  push    <jmp.&MSVCRT._except_handler3>
……一直单步往下走
0042F03C  push    eax
0042F03D  push    esi
0042F03E  push    ebx
0042F03F  push    ebx
0042F040  call    dword ptr [<&KERNEL32.GetModuleHandleA>; kernel32.GetModuleHandleA
0042F046  push    eax
0042F047  call    0042C667                               ; 后面就退出了,这里跟进
0042F04C  mov     dword ptr [ebp-68], eax
0042F04F  push    eax
0042F050  call    dword ptr [<&MSVCRT.exit>]             ; msvcrt.exit

0042C667  push    ebp
0042C668  mov     ebp, esp
0042C66A  sub     esp, 20
0042C66D  push    ebx
0042C66E  push    esi
0042C66F  mov     esi, 0042F2A8
0042C674  push    edi
0042C675  mov     ecx, esi
0042C677  call    0042C39B                               ; 关联文件
0042C67C  push    dword ptr [ebp+10]
0042C67F  call    <jmp.&MSVCRT.strlen>
0042C684  pop     ecx
0042C685  mov     edi, eax
0042C687  mov     ecx, esi
0042C689  call    0042C368                               ; 删除旧文件,解压缩数据并写新文件
0042C68E  mov     ebx, eax
0042C690  test    ebx, ebx
0042C692  je      short 0042C6D7                         ; 写新文件出错时不跳,这里跳过
……
0042C6D7  test    edi, edi
0042C6D9  jnz     short 0042C6E4
0042C6DB  mov     ecx, esi                               ; PocketCH.0042F2A8
0042C6DD  call    0042BB20                               ; 执行新建立的文件并监控等待程序退出
0042C6E2  jmp     short 0042C6EE
0042C6E4  push    dword ptr [ebp+10]
0042C6E7  mov     ecx, esi
0042C6E9  call    0042BCA2
0042C6EE  mov     esi, eax
0042C6F0  cmp     esi, -61
0042C6F3  jnz     short 0042C706
0042C6F5  push    10
0042C6F7  push    0
0042C6F9  push    004256B0                               ; ASCII "Failed to run.",LF,"Please get a new copy of this program."
0042C6FE  push    0
0042C700  call    dword ptr [<&USER32.MessageBoxA>]      ; USER32.MessageBoxA
0042C706  mov     eax, esi
0042C708  pop     edi
0042C709  pop     esi
0042C70A  pop     ebx
0042C70B  leave
0042C70C  retn    10
程序是用CreateProcessA来运行新进程,并用GetExitCodeProcess来监控程序退出,执行到这里时已经可以直接双击目录中的PocketCHM_Pro.exe来打开主程序了。
这里又有一个问题:是什么时候开始可以直接执行主程序的呢?再次CTRL+F2重新运行,每执行完一个API后双击一下目录中的PocketCHM_Pro.exe看看是否能运行,终于找到关键所在了:
代码:
0042EFD3  push    00402008                               ; [402008]=42BDDA
0042EFD8  push    00402000
0042EFDD  call    <jmp.&MSVCRT._initterm>                ; 根据堆栈数据跳转到42BDDA处执行

0042BDDA  call    0042BDE4

0042BDE4  mov     ecx, 0042F2A8
0042BDE9  jmp     0042BE09

0042BE09  mov     eax, 0042F13C
0042BE0E  call    <jmp.&MSVCRT._EH_prolog>
0042BE13  sub     esp, 10C
0042BE19  push    ebx
0042BE1A  push    esi
0042BE1B  mov     esi, ecx
0042BE1D  push    edi
0042BE1E  mov     dword ptr [ebp-14], esi
0042BE21  lea     edi, dword ptr [esi+C]
0042BE24  mov     ecx, edi
0042BE26  call    <jmp.&MFC42.#540_CString::CString>
0042BE2B  xor     ebx, ebx
0042BE2D  push    00423B70                               ; ASCII "PCHMPROM",映像名
0042BE32  mov     ecx, edi
0042BE34  mov     dword ptr [ebp-4], ebx
0042BE37  call    <jmp.&MFC42.#860_CString::operator=>
0042BE3C  push    5C
0042BE3E  call    <jmp.&MFC42.#823_operator new>
0042BE43  pop     ecx
0042BE44  mov     dword ptr [ebp-10], eax
0042BE47  cmp     eax, ebx
0042BE49  mov     byte ptr [ebp-4], 1
0042BE4D  je      short 0042BE62
0042BE4F  mov     edi, dword ptr [edi]
0042BE51  push    ebx
0042BE52  push    ebx
0042BE53  push    96000                                  ; 内存映像大小
0042BE58  push    edi
0042BE59  mov     ecx, eax
0042BE5B  call    0042E57D                               ; 用CreateFileMappingA建立子进程共享数据,关键,跟进
……
0042E71D  push    dword ptr [esi+C]
0042E720  call    <jmp.&MFC42.#521_CSingleLock::CSingleLock>   ; 锁定数据
0042E725  jmp     short 0042E729
0042E727  xor     eax, eax
0042E729  cmp     eax, edi
0042E72B  mov     byte ptr [ebp-4], 1
0042E72F  mov     dword ptr [esi+10], eax
0042E732  je      0042E80B
0042E738  push    -1
0042E73A  mov     ecx, eax
0042E73C  call    <jmp.&MFC42.#4167_CSingleLock::Lock>
0042E741  push    dword ptr [ebp+8]                            ; MapName="PCHMPROM"
0042E744  mov     eax, dword ptr [esi+28]
0042E747  neg     eax
0042E749  push    dword ptr [ebp+C]                            ; MaximumSizeLow=96000
0042E74C  lea     ecx, dword ptr [esi+40]
0042E74F  sbb     eax, eax
0042E751  push    edi                                          ; MaximumSizeHigh=0
0042E752  and     eax, ecx
0042E754  push    4                                            ; Protection=PAGE_READWRITE
0042E756  push    eax                                          ; pSecurity=00382690
0042E757  push    -1                                           ; hFile=FFFFFFFF
0042E759  call    dword ptr [<&KERNEL32.CreateFileMappingA>]   ; CreateFileMappingA建立内存映像
0042E75F  mov     ebx, dword ptr [<&KERNEL32.GetLastError>]    ; ntdll.RtlGetLastWin32Error
0042E765  mov     dword ptr [esi+24], eax                      ; 保存hFile
0042E768  call    ebx                                          ; 建立映像时是否出错?
0042E76A  cmp     eax, 0B7
0042E76F  jnz     short 0042E774
0042E771  mov     dword ptr [esi+14], edi
0042E774  mov     eax, dword ptr [esi+24]
0042E777  cmp     eax, edi
0042E779  je      short 0042E7F9
0042E77B  push    edi
0042E77C  push    edi
0042E77D  push    edi
0042E77E  push    6
0042E780  push    eax                                          ; hFile
0042E781  call    dword ptr [<&KERNEL32.MapViewOfFile>]        ; MapViewOfFile生成映像
0042E787  cmp     eax, edi
0042E789  mov     dword ptr [esi+20], eax                      ; 保存映像地址
0042E78C  je      short 0042E7F9
0042E78E  cmp     dword ptr [esi+14], edi                      ; 生成映像是否出错?
0042E791  je      short 0042E7B7
0042E793  mov     ebx, dword ptr [ebp+C]
0042E796  push    ebx
0042E797  push    edi
0042E798  push    eax
0042E799  call    <jmp.&MSVCRT.memset>
0042E79E  lea     eax, dword ptr [esi+1C]
0042E7A1  push    4
0042E7A3  push    eax
0042E7A4  mov     dword ptr [eax], ebx
0042E7A6  mov     eax, dword ptr [esi+20]
0042E7A9  add     eax, 4
0042E7AC  push    eax
0042E7AD  call    <jmp.&MSVCRT.memcpy>                         ; 写映像数据
0042E7B2  add     esp, 18
0042E7B5  jmp     short 0042E7C9
0042E80E  cmp     ecx, edi
0042E810  je      short 0042E817
0042E812  call    <jmp.&MFC42.#6307_CSingleLock::Unlock>       ; 解锁数据
0042E817  mov     ecx, dword ptr [ebp-C]
0042E81A  mov     eax, esi
0042E81C  pop     edi
0042E81D  pop     esi
0042E81E  pop     ebx
0042E81F  mov     dword ptr fs:[0], ecx
0042E826  leave
0042E827  retn    10

返回后继续用循环写映像数据:

0042BE82  mov     edi, 00422024                           ; 字符串初始地址
0042BE87  mov     eax, dword ptr [edi]                    ; 循环写映像数据
0042BE89  mov     ebx, dword ptr [edi-4]
0042BE8C  xor     ecx, ecx
0042BE8E  cmp     dword ptr [edi-8], 00423B6C
0042BE95  push    eax
0042BE96  push    eax
0042BE97  setne   cl
0042BE9A  mov     dword ptr [ebp-10], ecx
0042BE9D  call    <jmp.&MSVCRT.strlen>
0042BEA2  pop     ecx
0042BEA3  mov     ecx, dword ptr [esi+10]
0042BEA6  push    eax
0042BEA7  push    ebx
0042BEA8  push    dword ptr [ebp-10]
0042BEAB  call    0042EB58                                ; 继续向映像中写数据
0042BEB0  test    eax, eax
0042BEB2  jnz     short 0042BECF                          ; 写映像出错时不跳
0042BEB4  mov     ecx, dword ptr [esi+10]
0042BEB7  call    0042E8FF
0042BEBC  cmp     eax, 0E
0042BEBF  jnz     short 0042BECF
0042BEC1  push    0
0042BEC3  push    0
0042BEC5  push    00423B24                                ; ASCII "!!!ERROR_OUTOFMEMORY-1!!!-find 'remark203153' in your dsw's source code"
0042BECA  call    <jmp.&MFC42.#1200_AfxMessageBox>
0042BECF  add     edi, 0C                                 ; 取下一字符串
0042BED2  cmp     edi, 00423AB8                           ; ASCII "ork, we dream......"
0042BED8  jl      short 0042BE87                          ; 循环结束?
至此引导文件的执行过程已经分析完毕,再回忆一下执行步骤:
建立名为"PCHMPROM"的内存映像删除已存在的主程序解压缩数据并写入到新建立的主程序中执行新建立的文件并等待退出

五、分析主程序:登堂入室

然后再从头开始分析主程序PocketCHM_Pro.exe:
代码:
0046BB10 >push    ebp                              ;  程序入口
0046BB11  mov     ebp, esp
0046BB13  push    -1
0046BB15  push    004875B8
0046BB1A  push    <jmp.&MSVCRT._except_handler3>   ;  SE 处理程序安装

0046BC34  push    eax
0046BC35  push    esi
0046BC36  push    ebx
0046BC37  push    ebx                                        ; /pModule
0046BC38  call    dword ptr [<&KERNEL32.GetModuleHandleA>]   ; \GetModuleHandleA
0046BC3E  push    eax
0046BC3F  call    0046BECA                                   ;  后面就退出了,这里跟进
0046BC44  mov     dword ptr [ebp-68], eax
0046BC47  push    eax                                        ; /status
0046BC48  call    dword ptr [<&MSVCRT.exit>]                 ; \exit

在0046BC3F  |.  >call    0046BECA处跟进:

0046BECA  /$  >push    dword ptr [esp+10]
0046BECE  |.  >push    dword ptr [esp+10]
0046BED2  |.  >push    dword ptr [esp+10]
0046BED6  |.  >push    dword ptr [esp+10]
0046BEDA  |.  >call    <jmp.&MFC42.#1576_AfxWinMain>    ;  先跟进MFC里看看吧
0046BEDF  \.  >retn    10

73D3CF2B >mov     edi, edi
73D3CF2D  push    ebx
73D3CF2E  push    esi
73D3CF2F  push    edi
73D3CF30  or      ebx, FFFFFFFF
73D3CF33  call    #1175_AfxGetThread
73D3CF38  mov     esi, eax
73D3CF3A  call    #1168_AfxGetModuleState
73D3CF3F  push    dword ptr [esp+1C]
73D3CF43  mov     edi, dword ptr [eax+4]
73D3CF46  push    dword ptr [esp+1C]
73D3CF4A  push    dword ptr [esp+1C]
73D3CF4E  push    dword ptr [esp+1C]
73D3CF52  call    #1575_AfxWinInit
73D3CF57  test    eax, eax
73D3CF59  je      short 73D3CF97
73D3CF5B  test    edi, edi
73D3CF5D  je      short 73D3CF6D
73D3CF5F  mov     eax, dword ptr [edi]
73D3CF61  mov     ecx, edi
73D3CF63  call    dword ptr [eax+8C]               ; <jmp.&MFC42.#3922_CWinApp::InitApplication>
73D3CF69  test    eax, eax
73D3CF6B  je      short 73D3CF97
73D3CF6D  mov     eax, dword ptr [esi]
73D3CF6F  mov     ecx, esi
73D3CF71  call    dword ptr [eax+58]               ; PocketCH.0042682D这里又跳回程序了

0042686B  push    eax
0042686C  call    0042E7F7                                       ;  GetUserNameA读取计算机用户名
00426871  push    eax
00426872  lea     ecx, dword ptr [esi+D5C]
00426878  mov     byte ptr [ebp-4], 1
0042687C  call    <jmp.&MFC42.#858_CString::operator=>
00426881  and     byte ptr [ebp-4], 0
00426885  lea     ecx, dword ptr [ebp-14]
00426888  call    <jmp.&MFC42.#800_CString::~CString>
0042688D  lea     eax, dword ptr [ebp-14]
00426890  mov     ecx, ebx
00426892  push    eax
00426893  call    0042E775                                       ;  GetComputerNameA读取计算机名
00426898  push    eax
00426899  lea     ecx, dword ptr [esi+D60]
0042689F  mov     byte ptr [ebp-4], 2
004268A3  call    <jmp.&MFC42.#858_CString::operator=>
004268A8  and     byte ptr [ebp-4], 0
004268AC  lea     ecx, dword ptr [ebp-14]
004268AF  call    <jmp.&MFC42.#800_CString::~CString>
004268B4  mov     ecx, esi
004268B6  call    0042920B                                       ;  注册算法过程,想研究算法的可以跟进
004268BB  mov     edi, 004A9DD8
004268C0  mov     ecx, edi
004268C2  call    0045CF1D                                      ;  读注册表存的日期,并回写当前日期
004268C7  mov     ecx, edi
004268C9  call    0045D6A1                                      ;  减出试用剩下的秒数
004268CE  mov     eax, dword ptr [4A9DF4]
004268D3  xor     edx, edx
004268D5  mov     ecx, 15180                                    ;  86400
004268DA  div     ecx                                           ;  EAX=试用剩下的天数
004268DC  mov     ecx, ebx
004268DE  mov     dword ptr [esi+D2C], eax
004268E4  lea     eax, dword ptr [ebp-44]
004268E7  push    eax
004268E8  call    0042CE7F                                      ;  GetSystemMetrics
004268ED  mov     ecx, dword ptr [eax]
004268EF  mov     dword ptr [esi+D20], ecx
004268F5  mov     ecx, esi
004268F7  mov     eax, dword ptr [eax+4]
004268FA  mov     dword ptr [esi+D24], eax
00426900  call    <jmp.&MFC42.#2621_CWinApp::Enable3dControls>
00426905  push    dword ptr [4AA104]
0042690B  lea     ecx, dword ptr [ebp-24]
0042690E  call    <jmp.&MFC42.#860_CString::operator=>
00426913  lea     ecx, dword ptr [ebp-24]
00426916  call    <jmp.&MFC42.#4204_CString::MakeUpper>
0042691B  push    004A6460                                      ;  ASCII "PCHMPROM"
00426920  lea     ecx, dword ptr [ebp-30]
00426923  call    <jmp.&MFC42.#537_CString::CString>
00426928  push    ecx
00426929  lea     eax, dword ptr [ebp-30]
0042692C  mov     ecx, esp
0042692E  mov     dword ptr [ebp-20], esp
00426931  push    eax
00426932  mov     byte ptr [ebp-4], 3
00426936  call    <jmp.&MFC42.#535_CString::CString>
0042693B  mov     ecx, esi
0042693D  call    004276DE                                      ;  用CreateFileMapping读取引导程序的数据
00426942  mov     ecx, esi
00426944  call    004290CA                                      ;  如果前面读取失败这里会异常退出
这里有几个比较重要的过程:
004268B6  call    0042920B;  注册算法过程,想研究算法的可以跟进
004268C2  call    0045CF1D;  读注册表存的日期,并回写当前日期
004268C9  call    0045D6A1;  减出试用剩下的秒数
0042693D  call    004276DE;  用CreateFileMapping读取引导程序的数据

其中注册算法过程太烦人了,研究了一下,放弃了;读写注册表和算出试用期很简单,略过;重点看看是如何读取引导程序数据的:
代码:
……
00427711  push    96000                                         ;  映像大小=96000
00427716  mov     ecx, eax
00427718  push    dword ptr [ebp+8]                             ;  映像名="PCHMPROM"
0042771B  call    00469E4C                                      ;  读取映像数据

……
0046A00B  push    -1
0046A00D  mov     ecx, eax
0046A00F  call    <jmp.&MFC42.#4167_CSingleLock::Lock>          ;  锁定数据
0046A014  push    dword ptr [ebp+8]                             ; /MapName
0046A017  mov     eax, dword ptr [esi+28]                       ; |
0046A01A  neg     eax                                           ; |
0046A01C  push    dword ptr [ebp+C]                             ; |MaximumSizeLow
0046A01F  lea     ecx, dword ptr [esi+40]                       ; |
0046A022  sbb     eax, eax                                      ; |
0046A024  push    edi                                           ; |MaximumSizeHigh
0046A025  and     eax, ecx                                      ; |
0046A027  push    ebx                                           ; |Protection
0046A028  push    eax                                           ; |pSecurity
0046A029  push    -1                                            ; |hFile = FFFFFFFF
0046A02B  call    dword ptr [<&KERNEL32.CreateFileMappingA>]    ; \建立映像
0046A031  mov     edi, dword ptr [<&KERNEL32.GetLastError>]     ;  ntdll.RtlGetLastWin32Error
0046A037  mov     dword ptr [esi+24], eax
0046A03A  call    edi                                           ; [GetLastError
0046A03C  cmp     eax, 0B7                                      ;  映像已经存在?
0046A041  jnz     short 0046A047
0046A043  and     dword ptr [esi+14], 0                         ;  映像已经存在时置标志位
0046A047  mov     eax, dword ptr [esi+24]
0046A04A  xor     ecx, ecx
0046A04C  cmp     eax, ecx
0046A04E  je      0046A0F9
0046A054  push    ecx                                           ; /MapSize => 0
0046A055  push    ecx                                           ; |OffsetLow => 0
0046A056  push    ecx                                           ; |OffsetHigh => 0
0046A057  push    6                                             ; |AccessMode = 6
0046A059  push    eax                                           ; |hMapObject
0046A05A  call    dword ptr [<&KERNEL32.MapViewOfFile>]         ; \读取映像
0046A060  mov     edx, eax
0046A062  test    edx, edx
0046A064  mov     dword ptr [esi+20], edx
0046A067  je      0046A0F9
0046A06D  cmp     dword ptr [esi+14], 0                         ;  映像是否存在?
0046A071  je      short 0046A098
0046A073  mov     ecx, dword ptr [ebp+C]                        ;  映像不存在时:
0046A076  mov     edi, edx
0046A078  mov     edx, ecx
0046A07A  xor     eax, eax
0046A07C  shr     ecx, 2
0046A07F  rep     stos dword ptr es:[edi]                       ;  把映像数据全部置为0
0046A081  mov     ecx, edx
0046A083  push    ebx
0046A084  and     ecx, 3
0046A087  rep     stos byte ptr es:[edi]
0046A089  lea     eax, dword ptr [esi+1C]
0046A08C  mov     ecx, edx
0046A08E  push    eax
0046A08F  mov     dword ptr [eax], ecx
0046A091  mov     eax, dword ptr [esi+20]
0046A094  add     eax, ebx
0046A096  jmp     short 0046A0A0
0046A098  add     edx, 4
0046A09B  push    ebx
0046A09C  push    edx
0046A09D  lea     eax, dword ptr [esi+1C]
0046A0A0  push    eax                                           ; |dest
0046A0A1  call    <jmp.&MSVCRT.memcpy>                          ; \读映像的4-7字节
六、转单进程:胜利在望

分析完主程序可以发现,程序从引导程序创建的名为"PCHMPROM"的内存映像中读取数据,如果映像不存在程序就拒绝执行。那么转单进程的方法就是把引导程序创建的内存映像作为一个区段添加到主程序中,在主程序读取映像时直接指向这个区段,即可完全转单进程。
具体方法为:

1.在OD创建好内存映像后,把内存映像另存为mem.bin文件,用PE工具打开主程序PocketCHM_Pro.exe,我这里用的工具是Stud_PE国庆版,选择“新建区段”,区段名为“.mem”,原始大小和虚拟大小都设为96000,选择“区段来自文件”并指定刚才另存出来的内存映像文件mem.bin,保存退出。

2.用OD打开已添加区段的主程序,按CTRL+G,填入46A014来到建立内存映像的位置,修改以下代码:
代码:
0046A014      B8 00505800   mov     eax, 00585000                ;  .mem区段的首地址
0046A019      8366 14 00    and     dword ptr [esi+14], 0        ;  置标志位
0046A01D      EB 41         jmp     short 0046A060               ;  跳往建立完映像后的位置继续执行
3.直接F9测试一下,程序果然跑起来了!用16进制工具把修改的数据写到程序中。

七、尾声:暴破

程序的注册算法有点复杂,但是做完注册检测后只写了一个注册标志位,暴破起来就相当简单了:
找到注册算法过程中的这个位置并修改:
代码:
00429274  |.  8378 F8 0C       cmp     dword ptr [eax-8], 0C     ;  判断用户名注册码的长度
00429278      33FF             xor     edi, edi                  ;  //modify
0042927A      47               inc     edi                       ;  //modify
0042927B      EB 06            jmp     short 00429283            ;  //modify
0042927D  |.  8378 F8 18       cmp     dword ptr [eax-8], 18
00429281  |.  74 0B            je      short 0042928E
00429283  |>  89BB 280D0000    mov     dword ptr [ebx+D28], edi  ;  置标志位,为1时已注册
00429289  |.  E9 ED040000      jmp     0042977B
用16进制工具把标为modify的地方改好后试运行一下,主界面上的“Unregistered copy. Available trial days:30”已经没有了,菜单中的注册项也没有了,再测试一下编译CHM文件和“Read CHM on Your PDA”这些有限制的功能,没发现暗桩。至此已大功告成!

后记:
什么时候再静下心来分析一下算法吧。。。
用资源修改工具打开可以发现这个程序很多地方都有中文,是汉化?还是国产软件?迷惑ing...

by lelfei on 2008.4.29 14:30