增加你的记事本--给记事本加上文本预览功能
现在记事本是越来越鸡肋了,随便找个文本编辑器都能把它给替代了,为了增加记事本在我的硬盘上的存活时间,我决定给它增加点特色功能。现在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
代码:
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
代码:
01002D3D |. 68 80A60001 push 0100A680
100A6C4 = 0100A680 + 0x44
从上面的代码找到给它赋值的代码:
代码:
01002D79 |. C705 C4A60001>mov dword ptr [100A6C4], 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]
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; }
建立一个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"
代码:
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对话框再进行上面的操作就可以看到效果了。
祝大家玩的愉快!