发表于 2019-6-14 19:47

申请ID:thebutterfly

1、申请ID:thebutterfly
2、个人邮箱:u223110@163.com
3、原创精华文章:[原创](逆向)一个ReverseMe的完全分析
一个简单ReverseMe的完全分析

这个ReverseMe是<<加密与解密>>第2版P89提到的ReverseMe01.exe, 但是书上并没有给出完全分析, 在本文中给出.
阅读本文之前读者应当熟悉窗口程序的基本结构和基本API函数
为了方便新手阅读, 代码尽量保持良好的可读性, 这样做同时也是因为本文是采用纯静态反汇编分析的方法
文章很菜, 还望各位不吝赐教

一. 工欲善其事, 必先利其器
首先准备好工具, 这里我们需要3样工具, 他们是:
1. Stud_PE或PEiD...............获取欲逆向文件的第一手信息, 包括编写语言, 是否加壳等
2. IDA.........................强大的逆向工程工具, 把它用在单纯的破解上可以说是杀鸡用牛刀了
3. MSDN........................我们是新手, 有不懂的地方它永远是最好的老师

二. 知彼知己, 百战不殆

工具准备好了, 可以正式开始了, 不过在开始之前, 我们要问自己以下几个问题:
1.这个程序行为如何?
运行之, 出现一小窗口, 上有一按钮和一文本框, 按钮上有"not reversed"几个字, 单击按钮会弹出对话框"Ok, for now, mission failed".

2.这个程序是否加壳?是何语言编写?该语言编写的程序有什么特点?
Peid显示为无壳, 汇编语言编写, 汇编语言编写的程序反汇编结果基本与源码一致, 可读性很好.

3.这个程序用到了哪些API?各是干什么的?
用Peid查看其输入表, 结果如下, 我一一作简要的解释供参考, 详细解释请查阅Msdn
基本上都是很基本的窗口程序API

USER32.dll
    DestroyWindoword:141 rva: 00002010
    清除一个窗口并导致一个WM_DESTROY消息
    GetWindowTextAord:347 rva: 00002014
    获取窗口标题(或者按钮, 文本框)文本
    LoadCursorAord:407 rva: 00002018
    装载光标
    LoadIconAord:411 rva: 0000201C
    装载图标
    MessageBoxAord:443 rva: 00002020
    不用多说了, 大家都知道
    PostQuitMessageord:477 rva: 00002024
    在消息队列里加入一条WM_QUIT消息
    DispatchMessageAord:148 rva: 00002028
    向窗口过程分发消息, 窗口程序基本API之一
    GetMessageAord:296 rva: 0000202C
    取消息队列消息, 基本函数
    SetFocusord:555 rva: 00002030
    设置窗口焦点
    SetWindowTextAord:601 rva: 00002034
    设置窗口文本
    ShowWindoword:613 rva: 00002038
    设置窗口显隐状态
    TranslateMessageord:637 rva: 0000203C
    翻译消息
    UpdateWindoword:651 rva: 00002040
    刷新窗口
    DefWindowProcAord:131 rva: 00002044
    消息默认处理函数, 基本函数之一
    CreateWindowExAord:88 rva: 00002048
    建立窗口
    RegisterClassExAord:495 rva: 0000204C
    注册类
    SendMessageAord:528 rva: 00002050
    向窗口过程发送消息, 阻塞型函数
   
KERNEL32.dll
    GetModuleHandleAord:273 rva: 00002000
    获取模块句柄, 基本函数
    GetCommandLineAord:182 rva: 00002004
    获取命令行
    ExitProcessord:117 rva: 00002008
    退出进程

三. 实战

下面我们开始逆向之旅

我们目的是完全逆向这个程序, 由于程序很简单, 从入口来分析是合理的

         start         proc near
.text:00401000               push    NULL            ; lpModuleName
.text:00401002               call    GetModuleHandleA
.text:00401007               mov   hInstance, eax

.text:0040100C               call    GetCommandLineA

.text:00401011               push    SW_SHOWDEFAULT; nCmdShow
.text:00401013               push    lpCmdLine       ; lpCmdLine
.text:00401019               push    NULL            ; hPrevInstance
.text:0040101B               push    hInstance       ; hInstance
.text:00401021               call    WinMain

.text:00401026               push    eax             ; uExitCode
.text:00401027               call    ExitProcess
.text:00401027 start         endp

很标准的入口初始化过程: 首先获取本模块句柄, 然后获取命令行指针, 再启动WinMain主函数, 最后ExitProcess退出.
不知大家注意到没, 这个入口过程有一个小错误, 看出来了吗?就是那个lpCmdLine参数不正确, 应当push eax才对

我们看看WinMain里有什么

; int __stdcall WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPCSTR lpCmdLine,int nCmdShow)
.text:0040102C WinMain         proc near               ; CODE XREF: start+21p
.text:0040102C
.text:0040102C hWnd            = dword ptr -50h
.text:0040102C stMsg         = MSG ptr -4Ch
.text:0040102C stWndClassex    = WNDCLASSEXA ptr -30h
.text:0040102C hInstance       = dword ptr8
.text:0040102C hPrevInstance   = dword ptr0Ch
.text:0040102C lpCmdLine       = dword ptr10h
.text:0040102C nCmdShow      = dword ptr14h
.text:0040102C
.text:0040102C               push    ebp
.text:0040102D               mov   ebp, esp
.text:0040102F               add   esp, -50h

.text:00401032               mov   , SIZEOF WNDCLASSEX
.text:00401039               mov   , 3
.text:00401040               mov   , offset WndProc
.text:00401047               mov   , 0
.text:0040104E               mov   , 0
.text:00401055               push   
.text:00401058               pop   
.text:0040105B               mov   , 0Fh + 1h
.text:00401062               mov   , offset aWhatmenu ; "WhatMenu"
.text:00401069               mov   , offset ClassName ; "SupremeDickHead"

.text:00401070               push    IDI_APPLICATION ; lpIconName
.text:00401075               push    NULL            ; hInstance
.text:00401077               call    LoadIconA
.text:0040107C               mov   , eax
.text:0040107F               mov   , eax

.text:00401082               push    IDC_ARROW       ; lpCursorName
.text:00401087               push    NULL            ; hInstance
.text:00401089               call    LoadCursorA
.text:0040108E               mov   , eax

以上一段代码初始化窗口类, 类名为"SupremeDickHead", 窗口程序为WndProc, 由于反汇编代码已经最大程度的整理过了, 所以如果读者熟悉窗口程序的话应该很容易看明白, 我就不作解释了

.text:00401091               lea   eax,
.text:00401094               push    eax             ; WNDCLASSEXA *
.text:00401095               call    RegisterClassExA

注册窗口类

.text:0040109A               push    NULL            ; lpParam
.text:0040109C               push    ; hInstance
.text:0040109F               push    NULL            ; hMenu
.text:004010A1               push    NULL            ; hWndParent
.text:004010A3               push    0C8h            ; nHeight
.text:004010A8               push    12Ch            ; nWidth
.text:004010AD               push    0C8h            ; Y
.text:004010B2               push    0C8h            ; X
.text:004010B7               push    80C80000h       ; dwStyle
.text:004010BC               push    offset Caption; "ReverseMe #1"
.text:004010C1               push    offset ClassName ; "SupremeDickHead"
.text:004010C6               push    0               ; dwExStyle
.text:004010C8               call    CreateWindowExA
.text:004010CD               mov   , eax

创建窗口, 窗口标题为 "ReverseMe #1", 没有菜单

.text:004010D0               push    SW_SHOWNORMAL   ; nCmdShow
.text:004010D2               push          ; hWnd
.text:004010D5               call    ShowWindow

显示窗口

.text:004010DA               push          ; hWnd
.text:004010DD               call    UpdateWindow
.text:004010E2

刷新窗口

.text:004010E2 MessageLoop:                            ; CODE XREF: WinMain+DBj
.text:004010E2               push    0               ; wMsgFilterMax
.text:004010E4               push    0               ; wMsgFilterMin
.text:004010E6               push    NULL            ; hWnd
.text:004010E8               lea   eax,
.text:004010EB               push    eax             ; lpMsg
.text:004010EC               call    GetMessageA

.text:004010F1               or      eax, eax      ;收到WM_QUIT消息就退出
.text:004010F3               jz      short Exit

.text:004010F5               lea   eax,
.text:004010F8               push    eax             ; lpMsg
.text:004010F9               call    TranslateMessage

.text:004010FE               lea   eax,
.text:00401101               push    eax             ; lpMsg
.text:00401102               call    DispatchMessageA

.text:00401107               jmp   short MessageLoop

消息循环

.text:00401109
.text:00401109 Exit:                                 ; CODE XREF: WinMain+C7j
.text:00401109               mov   eax,
.text:0040110C               leave
.text:0040110D               retn    10h
.text:0040110D WinMain         endp

这个WinMain是非常标准的窗口程序主函数, 很可惜, 没有什么特别之处, 我就不多说了, 如果读者还是不理解原理请参考Windows程序设计相关书籍, 论坛置顶帖有介绍的

重点在WndProc函数处, 下面我们一起来看看这个函数

; int __stdcall WndProc(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam)
.text:00401110 WndProc         proc near               ; DATA XREF: WinMain+14o
.text:00401110
.text:00401110 hWnd            = dword ptr8
.text:00401110 Msg             = dword ptr0Ch
.text:00401110 wParam          = dword ptr10h
.text:00401110 lParam          = dword ptr14h
.text:00401110
.text:00401110               push    ebp
.text:00401111               mov   ebp, esp
.text:00401113               cmp   , WM_DESTROY
.text:00401117               jnz   short loc_401125
      ;if (Msg == WM_DESTROY)
.text:00401119               push    0               ; nExitCode
.text:0040111B               call    PostQuitMessage
.text:00401120               jmp   return

如果窗口关闭则发送退出消息使主程序退出消息循环

.text:00401125 loc_401125:                           ; CODE XREF: WndProc+7j
.text:00401125               cmp   , WM_CREATE
.text:00401129               jnz   short loc_4011A1
      else if   (Msg == WM_CREATE)
.text:0040112B               push    0               ; lpParam
.text:0040112D               push    hInstance       ; hInstance
.text:00401133               push    ID_EDIT         ; nControlID
.text:00401135               push          ; hWndParent
.text:00401138               push    19h             ; nHeight
.text:0040113A               push    0C8h            ; nWidth
.text:0040113F               push    23h             ; Y
.text:00401141               push    32h             ; X
.text:00401143               push    50800080h       ; dwStyle
.text:00401148               push    NULL            ; lpWindowName
.text:0040114A               push    offset aEdit    ; "edit"
.text:0040114F               push    WS_EX_CLIENTEDGE ; dwExStyle
.text:00401154               call    CreateWindowExA
.text:00401159               mov   hEdit, eax

在窗口中创建一个Edit控件

.text:0040115E               push    hEdit         ; hWnd
.text:00401164               call    SetFocus

并设置焦点

.text:00401169               push    NULL            ; lpParam
.text:0040116B               push    hInstance       ; hInstance
.text:00401171               push    ID_BUTTON       ; nControlID
.text:00401173               push          ; hWndParent
.text:00401176               push    19h             ; nHeight
.text:00401178               push    8Ch             ; nWidth
.text:0040117D               push    46h             ; Y
.text:0040117F               push    4Bh             ; X
.text:00401181               push    50000001h       ; dwStyle
.text:00401186               push    offset aNotReversed ; "Not Reversed"
.text:0040118B               push    offset aButton; "button"
.text:00401190               push    0               ; dwExStyle
.text:00401192               call    CreateWindowExA
.text:00401197               mov   hButton, eax

在文本编辑控件的下面创建一个按钮

.text:0040119C               jmp   return

以上一段是处理WM_CREATE消息的代码, 亦即窗口初始化代码. 这段代码创建了一个文本编辑控件和一个按钮, 就是主程序上所看到的两个控件

.text:004011A1
.text:004011A1 loc_4011A1:                           ; CODE XREF: WndProc+19j
.text:004011A1               cmp   , WM_COMMAND
.text:004011A8               jnz   loc_401317
      else if (Msg == WM_COMMAND)

以下是处理WM_COMMAND消息的代码, 冗长复杂, 后面我还会详细分析

.text:004011AE               mov   eax,
.text:004011B1               cmp   , 0 ; Is this message from a control?
.text:004011B5               jnz   loc_4012F6
.text:004011BB               cmp   ax, ID_BUTTON   ; Yes, it is from a control
.text:004011BF               jnz   short loc_4011EA

.text:004011C1               push    offset String   ; "Edit Box Is Good Mmmmkay ?"
.text:004011C6               push    hEdit         ; hWnd
.text:004011CC               call    SetWindowTextA

.text:004011D1               push    0               ; lParam
.text:004011D3               push    '#'             ; wParam
.text:004011D5               push    WM_KEYDOWN      ; Msg
.text:004011DA               push    hEdit         ; hWnd
.text:004011E0               call    SendMessageA

.text:004011E5               jmp   return

.text:004011EA
.text:004011EA loc_4011EA:                           ; CODE XREF: WndProc+AFj
.text:004011EA               cmp   ax, ID_EDIT
.text:004011EE               jnz   short loc_401202
.text:004011F0               push    NULL            ; lpString
.text:004011F2               push    hEdit         ; hWnd
.text:004011F8               call    SetWindowTextA
.text:004011FD               jmp   return

这一段很有趣, 程序首先判断消息是否来自一个子窗口控件(什么?你问为什么?那么我问你, WM_COMMND消息的lParam是什么含义? 对, 发送消息的控件句柄, 如果不是来自控件这个参数就是0啦), 如果不是则分别处理消息来自按钮,文本控件和一个未知ID的情况, 如果消息来自按钮则把Edit控件的文本设置为:"Edit Box Is Good Mmmmkay ?", 然后向Edit控件发送一个WM_KEYDOWN消息. 如果消息来自Edit, 程序会清除Edit控件的内容
问题是, 明知消息不是来自控件却要处理消息来自控件的情况, 这是干什么? 还有....

.text:00401202
.text:00401202 loc_401202:                           ; CODE XREF: WndProc+DEj
.text:00401202               cmp   ax, ID_WHATID
.text:00401206               jnz   loc_4012EC

.text:0040120C               mov   eax, offset loc_40123B
.text:00401211               jmp   eax

ID_WHATID(=3)是什么控件的ID? 不得而知. 如果这里跳走的话就会结束程序(下面会调用DestroyWindow函数)
到现在已经有很多疑问了, 我们不管, 继续向下看,

.text:00401213               push    200h
.text:00401218               push    offset szTextBuffer ; lpString
.text:0040121D               push    hEdit         ; hWnd
.text:00401223               call    GetWindowTextA

.text:00401228               push    MB_OK         ; uType
.text:0040122A               push    offset Caption; "ReverseMe #1"
.text:0040122F               push    offset szTextBuffer ; lpText
.text:00401234               push    NULL            ; hWnd
.text:00401236               call    MessageBoxA

这一段代码是永远也不可能执行了(jmp eax), 它作用是获取Edit的内容并用一个MessageBox把获取的内容显示出来

.text:0040123B
.text:0040123B loc_40123B:                           ; DATA XREF: WndProc+FCo
.text:0040123B               push    MB_OK         ; uType
.text:0040123D               push    offset Caption; "ReverseMe #1"
.text:00401242               push    offset aOkayForNowMiss ; "Okay, for now, mission failed !"
.text:00401247               push    NULL            ; hWnd
.text:00401249               call    MessageBoxA

显示一个"Okay, for now, mission failed !"对话框, 就是我们运行程序时看到的

.text:0040124E               push    200h            ; nMaxCount
.text:00401253               push    offset szTextBuffer ; lpString
.text:00401258               push    hEdit         ; hWnd
.text:0040125E               call    GetWindowTextA
.text:00401263               mov   ecx, eax

这时ecx存放有字符个数, 因为GetWindowTextA返回值(eax)为字符个数

.text:00401265               xor   edx, edx      ; edx 清零
.text:00401267               or      ebx, 0FFFFFFFFh
.text:0040126A               inc   ebx             ; ebx 清零

.text:0040126B               mov   eax, offset szTextBuffer

让eax指向字符串
怎么?要做字符串处理了呀, 此时eax指向字符串, ebx, edx为0

.text:00401270
.text:00401270 loc_401270:                           ; CODE XREF: WndProc+183j
.text:00401270               mov   bl,
.text:00401272               cmp   byte ptr , 0
.text:00401275               jz      short loc_401295
.text:00401277               cmp   byte ptr , '0'
.text:0040127A               jl      short loc_401295
.text:0040127C               cmp   byte ptr , '9'
.text:0040127F               ja      short loc_401295

验证字符串合法性, 如果发现了空字符串或者有除了0~9之外的字符就提示错误

.text:00401281               add   edx, ebx      ;ebx和edx相加
.text:00401283               shl   edx, 14h      ;相加的结果左移20位
.text:00401286               add   edx, ecx      ;再和字符个数相加
.text:00401288               inc   eax
.text:00401289               add   ecx, -1      ;处理下一个字符
.text:0040128C               test    edx, edx
.text:0040128E               jnz   short loc_401291
.text:00401290               inc   edx      ;edx如果是0的话则设置为1
.text:00401291
.text:00401291 loc_401291:                           ; CODE XREF: WndProc+17Ej
.text:00401291               test    ecx, ecx      ;结束否?
.text:00401293               jnz   short loc_401270

以上一段算法可以用C语言描述为:

int i = NumberOfChars;
char* str = StringFromEditControl;
int tmp = 0;

do {
    tmp += (int) *str;
    tmp <<= 20;
    tmp += i;
    str++;
    i--;
    if (tmp == 0) tmp++;
}
while (i != 0);

.text:00401295
.text:00401295 loc_401295:                           ; CODE XREF: WndProc+165j
.text:00401295                                       ; WndProc+16Aj ...
.text:00401295               test    edx, 0      
.text:0040129B               jg      short loc_4012B6
.text:0040129D               jz      short loc_4012CB

.text:0040129F               push    MB_OK               ; uType
.text:004012A1               push    offset Caption; "ReverseMe #1"
.text:004012A6               push    offset aThatNotANumber ; "That not a number ! CHEATHER !"
.text:004012AB               push    NULL               ; hWnd
.text:004012AD               call    MessageBoxA
.text:004012B2               leave
.text:004012B3               retn    10h

test edx, 0 ??!! 这意味着jg永远不能执行而jz会执行, 不过实际上这两个跳转之后都有一句jmp到ret处, 实际上都一样

.text:004012B6
.text:004012B6 loc_4012B6:                           ; CODE XREF: WndProc+18Bj
.text:004012B6               jmp   short locret_4012E0

.text:004012B8               push    MB_OK
.text:004012BA               push    offset Caption; "ReverseMe #1"
.text:004012BF               push    offset aGoodNumber ; "Good Number"
.text:004012C4               push    NULL               ; hWnd
.text:004012C6               call    MessageBoxA
.text:004012CB
.text:004012CB loc_4012CB:                           ; CODE XREF: WndProc+18Dj
.text:004012CB               jmp   short locret_4012E0

.text:004012CD               push    MB_OK
.text:004012CF               push    offset Caption; "ReverseMe #1"
.text:004012D4               push    offset aBadNumber ; "Bad Number"
.text:004012D9               push    NULL               ; hWnd
.text:004012DB               call    MessageBoxA
.text:004012E0
.text:004012E0 locret_4012E0:                        ; CODE XREF: WndProc:loc_4012B6j
.text:004012E0                                       ; WndProc:loc_4012CBj
.text:004012E0               leave
.text:004012E1               retn    10h

.text:004012E4               pop   ebp
.text:004012E5               push    eax
.text:004012E6               leave
.text:004012E7               retn    10h

.text:004012EA               jmp   short return

.text:004012EC
.text:004012EC loc_4012EC:                           ; CODE XREF: WndProc+F6j
.text:004012EC               push          ; hWnd
.text:004012EF               call    DestroyWindow
.text:004012F4               jmp   short return

刚才提到过了

.text:004012F6
.text:004012F6 loc_4012F6:                           ; CODE XREF: WndProc+A5j
.text:004012F6               cmp   ax, ID_BUTTON
.text:004012FA               jnz   short loc_401315
.text:004012FC               shr   eax, 10h
.text:004012FF               or      ax, ax
.text:00401302               jnz   short loc_401315

.text:00401304               push    0               ; lParam
.text:00401306               push    ID_WHATID       ; wParam
.text:00401308               push    WM_COMMAND      ; Msg
.text:0040130D               push          ; hWnd
.text:00401310               call    SendMessageA

.text:00401315
.text:00401315 loc_401315:                           ; CODE XREF: WndProc+1EAj
.text:00401315                                       ; WndProc+1F2j
.text:00401315               jmp   short return

这里终于知道ID_WHATID的来源了, 原来是这个SendMessage函数!

.text:00401317
.text:00401317 loc_401317:                           ; CODE XREF: WndProc+98j
.text:00401317               push        ; lParam
.text:0040131A               push        ; wParam
.text:0040131D               push           ; Msg
.text:00401320               push          ; hWnd
.text:00401323               call    DefWindowProcA
.text:00401328               leave
.text:00401329               retn    10h

.text:0040132C
.text:0040132C return:                                 ; CODE XREF: WndProc+10j
.text:0040132C                                       ; WndProc+8Cj ...
.text:0040132C               xor   eax, eax
.text:0040132E               leave
.text:0040132F               retn    10h
.text:0040132F WndProc         endp

四. 回顾

程序结束了, 现在回过头来看看程序中的微妙之处.

程序首先用标准的方法建立了一个窗口, 在窗口函数中, 程序处理了WM_CREATE, WM_DESTROY和WM_COMMAND这3个消息. 在处理WM_CREATE时, 程序分别建立了一个Edit控件和一个按钮; 在WM_COMMAND消息的处理中, 如果程序侦测到消息是按钮发出的, 则以ID_WHATID(等于3)为WPARAM, 0为LPARAM向自身发送WM_COMMAND消息. 由于在WM_COMMAND消息处理的开头, 程序会判断消息的来源, 仅当消息不是从子窗口控件发出时才处理(004011AE和004011FD之间的代码实际上不会执行), 所以00401213到004012EA的这段代码(就是那段算法代码)名义上是处理ID为ID_WHATID的"控件"发出的消息, 实质上是按钮消息处理过程的延续而已
关于那段算法代码, 由于不管结果是多少最后都要和0做and运算, 结果毫无疑问都是0, 亦即那句jz将肯定会执行. 所以这段算法是没有多大意义的. 但是这个程序毕竟是ReverseMe, 就是要求大家把不正确的内容修改正确. 至于到底怎么修改就看大家自由发挥了.

明白了这些, 相信大家再看<<加密与解密>>第2版P89~P91内容时应该觉得不难了吧一个简单ReverseMe的完全分析

这个ReverseMe是<<加密与解密>>第2版P89提到的ReverseMe01.exe, 但是书上并没有给出完全分析, 在本文中给出.
阅读本文之前读者应当熟悉窗口程序的基本结构和基本API函数
为了方便新手阅读, 代码尽量保持良好的可读性, 这样做同时也是因为本文是采用纯静态反汇编分析的方法
文章很菜, 还望各位不吝赐教

一. 工欲善其事, 必先利其器
首先准备好工具, 这里我们需要3样工具, 他们是:
1. Stud_PE或PEiD...............获取欲逆向文件的第一手信息, 包括编写语言, 是否加壳等
2. IDA.........................强大的逆向工程工具, 把它用在单纯的破解上可以说是杀鸡用牛刀了
3. MSDN........................我们是新手, 有不懂的地方它永远是最好的老师

二. 知彼知己, 百战不殆

工具准备好了, 可以正式开始了, 不过在开始之前, 我们要问自己以下几个问题:
1.这个程序行为如何?
运行之, 出现一小窗口, 上有一按钮和一文本框, 按钮上有"not reversed"几个字, 单击按钮会弹出对话框"Ok, for now, mission failed".

2.这个程序是否加壳?是何语言编写?该语言编写的程序有什么特点?
Peid显示为无壳, 汇编语言编写, 汇编语言编写的程序反汇编结果基本与源码一致, 可读性很好.

3.这个程序用到了哪些API?各是干什么的?
用Peid查看其输入表, 结果如下, 我一一作简要的解释供参考, 详细解释请查阅Msdn
基本上都是很基本的窗口程序API

USER32.dll
    DestroyWindoword:141 rva: 00002010
    清除一个窗口并导致一个WM_DESTROY消息
    GetWindowTextAord:347 rva: 00002014
    获取窗口标题(或者按钮, 文本框)文本
    LoadCursorAord:407 rva: 00002018
    装载光标
    LoadIconAord:411 rva: 0000201C
    装载图标
    MessageBoxAord:443 rva: 00002020
    不用多说了, 大家都知道
    PostQuitMessageord:477 rva: 00002024
    在消息队列里加入一条WM_QUIT消息
    DispatchMessageAord:148 rva: 00002028
    向窗口过程分发消息, 窗口程序基本API之一
    GetMessageAord:296 rva: 0000202C
    取消息队列消息, 基本函数
    SetFocusord:555 rva: 00002030
    设置窗口焦点
    SetWindowTextAord:601 rva: 00002034
    设置窗口文本
    ShowWindoword:613 rva: 00002038
    设置窗口显隐状态
    TranslateMessageord:637 rva: 0000203C
    翻译消息
    UpdateWindoword:651 rva: 00002040
    刷新窗口
    DefWindowProcAord:131 rva: 00002044
    消息默认处理函数, 基本函数之一
    CreateWindowExAord:88 rva: 00002048
    建立窗口
    RegisterClassExAord:495 rva: 0000204C
    注册类
    SendMessageAord:528 rva: 00002050
    向窗口过程发送消息, 阻塞型函数
   
KERNEL32.dll
    GetModuleHandleAord:273 rva: 00002000
    获取模块句柄, 基本函数
    GetCommandLineAord:182 rva: 00002004
    获取命令行
    ExitProcessord:117 rva: 00002008
    退出进程

三. 实战

下面我们开始逆向之旅

我们目的是完全逆向这个程序, 由于程序很简单, 从入口来分析是合理的

         start         proc near
.text:00401000               push    NULL            ; lpModuleName
.text:00401002               call    GetModuleHandleA
.text:00401007               mov   hInstance, eax

.text:0040100C               call    GetCommandLineA

.text:00401011               push    SW_SHOWDEFAULT; nCmdShow
.text:00401013               push    lpCmdLine       ; lpCmdLine
.text:00401019               push    NULL            ; hPrevInstance
.text:0040101B               push    hInstance       ; hInstance
.text:00401021               call    WinMain

.text:00401026               push    eax             ; uExitCode
.text:00401027               call    ExitProcess
.text:00401027 start         endp

很标准的入口初始化过程: 首先获取本模块句柄, 然后获取命令行指针, 再启动WinMain主函数, 最后ExitProcess退出.
不知大家注意到没, 这个入口过程有一个小错误, 看出来了吗?就是那个lpCmdLine参数不正确, 应当push eax才对

我们看看WinMain里有什么

; int __stdcall WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPCSTR lpCmdLine,int nCmdShow)
.text:0040102C WinMain         proc near               ; CODE XREF: start+21p
.text:0040102C
.text:0040102C hWnd            = dword ptr -50h
.text:0040102C stMsg         = MSG ptr -4Ch
.text:0040102C stWndClassex    = WNDCLASSEXA ptr -30h
.text:0040102C hInstance       = dword ptr8
.text:0040102C hPrevInstance   = dword ptr0Ch
.text:0040102C lpCmdLine       = dword ptr10h
.text:0040102C nCmdShow      = dword ptr14h
.text:0040102C
.text:0040102C               push    ebp
.text:0040102D               mov   ebp, esp
.text:0040102F               add   esp, -50h

.text:00401032               mov   , SIZEOF WNDCLASSEX
.text:00401039               mov   , 3
.text:00401040               mov   , offset WndProc
.text:00401047               mov   , 0
.text:0040104E               mov   , 0
.text:00401055               push   
.text:00401058               pop   
.text:0040105B               mov   , 0Fh + 1h
.text:00401062               mov   , offset aWhatmenu ; "WhatMenu"
.text:00401069               mov   , offset ClassName ; "SupremeDickHead"

.text:00401070               push    IDI_APPLICATION ; lpIconName
.text:00401075               push    NULL            ; hInstance
.text:00401077               call    LoadIconA
.text:0040107C               mov   , eax
.text:0040107F               mov   , eax

.text:00401082               push    IDC_ARROW       ; lpCursorName
.text:00401087               push    NULL            ; hInstance
.text:00401089               call    LoadCursorA
.text:0040108E               mov   , eax

以上一段代码初始化窗口类, 类名为"SupremeDickHead", 窗口程序为WndProc, 由于反汇编代码已经最大程度的整理过了, 所以如果读者熟悉窗口程序的话应该很容易看明白, 我就不作解释了

.text:00401091               lea   eax,
.text:00401094               push    eax             ; WNDCLASSEXA *
.text:00401095               call    RegisterClassExA

注册窗口类

.text:0040109A               push    NULL            ; lpParam
.text:0040109C               push    ; hInstance
.text:0040109F               push    NULL            ; hMenu
.text:004010A1               push    NULL            ; hWndParent
.text:004010A3               push    0C8h            ; nHeight
.text:004010A8               push    12Ch            ; nWidth
.text:004010AD               push    0C8h            ; Y
.text:004010B2               push    0C8h            ; X
.text:004010B7               push    80C80000h       ; dwStyle
.text:004010BC               push    offset Caption; "ReverseMe #1"
.text:004010C1               push    offset ClassName ; "SupremeDickHead"
.text:004010C6               push    0               ; dwExStyle
.text:004010C8               call    CreateWindowExA
.text:004010CD               mov   , eax

创建窗口, 窗口标题为 "ReverseMe #1", 没有菜单

.text:004010D0               push    SW_SHOWNORMAL   ; nCmdShow
.text:004010D2               push          ; hWnd
.text:004010D5               call    ShowWindow

显示窗口

.text:004010DA               push          ; hWnd
.text:004010DD               call    UpdateWindow
.text:004010E2

刷新窗口

.text:004010E2 MessageLoop:                            ; CODE XREF: WinMain+DBj
.text:004010E2               push    0               ; wMsgFilterMax
.text:004010E4               push    0               ; wMsgFilterMin
.text:004010E6               push    NULL            ; hWnd
.text:004010E8               lea   eax,
.text:004010EB               push    eax             ; lpMsg
.text:004010EC               call    GetMessageA

.text:004010F1               or      eax, eax      ;收到WM_QUIT消息就退出
.text:004010F3               jz      short Exit

.text:004010F5               lea   eax,
.text:004010F8               push    eax             ; lpMsg
.text:004010F9               call    TranslateMessage

.text:004010FE               lea   eax,
.text:00401101               push    eax             ; lpMsg
.text:00401102               call    DispatchMessageA

.text:00401107               jmp   short MessageLoop

消息循环

.text:00401109
.text:00401109 Exit:                                 ; CODE XREF: WinMain+C7j
.text:00401109               mov   eax,
.text:0040110C               leave
.text:0040110D               retn    10h
.text:0040110D WinMain         endp

这个WinMain是非常标准的窗口程序主函数, 很可惜, 没有什么特别之处, 我就不多说了, 如果读者还是不理解原理请参考Windows程序设计相关书籍, 论坛置顶帖有介绍的

重点在WndProc函数处, 下面我们一起来看看这个函数

; int __stdcall WndProc(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam)
.text:00401110 WndProc         proc near               ; DATA XREF: WinMain+14o
.text:00401110
.text:00401110 hWnd            = dword ptr8
.text:00401110 Msg             = dword ptr0Ch
.text:00401110 wParam          = dword ptr10h
.text:00401110 lParam          = dword ptr14h
.text:00401110
.text:00401110               push    ebp
.text:00401111               mov   ebp, esp
.text:00401113               cmp   , WM_DESTROY
.text:00401117               jnz   short loc_401125
      ;if (Msg == WM_DESTROY)
.text:00401119               push    0               ; nExitCode
.text:0040111B               call    PostQuitMessage
.text:00401120               jmp   return

如果窗口关闭则发送退出消息使主程序退出消息循环

.text:00401125 loc_401125:                           ; CODE XREF: WndProc+7j
.text:00401125               cmp   , WM_CREATE
.text:00401129               jnz   short loc_4011A1
      else if   (Msg == WM_CREATE)
.text:0040112B               push    0               ; lpParam
.text:0040112D               push    hInstance       ; hInstance
.text:00401133               push    ID_EDIT         ; nControlID
.text:00401135               push          ; hWndParent
.text:00401138               push    19h             ; nHeight
.text:0040113A               push    0C8h            ; nWidth
.text:0040113F               push    23h             ; Y
.text:00401141               push    32h             ; X
.text:00401143               push    50800080h       ; dwStyle
.text:00401148               push    NULL            ; lpWindowName
.text:0040114A               push    offset aEdit    ; "edit"
.text:0040114F               push    WS_EX_CLIENTEDGE ; dwExStyle
.text:00401154               call    CreateWindowExA
.text:00401159               mov   hEdit, eax

在窗口中创建一个Edit控件

.text:0040115E               push    hEdit         ; hWnd
.text:00401164               call    SetFocus

并设置焦点

.text:00401169               push    NULL            ; lpParam
.text:0040116B               push    hInstance       ; hInstance
.text:00401171               push    ID_BUTTON       ; nControlID
.text:00401173               push          ; hWndParent
.text:00401176               push    19h             ; nHeight
.text:00401178               push    8Ch             ; nWidth
.text:0040117D               push    46h             ; Y
.text:0040117F               push    4Bh             ; X
.text:00401181               push    50000001h       ; dwStyle
.text:00401186               push    offset aNotReversed ; "Not Reversed"
.text:0040118B               push    offset aButton; "button"
.text:00401190               push    0               ; dwExStyle
.text:00401192               call    CreateWindowExA
.text:00401197               mov   hButton, eax

在文本编辑控件的下面创建一个按钮

.text:0040119C               jmp   return

以上一段是处理WM_CREATE消息的代码, 亦即窗口初始化代码. 这段代码创建了一个文本编辑控件和一个按钮, 就是主程序上所看到的两个控件

.text:004011A1
.text:004011A1 loc_4011A1:                           ; CODE XREF: WndProc+19j
.text:004011A1               cmp   , WM_COMMAND
.text:004011A8               jnz   loc_401317
      else if (Msg == WM_COMMAND)

以下是处理WM_COMMAND消息的代码, 冗长复杂, 后面我还会详细分析

.text:004011AE               mov   eax,
.text:004011B1               cmp   , 0 ; Is this message from a control?
.text:004011B5               jnz   loc_4012F6
.text:004011BB               cmp   ax, ID_BUTTON   ; Yes, it is from a control
.text:004011BF               jnz   short loc_4011EA

.text:004011C1               push    offset String   ; "Edit Box Is Good Mmmmkay ?"
.text:004011C6               push    hEdit         ; hWnd
.text:004011CC               call    SetWindowTextA

.text:004011D1               push    0               ; lParam
.text:004011D3               push    '#'             ; wParam
.text:004011D5               push    WM_KEYDOWN      ; Msg
.text:004011DA               push    hEdit         ; hWnd
.text:004011E0               call    SendMessageA

.text:004011E5               jmp   return

.text:004011EA
.text:004011EA loc_4011EA:                           ; CODE XREF: WndProc+AFj
.text:004011EA               cmp   ax, ID_EDIT
.text:004011EE               jnz   short loc_401202
.text:004011F0               push    NULL            ; lpString
.text:004011F2               push    hEdit         ; hWnd
.text:004011F8               call    SetWindowTextA
.text:004011FD               jmp   return

这一段很有趣, 程序首先判断消息是否来自一个子窗口控件(什么?你问为什么?那么我问你, WM_COMMND消息的lParam是什么含义? 对, 发送消息的控件句柄, 如果不是来自控件这个参数就是0啦), 如果不是则分别处理消息来自按钮,文本控件和一个未知ID的情况, 如果消息来自按钮则把Edit控件的文本设置为:"Edit Box Is Good Mmmmkay ?", 然后向Edit控件发送一个WM_KEYDOWN消息. 如果消息来自Edit, 程序会清除Edit控件的内容
问题是, 明知消息不是来自控件却要处理消息来自控件的情况, 这是干什么? 还有....

.text:00401202
.text:00401202 loc_401202:                           ; CODE XREF: WndProc+DEj
.text:00401202               cmp   ax, ID_WHATID
.text:00401206               jnz   loc_4012EC

.text:0040120C               mov   eax, offset loc_40123B
.text:00401211               jmp   eax

ID_WHATID(=3)是什么控件的ID? 不得而知. 如果这里跳走的话就会结束程序(下面会调用DestroyWindow函数)
到现在已经有很多疑问了, 我们不管, 继续向下看,

.text:00401213               push    200h
.text:00401218               push    offset szTextBuffer ; lpString
.text:0040121D               push    hEdit         ; hWnd
.text:00401223               call    GetWindowTextA

.text:00401228               push    MB_OK         ; uType
.text:0040122A               push    offset Caption; "ReverseMe #1"
.text:0040122F               push    offset szTextBuffer ; lpText
.text:00401234               push    NULL            ; hWnd
.text:00401236               call    MessageBoxA

这一段代码是永远也不可能执行了(jmp eax), 它作用是获取Edit的内容并用一个MessageBox把获取的内容显示出来

.text:0040123B
.text:0040123B loc_40123B:                           ; DATA XREF: WndProc+FCo
.text:0040123B               push    MB_OK         ; uType
.text:0040123D               push    offset Caption; "ReverseMe #1"
.text:00401242               push    offset aOkayForNowMiss ; "Okay, for now, mission failed !"
.text:00401247               push    NULL            ; hWnd
.text:00401249               call    MessageBoxA

显示一个"Okay, for now, mission failed !"对话框, 就是我们运行程序时看到的

.text:0040124E               push    200h            ; nMaxCount
.text:00401253               push    offset szTextBuffer ; lpString
.text:00401258               push    hEdit         ; hWnd
.text:0040125E               call    GetWindowTextA
.text:00401263               mov   ecx, eax

这时ecx存放有字符个数, 因为GetWindowTextA返回值(eax)为字符个数

.text:00401265               xor   edx, edx      ; edx 清零
.text:00401267               or      ebx, 0FFFFFFFFh
.text:0040126A               inc   ebx             ; ebx 清零

.text:0040126B               mov   eax, offset szTextBuffer

让eax指向字符串
怎么?要做字符串处理了呀, 此时eax指向字符串, ebx, edx为0

.text:00401270
.text:00401270 loc_401270:                           ; CODE XREF: WndProc+183j
.text:00401270               mov   bl,
.text:00401272               cmp   byte ptr , 0
.text:00401275               jz      short loc_401295
.text:00401277               cmp   byte ptr , '0'
.text:0040127A               jl      short loc_401295
.text:0040127C               cmp   byte ptr , '9'
.text:0040127F               ja      short loc_401295

验证字符串合法性, 如果发现了空字符串或者有除了0~9之外的字符就提示错误

.text:00401281               add   edx, ebx      ;ebx和edx相加
.text:00401283               shl   edx, 14h      ;相加的结果左移20位
.text:00401286               add   edx, ecx      ;再和字符个数相加
.text:00401288               inc   eax
.text:00401289               add   ecx, -1      ;处理下一个字符
.text:0040128C               test    edx, edx
.text:0040128E               jnz   short loc_401291
.text:00401290               inc   edx      ;edx如果是0的话则设置为1
.text:00401291
.text:00401291 loc_401291:                           ; CODE XREF: WndProc+17Ej
.text:00401291               test    ecx, ecx      ;结束否?
.text:00401293               jnz   short loc_401270

以上一段算法可以用C语言描述为:

int i = NumberOfChars;
char* str = StringFromEditControl;
int tmp = 0;

do {
    tmp += (int) *str;
    tmp <<= 20;
    tmp += i;
    str++;
    i--;
    if (tmp == 0) tmp++;
}
while (i != 0);

.text:00401295
.text:00401295 loc_401295:                           ; CODE XREF: WndProc+165j
.text:00401295                                       ; WndProc+16Aj ...
.text:00401295               test    edx, 0      
.text:0040129B               jg      short loc_4012B6
.text:0040129D               jz      short loc_4012CB

.text:0040129F               push    MB_OK               ; uType
.text:004012A1               push    offset Caption; "ReverseMe #1"
.text:004012A6               push    offset aThatNotANumber ; "That not a number ! CHEATHER !"
.text:004012AB               push    NULL               ; hWnd
.text:004012AD               call    MessageBoxA
.text:004012B2               leave
.text:004012B3               retn    10h

test edx, 0 ??!! 这意味着jg永远不能执行而jz会执行, 不过实际上这两个跳转之后都有一句jmp到ret处, 实际上都一样

.text:004012B6
.text:004012B6 loc_4012B6:                           ; CODE XREF: WndProc+18Bj
.text:004012B6               jmp   short locret_4012E0

.text:004012B8               push    MB_OK
.text:004012BA               push    offset Caption; "ReverseMe #1"
.text:004012BF               push    offset aGoodNumber ; "Good Number"
.text:004012C4               push    NULL               ; hWnd
.text:004012C6               call    MessageBoxA
.text:004012CB
.text:004012CB loc_4012CB:                           ; CODE XREF: WndProc+18Dj
.text:004012CB               jmp   short locret_4012E0

.text:004012CD               push    MB_OK
.text:004012CF               push    offset Caption; "ReverseMe #1"
.text:004012D4               push    offset aBadNumber ; "Bad Number"
.text:004012D9               push    NULL               ; hWnd
.text:004012DB               call    MessageBoxA
.text:004012E0
.text:004012E0 locret_4012E0:                        ; CODE XREF: WndProc:loc_4012B6j
.text:004012E0                                       ; WndProc:loc_4012CBj
.text:004012E0               leave
.text:004012E1               retn    10h

.text:004012E4               pop   ebp
.text:004012E5               push    eax
.text:004012E6               leave
.text:004012E7               retn    10h

.text:004012EA               jmp   short return

.text:004012EC
.text:004012EC loc_4012EC:                           ; CODE XREF: WndProc+F6j
.text:004012EC               push          ; hWnd
.text:004012EF               call    DestroyWindow
.text:004012F4               jmp   short return

刚才提到过了

.text:004012F6
.text:004012F6 loc_4012F6:                           ; CODE XREF: WndProc+A5j
.text:004012F6               cmp   ax, ID_BUTTON
.text:004012FA               jnz   short loc_401315
.text:004012FC               shr   eax, 10h
.text:004012FF               or      ax, ax
.text:00401302               jnz   short loc_401315

.text:00401304               push    0               ; lParam
.text:00401306               push    ID_WHATID       ; wParam
.text:00401308               push    WM_COMMAND      ; Msg
.text:0040130D               push          ; hWnd
.text:00401310               call    SendMessageA

.text:00401315
.text:00401315 loc_401315:                           ; CODE XREF: WndProc+1EAj
.text:00401315                                       ; WndProc+1F2j
.text:00401315               jmp   short return

这里终于知道ID_WHATID的来源了, 原来是这个SendMessage函数!

.text:00401317
.text:00401317 loc_401317:                           ; CODE XREF: WndProc+98j
.text:00401317               push        ; lParam
.text:0040131A               push        ; wParam
.text:0040131D               push           ; Msg
.text:00401320               push          ; hWnd
.text:00401323               call    DefWindowProcA
.text:00401328               leave
.text:00401329               retn    10h

.text:0040132C
.text:0040132C return:                                 ; CODE XREF: WndProc+10j
.text:0040132C                                       ; WndProc+8Cj ...
.text:0040132C               xor   eax, eax
.text:0040132E               leave
.text:0040132F               retn    10h
.text:0040132F WndProc         endp

四. 回顾

程序结束了, 现在回过头来看看程序中的微妙之处.

程序首先用标准的方法建立了一个窗口, 在窗口函数中, 程序处理了WM_CREATE, WM_DESTROY和WM_COMMAND这3个消息. 在处理WM_CREATE时, 程序分别建立了一个Edit控件和一个按钮; 在WM_COMMAND消息的处理中, 如果程序侦测到消息是按钮发出的, 则以ID_WHATID(等于3)为WPARAM, 0为LPARAM向自身发送WM_COMMAND消息. 由于在WM_COMMAND消息处理的开头, 程序会判断消息的来源, 仅当消息不是从子窗口控件发出时才处理(004011AE和004011FD之间的代码实际上不会执行), 所以00401213到004012EA的这段代码(就是那段算法代码)名义上是处理ID为ID_WHATID的"控件"发出的消息, 实质上是按钮消息处理过程的延续而已
关于那段算法代码, 由于不管结果是多少最后都要和0做and运算, 结果毫无疑问都是0, 亦即那句jz将肯定会执行. 所以这段算法是没有多大意义的. 但是这个程序毕竟是ReverseMe, 就是要求大家把不正确的内容修改正确. 至于到底怎么修改就看大家自由发挥了.

明白了这些, 相信大家再看<<加密与解密>>第2版P89~P91内容时应该觉得不难了吧

Hmily 发表于 2019-6-18 18:12

https://bbs.pediy.com/thread-22990.htm 是这原帖主人吗?如果是在看雪论坛给hmilywen发一条短消息确认是本人申请,然后回复我。

发表于 2019-6-18 18:37

我发了,是本人。

Hmily 发表于 2019-6-19 17:39

游客 113.9.244.x 发表于 2019-6-18 18:37
我发了,是本人。

没有收到短消息,发成功了吗?
页: [1]
查看完整版本: 申请ID:thebutterfly