很早以前就看到一个人写的软件,利用APIHOOK技术拦截SetWindowText函数做了一个可以修改Windows计算器显示文字颜色的工具,当时就对找个软件很有兴趣,现在学习了一下简单的PEDIY技术,决定给Window计算器自身加上设置文本颜色的功能,文章中有不对的地方还望各位大侠不吝赐教。

本文用到的工具有Ollybdg1.10,LordPE,reshacker.

设计思路为:为计算器软件添加一个按钮,单击按钮后弹出一个颜色对话框,用户选择了颜色后,就颜色保存到一个全局地址。修改WM_CTRCOLORSTATIC(0x138)消息响应函数,在这里取出我们的数值,直接作为SetTextColor函数的第一个参数,这样Edit就能用我们自己定义的颜色输出文本了。

【1】第一步当然是先要用reshacker添加一个菜单了,我放在查看菜单的最下方,注意下,有好几个菜单都有查看,最好都给添加上我们的菜单。
这是我添加的菜单的代码,添加一个ID为14550(0x38D6)的菜单。
  MENUITEM SEPARATOR
  MENUITEM "设置文本颜色",  14550

【2】为了让我们的计算器能够弹出一个颜色选择对话框出来,我们必须调用函数ChooseColorW,该函数在COMDLG32.DLL中导出,但是Windows计算器并没有导入COMDLG32.DLL找个动态库,因此我们可以用LordPE这个软件将ChooseColorW这个API添加进去(具体方法看加密与解密第三版),记下ChooseColorW在输入表中的地址。

【3】准备工作完成后我们就可以来修改计算器完成我们的功能了,用OD载入计算器,为了将我们的菜单响应函数添加进去,可以对函数ShellAboutW函数下断点,然后选择帮助-关于计算器就可以断下,函数返回后按2次Ctrl+F9,到达下面的地方:
01004324  |.  E8 22E3FFFF           CALL calc.0100264B
01004329  |.  834D FC FF            OR DWORD PTR SS:[EBP-4],FFFFFFFF
0100432D  |.  817D 08 2E010000      CMP DWORD PTR SS:[EBP+8],12E
01004334  |.  74 07                 JE SHORT calc.0100433D
01004336  |.  6A 00                 PUSH 0

移动光标到01004324  按Enter进入这个函数
0100264B   $  B8 F8280101          MOV EAX,calc.010128F8
01002650   .  E8 FBFF0000          CALL calc.01012650
01002655   .  81EC CC000000      SUB ESP,0CC
0100265B   .  53                        PUSH EBX
0100265C   .  56                        PUSH ESI
0100265D   .  8B75 08                 MOV ESI,DWORD PTR SS:[EBP+8]
01002660   > \B8 8C000000          MOV EAX,8C
01002665   >  3BF0                     CMP ESI,EAX

在这里面我们要找到一个地方跳转到我们的代码中去。我选择01002660   ,因为将这个该成跳转后改动的汇编码比较少。
将这句修改为
01002660   . /E9 21110100           JMP calc.01013786

然后转到01013786编写以下代码:

01013786   > \B8 8C000000           MOV EAX,8C  ;被我们改动的那句,在这里补上
0101378B   .  81FE D6380000         CMP ESI,38D6 ;比较是不是我们的菜单
01013791   .  75 05                 JNZ SHORT calc.01013798;不是就跳
01013793   .  E8 69FFFFFF           CALL calc.0101370;是我们的菜单就调用函数弹出一个颜色选择对话框出来
01013798   >^ E9 C8EEFEFF           JMP calc.01002665;跳转到原来的代码处继续执行

【4】好了,现在到编写这个弹出颜色对话框的函数的时间了,由于这个函数的参数比较复杂,先简单的介绍下(以下摘自MSDN)

BOOL ChooseColor(          LPCHOOSECOLOR lpcc
);
Parameters

lpcc
[in, out] Pointer to a CHOOSECOLOR structure that contains information used to initialize the dialog box. When ChooseColor returns, this structure contains information about the user's color selection. 

lpcc是一个指向CHOOSECOLOR 结构体的指针
typedef struct {
    DWORD lStructSize;
    HWND hwndOwner;
    HWND hInstance;
    COLORREF rgbResult;
    COLORREF *lpCustColors;
    DWORD Flags;
    LPARAM lCustData;
    LPCCHOOKPROC lpfnHook;
    LPCTSTR lpTemplateName;
} CHOOSECOLOR, *LPCHOOSECOLOR;

因此我们必须要自己填充CHOOSECOLOR这个结构体。为了方便编写汇编代码,可以用VC写一个简单调用这个函数的例子,然后找着例子编写代码,那样出错的可能性要小得多。

以下是我编写好了的代码,只以后面的注释作为说明:

01013701  /$  55                    PUSH EBP
01013702  |.  8BEC                  MOV EBP,ESP;保存堆栈
01013704  |.  83EC 6C               SUB ESP,6C;分配局部变量大小
01013707  |.  53                    PUSH EBX;保存现场
01013708  |.  56                    PUSH ESI
01013709  |.  57                    PUSH EDI
0101370A  |.  51                    PUSH ECX
0101370B  |.  8D7D 94               LEA EDI,DWORD PTR SS:[EBP-6C];获取到CHOOSECOLOR结构体的首地址
0101370E  |.  B9 1B000000           MOV ECX,1B;将结构体大小保存到寄存器
01013713  |.  B8 00000000           MOV EAX,0;设置结构体的初始值
01013718  |.  F3:AB                 REP STOS DWORD PTR ES:[EDI];结构体初始化;相当于memset函数的功能
0101371A  |.  59                    POP ECX
0101371B  |.  894D FC               MOV DWORD PTR SS:[EBP-4],ECX
0101371E  |.  C745 D4 24000000      MOV DWORD PTR SS:[EBP-2C],24;给结构体的lStructSize赋值
01013725  |.  C745 E8 03000000      MOV DWORD PTR SS:[EBP-18],3;给结构体的Flags赋值,你可以修改为其他值看看效果
0101372C  |.  8B0D 69370101         MOV ECX,DWORD PTR DS:[1013769];这个地址是我保存获取的颜色值的地方
01013732  |.  894D E0               MOV DWORD PTR SS:[EBP-20],ECX;对rgbResult赋值
01013735  |.  C745 E4 08380101      MOV DWORD PTR SS:[EBP-1C],calc.01013808;对lpCustColors赋值
0101373C  |.  8B15 6C4D0101         MOV EDX,DWORD PTR DS:[1014D6C];
这一句是获取主程序的全局句柄
01013742  |.  8955 D8               MOV DWORD PTR SS:[EBP-28],EDX;对hwndOwner赋值,如果不赋值则会出现2个窗口的BUG
01013745  |.  8D55 D4               LEA EDX,DWORD PTR SS:[EBP-2C];获取结构体地址
01013748  |.  52                    PUSH EDX;压入ChooseColorW函数的参数
01013749      FF15 1CF00101         CALL DWORD PTR DS:[<&comdlg32.ChooseColorW>]             ; /pChooseColor;调用ChooseColorW产生一个颜色对话框
0101374F      3E:8B75 E0            MOV ESI,DWORD PTR DS:[EBP-20];将获取到的颜色值放到ESI
01013753  |.  8935 FC360101         MOV DWORD PTR DS:[10136FC],ESI;保存颜色作为一个全局变量
;为了在我们选择好颜色后能即时更新为我们选定的颜色,这里添加一个SendMessage(hWnd,WM_PAINT,0,0);通知窗口重绘.
01013759  |.  33C0                  XOR EAX,EAX
0101375B  |.  50                    PUSH EAX                                           ; /lParam = 0
0101375C  |.  50                    PUSH EAX                                           ; |wParam => 0
0101375D  |.  6A 0F                 PUSH 0F                                            ; |Message = WM_PAINT
0101375F  |.  FF35 6C4D0101         PUSH DWORD PTR DS:[1014D6C]                        ; |hWnd = 36080E
01013765  |.  FF15 3C110001         CALL DWORD PTR DS:[<&USER32.SendMessageW>]         ; \SendMessageW
;以下为清理现场
0101376B  |.  5F                    POP EDI
0101376C  |.  5E                    POP ESI
0101376D  |.  5B                    POP EBX
0101376E  |.  83C4 6C               ADD ESP,6C
01013771  |.  8BE5                  MOV ESP,EBP
01013773  |.  5D                    POP EBP
01013774  \.  C3                    RETN


【5】设置好了颜色后我们做的当然就是要让我们的颜色起作用了。在反汇编窗口上点右键,选择查找-所有分支,在
 地址=01006124   CMP EDI,7B   Switch (cases 5..138)
这行上单击右键,选择列出switch中的case:双击138(WM_CTLCOLORSTATIC),来到下面的地方
01006400  |> \FF75 14               PUSH DWORD PTR SS:[EBP+14]                               ; /hWnd; Case 138 (WM_CTLCOLORSTATIC) of switch 01006124
01006403  |.  FF15 C8100001         CALL DWORD PTR DS:[<&USER32.GetDlgCtrlID>]               ; \GetDlgCtrlID
01006409  |.  3D 93010000           CMP EAX,193
0100640E  |.  75 33                 JNZ SHORT calc.01006443;这里的判断是不是需要宠绘,因此用下一行开始修改
01006410      6A 05                 PUSH 5
01006412      FF15 24110001         CALL DWORD PTR DS:[<&USER32.GetSysColorBrush>]           ;  user32.GetSysColorBrush
01006418      8B35 F8100001         MOV ESI,DWORD PTR DS:[<&USER32.GetSysColor>]             ;  user32.GetSysColor

直接修改01006410为:
01006410   .  A1 FC360101           MOV EAX,DWORD PTR DS:[10136FC];获取用户设置的颜色
01006415   .  EB 1B                 JMP SHORT calc.01006432
01006417      90                    NOP

【6】到这里我们的功能基本上是做出来了,但是还是有一些BUG,就是你打开颜色对话框的时间过长的时候会产生一个提示计算超时的MessageBox,当然直接修改让这个信息框不弹出也是可以的,或者在弹出的时候加上一个判断条件,在ID与我们的菜单相等的时候不弹出应该会比较好,如果你有兴趣,请也修改一份传上来。

     由于本人初学PEDIY技术,文章中的错误之处还请大侠批评指正。

上传的附件 calc.rar