【屌丝的逆袭系列】没有源代码也能加功能也能玩美化
【使用工具】 Peid 0.94,OllyDbg(OllyIce),exeScope,Spy4Win
【运行平台】 WinXP
【软件名称】 IMMSG飞鸽传书 (下载)
【软件简介】 一个用于内网交流和传输文件的小工具
前些日子到同事的同学哪里去拷《康熙大帝》。可惜网连好后,就是访问不了。看来又是什么安全策略的问题了,本想重设一下。这时同事的同学就讲可以使用 飞鸽传书。
这个软件用起来后感觉还不错,很方便,不过也有些不足。界面如下
看上图可知。首先是窗体太窄,尤其是 显示用户名和主机名的那个LISTVIEW。看起来很不爽。第二个问题是ICON(图标)太难看。第三个呢在图上看不出来,就是双击运行后,界面并不先出来,而只是在拖盘上显示个图标,双击后,主界面才能出来,非常不方便。于是就想改一改。
下面我们就一起来在没有源码的情况下把这些问题给改一改。
1. 改变窗体的大小
在未进行更改前,以为改变窗体大小应该是一件很容易的事情,不就是断一下CreateWindowExA,然后看传递的参数(参数里包含宽度和高度的值),之后顺藤摸瓜找到存放宽度和高度值的内存地址,更改成我想要的值不就行了嘛。如果幸运的话宽度和高度都是以立即数的形式给PUSH的话那就更美好了。当然事实往往和想象中的总是有差距的。动手之后才知道自己想的是那么简单,当然这也和自己的知识不牢是分不开的。不过,这也不是什么坏事,虽然分析了很多无关紧要的东西,但也通过这些分析学到了不少东西。也不能完全算坏事。好了。废话不多讲了。下面开始分析过程吧
第一步当然是查壳,本来打算如果是猛壳(例如TMD之类)的话就直接放弃,不过一查之下,结果甚是让人高兴,不仅不是猛壳,甚至连壳都没加。后来才得知其实这是一款国外人写的免费软件。国外人的免费软件不加壳是很正常的,毕竟人家不象咱国人。把人家的源码或者软件拿来随便加点东西,然后就开源的闭源,没加壳的加壳,最后再把软件名字和作者一改,这就堂而惶之的宣称是××的作品了。用老罗的话说:就是真TM没见过这么流氓的。PEID切图如下。
从图中可以看出这个软件是用Microsoft Visual C++ 6.0 写的。
我用的这个算是比较新的userdb(存放各种开发工具生成的EXE文件的特征码的文本文件,PEID就是用他来识别开发工具的),而且这个软件的这个版本年代也比较久远了,理论上应该识别的准确率是很高的,当然我们还是不能完全相信PEID的,因为很多加壳的软件,都可以将其加壳后的EXE伪装成VC6编写的。所以还是用OD(OLLYDBG)载进来看看先。载入OD代码窗口显示如下:
004183D7 >/$ 55 push ebp 004183D8 |. 8BEC mov ebp, esp 004183DA |. 6A FF push -1 004183DC |. 68 18CB4100 push 0041CB18 004183E1 |. 68 2C9D4100 push 00419D2C ; SE 处理程序安装 004183E6 |. 64:A1 0000000>mov eax, dword ptr fs:[0] 004183EC |. 50 push eax 004183ED |. 64:8925 00000>mov dword ptr fs:[0], esp 004183F4 |. 83EC 58 sub esp, 58 004183F7 |. 53 push ebx 004183F8 |. 56 push esi 004183F9 |. 57 push edi 004183FA |. 8965 E8 mov dword ptr [ebp-18], esp 004183FD |. FF15 74B14100 call dword ptr [<&KERNEL32.GetVersion>; kernel32.GetVersion 00418403 |. 33D2 xor edx, edx 00418405 |. 8AD4 mov dl, ah 00418407 |. 8915 DC224200 mov dword ptr [4222DC], edx 0041840D |. 8BC8 mov ecx, eax 0041840F |. 81E1 FF000000 and ecx, 0FF 00418415 |. 890D D8224200 mov dword ptr [4222D8], ecx 0041841B |. C1E1 08 shl ecx, 8 0041841E |. 03CA add ecx, edx
看到注释里的SE 处理和那个GetVersion没有,有了这两个家伙基本可以确定PEID的识别是准确的了。当然光确定没加壳没用,我们还得继续前进,在OD的代码区右键,查找->所有模块中的名称。在弹出的窗口上随便选择一行后,输入 CreateWindowExA.会出现很多个,选择模块是USER32的那个,点右键->切换断点。当然可能有些人会问,为什么要断在USER32模块里,为什么不直接查找->本模块中的名称,这样不是能直接断在本程序里,而不用再从USER32模块里跟回本程序的领空了吗。当然这个问题对于大部分人来说,都是异常简单的,但对于新手还是有必要解释一些的。OD的反汇编能力虽然强,但对于有些动态调用(使用LoadLibary GetProcAddress)就不一定能分析出来他的API名称了,所以查找->本模块中的名称的时候,可能就会遗漏掉这些地方的调用。所以还是在断在USER32里比较安全,无论静态或动态调用,总得从我这里走,而且从API返回到程序领空也并不是一件困难的事情。懂点汇编的人就知道只要看栈顶就可以了(CALL一个函数的时候,在进行函数体之前,总要将代码的下一个地址压栈的,所以在函数的第一行的时候,栈顶就一定是调用函数下一个地址,当然这是在保护模式下,在实模式下,在栈顶和栈顶的下一位,分别存CS和偏移)。最重要的是OD里会直接提示你会返回到哪里,知道了返回的地址,那么他的上一行,一定就是调用的地方了,当然多重调用的话,可能第一次返回的并不你想要到的地方,不过不要紧,耐心慢慢一步步跟就是了。总能找到你想要的位置的:)。
设断后F9,断在这里,代码区代码如下:
77D2190B > 8BFF mov edi, edi 77D2190D 55 push ebp 77D2190E 8BEC mov ebp, esp 77D21910 68 01000040 push 40000001 77D21915 FF75 34 push dword ptr [ebp+34] 77D21918 FF75 30 push dword ptr [ebp+30] 77D2191B FF75 2C push dword ptr [ebp+2C] 77D2191E FF75 28 push dword ptr [ebp+28] 77D21921 FF75 24 push dword ptr [ebp+24] 77D21924 FF75 20 push dword ptr [ebp+20] 77D21927 FF75 1C push dword ptr [ebp+1C] 77D2192A FF75 18 push dword ptr [ebp+18] 77D2192D FF75 14 push dword ptr [ebp+14] 77D21930 FF75 10 push dword ptr [ebp+10] 77D21933 FF75 0C push dword ptr [ebp+C] 77D21936 FF75 08 push dword ptr [ebp+8] 77D21939 E8 B5FEFFFF call 77D217F3 77D2193E 5D pop ebp 77D2193F C2 3000 retn 30 77D21942 FFB5 F0FBFFFF push dword ptr [ebp-410] 77D21948 FF15 A810D177 call dword ptr [<&ntdll.RtlReleaseAct>; ntdll.RtlReleaseActivationContext
这个就是USER32里的CreateWindowExA 反汇编后的样子,其中上面代码的第一句就是,CreateWindowExA 的第一条语句了,当然上面的信息对我们来说是没有用的。不用管那么多,关键是看堆栈 。
0012FCB0 00416876 /CALL 到 CreateWindowExA 来自 复件_IPM.00416870 0012FCB4 00000000 |ExtStyle = 0 0012FCB8 0012FDA0 |Class = "ipmsg_class" 0012FCBC 004207CC |WindowName = "IPMsg" 0012FCC0 20CF0000 |Style = WS_OVERLAPPED|WS_MINIMIZEBOX|WS_MAXIMIZEBOX|WS_MINIMIZE|WS_SYSMENU|WS_THICKFRAME|WS_CAPTION 0012FCC4 80000000 |X = 80000000 (-2147483648.) 0012FCC8 80000000 |Y = 80000000 (-2147483648.) 0012FCCC 80000000 |Width = 80000000 (-2147483648.) 0012FCD0 80000000 |Height = 80000000 (-2147483648.) 0012FCD4 00000000 |hParent = NULL 0012FCD8 00000000 |hMenu = NULL 0012FCDC 00400000 |hInst = 00400000 0012FCE0 00000000 /lParam = NULL
OD给出的解释已经很详细了,重要的是Class="ipmsg_class"(类名)和WindowName="ipmsg"(窗体名)。另外因为只要程序每次使用CreateWindowEx创建窗体的时候,这里都会被断下来,所以这个地方是会被多次断下来的,所以每次被断下来的时候,都要记录下Class和WindowsName,一会儿再拿来和用工具查出来的主窗体的类名和窗体名进行比较,如果类名和窗体名和主窗体的类名和窗体名能对应上的话,就可以确定当时被断下来的那个就是主窗体了。一旦确实了主窗体被断的位置,如此只要再运行一次程序,从那个地方开始返回到程序的领空,这样就可以找到创建主窗体的CreateWindowExA的地方了,然后在他PUSH 宽度和高度的地方,设断,删除其它断点,这样就可以断在我们想要的位置上,再顺藤摸瓜就可以找到它的宽度和高度的地址了。当然你也完全可以先运行程序,然后用工具查出来主窗体信息后,在CreateWindowExA上设条件断点,可能会更加的方便。不过在这里我们已经这样了,就继续顺着这个思路继续往下走吧。OKAY,记录了类和窗体名后,在目前看来已经够用了,就不用管其它了,继续F9。发现此时拖盘上的图标已经出来了。而且OD也显示程序已经处于运行状态。看来只截到这一个调用了,那么这个窗体是主窗体的可能性应该是比较大了。于是双击拖盘图标,打开主窗体,发现没有被截下来,想应该主窗体的确是已经被创建了,这里只是ShowWindow或者SetWindowPos一下而已.所以才不会被CreateWindowExA断点给截下来。当然还是刚才那句,想的和现实总是有段距离的。打开妖哥的SPY4WIN.查看主窗体信息
头大的了,不是吧.和刚才截到的窗口信息简直风马牛 不相及嘛。如果只是标题名不一样,也许还能给点安慰,因为可以使用SetWindowText 重设下标题,而且也经常有人这样干,但如果窗口类名不一样,那就要另当别论了,虽说也可以使用GetClassInfo.重设类名。但一般情况下正常人类不会那样做。难道我们辛辛苦苦断下来的唯一一个窗口还是没用的???头都晕了,当然俺可也不是那种那么容易放弃的人,所以接着在刚才断下来的那个窗口上又下了一番工夫,包括跟踪窗口事件,因为我发现使用SPY4WIN将那个窗体(类名为img_class的那个)显示后,双击就会出现主窗体。于是跟了事件,发现在打开主窗体之前,他发了一个WM_ACTIVEAPP**.事件。于是用OD在那个窗口上下了事件断点,跟踪了半天,没发现什么东西,只好放弃了。看来还是得从头理思路了,既然不是在启动的时候创建的窗体。那么会不会是在双击拖盘图标的时候创建的呢,不过刚才在双击后,也的确没断下来,于是又思考是不是使用了CreateWindowExW.于是在它上面设断,的确断了几个下来,却也都是和主窗体风马牛不相及的。
看来还得再理一次,而且得换个思路,刚才忽略了一个很大的问题,我们一直假设他是在启动的时候就创建了窗体,但这个却是不一定的,因为他是在双击托盘里的图标后,才显示的窗体,那么会不会是双击托盘图标后,才创建的窗体呢,这个得先确定一下。这个不难确认,先启动,使用刚才记录下来的主窗体信息在SPY4WIN中查找,没发现窗体,双击拖盘图标,查找,找到了。看来的确是在双击后才创建的,但为什么双击后,没有截住他呢,难到不是使用CreateWindowExA创建窗体,而是使用的是DIALOG相关函数。一想到DIALOG,忽然想到了主窗体的类名,#32770,好象记的在那里看到过介绍,是特殊窗口的类,于是百度了一下,果然。这是DIALOG(对话框)的类名,看来应该使用DIALOG相关函数了,不过此时又犯嘀咕了。因为貌似在什么地方又见到过。讲DIALOG相关函数最会还是会调用CreateWindowExA( W ).这样看来,CreateWindowExA断不下来。DIALOG相关函数应该也没戏了,所以当时并没有报多大希望。但事实又一次证明我错了。DIALOG相关函数当然首选CreateDialogParamA(W)了,于是在它上面设断,居然给断下来了。倒掉了。不过具体什么原因就不追究了,目的达到就行了。毕竟只是改软件,又不是分析软件。
删除原来所有的断点,重新在CreateDialogParamA处设断,既然已经知道了是双击备拖盘图标后才创建的窗体,所以设断的时机当然是选在程序完全启动之后了。设断,然后双击拖盘图 标。断在了这里
77D35EA0 > 8BFF mov edi, edi 77D35EA2 55 push ebp 77D35EA3 8BEC mov ebp, esp 77D35EA5 53 push ebx 77D35EA6 56 push esi 77D35EA7 8B75 08 mov esi, dword ptr [ebp+8] 77D35EAA 33DB xor ebx, ebx 77D35EAC 53 push ebx 77D35EAD FF75 0C push dword ptr [ebp+C] 77D35EB0 6A 05 push 5 77D35EB2 56 push esi 77D35EB3 FF15 2404D777 call dword ptr [77D70424] ; kernel32.FindResourceExA 77D35EB9 3BC3 cmp eax, ebx 77D35EBB 74 40 je short 77D35EFD 77D35EBD 57 push edi 77D35EBE 50 push eax 77D35EBF 56 push esi 77D35EC0 FF15 C402D777 call dword ptr [77D702C4] ; kernel32.LoadResource
当然这些信息仍然不重要,重要的是 堆 栈(话外音:我日,不重要你老帖出来干嘛 回答:了解一下没有什么坏处)
0012FD5C 00415E68 /CALL 到 CreateDialogParamA 来自 复件_IPM.00415E62 0012FD60 00400000 |hInst = 00400000 0012FD64 00000065 |pTemplate = 65 0012FD68 00000000 |hOwner = NULL 0012FD6C 00415B5D |pDlgProc = 复件_IPM.00415B5D 0012FD70 00000000 /lParam = 0
因为在双击后,主窗体很快出来了,所以最大的可能性就是:第一个断下来的地地方就是创建主窗体的地方。看堆 栈 信息第一排。那个 来自复件_IPM.00415E62。这个就是本程序调用此函数的地方了。再看看栈顶(这个第一行就是栈顶)。红字那个地址00415E68 ,这个就是执行完这个函数后要返回到的地址。00415E68 - 00415E62 =6 。 CALL指令两个字节,后面加一个地址(32位正好四字节)不正好是6吗。所以,如果遇到OD没有提示的情况下,可以使用这种方法确定调用地址,或者干脆不用算了,直接回到栈顶所指向的地址00415E68,然后往上看一行就行了,那个地方肯定是调用的地方。当然既然已经知道主窗体是使用CreateDialogParamA创建的,那其实已经很简单了,根本连程序领空也不用回了,使用CreateDialogParamA,必然会使用 窗体模版,这个在资源里一定能找到。看堆 栈 信息第三行,|pTemplate = 65 这个就是模版的资源号了。当然这个是16进制的。因为下面我们要使用EXESCOPE,所以得转成十进制101,因为EXESCOPE里的资源号是用十进制表示的。我勒个了去,原来啥都不懂的时候,还知道使用EXESCOPE打开文件,更改窗体的样式。现在居然一时没想起来,反而饶了这样一个大圈子,倒掉了。当然郁闷归郁闷。改还是要改。将程序载入EXESCOPE。找资源号为101的资源。肯定是个窗体,有了EXESCOPE,那就随你想怎么样就怎么样了。具体就不讲了。反正改就是了。到这里,调整窗体大小算是完成
调整后界面:
当然看到这里的朋友也不要觉得上当了,虽然上面做了N多无用功,但涉及的破解知识点以及逆向思路还是不少的。
2. 改变图标
改变图标是个很简单的工作。用EXESCOPE直接就可以修改。需要注意的原来的图标是32*32 16色的,你也必须找个同样大小,同样16色的图标,否则替换不了。EXESCOPE是不能改变资源的大小的。ICON的格式有点类似于BMP,点阵式的。图片文件的大小(占用的字节数)只和分辨率和位数(16色是4位)有关,和内容无关,不象JPEG和GIF等一些压缩格式的图片,文件的大小和图片的内容有很大的关系。
使用EXESCOPE打开飞鸽传书,当然备份一份是必不可少的。这是一个好习惯。
文件-》导入,选择你要替换的那个ICON,点打开。我选择的是自己处理的一个ICON,当然也是比较难看了。不过感觉还是比原来的好看一点。
图标替换是完成了,但双击运行后。却发现了一个问题,就是这个图标颜色本身就显得有些暗,而且大部分又是透明的,所以显示在拖盘上就很不明显()而且如果已经习惯了以前的飞鸽图标,这突然一变,多少都会有些不习惯。所以我们有必要更改下拖盘图标,把他换成原来的图片。当然动手前还是要先确定思路,方法基本上和上篇一样,就是截API调用。先看上图,图标资源105 下面还有一个108 .打开后看和原来的图标差不多。所以就想可以把这个108 的图标换成原来的图标,然后将拖盘的图标改成使用108的。这样不就可以了嘛。
先做准备工作,将108 的图标换成原来的105的图标。这个方法和前面一样,原来的那个图标可以从备份的那个文件里导出来。具体过程就不讲了。一切就绪后。就得开始 断AP I。
提到拖盘图标。我们第一时间想到的当然是 Shell_NotifyIconA(W).下面我们来看看他的声明。
Shell_NotifyIconA(
DWORD dwMessage,
PNOTIFYICONDATAA lpData
)
dwMessage 有三个值 NIM_MODIFY,NIM_ADD,NIM_DELETE分别指修改,添加,删除图标。
LpData 是一个PNOTIFYICONDATAA。声明如下
typedef struct _NOTIFYICONDATAA { DWORD cbSize; HWND hWnd; UINT uID; UINT uFlags; UINT uCallbackMessage; HICON hIcon; CHAR szTip[64]; } NOTIFYICONDATAA, *PNOTIFYICONDATAA;
cbSize指本结构的大小
hWnd,指接收消息的窗体(当你左键单击,或者右键单击图标时,会自动发送一个消息(此消息由此结构后面的uCallbackMessage定义)给这个窗体))
UID 图标的ID
uFlags用来设置以下三个参数uCallbackMessage、hIcon、szTip是否有效.
UCallbackMessage 当对拖盘图标进行操作时(左键双击WM_LBUTTONDBCLICK)时,向窗体发送的消息号。目标窗体只须处理这个消息号,并通过lapram判断所进行的操作(如左键单击,双击,右键单击等)然后做相应的处理就行了。
HIcon 图标句柄,注意一下这个,这个是要重点分析的。
SzTip 拖盘提示语句.
具体怎么使用这个函数,在这里就不详述了,大家可以查查相关的资料。在这里我们唯一要注意的就是那个HICON hIcon。这个就是指拖盘的图标了,但不幸的是。HICON明显是个图标的句柄,而不是我们想要的图标的资源号。如果是资源号的话,直接就可以在这个地方改了,但如果是句柄的话,可能就得烦一些了。
一开始的思路是。断下这个API后,返回到程序领空,在PUSH 参数的时候,得到上述的那个结构的地址,然后找到 HICON 的位置,下内存写入断点。这样就可以断在 给HICON 赋值的地方,这个地方一般离得到HICON的地方不会很远,最有可能的就是上一行。这样就可以跟进到获取HICON的函数里,这个函数里一定会使用到图标资源号,这样就可以找到图标资源号 并修改它了。但是实际运行中。却发现那块内存不止被这一个结构使用,写入的太过频繁,所以放弃了。于是另寻思路。现在我们的目的是找到获得HICON的函数,而且这个函数要和图标资源号相关,那么有几个,LoadIconA(W),LoadImageA(W )。最有可能的就是LOADICON。
LoadIconA 声明如下
LoadIconA(
HINSTANCE hInstance,
LPCSTR lpIconName
);
hInstance.是指出资源所在的模块(可能是EXE也可能是DLL)的实例。
lpIconName是指向NULL字符结尾的字符串的指针,它包含图标名。当然在这里我们也可以传一个资源号(资源标识)进去。所以这个函数正是我们需要的。当然在VC里传资源标识
的时候。要使用MAKEINTRESOURCE 转换一次。要不然是不可以通过的。当然这个和逆向没关系。 思路确定了,下面就是要确认了
重新在OD里载入飞鸽传书。断在入口点。
菜单栏->查看->断点 删除所有断点。在代码区(反汇编区)右键->查找->所有模块中的名称,键入 LoadIconA,选择模块是user32 的那个,右键 切换断点。
查一查断点窗口,是不是多了一个断点,是的话。F9,断了下来,代码区就不看了,没什么用处,直接看 堆栈
0012FCFC 004041A4 /CALL 到 LoadIconA 来自 IPMSG.0040419E 0012FD00 00400000 |hInst = 00400000 0012FD04 00000069 /RsrcName = 105.
正好两个参数全在这了。HInst = 00400000 这个一眼就看出来了,明显是指本程序。WINDOWS用户级 的程序默认的加载地址就是这个。RsrcName = 105 。105 不就是原来的那个图标的资源号嘛。当然我们还不能高兴的那么早,因为这个时候加载的图标,不一定是给拖盘图标使用的,有可能是设置窗体图标的。所以还得走着看看。先回到程序领空里调用这个函数的地方。和原来一样,看第一行。“来自 IPMSG.0040419E“
0040419E 就是调用此函数的地方了。在代码区 右键->转到->表达式,填入 0040419E。代码区第一行是
0040419E |. FF15 8CB24100 call dword ptr [<&USER32.LoadIconA>] ; /LoadIconA
向上翻几行。
00404192 |. 6A 69 push 69 ; /RsrcName = 105. 00404194 |. 50 push eax ; |hInst => 00400000 00404195 |. 8975 C4 mov dword ptr [ebp-3C], esi ; | 00404198 |. 8975 C8 mov dword ptr [ebp-38], esi ; | 0040419B |. 8945 CC mov dword ptr [ebp-34], eax ; | 0040419E |. FF15 8CB24100 call dword ptr [<&USER32.LoadIconA>] ; /LoadIconA
其中最后一排就是 我们刚才找到的调用的地方。不管它,看第一行。Push 69. 十六进制的 69 不就是十进的105(6*16+9)嘛。好的。在这一行F2设断。CTRL+F2 重新载入程序。F9.断在了00404192 处。双击这一行,在弹出的窗口里将69(105)改成 6C(108)。F9。断在了LoadIconA处。看堆栈
0012FCFC 004041A4 /CALL 到 LoadIconA 来自 IPMSG.0040419E 0012FD00 00400000 |hInst = 00400000 0012FD04 00000069 /RsrcName = 108.
说明我们已经将图标的资源号改过了。
先不用管它,继续F9。又被断了。看堆栈
0012FCD0 00408823 /CALL 到 LoadIconA 来自 IPMSG.00408821 0012FCD4 00400000 |hInst = 00400000 0012FCD8 00000069 /RsrcName = 105.
调用来自另外一个地方,传过来的图标资源号还是原来的105.再看拖盘,图标没有出来。看来我们第一次改的那个地方,八成是不对的了。先记下 00408821 这个地址。继续F9。又被断了。堆栈
0012FCD0 0040885B /CALL 到 LoadIconA 来自 IPMSG.00408859 0012FCD4 00400000 |hInst = 00400000 0012FCD8 0000006C /RsrcName = 108.
虽然这里RsrcName 是 108 但调用地址明显和第一次断的地方不一样。所以这个地方应该就是加载真正的图标108(现在的图标108已经在前面被我们改过了)的地方。所以这个地方对我们来说,用处不大。继续F9。再次被断。堆栈
0012EA48 73658B1B /CALL 到 LoadIconA 来自 73658B15 0012EA4C 00000000 |hInst = NULL 0012EA50 00007F00 /RsrcName = IDI_APPLICATION
这个调用看地址就不在本程序的领空了,所以暂时先忽略掉它。继续F9。拖盘图标这时已经出来了。图标还是原来的。OD显示了程序处于运行状态。
从上面来看,在程序处于运行前,有四个地方调用了LoadIconA.刚才第一个地方已经试过了。不是正确的地方。第三次传入的参数是原来的108,所以应该关系也不大。至于第四个,返回地址根本不在程序领空,所以也不用管,那么剩下的只有第二个调用了。
Ctrl+F2重新载入,先不忙着F9。为了不让其它的断点影响到我们,先清除所有的断点。另外刚才我们也已经记下了第二个调用LoadIcon的地方的地址,就是 00408821。代码区,右键->转到->表达式,填入这个地址 点确定
00408819 |> /6A 69 push 69 0040881B |. FF35 AC224200 push dword ptr [4222AC] 00408821 |. FFD3 call ebx
最后一排就是刚才找到的那个调用的地方,当然也不用管它。看第一行。又是一个Push 69.好的。在这一行F2设置断点。F9。断在了这里。和刚才一样,改 65 为 6c. 没有再被断下来。程序也正常运行起来,图标也已经出来了,而且已经变成了108对应的图标,也就是我们先前导入到108里的那个原先程序的图标了。至此。算是差不多已经完成了。但还存在一个问题,就是我们目前还只是在OD里改了代码。此时改的也只是内存里的代码而已。重新载入后不又没有了嘛。不要紧。OD功能还是很强大的。
在代码区 右键->复制到可执行程序->所有修改。弹出提示框。点 全部复制 ,弹出窗口,不用管,直接关闭,此时会提示你是否保存,点是,选择我们要替换的文件,此时问你是否覆盖。是。OK,一切搞定,关闭OD。双击我们修改后的飞鸽传书。再看一看。拖盘图标是不是又和原来一样了哈J.
注:在实际当中,使用的分析方法和此略有不同。在改了第一个调用,发现不成功后。我就重新载入了 程序。然后在shell_notifyicon上设了断点,找到了调用它的地方,又设了断点。然后运行程序。记录每个调用LoadIconA的地方的返回地址,直到断点断在了调用shell_notifyicon的地方为止,那么我记录下来的最后一次调用LoadIconA的地方就是设置拖盘图标的那个LoadIcon 了。事后,发现,其实没这么麻烦。用上面的那个分析方法就已经能够确定了。故在这里将较为简单的方法作为主要介绍,而实际当中的分析方法只作为一个参考而已。
好的。到此。第二步总算也完成
3. 启动就显示窗体,而不是双击图标后才显示
下面我们来解决开篇里提到第三个问题:双击启动后不弹出窗体,而只是在拖盘上有个图标,需要双击拖盘图标才能打开主窗体 的问题。
在正式开始之前,还得先回忆下在解决第一个问题时分析出来的“成果”:1. 主窗体是在双击拖盘图标时创建的。2.点主窗体上的叉是关闭主窗体,而不是隐藏它。3. 创建主窗体的函数是CreateDialogParamA。 本节将在这些分析的基础上继续。
同样的先确定思路。本来的思路是在Shell_NotifyIcon 后,随便找个位置,将那个位置的代码改成jmp A。A就是我们写内存补丁的位置,在A位置调用CreateDialogParamA,然后显示窗体,最后,再跳回到原程序的相应 位置继续执行。这个方法是可行的,而且CreateDialogParamA本身就存在,不需要我们重建输入表,省了不少的事。但却也有些问题,例如CreateDialogParamA要指定窗体的处理函数,这个处理函数虽然很容易跟出来,在这里设置也不麻烦,但感觉上总有点不爽。另外CreateDialogParamA参数还有好几个,都要处理一番。还是有些麻烦(其实也不是特别麻烦)的。于是就想有没有更好一点的办法,这时忽然想起,主窗体是在双击拖盘图标后才创建的,而且每单击一次就会创建一个主窗体。那么极有可能的情况是,创建主窗体的过程已经在原程序被写在了一个方法里,这样我们就不需要直接调用CreateDialogParamA然后再去处理因为调用他而引起的一系列后续操作了。只须简单的调用下原程序里的那个创建窗体的函数即可。思路是确定了,剩下的就是证实和实现了。
将飞鸽传书载入OD,代码区->右键->查找->所有模块里的名称,输入CreateDialogParamA,找到模块是user32.dll的那个F2设断,F9 直到OD显示 运行.双击 拖盘图标,断了下来。
当然代码区不是我们关心的,看堆栈
0012FD5C 00415E68 /CALL 到 CreateDialogParamA 来自 IPMSG.00415E62 0012FD60 00400000 |hInst = 00400000 0012FD64 00000065 |pTemplate = 65 0012FD68 00000000 |hOwner = NULL 0012FD6C 00415B5D |pDlgProc = IPMSG.00415B5D 0012FD70 00000000 /lParam = 0
代码区->右键->转到->表达式,填入00415E68 (看堆栈第一排,如果不知道为什么 写它, 第一节有讲)。代码区来到这里。
00415E68 |. 85C0 test eax, eax 00415E6A |. 8946 20 mov dword ptr [esi+20], eax 00415E6D |. 75 0B jnz short 00415E7A 00415E6F |. 56 push esi ; /Arg1 00415E70 |. E8 CEFDFFFF call 00415C43 ; /IPMSG.00415C43 00415E75 |. 59 pop ecx 00415E76 |. 33C0 xor eax, eax 00415E78 |. EB 03 jmp short 00415E7D 00415E7A |> 6A 01 push 1 00415E7C |. 58 pop eax 00415E7D |> 5E pop esi 00415E7E /. C2 0400 retn 4
你可以向上翻一翻,这段代码的第一行也就是00415E68 的上一行就是调用CreateDialogParamA的语句,当然在这里我们不管这些,在这一行,F2.设断,F9运行到这里。此时窗体并没出来。F8(单步步过)几步走到00415E7E 处,此处是RETN。不用管,继续。来到这里。
0040753F |. 8B07 mov eax, dword ptr [edi] ; IPMSG.0041C2A0 00407541 |. 6A 0A push 0A 00407543 |. 8BCF mov ecx, edi 00407545 |. FF50 04 call dword ptr [eax+4] 00407548 |. 6A 01 push 1 ; /Arg2 = 00000001 0040754A |. 57 push edi ; |Arg1 0040754B |. 8BCE mov ecx, esi ; | 0040754D |. E8 B8220000 call 0040980A ; /IPMSG.0040980A 00407552 |. 395E 6C cmp dword ptr [esi+6C], ebx
还是继续F8,当走过00407545 处的那个CALL之后,窗体出来了(任务栏图标已经出来) 看来再遇到一个retn就差不多要找到我们要找的函数了。继续。来到00407592.此处是一个retn。继续。来到这里。
00405590 . /E9 CB000000 jmp 00405660 00405595 > |8B06 mov eax, dword ptr [esi] ; Cases A1 (WM_NCLBUTTONDOWN),201 (WM_LBUTTONDOWN) of switch 00405540 00405597 . |8BCE mov ecx, esi
向上翻一翻。
00405587 . 55 push ebp 00405588 . 55 push ebp 00405589 . 8BCE mov ecx, esi 0040558B . E8 E71E0000 call 00407477 00405590 . E9 CB000000 jmp 00405660 00405595 > 8B06 mov eax, dword ptr [esi] ; Cases A1 (WM_NCLBUTTONDOWN),201 (WM_LBUTTONDOWN) of switch 00405540
上段中红字的那行,就是刚才来到的那一行。看一下他的上一行 call 00407477.这个就是我们要找的函数了。他有两个参数。PUSH EBP ,PUSH EBP。此时EBP是0.唯一感觉奇怪的是两个PUSH下面的那个mov ecx,esi. 因为这个软件是采用VC写的。所以这个函数可能采用的是thiscall的调用方式,使用 ECX传递this指针,当然这只是猜测,未进一步分析。只是对程序在不同的时间进行了一下跟踪,发现ESI的值一直保持不变,所以在补丁里加入这句也不会影响程序的正常运行。
好了。确定了调用函数。下一步,要确定调用的地方了。具体怎么找调用的地方,和上面的步骤差不多,只要断一下Shell_NotifyIcon.然后不停的F8.找个合适的位置就是了。一开始的时候。找的是004080d5。后来发现在这里调用创建窗体的函数会在启动时创建两个主窗体。所以改在了004050AA处。当然在改跳转之前,我们还得在程序里找一块地方供我们写补丁。此时PEID再次出场。
用PEID打开程序。找到 EP 段字样,跟在他后面的有一个按钮,按钮的文本是”>”.点一下。随便选择一个(.text .rdata……).右键搜索全0处。弹出如下对话框
从图中可以看出来。.text 节的偏移 0001A72A处有大小为 8D6 的空白位置。
8D6 = 2000多字节,差不多 2K了,绝对是够用了。所以就确定在这里面写了。当然我们不能使用RVA,而应该使用 基址+RVA 此程序的基址是 400000.所以我们写内存补丁的地方,应该在41A72A- (41A72A+8D6)之间,我们就确定在41A730处吧,这个地址好记哈。至于具体为什么这样确定,我想应该不要讲了吧。从RVA的名字也猜出来了。RVA的意思:相对偏移地址。相对于什么呢,当然是程序的基址了,而且一般EXE的基址都是400000.当然也有不一样的。不一样也不难确定。看看OD就知道了,OD里反汇编出来的代码的地址都是线性地址,也就是说都加了基址的,所以只须看看在OD里程序领空里的地址是多少就大致清楚了。当然这种方法确定的不是很准。准一点可以使用PE查看工具,查看NT头部分。具体就不讲了,可自已去查相关资料。
好的。现在确定了 调用 函数,确定了内存补丁位置,确定了跳转至内存补丁的位置,那么下一步自然是实现从跑转位置跳到补丁位置,然后写内存补丁了。
重新回到OD。来到 004050AA 处。
004050AA处原代码如下:
004050AA . /75 15 jnz short 004050C1
改成
004050AA . /75 15 jnz 41A730
F8 运行到 41A730 处。
依次写入下列补丁程序。
0041A730 60 pushad // 保存现场 0041A731 6A 00 push 0 // 参见前面的分析 原来是PUSH EBP 0041A733 6A 00 push 0 // 同上 0041A735 8BCE mov ecx, esi // 这个可能是thiscall 0041A737 E8 3BCDFEFF call 00407477 // 调用创建窗体的函数 0041A73C 61 popad // 恢复现场。 0041A73D ^ 0F85 7EA9FEFF jnz 004050C1 // 回到正常轨道
以上的注释,是我加上去的,可不用写。
好。做完这些,在代码区->右键->复制到可执行程序->所有修改。弹出对话框,选全部复制,弹出窗体,直接关闭,问是否保存,点是,选择原来的文件替换。好了。关闭,OD。双击原来的程序。看看是不是已经可以,双击就弹出 窗体了哈。 :)
——