Poweroff 3.0.13功能改进过程
个人觉得Poweroff十分小巧的软件,我下载的是汉化版,未加壳168K,但它功能却非常强大,能定时开关机、关闭显示器、运行程序等等。可以说这是一款十分实用的软件。在使用过程中,发现在选项中的运行程序一项,设计的不是十分合理,其定时运行的程序后,Poweroff本身程序将推动响应,就是说,只有将程序关闭后,Poweroff才能继续运行后面的程序即关机,关显示器等操作,且这个功能只能运行应用程序,虽然可以选择任意文件,但经过测试发现非EXE文件会报错。
经过上面的实验,我对这个软件进行了调试。
打开OD,载入程序,运行。将动作中的选项点到“无”,然后在选项中点运行程序,点右边的程序按钮,选择要打开的程序比如“1.exe”。基于实验中的现像,可以判断这个软件运行外部程序调动的API函数为CreateProcessA,我们就对这个API函数设断,这个函数是在kernel32中。选立即执行选项,点启动,OD成功断下。这时OD当前行在API函数中如下:
kernel32.CreateProcessA
77E41BBC > 55 PUSH EBP
77E41BBD 8BEC MOV EBP,ESP
77E41BBF 6A 00 PUSH 0
77E41BC1 FF75 2C PUSH DWORD PTR SS:[EBP+2C]
77E41BC4 FF75 28 PUSH DWORD PTR SS:[EBP+28]
77E41BC7 FF75 24 PUSH DWORD PTR SS:[EBP+24]
77E41BCA FF75 20 PUSH DWORD PTR SS:[EBP+20]
77E41BCD FF75 1C PUSH DWORD PTR SS:[EBP+1C]
77E41BD0 FF75 18 PUSH DWORD PTR SS:[EBP+18]
77E41BD3 FF75 14 PUSH DWORD PTR SS:[EBP+14]
77E41BD6 FF75 10 PUSH DWORD PTR SS:[EBP+10]
77E41BD9 FF75 0C PUSH DWORD PTR SS:[EBP+C]
77E41BDC FF75 08 PUSH DWORD PTR SS:[EBP+8]
77E41BDF 6A 00 PUSH 0
77E41BE1 E8 4B170100 CALL kernel32.CreateProcessInternalA
77E41BE6 5D POP EBP
77E41BE7 C2 2800 RETN 28
77E41BEA > 6A 00 PUSH 0
77E41BEC FF7424 08 PUSH DWORD PTR SS:[ESP+8]
77E41BF0 E8 83860100 CALL kernel32.SleepEx
77E41BF5 C2 0400 RETN 4
我们要返回到上层,即调用函数的地方,在OD右侧的堆栈中的第一行点右键,选在反汇编窗口中跟随来到★处:
00401059 |. 83C4 0C ADD ESP,0C
0040105C |. 8D45 E8 LEA EAX,DWORD PTR SS:[EBP-18]
0040105F 50 PUSH EAX
00401060 8D45 A4 LEA EAX,DWORD PTR SS:[EBP-5C]
00401063 50 PUSH EAX
00401064 FF75 FC PUSH DWORD PTR SS:[EBP-4]
00401067 53 PUSH EBX
00401068 53 PUSH EBX
00401069 53 PUSH EBX
0040106A 53 PUSH EBX
0040106B 53 PUSH EBX
0040106C 57 PUSH EDI
0040106D 53 PUSH EBX
0040106E FF15 64514100 CALL DWORD PTR DS:[<&KERNEL32.CreateProcessA>] ; kernel32.CreateProcessA
★00401074 85C0 TEST EAX,EAX
00401076 75 0C JNZ SHORT cPowerOf.00401084
00401078 |. FF75 08 PUSH DWORD PTR SS:[EBP+8]
0040107B |. 56 PUSH ESI
0040107C |. E8 4F2E0000 CALL cPowerOf.00403ED0
00401081 |. 59 POP ECX
00401082 |. EB 33 JMP SHORT cPowerOf.004010B7
00401084 |> 68 40704100 PUSH cPowerOf.00417040 ; ASCII "Waiting for program to finish"
00401089 |. E8 472C0000 CALL cPowerOf.00403CD5
0040108E |. 59 POP ECX
往上看我们就可以看到这个API全部调用的参数的PUSH,这个API共有10个参数,具体用法大家可以到网上搜或者查阅MSDN。从这而也看到
不知道作者是否是有意的,在运行这个函数后没有关闭其内核对象,即调用一个CloseHandle函数,这就是运行外部程序后,Poweroff失去响应的原因所在。找到原因后,我们就要动手进行改造。这里我要把调用外部程序的API改为ShellExecuteA。为了修改成功,我自己随便写了段程序用到ShellExecuteA,调试发现其程序如下:
0040176A |. 6A 05 PUSH 5 ; /IsShown = 5
0040176C |. 6A 00 PUSH 0 ; |DefDir = NULL
0040176E |. 6A 00 PUSH 0 ; |Parameters = NULL
00401770 |. 68 5E304000 PUSH DragDrop.0040305E ; |FileName = "D:\crack\wya.exe"
00401775 |. 68 17314000 PUSH DragDrop.00403117 ; |Operation = "open"
0040177A |. FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hWnd
0040177D |. E8 10010000 CALL <JMP.&shell32.ShellExecuteA> ; \ShellExecuteA
这里我们就可以发现,要修改成功关键在两点,一个是替换的代码是否比原来的小,比原来的小就方便很多我们可以直接改,如果比原来的大就要动些手脚了,后面我们将发现很不幸是比原来大的,不过刚好我们可以学习一下嘛,总不能都能碰到方便的吧。其次是ShellExecuteA参数中的Operation是一个字符串,这个也是原程序中没有的。下面我就具体介绍解决方法。
首选就是要在原程序中加入"open"这个字符串,因为是4个字节,考虑到字符串最后为一个NULL即空字节,前面相邻的字符串之前至少也有一个NULL,所以我要找到原程序中有6个空字节的地方。在OD的左下方的内存窗口中点右键查找二进制000000000000,为了防止软件可以用到的一些空白的地方,所以要尽量往后找,最好是两个已有字符串的中间地带,我找到的是41C8F3,复制前面一小段数据,或者
后面的也可以。然后关闭OD程序,用16进制编辑软件如winhex等打开poweroff.exe查找刚才复制的数据,找到你需要的空白段,填入open(注意前后必须有一个00),保存文件。再次打开OD,载入程序运行,在内存窗口中来到41C8F3,看到了吧,有“open”了吧,我填的地方首地址是418F4即只空了一个字节。将这个地址记住,后面要用到。
在OD中来到0040105F,这是CreateProcessA第一个函数参数入栈的地方,我们的修改将从这而开始,右键,汇编,依次汇编如下★处
★0040105F . 6A 05 PUSH 5
★00401061 . 6A 00 PUSH 0
★00401063 . 6A 00 PUSH 0
★00401065 . 57 PUSH EDI
★★00401066 . E9 62390100 JMP cPowerOf.004149CD
0040106B . 53 PUSH EBX ; |pProcessSecurity
0040106C . 57 PUSH EDI ; |CommandLine
0040106D . 53 PUSH EBX ; |ModuleFileName
0040106E . FF15 64514100 CALL DWORD PTR DS:[<&KERNEL32.CreateProcessA>] ; \CreateProcessA
00401074 > 85C0 TEST EAX,EAX
00401076 . 75 0C JNZ SHORT cPowerOf.00401084
00401078 . FF75 08 PUSH DWORD PTR SS:[EBP+8]
0040107B . 56 PUSH ESI
0040107C . E8 4F2E0000 CALL cPowerOf.00403ED0
00401081 . 59 POP ECX
00401082 . EB 33 JMP SHORT cPowerOf.004010B7
00401084 > 68 40704100 PUSH cPowerOf.00417040 ; ASCII "Waiting for program to finish"
PUSH 5,两个PUSH 0这些就不说了,上面介绍函数时个就说过了,PUSH EDI是参考了CreateProcessA的参数,看到下面还有一个
PUSH EDI了吧,这里放着你在运行外部程序或者文件的路径。大家会认为上面介绍的ShellExecuteA有6个参数,这里压栈了4个怎么就没了呢。这就是我特别要指出的地方。开头就说了,替换的代码比原来的大,所以到这行程序就要停了,再换一行后面就不够用了,所以我们要跳走了看到这个JMP没,★★的地方。这个地址来源我也要介绍一下。一般的程序编译成EXE后,程序中都会有一些空白的地方,无论用哪种语言都一样,只不过有些多有些少一些罢了,这也就是为什么汇编语言写的程序最小的原因。我们在OD中往下翻,快到程序最后的时候有一大片的空白的地方,我们选一个起始地址,我选的是4149CD,所以在上面的代码中我用了JMP cPowerOf.004149CD,然后我们再来到004149CD继续我们的修改如下:
004149C3 CC INT3
004149C4 $- FF25 A4504100 JMP DWORD PTR DS:[<&KERNEL32.RtlUnwind>] ; ntdll.RtlUnwind
004149CA 00 DB 00
004149CB 00 DB 00
004149CC 00 DB 00
★004149CD > 68 F4C84100 PUSH cPowerOf.0041C8F4 ; |Operation = "open"
004149D2 . FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hWnd
004149D5 . E8 69F5FE76 CALL SHELL32.ShellExecuteA ; \ShellExecuteA
004149DA .^ E9 95C6FEFF JMP cPowerOf.00401074
004149DF 00 DB 00
004149E0 00 DB 00
看到了吧,这儿就用到了添加的字符串open,还记得地址吗,对写入PUSH 0041C8F4,下一个参数就是当然程序的句柄了,这个简单,熟悉软件调试的朋友应该知道,通常在调用API前的句柄放在EBP+8的堆栈中,这个大家最好用之前先验证一下,防止万一嘛,程序出错是小事,系统当掉就是大事了。然后是函数调用了,CALL SHELL32.ShellExecuteA解决。最后大家可别忘了,我们是从上面跳到这块空白地方来开垦的,当然要回去了,我们要把CreateProcessA完全替换掉,跳回来的地方就是它的下面一行即★处
0040106D 53 PUSH EBX
0040106E FF15 64514100 CALL DWORD PTR DS:[<&KERNEL32.CreateProcessA>] ; kernel32.CreateProcessA
★00401074 85C0 TEST EAX,EAX
00401076 75 0C JNZ SHORT cPowerOf.00401084
00401078 |. FF75 08 PUSH DWORD PTR SS:[EBP+8]
0040107B |. 56 PUSH ESI
这样就完全替换掉一部分,绕过一部分原来的代码了。接下来就保存修改了,在OD中点右键,选复制到可执行文件,选全部修改,关掉弹出来一个窗口,点是,就可以保存了。下面运行试试。选择可执行文件,定时运行OK,选择任意有关联的文件,如TXT,DOC,JPG等试试能打开,OK,而Poweroff也不会因为打开程序而停止运行了。成功!
总结:在没有源代码的条件下,通过逆向工程简单地修改软件达到更方便更人性化的使用,是一个往不错的选择。当然,大部分软件都是有版权保护的,一般也都禁止调试和修改,大家还是要把目的端正哦,呵呵,为了学习研究嘛。
原版下载:http://www.onlinedown.net/soft/16579.htm(华军)
大家可以到我签名中的网盘crack4000.5u6.net中下载修改好的Poweroff,欢迎大家使用和指导。
附加:上面的心得有少有缺陷的地方,比如楼下看雪老大提出段的问题,还有后来我发现调用API应该用导入表中的地址,而不应该直接调用,这样如果不同版本系统就不会因为API地址的不相同而不能运行同一程序了。
心得:这次修改这个软件,不但增加了自己的兴趣,而且又学到了很多新的东西。收获不小啊。