标 题: 【原创】增加你的记事本--给记事本加上文本预览功能
作 者: cntrump
时 间: 2010-04-30,17:41:34
链 接: http://bbs.pediy.com/showthread.php?t=112098
增加你的记事本--给记事本加上文本预览功能
现在记事本是越来越鸡肋了,随便找个文本编辑器都能把它给替代了,为了增加记事本在我的硬盘上的存活时间,我决定给它增加点特色功能。现在 PHOTOSHOP等图形处理软件都能在图像预览,不妨我也给记事本加上个文本预览...
一般的打开/保存文件对话框是这样的(图1):

再看记事本的打开/保存文件对话框(图2):

多了一个编码的选项。这说明记事本定制了模板。在OPENFILENAME结构中:
Flags 字段如果指明OFN_ENABLEHOOK|OFN_ENABLETEMPLATE这两个选项则可以自行定制文件对话框的行为,包括消息处 理等操作。
lpfnHook 字段指向的是消息处理的过程。
思路:
明白了上面这两点,思路就明确了,我们在lpfnHook指向的过程中找到处理WM_NOTIFY消息的地方,在记事本处理这个消息之前先跳转到我们事先 准备好的流程中,在我们自己的流程里面进行文本预览的操作,完成之后再跳回去让记事本继续处理,这样就完成了整个过程。
操作流程:
1.先找到lpfnHook指向的过程地址。OD载入记事本(建议先从系统目录复制一份出来再进行操作,以防不测),在命令行插件中下断 点:bp GetOpenFileNameW (记事本是用UNICODE方式编译的,断这个API就行了),F9运行记事本,用记事本打开一个文件。会 在浏览文件的时候被OD断在系统领空。这时查看堆栈窗口,数据如下:
代码:
0007FB48 01002D89 /CALL 到 GetOpenFileNameW 来自 NOTEPAD.01002D83
0007FB4C 0100A680 \pOpenFileName = NOTEPAD.0100A680
Ctrl+G 来到01002D89,也就是调用这个函数的地方。往上看,找到调用GetOpenFileName的代码:
代码:
01002D3D |. 68 80A60001 push 0100A680 ; /pOpenFileName = NOTEPAD.0100A680
01002D42 |. A3 B0A60001 mov dword ptr [100A6B0], eax ; |
01002D47 |. C705 8CA60001>mov dword ptr [100A68C], 0100A5E0 ; |
01002D51 |. C705 BCA60001>mov dword ptr [100A6BC], 010013C4 ; |UNICODE "txt"
01002D5B |. C705 B4A60001>mov dword ptr [100A6B4], 881064 ; |
01002D65 |. C705 98A60001>mov dword ptr [100A698], 1 ; |
01002D6F |. C705 C8A60001>mov dword ptr [100A6C8], 010013A0 ; |UNICODE "NpEncodingDialog"
01002D79 |. C705 C4A60001>mov dword ptr [100A6C4], 01002452 ; |
01002D83 |. FF15 D8120001 call dword ptr [<&comdlg32.GetOpenFil>; \GetOpenFileNameW
01002D89 |. 85C0 test eax, eax
由于GetOpenFileName的参数是一个结构体的指针(不要问我怎么知道的,查下MSDN),所以离 Call GetOpenFileName最近的
代码:
01002D3D |. 68 80A60001 push 0100A680
0100A680就是结构体OPENFILENAME的首地址,通过查MSDN可以知道lpfnHook字段在偏移0x44的地方,也就是
100A6C4 = 0100A680 + 0x44
从上面的代码找到给它赋值的代码:
代码:
01002D79 |. C705 C4A60001>mov dword ptr [100A6C4], 01002452 ; |
不用怀疑,01002452就是我们要找的过程地址了,Ctrl+G跳到01002452。
2.在lpfnHook指向的处理过程中找到处理WM_NOTIFY消息的地方,慢慢往下找。找到下面的代码处:
代码:
010025EB |> \817F 08 A6FDF>cmp dword ptr [edi+8], -25A ; Case 4E (WM_NOTIFY) of switch 0100246C
010025F2 |. 0F85 01010000 jnz 010026F9
010025F8 |. 8D85 F4FDFFFF lea eax, dword ptr [ebp-20C]
在OD的提示下,很容易就知道,如果消息是WM_NOTIFY,那么就会跳到010025EB来进行处理。
3.编写补丁代码(path code)
有两种方案,一是添加区段,在新增的区段里写代码。二是用第三方开发工具来写函数,利用DLL导出函数,再在程序里调用。
显然第二种方式比较灵活,对PE文件的path操作比较少。缺点就是运行时要多带一个dll不太好看。这里我们选择第二种方案。函数代码如下:
代码:
BOOL NEAR CALLBACK HandleNotify(HWND hDlg,LPOFNOTIFY pofn)
{
WCHAR wsFilePath[MAX_PATH] = {0};
WCHAR wsFileTitle[MAX_FNAME_LEN] = {0};
switch(pofn->hdr.code)
{
case CDN_SELCHANGE: //文件名选择更改消息
{
CommDlg_OpenSave_GetFilePath(GetParent(hDlg), wsFilePath, MAX_PATH); //获取选择的文件名(完整路径名称)
GetFileTitle(wsFilePath, wsFileTitle, MAX_FNAME_LEN);
SetDlgItemText(hDlg, IDC_ST_FILENAME, wsFileTitle);
DWORD dwFileAttr = GetFileAttributes(wsFilePath);
if (!(dwFileAttr & FILE_ATTRIBUTE_DIRECTORY)) //如果选中的是文件
{
BY_HANDLE_FILE_INFORMATION stFileInfo;
HANDLE hFile = CreateFile(wsFilePath, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
GetFileInformationByHandle(hFile, &stFileInfo);
DWORD dwMapSize = stFileInfo.nFileSizeLow > 1024?1024:stFileInfo.nFileSizeLow;
if (!dwMapSize) //如果文件大小为零
{
SetDlgItemText(hDlg, IDC_EDTPREV, TEXT(""));
break;
}
HANDLE hFileMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, dwMapSize, NULL);
CloseHandle(hFile);
LPWSTR lpVoid = (LPWSTR)MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, dwMapSize);
CloseHandle(hFileMap);
LPSTR lpMem;
INT nCount;
if (0xFEFF == lpVoid[0]) //如果是UNICODE文本文件
{
nCount = WideCharToMultiByte(CP_ACP, 0, lpVoid + 1, dwMapSize/sizeof(WCHAR) - 1, NULL, 0, NULL, NULL);
lpMem = (LPSTR)VirtualAlloc(NULL, nCount + 1, MEM_COMMIT, PAGE_READWRITE);
WideCharToMultiByte(CP_ACP, 0, lpVoid + 1, dwMapSize/sizeof(WCHAR), lpMem, nCount, NULL, NULL);
}
else
{
nCount = dwMapSize + sizeof(CHAR);
lpMem = (LPSTR)VirtualAlloc(NULL, nCount, MEM_COMMIT, PAGE_READWRITE);
lpMem[nCount] = '\0';
memcpy(lpMem, lpVoid, nCount);
}
UnmapViewOfFile(lpVoid);
for (INT i = 0; i < (nCount - 1); i++)
{
if (lpMem[i] == 0)
lpMem[i] = 0x20;
}
SetDlgItemTextA(hDlg, IDC_EDTPREV, (LPSTR)lpMem);
VirtualFree(lpMem, nCount, MEM_RELEASE);
}
else if (dwFileAttr & FILE_ATTRIBUTE_DIRECTORY) //如果是目录
{
SetDlgItemText(hDlg, IDC_EDTPREV, TEXT(""));
}
}
break;
}
return TRUE;
}
4.修改文件
建立一个DLL工程,把上面的函数导出,再来对PE文件进行操作:
代码:
010025EB |> \817F 08 A6FDF>cmp dword ptr [edi+8], -25A ; Case 4E (WM_NOTIFY) of switch 0100246C
010025F2 |. 0F85 01010000 jnz 010026F9
010025F8 |. 8D85 F4FDFFFF lea eax, dword ptr [ebp-20C]
修改为:
代码:
010025EB /E9 5A610000 jmp 0100874A
010025F0 |90 nop
010025F1 |90 nop
010025F2 |90 nop
010025F3 |90 nop
010025F4 |90 nop
010025F5 |90 nop
010025F6 |90 nop
010025F7 |90 nop
跳过来先执行我们的补丁代码:
代码:
0100874A 50 push eax
0100874B 8B45 14 mov eax, dword ptr [ebp+14]
0100874E 50 push eax
0100874F 8B45 08 mov eax, dword ptr [ebp+8]
01008752 50 push eax
01008753 FF15 40300101 call dword ptr [<&PrevText.HandleNotify>] ; PrevText.HandleNotify
01008759 58 pop eax
0100875A 58 pop eax
0100875B 58 pop eax
0100875C 817F 08 A6FDF>cmp dword ptr [edi+8], -25A
01008763 ^ 0F85 909FFFFF jnz 010026F9
01008769 ^ E9 8A9EFFFF jmp 010025F8
保存所有修改,打开文件试试看!

5.最后的完善,进行文件保存操作的时候,也可以看到文件预览框了。但是没有看到文件内容。不好,有预览框竟然不能看内容,不和谐!
下面进行最后的完善:
下断bp GetSaveOpenFileNameW
找到给lpfnHook赋值的地方:
代码:
01002C58 |. C705 B4A60001>mov dword ptr [100A6B4], 888866
01002C62 |. C705 C8A60001>mov dword ptr [100A6C8], 010013A0 ; UNICODE "NpEncodingDialog"
01002C6C |. C705 C4A60001>mov dword ptr [100A6C4], 01001A28 ;就是这句
01002C76 |. C705 8CA60001>mov dword ptr [100A68C], 0100A540
01002C80 |. C705 BCA60001>mov dword ptr [100A6BC], 010013C4 ; UNICODE "txt"
Ctrl + G来到01001A28,处理消息是一系列case语句,由于保存对话框没有处理WM_NOTIFY消息,所以我们只能在 default语句的地方做修改了
代码:
01001A4C |. 48 dec eax
01001A4D |. 0F85 1F010000 jnz 01001B72 ;这句是处理default的
让它跳转到,我们的代码处进行第二次判断
代码:
01001A4C |. 48 dec eax
01001A4D 0F85 1D6D0000 jnz 01008770 ;跳到我们的处理流程
代码:
01008770 8B45 0C mov eax, dword ptr [ebp+C] ;让 eax = uMsg
01008773 83E8 4E sub eax, 4E ;WM_NOTIFY消息的值是0x004E
01008776 75 0E jnz short 01008786 ;如果不是WM_NOTIFY消息就跳走,否则先进行预览。
01008778 8B45 14 mov eax, dword ptr [ebp+14]
0100877B 50 push eax
0100877C 8B45 08 mov eax, dword ptr [ebp+8]
0100877F 50 push eax
01008780 FF15 40300101 call dword ptr [<&PrevText.HandleNoti>; PrevText.HandleNotify
01008786 33C0 xor eax, eax
01008788 ^ E9 0294FFFF jmp 01001B8F
最后保存文件,再看看保存文件对话框,也可以进行内容预览了。
6.补充说明
要得到上面图中的效果,我们要先修改记事本的资源,要手工添加3个控件用于信息的显示,两个静态控件和一个文本框。
我是使用Restorator导出资源后手动修改的,调整位置的时候要有点小技巧。
在附件中包含了修改好的.rc资源文件,直接用Restorator导入替换原来的NPENCODINGDIALOG对话框再进行上面的操作就可以看到效 果了。
祝大家玩的愉快!
沒有留言:
發佈留言