本帖最后由 苏紫方璇 于 2015-7-28 20:18 编辑
【文章标题】: 无源码给程序添加功能-记事本标题添加当前时间
【文章作者】: 苏紫方璇
【软件名称】: WindowsXP记事本
【保护方式】: 无
【使用工具】: OD、Stub_PE、LordPe、ResHacker
【版权声明】: 本文原创于苏紫方璇, 转载请注明作者并保持文章的完整, 谢谢!
--------------------------------------------------------------------------------
首先放出成品图:
修改方法是:
使用资源编辑器(本例用的ResHacker)给记事本添加菜单项,开启时间和关闭时间,并且在后面填上菜单的ID(十分重要)
然后使用od载入记事本,有窗口的程序都会有消息循环,不然整个程序都会卡死在哪里,不接受任何的响应,而注册大多使用的是RegisterClassEx,所以在RegisterClassExW上下断,然后F9运行,程序断下后,在右下角的堆栈中找到唯一一个参数,这个参数指向了一个结构体。
在RegisterClassExW断下后,堆栈中可以看到参数
查询MSDN可知RegisterClassEx的原型为
[C++] 纯文本查看 复制代码 ATOM RegisterClassEx( CONST WNDCLASSEX *lpwcx // class data);
而其中WNDCLASSEX为一个结构体
这个结构体的定义为
[C++] 纯文本查看 复制代码 typedef struct _WNDCLASSEX {
UINT cbSize;
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName;
HICON hIconSm;
} WNDCLASSEX, *PWNDCLASSEX;
事件处理函数就在第三项,下面在参数上按右键数据窗口跟随0x01003429就是事件处理地址了。
所以这样就找到消息循环的事件处理的地址。
下面在事件处理头部下断看看。
事件处理函数原型
[C++] 纯文本查看 复制代码 LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM)
断下后堆栈如下
在本次修改中,只用考虑前三个参数就可以了,HWND窗口句柄,UINT消息类型,WPARAM传递的数据(这里是菜单的ID)
下面就考虑编写代码来实现上面添加菜单项的功能了。有两种方案,
1、直接patch这个文件,在程序中新建一个区段,然后查看导入表是否有需要的api,如果没有的话就要手动添加,然后在区段中使用纯汇编来编写全部代码。
2、编写一个dll文件,将将自己编写的事件处理函数导出,然后再手动添加到记事本的导入表中,最后在程序原来的事件处理中做一个跳转即可。
两种办法都有优点和缺点,第一种做完后只是一个单文件,汇编代码很精简,但纯汇编编写频繁调用api很是麻烦。第二种方法,dll的编写可以使用各种语言,代码灵活,但最后程序需要带一个dll文件。
所以我使用第二种办法,在码代码之前,先整理一下思路
首先写一个函数来替换程序自带的事件处理函数,在这里面要判断用户是否点了自建的菜单项,判断方法就是UINT=WM_COMMAND(事件为菜单触发事件),WPARAM=65或者66(ID为刚才编辑文件时填写的部分),如果不是的话就返回。如果是开启时间的话就调用SetTimer启动定时器。如果是关闭的话,就调用KillTimer来终止定时器就好了。
下面是程序代码
[C++] 纯文本查看 复制代码 /*
以下代码由苏紫方璇编写。我尽量在每行都做详细的注释
*/
#include <windows.h>
#include <string.h>
#include <stdio.h>
char OldTittle[255]; //记录原始标题
//DLL入口函数
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lp)
{
//加载时不做任何响应,直接返回加载成功
return TRUE;
}
//定时器的回调函数
void CALLBACK TimeProc(HWND hWnd, UINT uMsg, UINT nTimerid, DWORD dwTime)
{
char Caption[255];
char szTime[10];
SYSTEMTIME st = { 0 };
GetLocalTime(&st); //获取时间
sprintf(szTime, "%02d:%02d:%02d", st.wHour, st.wMinute, st.wSecond); //格式化字符串(把时间转成字符串“时:分:秒”)
GetWindowText(hWnd, Caption, 255); //获取程序标题
if (Caption[0] != '*') strcpy(OldTittle, Caption); //若第一个字符不是* 就把这个标题当做原始标题
sprintf(Caption, "* %s %s", OldTittle, szTime); //拼合字符串
SetWindowText(hWnd, Caption); //设置标题
}
//导出的函数,此函数原型应是
//LRESULT CALLBACK WndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
//但实际使用时,多调用了一次函数,所以将第一个参数改为LPVOID其实这个就是
//调用原程序中事件处理函数的地址(程序最终返回的地址)
extern "C"
__declspec(dllexport) LRESULT WINAPIV WndProc(LPVOID lp, HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
if (message == WM_COMMAND) //判断消息是否为WM_COMMAND
switch (wParam)
{
case 66: //如果是id=66(开启)就启动定时器,每秒一次
SetTimer(hWnd, 1, 1000, TimeProc);
break;
case 67: //如果是id=67(关闭)就取消定时器,并恢复原来的标题
KillTimer(hWnd, 1);
SetWindowText(hWnd, OldTittle);
break;
}
return 0;
}
编译好dll之后,使用lordPE添加导入表,方法,打开lordpe中的pe编辑器,选择文件,然后点击目录,点击输入表后面的“...”,在弹出的对话框中按右键,add import。在dll和api中填写dll的名字和导出的函数名。按加号添加后,保存即可。
也可以使用Stud PE来给记事本手动添加导入表。(使用别的程序完美添加,但我这个记事本不知为何,添加后报错,若有知道为什么的,还望告知。谢谢)
打开Stud PE,把文件拖进去,找到函数(Functions),在输入函数(Imported Function)里面点击右键添加新输入(Add new Import)
在新对话框中填入刚才编译的dll,选择导出的函数。点击添加到列表(Add to list),确定即可
回到刚才的导入表,找到刚才添加的那条,记下虚拟偏移地址rva和文件头界面的基址(ImageBase)
下面继续使用od载入记事本程序,在左下的内存窗口右键-转到-表达式(或者Ctrl+G)
里面选择RVA,列表中选择记事本程序。(如果没有rva选项的话,请将刚才记下的基址和rva相加,填入此处)。
点击确定就可以找到刚才导入的dll文件函数地址了。
记下这个地址,然后找到刚才的事件处理部分,在头部五个int3 还有函数第一句mov edi,edi(这句没用不用恢复)
用这些构造一个call,来调用dll。(dll的那个替换事件处理要使用__cdecl调用约定,即调用者负责清理堆栈,这样就可以达到dll中程序返回后,程序原来的事件处理还可以接着进行处理。)
修改前:
修改后:
然后保存到可执行文件,在重新附加一次。下断RegisterClassExW,回溯到调用部分。(右下调用堆栈第一行鼠标点一下,然后按回车)找到上面的一堆mov操作,把其中的事件处理函数地址改成刚才构造的call的地址即可。
修改前:
修改后:
保存,运行,程序稳定运行,点击显示时间也可以显示。
最后说点题外话,这种文章在网上一抓一大把,记事本、计算器都被改烂了,为什么还要发这个帖子,因为前几天小生大牛在群上的一席话,使我感触颇深,他说“你能在吾爱写上一篇学习心得可能就能挽救一个准备放弃的人,技术是共进的,如果学到东西然后悄无声息,那么论坛久而久之将没有人气。”其实这方面的东西原来就在看,只不过没看懂。最近又看了看,有些理解了就做了这么个东西,希望与大家共同学习下吧。
再此感谢@小生我怕怕 在群上的一番话,感谢@Hmily 提供这个良好的学习环境 感谢培训课程的各位讲师牺牲自己的时间来给我们讲课。
修改记事本.7z
(62.4 KB, 下载次数: 261)
最后的最后,以上是本人的一点拙见,如果有什么错误和不足之处,还请各位批评指出,如果各位觉得学到了什么,还请不要吝啬手中的热心和CB,都砸给我吧,我不嫌多。
--------------------------------------------------------------------------------
2015年07月28日
|