【文章标题】: 菜刀小试-为记事本加关闭提示框
【文章作者】: petnt
【软件名称】: NotePad.exe(XpSp2)
【使用工具】: OD\Stud_PE\UltraEdit-32
【操作平台】: XpSp2
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
--------------------------------------------------------------------------------
【前 言】
声明:本文创意来源于 Liebekmt 。
看了他的发帖,我动手实践了一下,现把整个过程和大体思路整理出来,没什么技术含量,仅供菜菜们交流,大侠请飘过。
【目 的】
在记事本退出之前,给出消息框提示,点击“是”正常退出,点击“否”取消返回。
【思 路】
1.在适当的位置截取退出消息。
2.写入实现功能的补丁代码。
【详细过程】
本例的重点在于如何寻找恰当的位置,只要位置合适,代码很好写。下面给出具体分析过程:
同样借鉴 Liebekmt 的想法,我们先寻找PostQuitMessage,准备在这里下手。顺着PostQuitMessage我们很容易发现整个消息处理函数。Liebekmt曾提到了如何更快捷的找到消息处理函数,我没有什么好方法,一般都是找一个程序肯定会自定义处理的一个消息,在这个消息上下断,返回程序领空的时候,大部分情况就是断在消息处理函数里了,如果不是,也不会跑得太远。消息处理函数OD会给出提示,如本例。再者,消息处理函数中有明显的消息类型比较,直接的如 cmp esi,XXX ,间接的如 sub esi,XX 后je等等,这些也可以作为我们确定它的标志。另外
,VC asm 中消息处理很有可能会用到DefWindowProc,通过他也可以定位到消息处理函数。下面给出记事本的消息处理函数(部分)。
01003429 8BFF mov edi, edi
0100342B /. 55 push ebp
0100342C |. 8BEC mov ebp, esp
0100342E |. 51 push ecx
0100342F |. 51 push ecx
01003430 |. 56 push esi
01003431 |. 8B75 0C mov esi, dword ptr [ebp+C]
01003434 |. 83FE 1C cmp esi, 1C ; Switch (cases 2..8001)
;;;;;;;;;;;;;;;;;此处省略大量代码;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
0100350D |. 0FBF45 16 movsx eax, word ptr [ebp+16]
01003511 |. 50 push eax
01003512 |. 56 push esi
01003513 |. E8 C8E4FFFF call 010019E0
01003518 |.^ EB A3 jmp short 010034BD
0100351A |> FF75 14 push dword ptr [ebp+14] ; Case 1 of switch 010034CE
0100351D |. 6A 01 push 1
0100351F |. 6A 05 push 5
01003521 |. E9 AD020000 jmp 010037D3
01003526 |> 6A 00 push 0 ; /ExitCode = 0; Case 2 (WM_DESTROY) of switch 01003434
01003528 |. FF15 F4110001 call dword ptr [<&USER32.PostQuitMess>; \PostQuitMessage <- 我们下断的函数
0100352E |.^ EB 8D jmp short 010034BD
01003530 |> FF75 14 push dword ptr [ebp+14] ; Case 8 (WM_KILLFOCUS) of switch 01003434
01003533 |. FF75 10 push dword ptr [ebp+10]
01003536 |. 52 push edx
01003537 |. E9 C1020000 jmp 010037FD
0100353C |> 8BC6 mov eax, esi
0100353E |. 83E8 10 sub eax, 10
01003541 |. 74 49 je short 0100358C
;;;;;;;;;;;;;;;;;此处省略大量代码;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
01003930 \.^ E9 9AFEFFFF jmp 010037CF
当我们运行并点击关闭按钮后,程序断在 Call PostQuitMessage的时候,请仔细观察被我们打开的记事本窗口。是的,我们已经发现不了记事本窗口了,他已经被Destroy掉了。所以在此处Patch是无法再返回到原来的程序了(以我的能力),我们应该再向前看一看。我们知道销毁窗口要用DestroyWindow,所以这次我们在这个函数上下断。
0100358C |> E8 A8040000 call 01003A39 ; Case 10 (WM_CLOSE) of switch 01003434
01003591 |. 33F6 xor esi, esi
01003593 |. 56 push esi
01003594 |. E8 76F1FFFF call 0100270F
01003599 |. 85C0 test eax, eax
0100359B |.^ 0F84 1CFFFFFF je 010034BD
010035A1 |. 56 push esi ; /Data
010035A2 |. 6A 02 push 2 ; |Command = HELP_QUIT
010035A4 |. FF35 74900001 push dword ptr [1009074] ; |Path = 00000011 ???
010035AA |. FF35 30980001 push dword ptr [1009830] ; |hWnd = NULL
010035B0 |. FF15 28120001 call dword ptr [<&USER32.WinHelpW>] ; \WinHelpW
010035B6 |. 85C0 test eax, eax
010035B8 |. 75 1D jnz short 010035D7
010035BA |. 68 10100000 push 1010 ; /Style = MB_OK|MB_ICONHAND|MB_SYSTEMMODAL
010035BF |. FF35 54900001 push dword ptr [1009054] ; |Title = 00000009 ???
010035C5 |. FF35 4C900001 push dword ptr [100904C] ; |Text = 00000007 ???
010035CB |. FF35 30980001 push dword ptr [1009830] ; |hOwner = NULL
010035D1 |. FF15 68120001 call dword ptr [<&USER32.MessageBoxW>>; \MessageBoxW
010035D7 |> FF35 34980001 push dword ptr [1009834] ; /hWnd = NULL
010035DD |. 8B35 A8110001 mov esi, dword ptr [<&USER32.Destroy>; |USER32.DestroyWindow
010035E3 |. FFD6 call esi ; \DestroyWindow <- 我们会返回到这里
010035E5 |. FF35 30980001 push dword ptr [1009830] ; /hWnd = NULL
010035EB |. FFD6 call esi ; \DestroyWindow
010035ED |. FF35 7CAB0001 push dword ptr [100AB7C] ; /hObject = NULL
010035F3 |. FF15 68100001 call dword ptr [<&GDI32.DeleteObject>>; \DeleteObject
010035F9 |.^ E9 BFFEFFFF jmp 010034BD
010035FE |> 33F6 xor esi, esi ; Case 1C (WM_ACTIVATEAPP) of switch 01003434
我们从我们返回出向上翻,顺藤摸瓜一直找到 0100358C 处,OD已经给出了提示,原来这是在处理一个WM_CLOSE的消息。所以我们可以初步推理:点了关闭按钮后,程序并没有马上PostQuitMessage,而是先收到并处理了一个WM_CLOSE的消息,并且在这个消息中做了一些事情后Destroy掉了窗口。我们要想在点击我们设计的“否”按钮后还能顺利地返回程序,必须在DestroyWindow之前进行我们的工作。为了保证对他的手术成功顺利,我们有必要具体分析一下上面那段代码到底做了什么。
能做什么呢?我们先来想一想。保存工作,我想应该是这样的。我们在 0100358C 处下断,重新打开程序,在程序中随便输入,不点保存,点击关闭。程序断下来后,我们F8单步跟踪,当步过 01003594 这个Call的时候,出现了保存窗口。我们继续来想一想:在保存提示中,我们点击是和否最终都要退出程序,当点击取消时程序会返回程序,不做任何操作(不正符合我们设计的“否”按钮的执行流程吗?嘿嘿)。再看紧跟在Call后的跳转,跳转到的地方如下:
010034BD |> 33C0 xor eax, eax ; Default case of switch 010034CE
010034BF |> 5F pop edi
010034C0 |. 5E pop esi
010034C1 |. C9 leave
010034C2 |. C2 1000 retn 10
呵呵,返回了。这肯定是“取消”按钮应该做的事情了。点击“是”或者“否”都会继续向下进行,这一点可以通过实践验证。下面的代码需要我们分析吗?当然需要,只是我们没必要分析他们要做什么了,因为他们做什么最终的结果都会走向灭亡,虽然如此,我们仍然能发现他的利用价值。我们发现了什么?瞧瞧,MessageBoxW(记下他在输入表中的位置),可以被我利用;主窗口的句柄放在[1009830](记下),我们也完全可以引用。至此,我们确定了Patch的地方。那就是 0100359B 处。为什么?因为在这里我们已经很明确地看出两条线路,一条走向希望,另一条要走向灭亡。这符合我们的思路。
0100359B之前进行patch,暂且不说有没有合适的地方和需不需要我们恢复原程序代码,如果我们点击了“是”,而记事本在保存工作中被点击了取消怎么办(如果自己写代码退出会丢掉保存功能)?我们已经答应了退出却又被硬生生的拉回了程序,多损面子,:)。
在这之后patch,暂且不说有没有合适的地方,在这之后执行的代码都是退出前执行的代码,程序准备退出但只进行了一部分,又被你拉了回来,寄存器值肯定有变化,部分数据也可能有变化,中途折回影不影响正常执行谁也不敢保证。
再说回来,这是一个长跳转,6个字节的指令,我们要加的jmp是5个字节指令,再加上这个跳转在什么情况下跳到什么地方我们一清二楚,这将不涉及补丁原程序指令问题。在其他地方,我们至少还需要加几条指令来恢复或模拟原代码执行。所以这里是我们的上上选。
找到了下手的位置,我们还需要考虑另一个位置的问题,就是补丁代码放在什么位置。随便找个全零的地方写可不可以,在调试的时候OD中当然可以,但你选的那个地方能不能写入文件,保证在载入文件的时候一起载入内存呢?所以,我们需要谨慎。我们来看看文件节数据。
No | Name | VSize | VOffset | RSize | ROffset | Charact. |
01 | .text | 00007748 | 00001000 | 00007800 | 00000400 | 60000020 |
02 | .data | 00001BA8 | 00009000 | 00000800 | 00007C00 | C0000040 |
03 | .rsrc | 00007F30 | 0000B000 | 00008000 | 00008400 | 40000040 |
从上面我们可以看到载入内存后,从 00008748-00008FFF之间应该是没有用的。瞧,文件中text段我们都看不到剩余空间,实际上有没有呢?用UltraEdit-32打开记事本看看。
00007B40 33 32 2E 64 6C 6C 00 00 00 00 00 00 00 00 00 00 32.dll..........
00007B50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00007B60 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00007B70 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00007B80 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00007B90 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00007BA0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00007BB0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00007BC0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00007BD0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00007BE0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00007BF0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
还好,在00007C00之前还有一块空地。虽然不算太大,也应该够用了。这部分空地对应载入内存的程序地址为 01008748 — 010087FF ,所以,应该把补丁程序写在这一区段内。(此处不明白着请参考PE格式)
好了,万事俱备,只欠东风了,我们写代码吧。
01008748 85C0 test eax, eax ; 继续程序中的判断
0100874A ^ 0F84 6DADFFFF je 010034BD ; 保存中按了取消按钮的返回地
01008750 60 pushad ; 保护现场
01008751 6A 04 push 4
01008753 68 98870001 push 01008798 ; 自定义字符串
01008758 68 7B870001 push 0100877B ; 自定义字符串
0100875D FF35 30980001 push dword ptr [1009830] ; 记事本窗口句柄
01008763 FF15 68120001 call dword ptr [<&USER32.Messa>; USER32.MessageBoxW <- 注意是 W
01008769 83F8 06 cmp eax, 6 ; 点击是还是否?
0100876C 61 popad ; 恢复现场
0100876D ^ 0F84 2EAEFFFF je 010035A1 ; 点击是则返回程序继续执行
01008773 ^ E9 45ADFFFF jmp 010034BD ; 点击否则同取消按钮
01008778 00 db 00
01008779 00 db 00
0100877A 00 db 00
0100877B FF FE 3B 4E BA 4E 0C FF A8 60 1F 77 84 76 81 89 73 51 89 63 11 62 17 54 1F FF 00 00 00 (提示字符串Unicode格式)
01008798 D0 63 3A 79 00 00 00 00 (提示标题Unicode格式)
别忘了将 0100359B 处跳转 改为 Jmp 01008748 ,OK,将所有更改保存到可执行文件,看看效果吧。
【总 结】
其实没什么技术含量,对于我们新手来说,重要的是多思考,多动手,这样我们才能获得更多的知识。至于 Liebekmt 提到在PostQuitMessage处patch点“是”也退出不了的问题,我想应该是没有平衡堆栈造成的(可参照原程序在PostQuitMessage之后的处理,也可查看堆栈自己手动平衡)。因为我们补丁代码的前后并没有改变堆栈,而且还是基本按照原程序的思路走的,所以没涉及到堆栈的平衡。如有分析不当之处,敬请各位大侠指出。
--------------------------------------------------------------------------------
【版权声明】: 本文原创于看雪技术论坛, 转载请注明作者并保持文章的完整, 谢谢!
2008年01月27日 15:01:08
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
上传的附件: