QUOTE(GHBB @ 2005年 12月 14日 19时 52分) [snapback]263349[/snapback] 我们用来练习的程序是Windows自带的‘记事本’程序。我们将为它添加一个和QQ一样的‘总在最前’的功能!
需要的工具:
Ollydbg(以下简称OD),Resource Hacker,LordPE,TOPO,HIEW
BTW:我的系统是windows2003,所用的记事本和98下的不同,稍有出入而已,大家只要掌握了方法一样可以修改98下的。
OK,Follow me...
整个过程大体可分为三步,我们一步一步来,先看看第一步:修改资源
我们要在‘查看’栏中加入一项‘总在最前’
Resource Hacker或其它资源编辑工具打开记事本。2000中的记事本是在c:windowssystem32目录中。左边会出现‘菜单’,展开它后点中那个2052,右边就会出现菜单的资源脚本!
找到这里
POPUP "查看(&V)"
{
MENUITEM "状态栏(&S)", 27
}
我们的新功能就加在这里面,看到那个27了吧,那就是‘状态栏’选项的ID,我们添加的‘总在最前’项也有个ID,但不能和其它选项的ID相同。我们把新ID定为28,修改结果参照这个:
POPUP "查看(&V)"
{
MENUITEM "状态栏(&S)", 27
MENUITEM "总在最前(&A)", 28
}
注意:Resource Hacker中用的是十进制,但程序中要用十六进制来表示,所以当我们修改程序的时候应该用1C来表示,记住这个1C后面要用到。
点击上面的‘编译脚本’,然后保存,退出,到这里我们的第一步就完成了!
第二步,调试分析
首先你要知道的是,windows是一个消息驱动的系统,我们以往在记事本中点击的,打开、保存、粘贴、复制等命令实质上都是象windows发送各种消息。当你点击菜单中的命令后,程序会根据该命令的ID(就是刚才我们修改资源时说的那个ID)来决定发送哪个消息。我们既然在菜单中添加了新功能,那就要找到程序中判断消息的地方。了解这些后我们就可以着手跟踪程序了!
运行OD,打开‘记事本’,既然是发送消息来判断我们点击的菜单项,那我们就用SendMessage下断点,在命令行中输入BPX SendMessageA来设置断点。
注意:在2000或者XP下请用SendMessageW设置断点,因为WIN2000和98不同,2000以后的系统使用的都是UNICODE,98是不支持UNICODE的,所以对win2000中自带的程序下断点的话就要用SendMessageW。(UNICODE并不在本文讨论范围内,所以不作过多解释,但希望大家注意这些‘细节’,感兴趣的可以自己找相关资料)。
回车后,当前窗口会变成‘互相调变的CALL’的窗口(就是原来的参考窗口),在窗口中点右键->排序->Destination,找找SendMessageW,有十多个!到底哪个是对我们有用的呢?告诉,都有用!因为他们都是‘记事本’处理菜单栏时用来发送消息的。这么多都中断的话会很烦,我们还是随便找个断点吧,把其它断点删了。我选的是地址在01003003的那个SendMessageW,先静态分析一下,双击此处,来到:
01002FE6 PUSH DWORD PTR DS:[100AB7C] ; /hObject = NULL
01002FEC CALL DWORD PTR DS:[<&GDI32.DeleteObjec> ; DeleteObject
01002FF2 PUSH 1 ; /lParam = 1
01002FF4 PUSH ESI ; |wParam
01002FF5 PUSH 30 ; |Message = WM_SETFONT
01002FF7 PUSH DWORD PTR DS:[1009838] ; |hWnd = NULL
01002FFD MOV DWORD PTR DS:[100AB7C],ESI ; |
01003003 CALL DWORD PTR DS:[<&USER32.SendMessag>; SendMessageW-停在这
看到上面的WM_SETFONT了吗?这里可能是程序初始化字体和更改字体的地方!在C中相应的代码可以看成
SendMessage (hWndEdit, WM_SETFONT, (WPARAM) hFont, 1);
上面的
01002FE6 PUSH DWORD PTR DS:[100AB7C] ; /hObject = NULL
01002FEC CALL DWORD PTR DS:[<&GDI32.DeleteObjec>; DeleteObject
可能是在释放资源,C语言中就是DeleteObject (hFont);
在WIN32下写过程序的可能会更清楚些。其实通过逆向工程你该能整理出一份该程序的源代码!我们现在做的只是在‘读’一小段一小段代码而已,不要紧,关键是方法!由上面OD的反汇编代码的注释中你能看出OD为我们做了很多事,SoftIce 和 TRW 是没有这些的,对于初学者来说OD是个不错的伴侣。看懂这些你后面才知道如何把你的C代码(或其它语言)转成汇编代码,我们的目的是为它添加一个功能,所以就不探讨过多的东西了。
OK继续,我们现在暂且认为这块就是‘格式’菜单里的那个‘字体’项,如果是这样的话,那我们可能就已经进入程序判断消息的地方了。好,我们往上看看先(现在是静态分析,多多留意OD右边的注释,你能看到很多有用的东西),看看这行
01002F19 PUSH EBX ; Case 1A of switch 01002ACB
‘Case 1A of switch 01002ACB’想到什么了?在C语言中有个switch语句还记得吗?我们可以这样想象一下
switch (n)
{
case 17:
return 0;
case 16:
return 0;
case 1A:
...
}
n就是传递进来的值,它和case中的值比较,符合哪个就去执行相应case后面的语句。相对于我们这个程序就是这样进行判断的。那上面的1A是什么呢?还记得我们第一步中说的吗?程序中用的都是十六进制的数,但Resource Hacker中的是十进制,把1A换成十进制就是26,Resource Hacker中你能找到相应的ID为26的是
MENUITEM "时间/日期(&D)tF5", 26
这回知道了吧?那个1A就是"时间/日期",你可以在01002F19处设个断点然后看看运行程序,点编辑中的‘时间/日期’看看是不是会中断在这里:)
现在我们知道了这里就是判断各个命令的地方,往上看你还能找到好多类似的
01002EC1 CMP WORD PTR DS:[100A800],BX ; Case 16 of switch 01002ACB
01002E67 MOV EAX,DWORD PTR DS:[100983C] ; Case 17 of switch 01002ACB
这些16,17你将他们转成十进制数也能在Resource Hacker中找到相应的功能,看来我们找对地方了。现在我们要知道它是怎么来判断消息的。我们还是看看01002F19这行,OD显示‘跳转来自01002DBE’那我们就看看01002DBE
01002DB5 CMP ESI,1A
01002DB8 JG NOTEPAD.01002F24
01002DBE JE NOTEPAD.01002F19
这下就明白了,ESI中就是传递进来的ID,它和1A比较相等的话就去执行‘时间/日期’动能,如果大于的话就去执行别的功能。用同样的方法,你能找到判断ID为16,17,10等其它ID的地方,你最好实际调试一次,我们现在就修改这里,因为我们的新功能的ID是1C,第三步我们更改的时候把01002DB8的JG改一下,让它跳到我们加入代码的地方就OK了。
第三步:DIY PE
我们加入的是总在最前功能,需要BringWindowToTop,SetWindowPos这两个函数来完成该功能,我们先来看看这两个函数
BOOL BringWindowToTop(HWND hWnd);
BOOL SetWindowPos(
HWND hWnd,
HWND hWndInsertAfter,
int X,
int Y,
int cx,
int cy,
UINT uFlags
);
看来我们除了要添加这两个函数外还要必须获得程序的hWnd(句柄)。程序中当然没有这两个函数了,所以我们要手动构造,不过讲手动修改PE文件的话,太长,足够再写一篇文章的了,所以这里我教大家用LordPE来构造函数,这样做的优点是当你要构造十几或几十个函数的时候会比手动快很多!
运行LordPE,点击PE Editor,选中记事本程序,然后点Directories,在ImportTable那添加如图1,会弹出ImportTable,在上面你能看到很多DLL名,在那点右键->add import->在DLL那输入user32.dll,API那输入BringWindowToTop,然后点加号,再在API那输入SetWindowPos,这样就成功添加了,在上面找到我们加入的user32.dll看看加入的两个函数,记住他们的ThunkRVA,如图2,一会要用到。OK,别忘了点保存!
构造完函数后,我们还要知道程序窗口的hWnd,再去OD的参考窗口看看有什么函数,找一个调用hWnd的,然后把它的hWnd保存起来供我们使用,最后我锁定了GetWindowTextW这个函数,双击,来到
010067A1 PUSH 104 ; /Count = 104 (260.)
010067A6 LEA EAX,DWORD PTR SS:[EBP+10] ; |
010067A9 PUSH EAX ; |Buffer
010067AA PUSH DWORD PTR DS:[1009830] ; |hWnd = NULL
010067B0 CALL DWORD PTR DS:[<&USER32.GetWindowTex>; GetWindowTextW
看看010067AA处的PUSH,那就是hWnd,看来不用我们自己保存了,直接用这个就可以了,记下010067AA处的机器码‘FF35 30980001’现在函数和窗口句柄都准备齐了,开始加入代码。写代码前我们先用TOPO为程序加入一个节,我们将在这个新节中写我们的代码,你也可以在程序中找空白的地方加入代码,但为了以后我们继续为它添加新的功能还是再加入一个节吧!否则程序会被你越改越乱的。我们为它加入200字节(在我们这个程序中足够了,你可以多加入些),Do it,显示新节的地址是01014000,offset为10200,记住这个地址(这个地址可能会不同,记住你自己哪显示的地址)。
运行HIEW(你也可以直接用OD更改代码),选中记事本程序,我们要先去判断消息的地方进行修改,按F5输入21B8(21B8是怎么来的?把上面判断消息的地址01002DB5转换成Offset就OK了,你可以用LordPE的FLC功能转换),来到判断消息的地方,按照第二步中我们说的把01002DB8改一下,让它跳到01014000,按F3,修改机器码为0F8F42120100按F9保存,看看变成jg 01014000了吧!关于这个这个机器码,你可以在OD或其它调试器中更改反汇编的代码,看看更改后OD的机器码!
现在该是加入真正的代码的时候了,按F5输入10200来到我们加入的新节的地址!先看看我们如何用汇编来写这段‘总在最前’的代码:
CMP ESI,1C
JE 0101400A ;如果是1C就跳到PUSH [1009830]执行我们的代码
JMP 01002F24 ;如果不是就跳回去执行程序自己的功能
PUSH [1009830] ;hWnd
CALL BringWindowToTop
PUSH 3
PUSH 0
PUSH 0
PUSH 0
PUSH 0
PUSH -1
PUSH [1009830] ;hWnd
CALL SetWindowPos
JMP 0100322D ;完成后返回,有兴趣的可以去0200322D看看
以上这些是汇编实现的方法,我们在HIEW中修改最好用机器码的形式,还是老方法先在OD中直接写汇编代码,把它的机器码记下来,然后用HIEW修改,其实这种机器码修改也是有规律的,慢慢你就会掌握的了。我把上面汇编代码的机器码列出来大家对照一下吧:
83FE1C
7405
E91AEFFEFF
FF3530980001
FF152D300101
6A03
6A00
6A00
6A00
6A00
6AFF
FF3530980001
FF1531300101
E9FAF1FEFF
OK,F9保存,大家运行程序看看效果吧!
你真热心````
我也替他谢谢你.
|