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地址的不相同而不能运行同一程序了。
心得:这次修改这个软件,不但增加了自己的兴趣,而且又学到了很多新的东西。收获不小啊。

  • 标 题: 答复
  • 作 者:kanxue
  • 时 间:2006-02-06 09:27

引用: 最初由 crack四K 发布
首选就是要在原程序中加入"open"这个字符串,因为是4个字节,考虑到字符串最后为一个NULL即空字节,前面相邻的字符串之前至少也有一个NULL,所以我要找到原程序中有6个空字节的地方。 


关于寻找补丁空间补充一些:

查看这个程序的.text、.rdata区块的属性,都是只读属性,也就是说程序运行期间不会向这个区块写数据,可以选空白处加入自己的数据或代码,一般区块后面部分都会有空地的。
而这个程序的.data区块属性是可读写(属性值是C0000040),意味着程序运行时会有数据写进去,例如一些全局变量等。因此一般补丁代码不会放到这区块,以免引起一些错误。