为你的Notepad打上只属于你的烙印
制作目的:
1、看完了《加密与解密》二次开发那一章,需要练手巩固一下
2、毕竟是第一次二次开发,总得留下什么做纪念,不是嘛(⊙_⊙)?
选择缘由:
1、上家公司有一位BOSS逆向很牛,当时他向我们介绍逆向这门学问时,曾举过自己改造Notepad的事情(给其增加置顶与半透明功能),让人好生羡慕,所以二次开发Notepad也有纪念的意味;
2、在Ollydbg调试时,用记事本记东西比较方便,不过没有置顶、半透明功能,导致经常需要切换来切换去,比较麻烦,所以给记事本增加这些功能还是比较有意义的;
补充说明:
下面讲述时假设你已经比较熟悉PE结构,最好还看过“二次开发”那一章节;
言归正传:
我们知道二次开发有2种方式,第一种是直接修改可执行文件,在上面添加功能;第二种是为可执行文件导出扩展接口,调用我们用高级语言写的程序来实现需要的功能;因为有练手的目的,所以我们打算二者都采用;
以下是最终的效果图:


相对于原先记事本的具体改变是:
1、在菜单栏中增加了“高级”菜单
2、其中“置顶”、“透明度”及“说明”三个子菜单功能,我们是通过直接修改可执行文件完成的;
3、同时为记事本增加了DLL调用接口(插件式),方便采用高级语言二次开发,DEMO中的DLL为“高级”菜单增加一个“计算器”菜单项,点击后打开系统计算器;
4、所有需要保存的设置信息我们均采用注册表保存(如置顶及透明度配置);
要实现上述的功能,我们必须先找到合适的点能够插入我们的代码,比如调用CreateWindow的地方,又或者消息循环处理WM_COMMAND的地方,不然一切都是白搭;
首先我们先逆向下Notepad的运行流程,具体就是先对RegisterClassW以及RegisterClassExW下断点,最后运行发现断点断在RegisterClassExW函数处,接下来我们按Alt+M打开内存窗口,对Notepad的代码段01001000按F2下断点后再继续运行,此时停下来的地方就是RegisterClassExW调用结束的地方;具体如下:
01004524 |. 8D45 D0 LEA EAX, [LOCAL.12]
01004527 |. 50 PUSH EAX ; /pWndClassEx = 0006FDF0
01004528 |. C745 F4 01000000 MOV [LOCAL.3], 1 ; |
0100452F |. 8975 E4 MOV [LOCAL.7], ESI ; |mynotepa.01000000
01004532 |. C745 F8 20900001 MOV [LOCAL.2], OFFSET mynotepa.szNotepad ; |UNICODE "Notepad"
01004539 |. C745 D8 29340001 MOV [LOCAL.10], mynotepa.NPWndProc ; |
01004540 |. C745 F0 06000000 MOV [LOCAL.4], 6 ; |
01004547 |. 897D D4 MOV [LOCAL.11], EDI ; |
0100454A |. 897D DC MOV [LOCAL.9], EDI ; |
0100454D |. 897D E0 MOV [LOCAL.8], EDI ; |
01004550 |. FF15 D0110001 CALL NEAR DWORD PTR DS:[<&USER32.RegisterClassExW>] ; \RegisterClassExW
01004556 |. 66:F7D8 NEG AX
我们从RegisterClassExW参数中可以得知消息循环处理代码在地址01003429处,函数名为NPWndProc(带debug信息就是好呀,O(∩_∩)O~);
随后我们对CreateWindowExW下断点执行,中断后按Alt+F9执行到调用结束处,具体如下:
01004667 |. 53 PUSH EBX ; /lParam = NULL
01004668 |. 56 PUSH ESI ; |hInst = 01001394
01004669 |. 53 PUSH EBX ; |hMenu = NULL
0100466A |. 53 PUSH EBX ; |hParent = NULL
0100466B |. FF35 709A0001 PUSH DWORD PTR DS:[1009A70] ; |Height = 19F (415.)
01004671 |. BE 94130001 MOV ESI, mynotepa.01001394 ; |
01004676 |. FF35 749A0001 PUSH DWORD PTR DS:[1009A74] ; |Width = 301 (769.)
0100467C |. FF35 7C9A0001 PUSH DWORD PTR DS:[1009A7C] ; |Y = 15B (347.)
01004682 |. FF35 789A0001 PUSH DWORD PTR DS:[1009A78] ; |X = 14C (332.)
01004688 |. 68 0000CF00 PUSH 0CF0000 ; |Style = WS_OVERLAPPED|WS_MINIMIZEBOX|WS_MAXIMIZEBOX|WS_SYSMENU|WS_THICKFRAME|WS_CAPTION
0100468D |. 56 PUSH ESI ; |WindowName = ""
0100468E |. 68 20900001 PUSH OFFSET mynotepa.szNotepad ; |Class = "Notepad"
01004693 |. 53 PUSH EBX ; |ExtStyle = 0
01004694 |. FF15 E0110001 CALL NEAR DWORD PTR DS:[<&USER32.CreateWindowExW>] ; \CreateWindowExW
0100469A |. 8BD0 MOV EDX, EAX
0100469C |. 3BD3 CMP EDX, EBX
稍微跟踪下这段代码,发现CreateWindow以及ShowWindow这些行为都包含在一个名为“NPInit”的函数中,相应call指令的地址是0100297B,机器码是5字节大小,具体如下:
01002974 |. 50 PUSH EAX ; |Arg3 = 01000000
01002975 |. FF75 0C PUSH [ARG.2] ; |Arg2 = FFFFFFFF
01002978 |. FF75 08 PUSH [ARG.1] ; |Arg1 = 7C930228
0100297B |. E8 E51B0000 CALL mynotepa.NPInit ; \NPInit
01002980 |. 85C0 TEST EAX, EAX ; mynotepa.01000000
01002982 |. 75 08 JNZ SHORT mynotepa.0100298C
01002984 |. 8975 E8 MOV [LOCAL.6], ESI
01002987 |. E9 B0000000 JMP mynotepa.01002A3C
0100298C |> 53 PUSH EBX
0100298D |. 56 PUSH ESI ; /Flags = WINEVENT_OUTOFCONTEXT|WINEVENT_SKIPOWNTHREAD|WINEVENT_SKIPOWNPROCESS|92348
0100298E |. 56 PUSH ESI ; |ThreadId = 9234B
0100298F |. FF15 0C110001 CALL NEAR DWORD PTR DS:[<&KERNEL32.GetCurrentProcessId>] ; |[GetCurrentProcessId
从中我们发现NPInit调用结束后会判断返回值是否为TRUE,如果是FALSE则表示失败,并直接退出;
因而我们可以在这里进行第一步的截获操作,使其jmp到我们指定的地方去,在那里先调用NPInit,随后判断返回值是否为TRUE;若TRUE则接着调用我们额外添加的初始化函数,用于初始化用户的置顶及透明度设置;
因为我们的主要功能是通过菜单点击调用的,所以我们还需要继续查找消息循环中WM_COMMAND消息的处理逻辑;对刚刚找到的消息循环处理过程入口下条件断点([ebp+0x0C]==WM_COMMAND)并单步跟踪,不难发现,Notepad对于WM_COMMAND的消息处理专门封装了一个名为NPCommand(01002B87)的函数来处理,同时发现该函数对于未知命令ID的处理逻辑位于地址010032ED处,具体如下:
010032CB |. 81FF FF020000 CMP EDI, 2FF
010032D1 |.^ 0F8E 2EF9FFFF JLE mynotepa.01002C05
010032D7 |. 81FF 01030000 CMP EDI, 301
010032DD |. 7E 14 JLE SHORT mynotepa.010032F3
010032DF |. 81FF 02030000 CMP EDI, 302
010032E5 |. 74 39 JE SHORT mynotepa.01003320
010032E7 |. 81FF 03030000 CMP EDI, 303
010032ED |.^ 0F85 12F9FFFF JNZ mynotepa.01002C05
010032F3 |> 8D85 DCFDFFFF LEA EAX, [LOCAL.137] ; Cases 300,301,303 of switch 01002BBE
010032F9 |. 50 PUSH EAX ; /lParam = 1000000
010032FA |. 8D85 F0FDFFFF LEA EAX, [LOCAL.132] ; |
01003300 |. 50 PUSH EAX ; |wParam = 1000000
01003301 |. 68 B0000000 PUSH 0B0 ; |Message = EM_GETSEL
01003306 |. FF35 38980001 PUSH DWORD PTR DS:[hwndEdit] ; |hWnd = NULL
0100330C |. FF15 40120001 CALL NEAR DWORD PTR DS:[<&USER32.SendMessageW>] ; \SendMessageW
因此我们可以截获地址010032ED处的JNZ指令,使其跳转到我们扩增的命令处理处,处理完之后,我们再让其跳回至地址01002C05处,执行接下来的代码;
找到上述插入点后,接下来我们就可以放心的去打造我们所需的个性化Notepad了;
首先给Notepad增加菜单资源、对话框资源及加速键资源,建议采用VS编辑对话框资源(可视化编辑比较方便),具体如下截图所示:

在NPInit中我们发现被载入的加速键资源名为MAINACC,所以我们只需编辑名为MAINACC的加速键资源,给其增加30001(置顶),50001(计算器)二个加速键设置:

透明度设置对话框资源效果如下(其中对话框ID是30101,透明度值输出控件ID是30102,trackbar的ID是30103):

修改好上述资源后,我们接下来查看导入表中是否有我们需要的所有函数,如果没有的话,可以通过LordPE为其增加。在实际开发过程中,我们发现有如下几组函数我们需要用到,但是原导入表没有,分别列表如下(你可以现在全部添加,也可以在开发过程中逐步添加):
RegOpenKeyExW ADVAPI32.dll 读取注册表时用到
RegCreateKeyExW ADVAPI32.dll 写入注册表时用到
InitCommonControlsEx COMCTL32.dll 透明度设置对话框中TrackBar控件用到
SetTextColor GDI32.dll 透明度大小显示时用到
CreateSolidBrush GDI32.dll 透明度大小显示时用到
SetWindowPos USER32.dll 设置置顶时用到
SetLayeredWindowAttributes USER32.dll 设置透明度时用到
GetSysColor USER32.dll 透明度大小显示时用到
为了可持续开发,我们的目标不仅仅是本次的二次开发,我们还需要为未来的第N次开发做好规划;所以在地址空间布局上我们采用增加单独段的方式去扩展,而不是缝隙法;为此我们新增一个.mynpd数据段及.mynpc代码段,同时将来布置代码时,新增代码段中的各个代码块之间的间隔布置的大一点,以免未来扩充或者改动时发现没有空间,只好jmp来jmp去,那样不管在性能还是代码可读性上都比较差一点;
布置好空间的效果如图:(其中.Slivana是LordPE增加的新的导入表段,这里先不管三七二十一重建导入表,是为了让我们新增的段位于最后,便于将来可以连续地址空间扩增代码段)

最后就是完成上述功能的全部代码改动及分析:
首先我们跳转到原先call NPInit的地方(0100297B),采用JMP 01016000指令来替换,使其跳转到我们新增的地址空间中去,然后我们在01016000地址处新增代码,具体如下:
;初始操作逻辑处理处
01016000 CALL mynotepa.NPInit ;先调用原先的NPInit函数
01016005 TEST EAX, EAX
01016007 JE mynotepa.01002980 ;如果NPInit()==FALSE,则直接返回原逻辑处
0101600D PUSH DWORD PTR DS:[hwndNP] ;参数是hwndNP
01016013 CALL mynotepa.01016401 ;InitMyNotepad:用于初始化置顶、透明度设置
01016018 PUSH DWORD PTR DS:[hwndNP] ;参数是hwndNP
0101601E CALL mynotepa.01016A00 ;调用我们插件DLL的初始化函数
01016023 JMP mynotepa.01002980 ;处理完后再返回到原逻辑处
其中DS:[hwndNP]是Notepad窗口的句柄,在跟踪NPInit的时候,可以看到其创建完窗口后会把句柄保存到hwndNP中,我们在初始化的时候正好可以拿来用;
接下来我们修改原地址010032ED处的指令为JMP 01016050,使其跳转到我们扩展的命令处理逻辑处,具体如下:
;菜单命令逻辑处理处
01016050 LEA EAX, DWORD PTR DS:[EDI+FFFF8AD0] ;当前命令ID-30000
01016056 CMP EAX, 3
01016059 JA mynotepa.01016150 ;如果值大于3,则说明ID的处理不在此处,我们需要调用DLL插件中的函数尝试能否处理下
0101605F PUSH DWORD PTR SS:[EBP+8] ;接下来所以命令处理函数,我们均以hwndNP为参数,所以直接在开头调用下,免去每次调用的麻烦
01016062 DEC EAX ;ID==30001,置顶
01016063 JNZ SHORT mynotepa.0101606F
01016065 CALL mynotepa.01016500 ;SetTopMost:设置/取消置顶功能
0101606A JMP mynotepa.01003367 ;成功处理完之后直接返回到正确处理了命令的地方
0101606F DEC EAX ;ID==30002,透明度
01016070 JNZ SHORT mynotepa.0101607C
01016072 CALL mynotepa.01016600 ;SetTransparency:弹出对话框,让用户设置透明度
01016077 JMP mynotepa.01003367 ;成功处理完之后直接返回到正确处理了命令的地方
0101607C DEC EAX ;ID==30003,说明
0101607D JNZ mynotepa.01016141
01016083 CALL mynotepa.01016954 ;ShowAboutDlg:弹出说明对话框
01016088 JMP mynotepa.01003367 ;成功处理完之后直接返回到正确处理了命令的地方
。
。
。
01016141 POP EAX ;放置这么远是为了给将来N次开发Notepad留下空间
。
。
。
01016150 MOV EAX, DWORD PTR DS:[10151F8] ;判断DLL中MynpexCommandProc函数指针是否存在
01016155 TEST EAX, EAX ;为0表明不存在
01016157 JE mynotepa.01002C05 ;无法处理菜单命令时,跳转回原无法处理的逻辑处
;目前demo中实现了打开计算器的功能,具体见DLL实现代码
0101615D PUSH DWORD PTR SS:[EBP+10] ;lParam
01016160 PUSH DWORD PTR SS:[EBP+C] ;wParam
01016163 PUSH DWORD PTR SS:[EBP+8] ;hwndNP
01016166 CALL NEAR EAX ;DLL中专门用于处理菜单命令的MynpexCommandProc
01016168 TEST EAX, EAX ;返回值为1则表明成功处理
0101616A JE mynotepa.01002C05 ;成功处理完之后直接返回到正确处理了命令的地方
01016170 JMP mynotepa.01003367 ;无法处理菜单命令时,跳转回原无法处理的逻辑处
以下是上述逻辑中被调用函数的具体实现及注解:
[ebp+0x14] lpcbData
[ebp+0x10] lpData
[ebp+0xC] dwType
[ebp+0x8] lpValueName
function GetValInReg 按照用户指定的key,把数据从注册表HKEY_LOCAL_MACHINE\SOFTWARE\MyNotepad键下读取出来
[ebp-0x4] hKey
[ebp-0x8] dwValType
01016200 PUSH EBP
01016201 MOV EBP, ESP
01016203 SUB ESP, 8
01016206 PUSH EBX
01016207 XOR EBX, EBX
01016209 CMP DWORD PTR SS:[EBP+8], EBX
0101620C JE SHORT mynotepa.0101626B ;je label_failure
0101620E CMP DWORD PTR SS:[EBP+10], EBX
01016211 JE SHORT mynotepa.0101626B ;je label_failure
01016213 CMP DWORD PTR SS:[EBP+14], EBX
01016216 JE SHORT mynotepa.0101626B ;je label_failure
01016218 LEA EAX, DWORD PTR SS:[EBP-4]
0101621B PUSH EAX ;&hKey
0101621C PUSH 1 ;KEY_QUERY_VALUE
0101621E PUSH EBX ;0
0101621F PUSH mynotepa.01015000 ;L"SOFTWARE\\MyNotepad"
01016224 PUSH 80000002 ;HKEY_LOCAL_MACHINE
01016229 CALL NEAR DWORD PTR DS:[<&ADVAPI32.RegOpenKeyExW>] ;ADVAPI32.RegOpenKeyExW
0101622F TEST EAX, EAX
01016231 JNZ SHORT mynotepa.0101626B ;jnz label_failure
01016233 PUSH DWORD PTR SS:[EBP+14] ;lpcbData
01016236 PUSH DWORD PTR SS:[EBP+10] ;lpData
01016239 LEA EAX, DWORD PTR SS:[EBP-8]
0101623C PUSH EAX ;&dwValType
0101623D PUSH EBX ;0
0101623E PUSH DWORD PTR SS:[EBP+8] ;lpValueName
01016241 PUSH DWORD PTR SS:[EBP-4] ;hKey
01016244 CALL NEAR DWORD PTR DS:[<&ADVAPI32.RegQueryValueExW>] ;ADVAPI32.RegQueryValueExW
0101624A MOV EBX, EAX
0101624C PUSH DWORD PTR SS:[EBP-4] ;hKey
0101624F CALL NEAR DWORD PTR DS:[<&ADVAPI32.RegCloseKey>] ;ADVAPI32.RegCloseKey
01016255 TEST EBX, EBX ;不为0表明查询失败情况
01016257 JNZ SHORT mynotepa.0101626B ;jnz label_failure
01016259 MOV EAX, DWORD PTR SS:[EBP-8]
0101625C CMP EAX, DWORD PTR SS:[EBP+C] ;dwValType != dwType
0101625F JNZ SHORT mynotepa.0101626B ;jnz label_failure
01016261 XOR EAX, EAX
01016263 INC EAX
label_return:
01016264 POP EBX
01016265 MOV ESP, EBP
01016267 POP EBP
01016268 RETN 10
label_failure:
0101626B XOR EAX, EAX
0101626D JMP SHORT mynotepa.01016264 ;jmp label_return
由于代码过长,不好排版,所以接下来的代码及注释我放到了附件“代码及注解.txt”中,同时二次开发后的Notepad及插件DLL的示例代码也均在附件中;
