dustland

dustball in dustland

windows XP notepad.exe逆向分析

windows XP notepad.exe逆向分析

基本结构和我们写的差不多,都是主窗口加一个编辑控件

不同的是,人家的有更nb的地方:

有状态栏,可以显示当前输入位置(行号,列号),状态栏是钩子驱动的

可以分析命令行

寻找WinMain

ida一开始会停在程序入口点,start函数这里,显然不是WinMain函数,也不是main函数,是运行环境初始化函数,目前尚未学习WinMain之前的执行过程,现在的任务就是找到WinMain在哪里

start函数看起来很乱不好分析,在functions列表中也没有找到WinMain字样,但是函数列表里面有一个DialogFunc,估计是某个窗口过程函数调用的对话框

如果是主窗口过程,那么就离着找到WinMain不远了

从DialogFunc上查看交叉引用

image-20220831223117517

到这个sub_1002919函数看看去

1
int __stdcall sub_1002919(HWND hWnd, __int16 a2, int a3);

这个函数只有三个参数,而窗口过程函数要有四个参数

1
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);

显然sub_1002919还不是窗口过程函数,那么再看sub_1002919的交叉引用

image-20220831223421292

这两个交叉引用是在同一个函数中的,现在去sub_103134看看

1
LRESULT __thiscall sub_1003134(void *this, HWND hWnd, UINT Msg, WPARAM wParam, LPARAM a5);

光从函数定义上就能看出,这十有八九是一个窗口过程了

再看看sub_1003134的交叉引用

image-20220831223624249

去sub_1004143看看

这个函数在注册窗口类,sub_1003134是注册给这个窗口类的过程回调函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
BOOL __usercall sub_1004143@<eax>(HINSTANCE a1@<esi>)
{
int v1; // eax
WNDCLASSEXW v3; // [esp+4h] [ebp-30h] BYREF

v3.cbSize = 48;
v1 = GetSystemMetrics(41);
v3.hCursor = LoadCursorW(0, (LPCWSTR)(32513 - (v1 != 0)));
v3.hIcon = LoadIconW(a1, (LPCWSTR)2);
v3.hIconSm = (HICON)LoadImageW(a1, (LPCWSTR)2, 1u, 16, 16, 0);
v3.lpszMenuName = (LPCWSTR)1;
v3.hInstance = a1;
v3.lpszClassName = ClassName;
v3.lpfnWndProc = (WNDPROC)sub_1003134;
v3.hbrBackground = (HBRUSH)6;
v3.style = 0;
v3.cbClsExtra = 0;
v3.cbWndExtra = 0;
return RegisterClassExW(&v3) != 0;
}

清晰了

再看看谁调用了sub_1004143估计就是WinMain了

image-20220831223754846

去sub_10041CA看看

1
int __stdcall sub_10041CA(HINSTANCE hInstance, HGDIOBJ h, int a3, int nCmdShow)

和WinMain函数的接口一模一样,四个参数,__stdcall调用约定,int返回值

1
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,PSTR szCmdLine, int iCmdShow);

看看sub_10041CA函数正文,它加载了键盘加速键,加载了光标,最重要的是,它有消息循环

1
2
3
4
5
6
7
8
9
10
while ( GetMessageW(&Msg, 0, 0, 0) )
{
if ( Msg.message == 80 )
PostMessageW(hWndParent, 0x8001u, 0, 0);
if ( (!hDlg || !IsDialogMessageW(hDlg, &Msg)) && !TranslateAcceleratorW(hWndParent, hAccTable, &Msg) )
{
TranslateMessage(&Msg);
DispatchMessageW(&Msg);
}
}

现在就可以肯定地把sub_100041CA更名为WinMain

image-20220831225104278

主函数WinMain

各种哑名起一个相对合理的名字,各种常数改成枚举值,这样反汇编的WinMain就看起来很顺眼了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
WPARAM __stdcall WinMain(HINSTANCE hInstance, HGDIOBJ hPrevInstance, int a3, int nCmdShow)
{
const WCHAR *rawCMDLine; // edi
HMODULE sysMetrics; // eax
FARPROC RegisterPenApp; // eax
const WCHAR *pureCMDLine; // eax
DWORD pid; // eax
HWINEVENTHOOK hHook; // ebx
struct tagMSG Msg; // [esp+8h] [ebp-20h] BYREF
void (__stdcall *regPenApp)(_DWORD, _DWORD); // [esp+24h] [ebp-4h]

rawCMDLine = GetCommandLineW();
sysMetrics = GetSystemMetrics(41);
RegisterPenApp = GetProcAddress(sysMetrics, "RegisterPenApp");
regPenApp = RegisterPenApp;
if ( RegisterPenApp )
(RegisterPenApp)(1, 1);
pureCMDLine = CmdLineNoProgName(rawCMDLine); // 去掉命令行中的程序名
if ( myRegister(hInstance, hPrevInstance, pureCMDLine, nCmdShow) )// 注册窗口类,创建窗口实例
{
pid = GetCurrentProcessId(); // 当前进程id
hHook = SetWinEventHook(EVENT_OBJECT_LOCATIONCHANGE, EVENT_OBJECT_LOCATIONCHANGE, 0, pfnWinEventProc, pid, 0, 0);// 钩子只对EVENT_OBJECT_LOCATIONCHANGE关心,只要是记事本的形状位置发生变化,就会触发这个钩子
while ( GetMessageW(&Msg, 0, 0, 0) ) // 消息循环
{
if ( Msg.message == WM_INPUTLANGCHANGEREQUEST )
PostMessageW(hWndParent, 0x8001u, 0, 0);
if ( (!hDlg || !IsDialogMessageW(hDlg, &Msg)) && !TranslateAcceleratorW(hWndParent, hAccTable, &Msg) )// 判断是不是查找框的消息
{
TranslateMessage(&Msg);
DispatchMessageW(&Msg);
}
}
sub_10018B0(); // 释放"页面设置"结构体
LocalFree(hMem);
if ( hHook )
UnhookWinEvent(hHook);
}
else
{
Msg.wParam = 0;
}
if ( regPenApp )
regPenApp(1, 0);
return Msg.wParam;
}

和我们自己写的记事本有一些不同的地方,

首先是RegisterPenApp这个东西,上网查也没有查到详细的资料,RegisterPenApp这里说是win95之前的API,太老了不让用了,这个问题不大

然后是对WinMain的命令行剥了壳,去掉了第一个参数,程序目录,这个问题不大

然后给本进程注册了一个钩子,针对EVENT_OBJECT_LOCATIONCHANGE事件,这个需要研究一下

事件钩子hookfunc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
HWND __stdcall hookfunc(int flag)
{
LRESULT lineNumber; // eax
int logicLineNumber; // edi
HWND colIndex; // eax
WPARAM logicColNumber; // ebx
wchar_t Buffer[128]; // [esp+Ch] [ebp-108h] BYREF
LPARAM lParam; // [esp+10Ch] [ebp-8h] BYREF
WPARAM wParam; // [esp+110h] [ebp-4h] BYREF

SendMessageW(hWnd, EM_GETSEL, &wParam, &lParam);// 获取正在高亮的位置信息
lineNumber = SendMessageW(hWnd, EM_LINEFROMCHAR, wParam, 0);// 获取高亮区域的字符索引
logicLineNumber = lineNumber + 1; // 人从1开始计数,计算机从0开始计数
colIndex = SendMessageW(hWnd, EM_LINEINDEX, lineNumber, 0);// 行号
logicColNumber = wParam - colIndex + 1;
if ( flag || logicColNumber != historyColNumber || logicLineNumber != historyLineNumber )
{
snwprintf(Buffer, 0x7Fu, Format, logicLineNumber, wParam - colIndex + 1);// 第几行第几列打印到Buffer
colIndex = hwndStatus; // hwndStatus状态框句柄
Buffer[127] = 0; // 最后一个字符置零
if ( hwndStatus )
colIndex = SendMessageW(hwndStatus, SB_SETTEXTW, 1u, Buffer);// 如果当前存在状态框,则向状态狂发送Buffer
}
historyLineNumber = logicLineNumber; // 更新当前行号
historyColNumber = logicColNumber;
return colIndex;
}

钩子对于EVENT_OBJECT_LOCATIONCHANGE感兴趣,只要是输入位置发生变化就会收到该钩子消息

该钩子的作用是,更新记录的鼠标位置,并向状态框发送消息,让状态框实时显示当前输入位置

啥是状态栏

这个钩子在消息循环之前注册生效,在消息循环之后析构,可以认为在notepad主窗口的可见时期内,钩子都是生效的,只要是开启了状态栏,钩子就会通知状态栏更新输入位置状态

到此钩子函数分析完毕,下面需要看看myRegister都是注册了什么,然后看看主窗口,搜索框,状态栏各自的窗口过程是啥样的

啥都干myRegister

WinMain中是这样调用myRegister函数的

1
myRegister(hInstance, hPrevInstance, pureCMDLine, nCmdShow)

此处的pureCMDLine已经剥去了第一个参数,即程序位置

myRegister干了很多事,包括

注册主窗口类

创建主窗口实例

创建状态栏窗口

初始化菜单

命令行分析(反编译实在太乱,一时间看不清都干了啥)

粗略上看命令行分析支持从命令行打开已有文件,新建文件,改变编码方式等等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
int __stdcall myRegister(HINSTANCE hInstance, HGDIOBJ oldhFont, LPSTR pureCmdLine, int nCmdShow)
{
HINSTANCE hInst; // esi
int v6; // eax
HACCEL hAccelerator; // eax
void *v8; // ecx
HWND hwnd; // edx
HMENU hMenu; // eax
HMENU hSubMenu; // eax
int hDeviceCaps; // eax
unsigned __int16 *iterCMD; // eax
unsigned __int16 *CopyIterCMD; // edi
int v15; // eax
int v16; // eax
WCHAR Name[32]; // [esp+8h] [ebp-98h] BYREF
WINDOWPLACEMENT wndpl; // [esp+48h] [ebp-58h] BYREF
struct tagRECT Rect; // [esp+74h] [ebp-2Ch] BYREF
struct tagRECT rectStatus; // [esp+84h] [ebp-1Ch] BYREF
LPARAM lParam[2]; // [esp+94h] [ebp-Ch] BYREF
HDC hdc; // [esp+9Ch] [ebp-4h]

WM_FINDREPLACE = RegisterWindowMessageW(L"commdlg_FindReplace");// 注册人工消息,只要是调用查找框就会发出该消息
if ( !WM_FINDREPLACE )
return 0;
WM_HELP = RegisterWindowMessageW(L"commdlg_help");// 注册帮助框消息
if ( !WM_HELP )
return 0;
hdc = GetDC(0); // 设备环境句柄
if ( !hdc )
return 0;
hInst = hInstance; // 应用程序句柄
if ( !sub_1003D57(hInstance) )
return 0;
v6 = GetSystemMetrics(41); // SM_PENWINDOWS,还是和Pen相关的,老古董了
hCursor_0 = LoadCursorW(0, (32513 - (v6 != 0)));// 加载了一个寂寞
hCursor = LoadCursorW(0, 0x7F02); // 32706号光标类型?然而不是系统预定义的?
hAccelerator = LoadAcceleratorsW(hInst, L"MainAcc");// 加载键盘加速键
hAccTable = hAccelerator; // 这拷贝了一个寂寞吧
if ( !hCursor || !hAccelerator || !oldhFont && !myRegistClass(hInst) )// 看看这一伙子都就位了吗
return 0;
::hInstance = hInst;
psdw.lStructSize = 84; // 页面设置
psdw.hDevMode = 0;
psdw.hDevNames = 0;
psdw.hInstance = hInst;
sub_10018DF(v8); // 页面设置相关
setFont(); // 字体设置相关
hwnd = CreateWindowExW(0, ClassName, &szText, WS_OVERLAPPEDWINDOW, X, Y, nWidth, nHeight, 0, 0, hInst, 0);// 创建窗口实例,窗口类ClassName="notepad"已经在myRegisterClass注册过
hwndParent = hwnd;
psdw.hwndOwner = hwnd;
if ( !hwnd )
return 0;
if ( nWidth != 0x80000000 && nHeight != 0x80000000 )
{
memset(&wndpl, 0, sizeof(wndpl));
wndpl.rcNormalPosition.left = X; // 设置窗口在屏幕上的位置信息
wndpl.rcNormalPosition.right = nWidth + X;
wndpl.rcNormalPosition.top = Y;
wndpl.rcNormalPosition.bottom = nHeight + Y;
wndpl.length = 44;
SetWindowPlacement(hwnd, &wndpl);
hwnd = hwndParent;
}
DragAcceptFiles(hwnd, 1); // 允许往hwnd窗口拖拽打开文件
GetClientRect(hwndParent, &Rect); // 获取主窗口客户区
hWnd = CreateWindowExW( // 创建编辑控件窗口,以主窗口作为父窗口,占据主窗口的整个客户区
0x200u,
L"Edit",
&szText,
wParam1 != 0 ? 0x50200104 : 0x50300104,// 是否具有水平滚动轴
0,
0,
Rect.right,
Rect.bottom - 100,
hwndParent,
0xF,
hInstance,
0);
if ( !hWnd )
return 0;
hwndStatus = CreateStatusWindowW( // 创建状态栏窗口实例
(*&isStatusWndVisible != 0 ? WS_VISIBLE : 0) | 0x44800000,// WS_CHILD|WS_CLIPSIBLINGS|WS_BORDER
// 子窗口|被其他子窗口重叠时不重绘|具有边线

&szText,
hwndParent,
0x401u); // 状态栏窗口控制标识符
if ( !hwndStatus ) // 检测状态栏窗口是否创建成功
return 0;
hookfunc(1); // 创建完状态栏窗口之后立刻更新当前行号列号状态,如果状态栏可见则通知状态栏重绘
GetClientRect(hwndStatus, &rectStatus); // 获取状态窗口的客户区
cyStatus = rectStatus.bottom - rectStatus.top;// 状态栏高度
lParam[1] = -1;
lParam[0] = 3 * (rectStatus.right - rectStatus.left) / 4;
SendMessageW(hwndStatus, SB_SETPARTS, 2u, lParam);// 状态栏分成两部分,0号部分占据了状态栏的前四分之三,1号部分占据剩下所有
SendMessageW(hWnd, EM_FMTLINES, wParam1, 0); // wParam1标志着是否自动换行,如果wParam1=1则不自动换行,wParam=0则自动换行
if ( wParam1 ) // 如果不自动换行
{
hMenu = GetMenu(hwndParent);
hSubMenu = GetSubMenu(hMenu, 3); // 父窗口的第三个菜单,查看,状态栏
EnableMenuItem(hSubMenu, 0x1Bu, 1u); // 0x1B号菜单使能,推测是状态栏使能
}
hDeviceCaps = GetDeviceCaps(hdc, 90); // 获取设备信息
lf.lfHeight = -MulDiv(*&nNumber, hDeviceCaps, 720);
hFont = CreateFontIndirectW(&lf); // 加载字体
oldhFont = SelectObject(hdc, hFont); // 将新字体选入设备环境
GetTextFaceW(hdc, 32, Name); // 获得当前使用的字体名称
SelectObject(hdc, oldhFont);
if ( lstrcmpiW(Name, lf.lfFaceName) ) // 如果目前的字体和原来记录的字体不同则更新
{
EnumFontsW(hdc, lf.lfFaceName, Proc, &lf);
DeleteObject(hFont);
hFont = CreateFontIndirectW(&lf);
}
SendMessageW(hWnd, WM_SETFONT, hFont, 0); // 通知hWnd窗口改变字体,hWnd是编辑控件句柄
ReleaseDC(0, hdc);
word_1009800 = 0;
hMem = LocalAlloc(0x42u, 2u);
PostMessageW(hWnd, EM_LIMITTEXT, 0, 0); // 设置hWnd窗口的字数限制
sub_1001C5B(lpString2);
ShowWindow(hwndParent, nCmdShow); // 显示窗口
SetCursor(hCursor_0); // 修改光标
iterCMD = getNextCMD(pureCmdLine); //从这里开始就是对于命令行的处理了
encodingMode = -1;
CopyIterCMD = iterCMD;
if ( !Lstrcmp(iterCMD, L"/A") ) // 命令行上是否有/A
{
encodingMode = 0;
LABEL_25:
CopyIterCMD = getNextCMD(CopyIterCMD + 2);
goto LABEL_26;
}
if ( !Lstrcmp(CopyIterCMD, L"/W") ) // 命令行上是否有/W
encodingMode = 1;
if ( encodingMode != -1 )
goto LABEL_25;
LABEL_26:
v15 = sub_1003E29(CopyIterCMD);
if ( !v15 )
{
if ( sub_1003F7F(CopyIterCMD, nCmdShow) )
{
PostMessageW(hwndParent, WM_CLOSE, 0, 0);
return 1;
}
if ( *CopyIterCMD )
{
sub_1003C5B(FileName, CopyIterCMD);
hFile = CreateFileW(FileName, 0x80000000, 3u, 0, 3u, 0x80u, 0);// 命令行上指定的文件不存在,需要新建
if ( hFile != -1 )
goto LABEL_41;
if ( GetLastError() == 2 )
{
v16 = sub_1001F55(hwndParent, lpCaption, dword_1008020, FileName, 0x33u);
if ( v16 == 2 )
return 0;
if ( v16 == 6 )
hFile = CreateFileW(FileName, 0xC0000000, 3u, 0, 4u, 0x80u, 0);
}
else
{
sub_1004A24(FileName);
sub_1001C5B(lpString2);
lstrcpyW(FileName, lpString2);
}
if ( hFile != -1 )
LABEL_41:
sub_1004D75(FileName, encodingMode);
}
LABEL_42:
sub_10040D7(&word_10095E0);
sub_10040D7(&word_1009540);
memset(&ofnw, 0, sizeof(ofnw));
ofnw.hInstance = hInstance; // 保存/另存为框 结构体
memset(&findReplace, 0, sizeof(findReplace));
ofnw.lStructSize = 88;
ofnw.hwndOwner = hwndParent;
ofnw.nMaxFile = 260;
findReplace.lStructSize = 40;
findReplace.hwndOwner = hwndParent;
SendMessageW(hWnd, EM_GETSEL, &nCmdShow, &hInstance);
SendMessageW(hWnd, EM_SETSEL, nCmdShow, hInstance);
SendMessageW(hWnd, EM_SCROLLCARET, 0, 0);
if ( (GetKeyboardLayout(0) & 0x3FF) == 0x11 )// 获取键盘布局模式
SendMessageW(hWnd, EM_SETIMESTATUS, 1u, 1);
return 1;
}
if ( v15 != 2 )
goto LABEL_42;
return 0;
}

注册窗口类myRegistClass

创建主窗口类,没什么特别之处

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
BOOL __usercall myRegistClass@<eax>(HINSTANCE a1@<esi>)
{
int v1; // eax
WNDCLASSEXW WndClass; // [esp+4h] [ebp-30h] BYREF

WndClass.cbSize = 48;
v1 = GetSystemMetrics(41);
WndClass.hCursor = LoadCursorW(0, (32513 - (v1 != 0)));
WndClass.hIcon = LoadIconW(a1, 2);
WndClass.hIconSm = LoadImageW(a1, 2, 1u, 16, 16, 0);
WndClass.lpszMenuName = 1;
WndClass.hInstance = a1;
WndClass.lpszClassName = ClassName;//关键,设置主窗口类名
WndClass.lpfnWndProc = WndProc;//关键,注册主窗口过程
WndClass.hbrBackground = 6;
WndClass.style = 0;
WndClass.cbClsExtra = 0;
WndClass.cbWndExtra = 0;
return RegisterClassExW(&WndClass) != 0;
}

WndProc主窗口过程,基本上和我们自己写的差不多的逻辑,处理的消息基本上就是那一些