【文章标题】: XP记事本增加置顶显示功能
【文章作者】: ciker
【作者邮箱】: lyq1373@sohu.com
【软件名称】: 记事本
【使用工具】: OD,stud_PE,Uedit32,eXeScope
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
--------------------------------------------------------------------------------
【详细过程】
本人最近帮别人在网上买彩票,号码记在记事本上,每次总要点下记事本,记几个号,在切换到网页买号,就想最好能把
记事本置顶,就不用这么烦了。这好最近再学习PE结构,趁机练练手,写点总结,失误之处敬请诸位大侠赐教!
首先能将窗口置顶的API函数是SetWindowPos(
HWND hWnd, // handle to window
HWND hWndInsertAfter, // placement-order handle
int X, // horizontal position
int Y, // vertical position
int cx, // width
int cy, // height
UINT uFlags // window-positioning flags
);其中hWnd是窗口句柄,
hWndInsertAfter指定窗口风格,这里设为HWND_TOPMOST,
X,Y,cx,cy是窗口大小,设为默认NULL,
uFlags是窗口位置标置,设为SSP_NOSIZE|WSP_NOMOVE;
用VC++6.0编译,反汇编后得知,HWND_TOPMOST的代码是FF,SSP_NOSIZE|WSP_NOMOVE的代码是3,其余是0;
第二步在记事本上增加置顶菜单,用eXeScope打开记事本,在菜单项的查看中增加子菜单“置顶”,ID设为70,保存;
第三步要使置顶功能生效,也是最关键的一步。首先用stud_PE打开记事本,发现它的USER32.dll中没有SetWindowPos函数
,如果要将它插入到原USER32.dll中,势必会影响其他函数的地址,增加工作量,因此最好在原IID数组中再增加一个IID
数组来存放USER32.dll.
1、用stud_PE查看,输入表的RVA是0x7604,Raw是0x6A04,大小是0xC8。RVA与Raw不同,要特别注意!
2、由于原空间不够,而它的.text空间也压缩的不够再存放一个输入表,所以只好将它移到.data区,0x7E00处。用Uedit32
选择0x6A04处0xC8大小的数据块,复制到0x7E00处(注意是覆盖原数据,粘贴时也要选择0xC8大小的块)。
3、在刚才复制的输入表中添加新的函数。由于记事本的输入表是一个绑定输入,所以要先找到USER32.dll的SetWindowPos
函数地址,在OD中找到函数地址是0x77D1C01B,用Uedit32写入新的输入函数项:
dll名称USER32.dll
Characteristics: 000092FC
TimeDateStamp: FFFFFFFF
ForwarderChain: FFFFFFFF
First thunk RVA: 000092FC
同时将目录表里的输入表RVA改成0x7E00,IID数组大小为0xDC,已修改好的IID如下所示:
00007e00h: 90 79 00 00 FF FF FF FF FF FF FF FF AC 7A 00 00 ; 恲..瑉..
00007e10h: C4 12 00 00 40 78 00 00 FF FF FF FF FF FF FF FF ; ?..@x..
00007e20h: FA 7A 00 00 74 11 00 00 80 79 00 00 FF FF FF FF ; 鷝..t...€y..
00007e30h: FF FF FF FF 3A 7B 00 00 B4 12 00 00 EC 76 00 00 ; :{..?..靨..
00007e40h: FF FF FF FF FF FF FF FF 5E 7B 00 00 20 10 00 00 ; ^{.. ...
00007e50h: B8 79 00 00 FF FF FF FF FF FF FF FF 76 7C 00 00 ; 竬..v|..
00007e60h: EC 12 00 00 CC 76 00 00 FF FF FF FF FF FF FF FF ; ?..蘶..
00007e70h: 08 7D 00 00 00 10 00 00 58 77 00 00 FF FF FF FF ; .}......Xw..
00007e80h: FF FF FF FF EC 80 00 00 8C 10 00 00 F4 76 00 00 ; 靲..?..魐..
00007e90h: FF FF FF FF FF FF FF FF 5E 82 00 00 28 10 00 00 ; ^?.(...
00007ea0h: 54 78 00 00 FF FF FF FF FF FF FF FF 3C 87 00 00 ; Tx..<?.
00007eb0h: 88 11 00 00 FC 92 00 00 FF FF FF FF FF FF FF FF ; ?..鼟..
00007ec0h: E0 92 00 00 FC 92 00 00 00 00 00 00 00 00 00 00 ; 鄴..鼟..........
00007ed0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ; ................
00007ee0h: 55 53 45 52 33 32 2E 64 6C 6C 00 00 00 00 53 65 ; USER32.dll....Se
00007ef0h: 74 57 69 6E 64 6F 77 50 6F 73 00 00 1B C0 D1 77 ; tWindowPos...姥w
调用SetWindowPos函数的汇编代码是CAll [基址+92FC]
第四步要找到窗口函数WndProc,Windows调用窗口函数时传递4个参数:hWnd为窗口句柄,message定义消息类型,wParam 和lParam参数包含附加信息。在OD中很容易看到一个Switch 0x1002BBE,它有很多case:1…303,正好和在eXeScope中看到的menu菜单的ID号相同,随便设个断点即可验证。找到WinProc的Default Case位置
0101002C05 |> /33C0 xor eax, eax ; Default case of switch 01002BBE
01002C07 |. |E9 5E070000 jmp 0100336A
在程序的.text段中找空位,在0x7B48后有段空间,转换成RVA是0x8748,所以改跳转为jmp 01008748
下面就调用SetWindowPos函数,首先要找到窗口句柄,WinProc的第一个参数正好就是,在OD中看就在堆栈[ebp+8]中, 所以SetWindowPos函数的所有参数都有了,调用即可,调用结束后再返回Default Case,下面是具体代码:
01008748 > \8B45 08 mov eax, [ebp+8]
0100874B . 6A 03 push 3 ; /Flags =WP_NOSIZE|SWP_NOMOVE
0100874D . 6A 00 push 0 ; |Height = 0
0100874F . 6A 00 push 0 ; |Width = 0
01008751 . 6A 00 push 0 ; |Y = 0
01008753 . 6A 00 push 0 ; |X = 0
01008755 . 6A FF push -1 ; |InsertAfter = HWND_TOPMOST
01008757 . 50 push eax ; |hWnd
01008758 . FF15 FC920001 call [10092FC] ; \SetWindowPos
0100875E .^ E9 07ACFFFF jmp 0100336A
所有工作结束,保存后即可运行,在“查看”菜单的子菜单中能看到“置顶”项,点击后记事本就在屏幕最前了!
--------------------------------------------------------------------------------
【经验总结】
本例还有些可改动的地方,如跳转到1008748后应该再加个判断菜单ID,但由于没有什么其他的case,所以影响不大,可
以省略
最困难的地方是输入表的改动,由于是绑定输入表,而且RVA和Raw不同,所以容易出错
--------------------------------------------------------------------------------
【版权声明】: 本文原创于看雪技术论坛, 转载请注明作者并保持文章的完整, 谢谢!
2007年10月28日 21:24:12
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课