吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 5918|回复: 3
收起左侧

[会员申请] 申请ID:thebutterfly

[复制链接]
吾爱游客  发表于 2019-6-14 19:47
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
    DestroyWindow  ord:141 rva: 00002010
    清除一个窗口并导致一个WM_DESTROY消息
    GetWindowTextA  ord:347 rva: 00002014
    获取窗口标题(或者按钮, 文本框)文本
    LoadCursorA  ord:407 rva: 00002018
    装载光标
    LoadIconA  ord:411 rva: 0000201C
    装载图标
    MessageBoxA  ord:443 rva: 00002020
    不用多说了, 大家都知道
    PostQuitMessage  ord:477 rva: 00002024
    在消息队列里加入一条WM_QUIT消息
    DispatchMessageA  ord:148 rva: 00002028
    向窗口过程分发消息, 窗口程序基本API之一
    GetMessageA  ord:296 rva: 0000202C
    取消息队列消息, 基本函数
    SetFocus  ord:555 rva: 00002030
    设置窗口焦点
    SetWindowTextA  ord:601 rva: 00002034
    设置窗口文本
    ShowWindow  ord:613 rva: 00002038
    设置窗口显隐状态
    TranslateMessage  ord:637 rva: 0000203C
    翻译消息
    UpdateWindow  ord:651 rva: 00002040
    刷新窗口
    DefWindowProcA  ord:131 rva: 00002044
    消息默认处理函数, 基本函数之一
    CreateWindowExA  ord:88 rva: 00002048
    建立窗口
    RegisterClassExA  ord:495 rva: 0000204C
    注册类
    SendMessageA  ord:528 rva: 00002050
    向窗口过程发送消息, 阻塞型函数
   
KERNEL32.dll
    GetModuleHandleA  ord:273 rva: 00002000
    获取模块句柄, 基本函数
    GetCommandLineA  ord:182 rva: 00002004
    获取命令行
    ExitProcess  ord: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 ptr  8
.text:0040102C hPrevInstance   = dword ptr  0Ch
.text:0040102C lpCmdLine       = dword ptr  10h
.text:0040102C nCmdShow        = dword ptr  14h
.text:0040102C
.text:0040102C                 push    ebp
.text:0040102D                 mov     ebp, esp
.text:0040102F                 add     esp, -50h

.text:00401032                 mov     [ebp+stWndClassex.cbSize], SIZEOF WNDCLASSEX
.text:00401039                 mov     [ebp+stWndClassex.style], 3
.text:00401040                 mov     [ebp+stWndClassex.lpfnWndProc], offset WndProc
.text:00401047                 mov     [ebp+stWndClassex.cbClsExtra], 0
.text:0040104E                 mov     [ebp+stWndClassex.cbWndExtra], 0
.text:00401055                 push    [ebp+hInstance]
.text:00401058                 pop     [ebp+stWndClassex.hInstance]
.text:0040105B                 mov     [ebp+stWndClassex.hbrBackground], 0Fh + 1h
.text:00401062                 mov     [ebp+stWndClassex.lpszMenuName], offset aWhatmenu ; "WhatMenu"
.text:00401069                 mov     [ebp+stWndClassex.lpszClassName], offset ClassName ; "SupremeDickHead"

.text:00401070                 push    IDI_APPLICATION ; lpIconName
.text:00401075                 push    NULL            ; hInstance
.text:00401077                 call    LoadIconA
.text:0040107C                 mov     [ebp+stWndClassex.hIcon], eax
.text:0040107F                 mov     [ebp+stWndClassex.hIconSm], eax

.text:00401082                 push    IDC_ARROW       ; lpCursorName
.text:00401087                 push    NULL            ; hInstance
.text:00401089                 call    LoadCursorA
.text:0040108E                 mov     [ebp+stWndClassex.hCursor], eax

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

.text:00401091                 lea     eax, [ebp+stWndClassex]
.text:00401094                 push    eax             ; WNDCLASSEXA *
.text:00401095                 call    RegisterClassExA

注册窗口类

.text:0040109A                 push    NULL            ; lpParam
.text:0040109C                 push    [ebp+hInstance] ; 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     [ebp+hWnd], eax

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

.text:004010D0                 push    SW_SHOWNORMAL   ; nCmdShow
.text:004010D2                 push    [ebp+hWnd]      ; hWnd
.text:004010D5                 call    ShowWindow

显示窗口

.text:004010DA                 push    [ebp+hWnd]      ; 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, [ebp+stMsg]
.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, [ebp+stMsg]
.text:004010F8                 push    eax             ; lpMsg
.text:004010F9                 call    TranslateMessage

.text:004010FE                 lea     eax, [ebp+stMsg]
.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, [ebp+stMsg.wParam]
.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 ptr  8
.text:00401110 Msg             = dword ptr  0Ch
.text:00401110 wParam          = dword ptr  10h
.text:00401110 lParam          = dword ptr  14h
.text:00401110
.text:00401110                 push    ebp
.text:00401111                 mov     ebp, esp
.text:00401113                 cmp     [ebp+Msg], 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     [ebp+Msg], 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    [ebp+hWnd]      ; 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    [ebp+hWnd]      ; 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     [ebp+Msg], WM_COMMAND
.text:004011A8                 jnz     loc_401317
        else if (Msg == WM_COMMAND)

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

.text:004011AE                 mov     eax, [ebp+wParam]
.text:004011B1                 cmp     [ebp+lParam], 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, [eax]
.text:00401272                 cmp     byte ptr [eax], 0
.text:00401275                 jz      short loc_401295
.text:00401277                 cmp     byte ptr [eax], '0'
.text:0040127A                 jl      short loc_401295
.text:0040127C                 cmp     byte ptr [eax], '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    [ebp+hWnd]      ; 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    [ebp+hWnd]      ; 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    [ebp+lParam]    ; lParam
.text:0040131A                 push    [ebp+wParam]    ; wParam
.text:0040131D                 push    [ebp+Msg]       ; Msg
.text:00401320                 push    [ebp+hWnd]      ; 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
    DestroyWindow  ord:141 rva: 00002010
    清除一个窗口并导致一个WM_DESTROY消息
    GetWindowTextA  ord:347 rva: 00002014
    获取窗口标题(或者按钮, 文本框)文本
    LoadCursorA  ord:407 rva: 00002018
    装载光标
    LoadIconA  ord:411 rva: 0000201C
    装载图标
    MessageBoxA  ord:443 rva: 00002020
    不用多说了, 大家都知道
    PostQuitMessage  ord:477 rva: 00002024
    在消息队列里加入一条WM_QUIT消息
    DispatchMessageA  ord:148 rva: 00002028
    向窗口过程分发消息, 窗口程序基本API之一
    GetMessageA  ord:296 rva: 0000202C
    取消息队列消息, 基本函数
    SetFocus  ord:555 rva: 00002030
    设置窗口焦点
    SetWindowTextA  ord:601 rva: 00002034
    设置窗口文本
    ShowWindow  ord:613 rva: 00002038
    设置窗口显隐状态
    TranslateMessage  ord:637 rva: 0000203C
    翻译消息
    UpdateWindow  ord:651 rva: 00002040
    刷新窗口
    DefWindowProcA  ord:131 rva: 00002044
    消息默认处理函数, 基本函数之一
    CreateWindowExA  ord:88 rva: 00002048
    建立窗口
    RegisterClassExA  ord:495 rva: 0000204C
    注册类
    SendMessageA  ord:528 rva: 00002050
    向窗口过程发送消息, 阻塞型函数
   
KERNEL32.dll
    GetModuleHandleA  ord:273 rva: 00002000
    获取模块句柄, 基本函数
    GetCommandLineA  ord:182 rva: 00002004
    获取命令行
    ExitProcess  ord: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 ptr  8
.text:0040102C hPrevInstance   = dword ptr  0Ch
.text:0040102C lpCmdLine       = dword ptr  10h
.text:0040102C nCmdShow        = dword ptr  14h
.text:0040102C
.text:0040102C                 push    ebp
.text:0040102D                 mov     ebp, esp
.text:0040102F                 add     esp, -50h

.text:00401032                 mov     [ebp+stWndClassex.cbSize], SIZEOF WNDCLASSEX
.text:00401039                 mov     [ebp+stWndClassex.style], 3
.text:00401040                 mov     [ebp+stWndClassex.lpfnWndProc], offset WndProc
.text:00401047                 mov     [ebp+stWndClassex.cbClsExtra], 0
.text:0040104E                 mov     [ebp+stWndClassex.cbWndExtra], 0
.text:00401055                 push    [ebp+hInstance]
.text:00401058                 pop     [ebp+stWndClassex.hInstance]
.text:0040105B                 mov     [ebp+stWndClassex.hbrBackground], 0Fh + 1h
.text:00401062                 mov     [ebp+stWndClassex.lpszMenuName], offset aWhatmenu ; "WhatMenu"
.text:00401069                 mov     [ebp+stWndClassex.lpszClassName], offset ClassName ; "SupremeDickHead"

.text:00401070                 push    IDI_APPLICATION ; lpIconName
.text:00401075                 push    NULL            ; hInstance
.text:00401077                 call    LoadIconA
.text:0040107C                 mov     [ebp+stWndClassex.hIcon], eax
.text:0040107F                 mov     [ebp+stWndClassex.hIconSm], eax

.text:00401082                 push    IDC_ARROW       ; lpCursorName
.text:00401087                 push    NULL            ; hInstance
.text:00401089                 call    LoadCursorA
.text:0040108E                 mov     [ebp+stWndClassex.hCursor], eax

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

.text:00401091                 lea     eax, [ebp+stWndClassex]
.text:00401094                 push    eax             ; WNDCLASSEXA *
.text:00401095                 call    RegisterClassExA

注册窗口类

.text:0040109A                 push    NULL            ; lpParam
.text:0040109C                 push    [ebp+hInstance] ; 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     [ebp+hWnd], eax

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

.text:004010D0                 push    SW_SHOWNORMAL   ; nCmdShow
.text:004010D2                 push    [ebp+hWnd]      ; hWnd
.text:004010D5                 call    ShowWindow

显示窗口

.text:004010DA                 push    [ebp+hWnd]      ; 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, [ebp+stMsg]
.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, [ebp+stMsg]
.text:004010F8                 push    eax             ; lpMsg
.text:004010F9                 call    TranslateMessage

.text:004010FE                 lea     eax, [ebp+stMsg]
.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, [ebp+stMsg.wParam]
.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 ptr  8
.text:00401110 Msg             = dword ptr  0Ch
.text:00401110 wParam          = dword ptr  10h
.text:00401110 lParam          = dword ptr  14h
.text:00401110
.text:00401110                 push    ebp
.text:00401111                 mov     ebp, esp
.text:00401113                 cmp     [ebp+Msg], 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     [ebp+Msg], 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    [ebp+hWnd]      ; 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    [ebp+hWnd]      ; 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     [ebp+Msg], WM_COMMAND
.text:004011A8                 jnz     loc_401317
        else if (Msg == WM_COMMAND)

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

.text:004011AE                 mov     eax, [ebp+wParam]
.text:004011B1                 cmp     [ebp+lParam], 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, [eax]
.text:00401272                 cmp     byte ptr [eax], 0
.text:00401275                 jz      short loc_401295
.text:00401277                 cmp     byte ptr [eax], '0'
.text:0040127A                 jl      short loc_401295
.text:0040127C                 cmp     byte ptr [eax], '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    [ebp+hWnd]      ; 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    [ebp+hWnd]      ; 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    [ebp+lParam]    ; lParam
.text:0040131A                 push    [ebp+wParam]    ; wParam
.text:0040131D                 push    [ebp+Msg]       ; Msg
.text:00401320                 push    [ebp+hWnd]      ; 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
我发了,是本人。

点评

没有收到短消息,发成功了吗?  详情 回复 发表于 2019-6-19 17:39
Hmily 发表于 2019-6-19 17:39
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-15 09:42

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表