首页
社区
课程
招聘
[原创]菜刀小试-为记事本加关闭提示框
发表于: 2008-1-27 21:32 5391

[原创]菜刀小试-为记事本加关闭提示框

petnt 活跃值
12
2008-1-27 21:32
5391
【文章标题】: 菜刀小试-为记事本加关闭提示框
【文章作者】: 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直播授课

上传的附件:
收藏
免费 0
支持
分享
最新回复 (12)
雪    币: 47147
活跃值: (20380)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
2
动手实践才进步的更快。
petnt也可看看这篇文章的思路:
标 题: 记事本功能增加方案
作 者: peansen
时 间: 2005-10-05,22:13
链 接: http://bbs.pediy.com/showthread.php?t=17376
2008-1-27 21:45
0
雪    币: 485
活跃值: (12)
能力值: ( LV9,RANK:490 )
在线值:
发帖
回帖
粉丝
3
谢谢老大,正在拜读!
2008-1-27 21:58
0
雪    币: 200
能力值: (RANK:10 )
在线值:
发帖
回帖
粉丝
4
非常感谢petnt大哥的文章,先顶再看
2008-1-27 22:45
0
雪    币: 221
能力值: (RANK:10 )
在线值:
发帖
回帖
粉丝
5
受教了,谢谢petnt-_-
2008-1-27 23:28
0
雪    币: 201
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
upupup,我是菜鸟。
2008-1-28 09:35
0
雪    币: 243
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
非常棒。。。先去买电脑了。。这电脑实在太破了。。弄玩之后。我也做一遍。。。

PS: 有精华的都不是菜鸟。。。。。嘿嘿。我现在看贴基本只关注有精华的人。。看雪老大给精华的人的文章都不错的。。。嘿嘿。。
2008-1-28 09:58
0
雪    币: 193
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
能否问一下,怎么在消息上下断了?
2008-2-25 22:44
0
雪    币: 485
活跃值: (12)
能力值: ( LV9,RANK:490 )
在线值:
发帖
回帖
粉丝
9
你想下什么消息的断?
2008-2-25 22:54
0
雪    币: 193
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
很多消息啊,我都想知道呀,不同的消息是不同的方法吗?
2008-2-25 23:02
0
雪    币: 485
活跃值: (12)
能力值: ( LV9,RANK:490 )
在线值:
发帖
回帖
粉丝
11
2008-2-25 23:05
0
雪    币: 193
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
在记事本的最顶层窗口上设消息断点,为啥提示这个:
无法读取调试进程的内存. 位于 FFFF012F 的断点已被删除.
2008-2-25 23:14
0
雪    币: 223
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
rst
13
呵呵,记事本都被大家玩烂了
2008-2-26 02:00
0
游客
登录 | 注册 方可回帖
返回
//