• 标 题:一款工具的逆向分析与再实现
  • 作 者:nbw
  • 时 间:2004-12-22,02:06
  • 链 接:http://bbs.pediy.com

一款工具的逆向分析与再实现

作者: nbw   www.vxer.com

目的:    逆向分析一款窗口句柄察看工具(dRag0nMa 大侠写的hwnd_pwd)
        并重新写出来

分析工具:  IDA,fly_OD
编写语言:  Masm32(原软件估计是VC)
备注:    刚看了firstrose兄弟写的逆向文章,自己也想来练练手。未曾征得原作者的同意就发出笔记,请多原谅!
        感谢悠长假期的帮助。

原软件用upx加壳,脱掉。

用HackRes查看一下资源,可以获得资源文件:

101 DIALOGEX 0, 0, 181, 53
STYLE DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "WinClass&HWND+viEw\"*\"pwd"
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
FONT 8, "Times New Roman"
{
   CONTROL "", 1000, EDIT, ES_LEFT | ES_READONLY | WS_CHILD | WS_VISIBLE | WS_DISABLED | WS_GROUP | WS_TABSTOP, 41, 13, 122, 12 
   CONTROL "", 1005, EDIT, ES_LEFT | ES_READONLY | WS_CHILD | WS_VISIBLE | WS_DISABLED | WS_TABSTOP, 40, 31, 91, 12 
   CONTROL "Win.Class:", 1037, STATIC, SS_LEFT | WS_CHILD | WS_VISIBLE | WS_GROUP, 7, 13, 35, 8 , 0x00000020
   CONTROL "HWND:", -1, STATIC, SS_LEFT | WS_CHILD | WS_VISIBLE | WS_GROUP, 8, 31, 29, 9 
   CONTROL "", -1, BUTTON, BS_GROUPBOX | WS_CHILD | WS_VISIBLE, 2, 0, 174, 47 
   CONTROL "cOde:dRag0nMa", -1, STATIC, SS_RIGHT | WS_CHILD | WS_VISIBLE | WS_DISABLED | WS_GROUP | WS_TABSTOP, 113, 41, 58, 8 
}

需要注意的是:

CAPTION "WinClass&HWND+viEw\"*\"pwd"
这里在asm中编译不通过,直接去掉\"就可以,改为:
CAPTION "WinClass&HWND+viEw*pwd"
但是它这里的LANGUAGE和FONT我这边不能用(估计我的机器太烂,装配不全),所以最后看到的界面比原来的软件要大。

代码初始:

mBC:00401048                 push    1
mBC:0040104A                 call    ds:GetCommandLineA
mBC:00401050                 push    eax
mBC:00401051                 push    0
mBC:00401053                 push    0               ; lpModuleName
mBC:00401055                 call    ds:GetModuleHandleA
mBC:0040105B                 push    eax
mBC:0040105C                 call    sub_401213
mBC:00401061                 push    eax             ; uExitCode
mBC:00401062                 call    ds:ExitProcess
mBC:00401068                 retn

其中,call    sub_401213为:

mov     eax, [esp+hInstance]
push    0               ; dwInitParam
push    offset DialogFunc ; lpDialogFunc
push    0               ; hWndParent
push    65h             ; lpTemplateName
push    eax             ; hInstance
mov     ds:dword_401344, eax
call    ds:DialogBoxParamA ; Create a modal dialog box from a
xor     eax, eax

这里需要注意的是:
push    65h 
这个地方为主窗口资源ID

mov     ds:dword_401344, eax
由于eax为进程句柄,因此,我们用  mov     hInstance, eax代替

xor     eax, eax
故,下面的  call    ExitProcess参数为NULL

理解了call    sub_401213的含义,故此,把该函数去掉,只保留该函数的主体。

进入主消息处理瞅瞅:

DialogFunc      proc near               ; DATA XREF: sub_401213+6o
mBC:004010DE hDlg            = dword ptr  8
mBC:004010DE arg_4           = dword ptr  0Ch

mBC:004010DE                 push    ebp
mBC:004010DF                 mov     ebp, esp
mBC:004010E1                 mov     eax, [ebp+arg_4] ; wMsg

其中ebp + hDlg相当于我们的hWnd,ebp+arg_4相当于我们的wMsg。代码开头就是编译器的经典call处理,我们作汇编的,不管这个。

mBC:004010E4                 sub     eax, 0Fh
mBC:004010E7                 jz      loc_40120D

这2句是错误消息处理,如果消息是0Fh,那么直接返回。代码中应该做如下处理:
  sub  eax, 0Fh
  cmp  eax, 0
  jne  @F
  xor  eax, eax
  ret
@@:          ;normal Message
那么0Fh究竟是什么消息呢?一查查了一大堆,也懒得去猜。因此我也不晓得。

继续向下看:

mBC:004010ED                 dec     eax
mBC:004010EE                 jz      loc_4011F7

如果传来的消息为0F + 1(注意上面已经sub eax, 0Fh) ,那么:

mBC:004011F7                 push    1               ; uIDEvent
mBC:004011F9                 push    [ebp+hDlg]      ; hWnd
mBC:004011FC                 call    ds:KillTimer
mBC:00401202                 push    0               ; nResult
mBC:00401204                 push    [ebp+hDlg]      ; hDlg
mBC:00401207                 call    ds:EndDialog
mBC:0040120D                 xor     eax, eax
mBC:0040120F                 pop     ebp
mBC:00401210                 retn    10h

就是:
push 1
push hWnd
call KillTimer
push 0
push hWnd
call EndDialog
xor  eax, eax
很明显是close事件。那么处理的消息是不是WM_CLOSE呢?可以认为是的,因为 (0F + 1) equ 10h equ WM_CLOSE。
因此,这里可以翻译成:
  .if  wMsg == WM_CLOSE
    invoke  KillTimer,hWnd,1
    invoke  EndDialog,hWnd,NULL
    xor eax, eax
  .endif


再往下看下一条消息吧:
  push    0               ; lpTimerFunc
  push    12Ch            ; uElapse
  push    1               ; nIDEvent
  push    hWnd
  call    SetTimer
  push    offset Rect     ; lpRect
  push    hWnd
  call    GetWindowRect
  push    41h             ; uFlags
  push    Rect.bottom  ; cy
  push    Rect.right   ; cx
  push    Rect.top     ; Y
  push    Rect.left    ; X
  push    0FFFFFFFFh      ; hWndInsertAfter
  push    hWnd
  call    SetWindowPos
  push    1
  pop     eax
  jmp     short loc_40120F
short loc_40120F:
  pop    ebp
  ret

SetTimer设定一个时间,300毫秒监测一次,lpTimerFunc 为0,也就是这个timer专门供主进程用。
GetWindowRect获取主窗口位置。Rect结构定义要注意以下。具体参考代码。
SetWindowPos设定窗体显示属性,比如说总置顶显示。
最后是mov eax, 1,然后ret
这个消息数值为110h,参考以下功能,可以判定为WM_INITDIALOG。

再看下一条消息处理:

mBC:004011F2                 push    1
mBC:004011F4                 pop     eax
mBC:004011F5                 jmp     short loc_40120F

完成mov eax, 1然后退出。
该消息值为111h,判定为WM_COMMAND。因为资源定义中使用了menu,因此这里怀疑是否为用来处理menu消息的部分。但是由于没用到,所以程序中我删除了这个。


继续看下一条:

消息检测为:

mBC:00401106                 dec     eax
mBC:00401107                 dec     eax
mBC:00401108                 jz      short loc_401132

消息数值为: 111h + 2 = 113h,查一下文档,就2个消息为该值,毋庸置疑,该消息是WM_TIMER ,总算到了核心部分。
消息处理如下:

mBC:00401132 loc_401132:                             ; CODE XREF: DialogFunc+2Aj
mBC:00401132                 push    esi
mBC:00401133                 push    edi
mBC:00401134                 push    offset Point    ; lpPoint
mBC:00401139                 call    ds:GetCursorPos
mBC:0040113F                 push    ds:Point.y
mBC:00401145                 push    ds:Point.x      ; Point
mBC:0040114B                 call    ds:WindowFromPoint ; Get handle of the window that
mBC:0040114B                                         ; contains the specified poin
mBC:00401151                 push    eax
mBC:00401152                 push    50h          ;注意这里是显示数据的label的ID
mBC:00401154                 mov     ds:hWnd, eax    ;保存鼠标所在的窗口句柄,用于下一步调用
mBC:00401159                 call    sub_401069
mBC:0040115E                 pop     ecx
mBC:0040115F                 mov     esi, ds:SetDlgItemTextA
mBC:00401165                 pop     ecx
mBC:00401166                 push    offset String   ; lpString
mBC:0040116B                 push    3EDh            ; nIDDlgItem
mBC:00401170                 push    [ebp+hDlg]      ; hDlg
mBC:00401173                 call    esi ; SetDlgItemTextA
mBC:00401175                 mov     edi, offset ClassName
mBC:0040117A                 push    10h             ; nMaxCount
mBC:0040117C                 push    edi             ; lpClassName
mBC:0040117D                 push    ds:hWnd         ; hWnd
mBC:00401183                 call    ds:GetClassNameA
mBC:00401189                 push    edi             ; lpString
mBC:0040118A                 push    3E8h            ;注意这里是显示数据的label的ID
mBC:0040118F                 push    [ebp+hDlg]      ; hDlg
mBC:00401192                 call    esi ; SetDlgItemTextA
mBC:00401194                 push    0               ; lParam
mBC:00401196                 push    0               ; wParam
mBC:00401198                 push    0CCh            ; Msg
mBC:0040119D                 push    ds:hWnd         ; hWnd
mBC:004011A3                 call    ds:PostMessageA


首先,通过GetCursorPos获取鼠标所在位置,然后通过WindowFromPoint函数获取该位置处窗口句炳,利用SetDlgItemTextA显示出来。
再用GetClassNameA获取该窗口类,显示出来。

其中有函数:
push    eax
push    50h
mov     hWnd, eax
call    sub_401069
去瞅瞅:

mBC:00401070                 mov     esi, [ebp+arg_4]        ;取获得的窗口句柄
mBC:00401073                 xor     ecx, ecx
mBC:00401075                 test    esi, esi
mBC:00401077                 jz      short loc_4010A7        ;如果没取到,便退出
mBC:00401079                 push    edi
mBC:0040107A

;****************************
;这个小循环,通过除以10h,依次取出来窗口句柄的每一位(肯定小于0x10了)。
;如果小于9,就加上30h,转换成真正的9,否则,加上37h,转换成字母。
;不过转换过来的顺序颠倒过来了
;比如B0638 转换成:
; 38 33 36 30 42 00

mBC:0040107A                 mov     eax, esi
mBC:0040107C                 push    10h
mBC:0040107E                 cdq
mBC:0040107F                 pop     edi
mBC:00401080                 idiv    edi
mBC:00401082                 lea     eax, [ebp+ecx+var_C]
mBC:00401086                 cmp     dl, 9
mBC:00401089                 mov     [eax], dl
mBC:0040108B                 jle     short loc_401092
mBC:0040108D                 add     dl, 37h
mBC:00401090                 jmp     short loc_401095
mBC:00401092
mBC:00401092 loc_401092:                             ; CODE XREF: sub_401069+22j
mBC:00401092                 add     dl, 30h
mBC:00401095
mBC:00401095 loc_401095:                             ; CODE XREF: sub_401069+27j
mBC:00401095                 mov     [eax], dl
mBC:00401097                 mov     eax, esi
mBC:00401099                 push    10h
mBC:0040109B                 cdq
mBC:0040109C                 pop     esi
mBC:0040109D                 idiv    esi
mBC:0040109F                 inc     ecx
mBC:004010A0                 mov     esi, eax
mBC:004010A2                 test    esi, esi
mBC:004010A4                 jnz     short loc_40107A
;****************************

mBC:004010AF                 mov     dl, byte ptr [ebp+ecx+var_C]      ;把转换后的数据移动到输出缓冲区
mBC:004010B3                 mov     ds:String[eax], dl
mBC:004010B9                 inc     eax
mBC:004010BA                 dec     ecx
mBC:004010BB                 jns     short loc_4010AF

;在窗口句柄输出缓冲区最后添加“(H)”
mBC:004010BD                 mov     ds:String[eax], 28h        ;28h为“(”的ASCII码
mBC:004010C4                 mov     ds:byte_401239[eax], 48h    ;48h为“H”的ASCII码
mBC:004010CB                 mov     ds:byte_40123A[eax], 29h    ;29h为“)”的ASCII码
mBC:004010D2                 and     ds:byte_40123B[eax], 0

现在看来这个函数 sub_401069 就是把获取得窗口转换成可以让用户看的数据,然后在后面添加“(H)”,表示是16进制。
我翻译一下:

      xor  ecx, ecx
      mov  esi, offset szString1    
    @@:               ;这个小循环用来把句柄数值转换成字符串,存放在szString1,不过存放的数值是倒的
      test  eax, eax
      jz    @lExit
      xor  edx, edx
      mov  edi, 10h
      idiv  edi
      .if    dl >  9
        add  dl, 37h
      .else
        add  dl, 30h
      .endif
      mov  byte ptr [esi], dl
      inc  esi
      inc  ecx
      test  eax, eax
      jnz  @B

    @lExit:
      mov  edi, offset szString2
      dec  esi
    @@:              ;把倒过来的字符串再修正回来,放在szString2
      mov  al, BYTE ptr [esi]
      mov  BYTE ptr [edi], al
      dec  ecx
      dec  esi
      inc  edi
      test  ecx, ecx
      jnz  @B
      
      mov     BYTE ptr [edi ], 28h      ;填写句柄字符串最后的"(H)"
      mov     BYTE ptr [edi+1], 48h
      mov     BYTE ptr [edi +2 ], 29h
      mov     BYTE ptr [edi +3 ], 00

写的比较垃圾,凑和用吧。

看下一个消息:

mBC:0040110A                 sub     eax, 0EEh
mBC:0040110F                 jnz     loc_40120D

WM_TIMER = 113h + 0EEh,估计是WM_LBUTTONDOWN。

而loc_40120D:
xor     eax, eax
ret

其实就是消息case的返回。

由于这里是jnz,就是说如果不是WM_LBUTTONDOWN,就返回。

结合上面的代码,WM_LBUTTONDOWN是最后一条需要专门处理的消息。处理如下:

mBC:00401115                 call    ds:ReleaseCapture
mBC:0040111B                 push    0               ; lParam
mBC:0040111D                 push    2               ; wParam
mBC:0040111F                 push    0A1h            ; Msg
mBC:00401124                 push    [ebp+hDlg]      ; hWnd
mBC:00401127                 call    ds:SendMessageA

解释一下,就是当本窗口发生鼠标事件时候,不接受鼠标的位置(减轻程序的处理负担)。

具体翻译参考以下代码就行。

总结一下:

总体来说,程序流程规整,便于分析。但是也存在一些问题,比如:

1、他这里每隔300毫秒,利用鼠标api和句柄api来检测窗口句柄和类,虽然程序实现比较容易,但是效率不高,一般来说应该用hook拦截鼠标消息。
2、另外,多处理了WM_COMMAND消息(或许有用)。
3、对字符串的处理(:0040107A),没有处理好窗口句柄,因为存在本进程句柄和获取的窗口句柄,最好多添加一个hWnd变量,用起来也方便。

我这里代码写的比较烂,时间仓促,自己也太垃圾,不足之处多包涵。

附程序代码:


资源文件:

“Main.rc”文件:

#define DLG_MAIN 101
#include "resource.h"

DLG_MAIN DIALOGEX 0, 0, 181, 53
CAPTION "WinClass&HWND+viEw*pwd"
STYLE DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU
BEGIN
   CONTROL "", 1000, EDIT, ES_LEFT | ES_READONLY | WS_CHILD | WS_VISIBLE | WS_DISABLED | WS_GROUP | WS_TABSTOP, 41, 13, 122, 12 
   CONTROL "", 1005, EDIT, ES_LEFT | ES_READONLY | WS_CHILD | WS_VISIBLE | WS_DISABLED | WS_TABSTOP, 40, 31, 91, 12 
   CONTROL "Win.Class:", 1037, STATIC, SS_LEFT | WS_CHILD | WS_VISIBLE | WS_GROUP, 7, 13, 35, 8 , 0x00000020
   CONTROL "HWND:", -1, STATIC, SS_LEFT | WS_CHILD | WS_VISIBLE | WS_GROUP, 8, 31, 29, 9 
   CONTROL "", -1, BUTTON, BS_GROUPBOX | WS_CHILD | WS_VISIBLE, 2, 0, 174, 47 
   CONTROL "cOde:dRag0nMa", -1, STATIC, SS_RIGHT | WS_CHILD | WS_VISIBLE | WS_DISABLED | WS_GROUP | WS_TABSTOP, 113, 41, 58, 8 
END
IDM_MAIN MENUEX
BEGIN
    MENUITEM "",,0x00000800
END


“Main.asm”文件:

.386
.model flat, stdcall
option casemap :none

;---------------------------------------------------------------------------
include    nbwFun.inc

_ProcDlgMain  PROTO :DWORD,:DWORD,:DWORD,:DWORD

;---------------------------------------------------------------------------
.data
  
  lRect    RECT <> 
  lPoint  POINT <>
  szClassName  db  16 dup(?)

.data?
  lhWnd      dd  ?
  szString1  db  16 dup(?)
  szString2  db  16 dup(?)

.code

start:

  push    1
  call    GetCommandLineA
  push    eax
  push    0
  push    0               ; lpModuleName
  call    GetModuleHandleA
  push    0               ; dwInitParam
  push    offset _ProcDlgMain ; lpDialogFunc
  push    0               ; hWndParent
  push    65h        ; lpTemplateName
  push    eax             ; hInstance
  call    DialogBoxParam  ; Create a modal dialog box

  push    NULL
  call    ExitProcess

;---------------------------------------------------------------------------

_ProcDlgMain  proc  uses ebx edi esi hWnd,wMsg,wParam,lParam
  mov  eax,wMsg
  .if  eax == WM_CLOSE
    invoke  EndDialog,hWnd,NULL
  .elseif  eax == WM_INITDIALOG
      push    0               ; lpTimerFunc
      push    12Ch            ; uElapse
      push    1               ; nIDEvent
      push    hWnd
      call    SetTimer
      push    offset lRect     ; lpRect
      push    hWnd
      call    GetWindowRect
      push    41h             ; uFlags
      push    lRect.bottom  ; cy
      push    lRect.right   ; cx
      push    lRect.top     ; Y
      push    lRect.left    ; X
      push    0FFFFFFFFh    ; hWndInsertAfter = HWND_TOPMOST
      push    hWnd
      call    SetWindowPos
      push    1
      pop     eax
      ret
;  .elseif  eax == WM_COMMAND    ;原来软件中对此进行处理。我这里注释掉了
;      mov  eax, TRUE
;      ret
  .elseif  eax == WM_TIMER
      push    offset lPoint    ; lpPoint
      call    GetCursorPos
      push    lPoint.y
      push    lPoint.x      ; Point
      call    WindowFromPoint ; Get handle of the window that

      push    eax       ;获取的窗口句柄
      pop    lhWnd       ;保存,供下一步应用

      xor  ecx, ecx
      mov  esi, offset szString1    
    @@:               ;这个小循环用来把句柄数值转换成字符串,存放在szString1,不过存放的数值是倒的
      test  eax, eax
      jz    @lExit
      xor  edx, edx
      mov  edi, 10h
      idiv  edi
      .if    dl >  9
        add  dl, 37h
      .else
        add  dl, 30h
      .endif
      mov  byte ptr [esi], dl
      inc  esi
      inc  ecx
      test  eax, eax
      jnz  @B

    @lExit:
      mov  edi, offset szString2
      dec  esi
    @@:              ;把倒过来的字符串再修正回来,放在szString2
      mov  al, BYTE ptr [esi]
      mov  BYTE ptr [edi], al
      dec  ecx
      dec  esi
      inc  edi
      test  ecx, ecx
      jnz  @B
      
      mov     BYTE ptr [edi ], 28h      ;填写句柄字符串最后的"(H)"
      mov     BYTE ptr [edi+1], 48h
      mov     BYTE ptr [edi +2 ], 29h
      mov     BYTE ptr [edi +3 ], 00

      push    offset szString2
      push    3EDh
      push    hWnd
      call    SetDlgItemText

      mov     edi, offset szClassName
      push    10h             ; nMaxCount
      push    edi             ; lpClassName
      push    lhWnd         ; hWnd
      call    GetClassName
      mov     edi, offset szClassName
      push    edi             ; lpString
      push    3E8h            ; nIDDlgItem
      push    hWnd
      call    SetDlgItemText          ; SetDlgItemTextA
      push    0               ; lParam
      push    0               ; wParam
      push    0CCh            ; Msg
      push    hWnd
      call    PostMessageA

  .elseif  eax == WM_LBUTTONDOWN

      call    ReleaseCapture
      push    0               ; lParam
      push    2               ; wParam
      push    0A1h            ; Msg
      push    hWnd
      call    SendMessageA

  .else
    mov  eax,FALSE
    ret
  .endif
  xor  eax, eax
  ret
_ProcDlgMain  endp
;---------------------------------------------------------------------------

end  start

   编译成功后4.5k,比原来软件12k稍微小点