快雪时晴2009年2月8日

问题提出:
下了最新的PEBrowse Professional Interactive, native and managed code debugger.  Last update 1/24/2009.
http://www.smidgeonsoft.prohosting.com/
发现在调试某些程序如局域网通讯软件飞秋FeiQ时,界面常被遮盖,影响调试,找来找去没看到有类似OD的窗口置顶(ALWAYS ON TOP)选项。

记起cxlrb的一篇DIY文章,搜了下:

标 题: 【原创】用 OD 给 EXE 文件添加一个对话框初探
作 者: cxlrb
时 间: 2006-06-08,17:16
链 接: http://bbs.pediy.com/showthread.php?t=27054

【文章标题】: 用OD给exe文件添加一个对话框初探
【文章作者】: CxLrb
【作者邮箱】: cxlrb@yahoo.com.cn
【作者主页】: http://unpack.blog.sohu.com/
【作者QQ号】: 21252130
【软件名称】: Pe_optimizer1.4汉化版by.CxLrb
【下载地址】: 自己搜索下载
【使用工具】: OD,HexDecChar,XN Resource Editor


不过Cxlrb给出的是MFC程序的改造方法,先找到资源项ID,再找CMP R,XXXX的形式。
我这里的PEBrowseDbg v8.13.1是DELPHI编写的,查找事件改造方法还不太一样。


PE Explorer199r5 查看资源文件并反编译(Delphi6)非常方便,很容易找到菜单项Tile对应的处理事件proc:

代码:
 0061230C                           TMainForm.WindowTile:
 0061230C  55                            push  ebp
 0061230D  8BEC                          mov  ebp,esp
 0061230F  83C4F8                        add  esp,FFFFFFF8h
 00612312  8955F8                        mov  [ebp-08h],edx
 00612315  8945FC                        mov  [ebp-04h],eax
 00612318  8B45FC                        mov  eax,[ebp-04h]
 0061231B  E8ECFBE6FF                    call  SUB_L00481F0C
 00612320  59                            pop  ecx
 00612321  59                            pop  ecx
 00612322  5D                            pop  ebp
 00612323  C3                            retn
不知道置顶的具体参数,看看资源中其他项的属性Ex_Tyle就有WS_EX_TOPMOST
好了,google一下置顶的C语句(很尴尬,没用过VC编写MFC或是GUI程序)
http://www.google.cn/codesearch/p?hl...pmost%20lang:c
void VID_Front_f( void )
{
        SetWindowLong( cl_hwnd, GWL_EXSTYLE, WS_EX_TOPMOST );
        SetForegroundWindow( cl_hwnd );
}
//时候证明这两句没能完成任务,仅在任务栏图标提示性的闪两下(OD调试时),界面还是被遮盖。


看看常量定义,又是本机搜素一下dwing的那个VC6RC2_lite带*.h:
\VC6RC2\VC98\Include
   283,697 WINUSER.H
#define GWL_EXSTYLE         (-20)
#define WS_EX_TOPMOST           0x00000008L


现在要找的数据就是cl_hwnd即主窗口hwnd了,在OD中查看窗口:
000F070C  G:\DOWNLOAD\n.exe (2700) (STOPP  Topmost  000209AD  17CF0000  00010110  主  004619F4  TMainForm
 0002099E    000F070C  =句柄  0  54000100    主  004619F4  TStatusBar
 000209A0    000F070C  =句柄  0  5600084E  00010000  主  004619F4  TToolBar
 00100704    000F070C  =句柄  0  54010017  00000200  主  004619F4  TTreeView

此次进程主窗口为000F070C,在OD中ALT+M,在进程DATA区段查找,找到一处:0x00626130(如此精确,如此幸运)
下硬件访问断点,CTRL+F2重新加载,F9运行,出现PEBr界面后随便选择一个目标程序加载(PEBr加载,非OD),
第三次中断后,该地址处有赋值了(当然每次运行不一样了)
0047BB0D  |.  6A EC               PUSH -14                                 ; /Index = GWL_EXSTYLE
0047BB0F  |.  53                  PUSH EBX                                 ; |hWnd = 000F070C ('G:\DOWNLOAD\n.exe (2700) (STO...',class='TMainForm')
0047BB10  |.  E8 5FC4F8FF         CALL <JMP.&user32.GetWindowLongA>        ; \GetWindowLongA
0047BB15  |.  A8 08               TEST AL,8
0047BB17  |.  75 11               JNZ SHORT PEBrowse.0047BB2A
0047BB19  |.  833D 30616200 00    CMP DWORD PTR DS:[626130],0
0047BB20  |.  75 17               JNZ SHORT PEBrowse.0047BB39
0047BB22  |.  891D 30616200       MOV DWORD PTR DS:[626130],EBX      ;这里赋值
0047BB28  |. /EB 0F         JMP SHORT PEBrowse.0047BB39        ;;停在这里
//回到上一行查看
EBX=0007066A
DS:[00626130]=0007066A

可以看出,该hwnd放在一个全局变量里面,后面可以直接使用了。

好了,现在就重利用Tile菜单项事件,夹带处理一下置顶语句吧,省了再添加一项“Alway On Top”菜单,一切为了省事,简化。

OD中CTRL+N看了下,
SetWindowLong( cl_hwnd, GWL_EXSTYLE, WS_EX_TOPMOST );
SetForegroundWindow( cl_hwnd );
两个API都有,又免去了导入。

考虑到要求能够取消置顶,恢复原窗口参数,需再用到一个api:
LONG GetWindowLong(  HWND  hWnd,  // handle of window
    int  nIndex   // offset of value to retrieve
   );

注意,为了跨平台,不要直接用API地址,参照原程序中的方法用CALL JMP地址:
00408174   $- FF25 4CB76500 JMP DWORD PTR DS:[<&user32.SetForeground>;  user32.SetForegroundWindow
004081CC   $- FF25 20B76500 JMP DWORD PTR DS:[<&user32.SetWindowLong>;  user32.SetWindowLongA
00407F74   $- FF25 48B86500 JMP DWORD PTR DS:[<&user32.GetWindowLongA>];user32.GetWindowLongA


修改原WindowTile事件响应PROC头为:
0061230C     /E9 8F2C0100     JMP PEBrowse.00624FA0    ;跳到PE空白处写代码
00612311     |90              NOP

找到PE尾部的空白处写如下代码:
00624FA0      60              PUSHAD
00624FA1      9C              PUSHFD
00624FA2      6A EC           PUSH -14
00624FA4      FF35 30616200   PUSH DWORD PTR DS:[626130]
00624FAA      E8 C52FDEFF     CALL <JMP.&user32.GetWindowLongA>
00624FAF      83F0 08         XOR EAX,8
00624FB2      50              PUSH EAX
00624FB3      6A EC           PUSH -14
00624FB5      FF35 30616200   PUSH DWORD PTR DS:[626130]
00624FBB      E8 0C32DEFF     CALL <JMP.&user32.SetWindowLongA>
00624FC0      FF35 30616200   PUSH DWORD PTR DS:[626130]
00624FC6      E8 A931DEFF     CALL <JMP.&user32.SetForegroundWindow>
00624FCB      9D              POPFD
00624FCC      61              POPAD
00624FCD      55              PUSH EBP
00624FCE      8BEC            MOV EBP,ESP
00624FD0      83C4 F8         ADD ESP,-8
00624FD3    ^ E9 39D3FEFF     JMP PEBrowse.00612311    ;返回原事件响应PROC
00624FD8      90              NOP

奇怪,OD没法保存,提示“在可执行文件中无法定位数据”,现把二进制记录下:
60 9C 6A EC FF 35 30 61 62 00 E8 C5 2F DE FF 83 F0 08 50 6A EC FF 35 30 61 62 00 E8 0C 32 DE FF
FF 35 30 61 62 00 E8 A9 31 DE FF 9D 61 55 8B EC 83 C4 F8 E9 39 D3 FE FF 90

用HEXWORKSHOP打开看,该空白地址处原来有很多数据,似乎是API IMPORT等信息????怎么在OD中又全0了呢?不懂,等专家回答。


只好拿ZEROADD新增一个KXSQ区段400H大小起始地址00733000:
原PROC头:
0061230C    - E9 EF0C1200   JMP PEBrowse.00733000
00612311      90            NOP

新区段KXSQ:
00733000    60                PUSHAD
00733001    9C                PUSHFD
00733002    6A EC             PUSH -14
00733004    FF35 30616200     PUSH DWORD PTR DS:[626130]
0073300A    E8 654FCDFF       CALL <JMP.&user32.GetWindowLongA>
0073300F    83F0 08           XOR EAX,8
00733012    50                PUSH EAX
00733013    6A EC             PUSH -14
00733015    FF35 30616200     PUSH DWORD PTR DS:[626130]
0073301B    E8 AC51CDFF       CALL <JMP.&user32.SetWindowLongA>
00733020    FF35 30616200     PUSH DWORD PTR DS:[626130]
00733026    E8 4951CDFF       CALL <JMP.&user32.SetForegroundWindow>
0073302B    9D                POPFD
0073302C    61                POPAD
0073302D    55                PUSH EBP
0073302E    8BEC              MOV EBP,ESP
00733030    83C4 F8           ADD ESP,-8
00733033  - E9 D9F2EDFF       JMP PEBrowse.00612311
00733038    90                NOP
注意:二进制串复制过来后,3个CALL和1个JMP都要调整到正确的地址数值。
60 9C 6A EC FF 35 30 61 62 00 E8 65 4F CD FF 83 F0 08 50 6A EC FF 35 30 61 62 00 E8 AC 51 CD FF
FF 35 30 61 62 00 E8 49 51 CD FF 9D 61 55 8B EC 83 C4 F8 E9 D9 F2 ED FF 90

还是不起作用,google了下,也许是多窗口的缘故。
由于PEBrDbg包含多个子窗口,光设置主窗口TOPMOST属性不起作用,子窗口还是被被调试窗口遮盖。
而且一开始再应加个检测是否主窗口hwnd已经获取(非0)。
继续改进:

#define GW_CHILD            5
00407EFC   $- FF25 80B86500 JMP DWORD PTR DS:[<&user32.GetWindow>]   ;  user32.GetWindow


最终要改成如此形式:
if(hWndParent != NULL)
    {
        HWND hWndChild ;
        while((hWndChild = GetWindow(hWndParent,GW_CHILD)) != NULL)//不知道如何遍历子窗口,暂时这样表述一下
        {
            SetWindowPos(hWndChild,HWND_TOPMOST,0,0,800,480,SWP_SHOWWINDOW);
        }
        SetWindowPos(hWndParent,HWND_TOPMOST,0,0,800,480,SWP_SHOWWINDOW);   
    }
}

==================
00733000    60              PUSHAD
00733001    9C              PUSHFD
00733002    833D 30616200 0>CMP DWORD PTR DS:[626130],0
00733009    75 15           JNZ SHORT PEBrowse.00733020
0073300B    90              NOP
0073300C    90              NOP
0073300D    90              NOP
0073300E    90              NOP
0073300F    9D              POPFD
00733010    61              POPAD
00733011    55              PUSH EBP
00733012    8BEC            MOV EBP,ESP
00733014    83C4 F8         ADD ESP,-8
00733017  - E9 F5F2EDFF     JMP PEBrowse.00612311
0073301C    90              NOP
0073301D    90              NOP
0073301E    90              NOP
0073301F    90              NOP
00733020    6A 05           PUSH 5
00733022    FF35 30616200   PUSH DWORD PTR DS:[626130]
00733028    E8 CF4ECDFF     CALL <JMP.&user32.GetWindow>
0073302D    85C0            TEST EAX,EAX
0073302F    74 26           JE SHORT PEBrowse.00733057
00733031    90              NOP
00733032    90              NOP
00733033    90              NOP
00733034    90              NOP
00733035    50              PUSH EAX
00733036    6A EC           PUSH -14
00733038    50              PUSH EAX
00733039    E8 364FCDFF     CALL <JMP.&user32.GetWindowLongA>
0073303E    8BD8            MOV EBX,EAX
00733040    83F3 08         XOR EBX,8
00733043    58              POP EAX                                  ; kernel32.7C816FD7
00733044    50              PUSH EAX
00733045    53              PUSH EBX
00733046    6A EC           PUSH -14
00733048    50              PUSH EAX
00733049    E8 7E51CDFF     CALL <JMP.&user32.SetWindowLongA>
0073304E    E8 2151CDFF     CALL <JMP.&user32.SetForegroundWindow>
00733053  ^ EB CB           JMP SHORT PEBrowse.00733020
00733055    90              NOP
00733056    90              NOP
00733057    6A EC           PUSH -14
00733059    FF35 30616200   PUSH DWORD PTR DS:[626130]
0073305F    E8 104FCDFF     CALL <JMP.&user32.GetWindowLongA>
00733064    83F0 08         XOR EAX,8
00733067    50              PUSH EAX
00733068    6A EC           PUSH -14
0073306A    FF35 30616200   PUSH DWORD PTR DS:[626130]
00733070    E8 5751CDFF     CALL <JMP.&user32.SetWindowLongA>
00733075    FF35 30616200   PUSH DWORD PTR DS:[626130]
0073307B    E8 F450CDFF     CALL <JMP.&user32.SetForegroundWindow>
00733080  ^ EB 8D           JMP SHORT PEBrowse.0073300F
00733082    90              NOP

==============
60 9C 83 3D 30 61 62 00 00 75 15 90 90 90 90 9D 61 55 8B EC 83 C4 F8 E9 F5 F2 ED FF 90 90 90 90
6A 05 FF 35 30 61 62 00 E8 CF 4E CD FF 85 C0 74 26 90 90 90 90 50 6A EC 50 E8 36 4F CD FF 8B D8
83 F3 08 58 50 53 6A EC 50 E8 7E 51 CD FF E8 21 51 CD FF EB CB 90 90 6A EC FF 35 30 61 62 00 E8
10 4F CD FF 83 F0 08 50 6A EC FF 35 30 61 62 00 E8 57 51 CD FF FF 35 30 61 62 00 E8 F4 50 CD FF
EB 8D 90


循环子窗口错!
当然错,因为没有正确遍历,死循环嘛。


有问题,找Google,提示用下面这个函数:
BOOL SetWindowPos(
    HWND  hwnd,  // handle of window
    HWND  hwndInsertAfter,  // placement-order handle
    int  x,  // horizontal position
    int  y,  // vertical position
    int  cx,  // width
    int  cy,  // height
    UINT  fuFlags   // window-positioning flags
   );
#define SWP_NOSIZE          0x0001
#define SWP_NOMOVE          0x0002
#define HWND_TOP        ((HWND)0)
#define HWND_BOTTOM     ((HWND)1)
#define HWND_TOPMOST    ((HWND)-1)
#define HWND_NOTOPMOST  ((HWND)-2)


再次用OD在程序中找到:
004081DC   $- FF25 18B76500 JMP DWORD PTR DS:[<&user32.SetWindowPos>>;  user32.SetWindowPos

好了,最终修改如下:
原PROC头:
代码:
0061230C    - E9 EF0C1200   JMP PEBrowse.00733000
00612311      90            NOP
新补区段起始位置:
代码:
00733000    60              PUSHAD
00733001    9C              PUSHFD
00733002    833D 30616200 0>CMP DWORD PTR DS:[626130],0
00733009    75 15           JNZ SHORT PEBrowse.00733020
0073300B    90              NOP
0073300C    90              NOP
0073300D    90              NOP
0073300E    90              NOP
0073300F    9D              POPFD
00733010    61              POPAD
00733011    55              PUSH EBP
00733012    8BEC            MOV EBP,ESP
00733014    83C4 F8         ADD ESP,-8
00733017  - E9 F5F2EDFF     JMP PEBrowse.00612311
0073301C    90              NOP
0073301D    90              NOP
0073301E    90              NOP
0073301F    90              NOP
00733020    90              NOP
00733021    6A EC           PUSH -14
00733023    FF35 30616200   PUSH DWORD PTR DS:[626130]
00733029    E8 464FCDFF     CALL <JMP.&user32.GetWindowLongA>
0073302E    83E0 08         AND EAX,8
00733031    83F8 08         CMP EAX,8
00733034    75 07           JNZ SHORT PEBrowse.0073303D
00733036    B8 FEFFFFFF     MOV EAX,-2
0073303B    EB 05           JMP SHORT PEBrowse.00733042
0073303D    B8 FFFFFFFF     MOV EAX,-1
00733042    6A 03           PUSH 3
00733044    6A 00           PUSH 0
00733046    6A 00           PUSH 0
00733048    6A 00           PUSH 0
0073304A    6A 00           PUSH 0
0073304C    50              PUSH EAX
0073304D    FF35 30616200   PUSH DWORD PTR DS:[626130]
00733053    E8 8451CDFF     CALL <JMP.&user32.SetWindowPos>
00733058  ^ EB B5           JMP SHORT PEBrowse.0073300F
0073305A    90              NOP
0073305B    90              NOP
=========
60 9C 83 3D 30 61 62 00 00 75 15 90 90 90 90 9D 61 55 8B EC 83 C4 F8 E9 F5 F2 ED FF 90 90 90 90
90 6A EC FF 35 30 61 62 00 E8 46 4F CD FF 83 E0 08 83 F8 08 75 07 B8 FE FF FF FF EB 05 B8 FF FF
FF FF 6A 03 6A 00 6A 00 6A 00 6A 00 50 FF 35 30 61 62 00 E8 84 51 CD FF EB B5 90

测试通过。

最后用PE Explorer199r5把菜单项&Tile改为“&Tile and Top switch”,保存,收工。