0%

crackme46

开始拿到本题我也一脸茫然,也没有游戏说明。于是就随便看了看,发现菜单上的三个选项都提示Nope doesn’t work yet!,即这三个功能都不能用,再看看这个题目的分类是Menu,我想,这题就是要我们让这三个选项能正常使用吧,就类似于Crackme041那题,要我们加代码。开始呢,我想到的就是在结尾处添加代码,用ollydbg搜索了一下,发现很多必要的Api函数没有导入进去,手动添加又太麻烦。而且要加三个功能,汇编代码量肯定很大。于是就想起了dll注入,这个技术,我也是现学现用。它的原理是通过某种手段,让程序加载我们的dll,dll中的DllMain函数就会自动执行,我们可以把一些代码放这个函数里。这种技术也应用于许多病毒程序中。

为什么dll注入后可以控制应用程序?

在Windows中,每个应用程序都是独立运行在自己的内存空间,有没有发现,我们用ollydbg打开exe,第一行指令的地址总是0x401000 ,程序A从这个地址开始执行,程序B也从这个地址开始执行,但是他们互不影响,即这些地址并不是物理地址,而是一个相对的地址。每个程序有独立的地址空间,自己空间里的线程可以随便操控自己的内存,那么,如果我们把自己的dll加载到程序的空间里去,这个dll就成了程序的一部分,可以操控这个程序的内存。

首先,我们来写dll注入程序,

要将dll注入到一个进程中,我们利用CreateRemoteThread在exe进程中运行LoadLibrary线程来加载指定的dll文件,在这里还要注意目标exe使用的是LoadLibraryA还是LoadLibrayW,要一致才能成功,这个坑了我好久

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
//注入dll  
#include <windows.h>
#include <stdio.h>
#include <TlHelp32.h>

//根据进程名查找进程PID
DWORD getProcessHandle(LPCTSTR lpProcessName) {
DWORD dwRet = 0;
HANDLE hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
if(hSnapShot == INVALID_HANDLE_VALUE) {
printf("\n获得进程快照失败%d",GetLastError());
return dwRet;
}

PROCESSENTRY32 pe32;//声明进程入口对象
pe32.dwSize = sizeof(PROCESSENTRY32);//填充进程入口对象大小
Process32First(hSnapShot,&pe32);//遍历进程列表
do {
if(!lstrcmp(pe32.szExeFile,lpProcessName)) { //查找指定进程名的PID
dwRet = pe32.th32ProcessID;
break;
}
} while (Process32Next(hSnapShot,&pe32));
CloseHandle(hSnapShot);
return dwRet;//返回
}

BOOL InjectDll(DWORD dwPID, char* szDllPath) {
HANDLE hProcess = NULL;
HANDLE hThread = NULL;
HMODULE hMod = NULL;
LPVOID pRemoteBuf = NULL; //存储dll路径字符串的起始地址
DWORD dwBufSize = (DWORD)(strlen(szDllPath)+1)*sizeof(char); // dll路径字符串的大小
LPTHREAD_START_ROUTINE pThreadProc; // 存储LoadLibrary函数的地址
// 使用dwPID获取目标进程句柄
if(!(hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE,dwPID))) {
printf("OpenProcess(%d) failed!!![%d]\n",dwPID,GetLastError());
return FALSE;
}
// 在目标进程exe内存中分配szDLLName大小的内存
pRemoteBuf = VirtualAllocEx(hProcess,NULL,dwBufSize,MEM_COMMIT,PAGE_READWRITE);
// 将dll路径写入分配的内存
WriteProcessMemory(hProcess,pRemoteBuf,szDllPath,dwBufSize,NULL);
// 获取LoadLibraryW() API的地址
//注意 LoadLibraryA和LoadLibraryW,要与exe的一样,不然不能成功
hMod = GetModuleHandle(TEXT("kernel32.dll"));
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod,"LoadLibraryA");
// 在对应的exe中运行线程
hThread = CreateRemoteThread(hProcess,NULL,0,pThreadProc,pRemoteBuf,0,NULL);
WaitForSingleObject(hThread,INFINITE);
CloseHandle(hThread);
CloseHandle(hProcess);
return TRUE;
}
// 提权函数
BOOL EnableDebugPriv() {
HANDLE hToken;
LUID sedebugnameValue;
TOKEN_PRIVILEGES tkp;
if ( ! OpenProcessToken( GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken ) ) {
printf("提权失败。");
return FALSE;
}
if ( ! LookupPrivilegeValue( NULL, SE_DEBUG_NAME, &sedebugnameValue ) ) {
CloseHandle( hToken );
printf("提权失败。");
return FALSE;
}
tkp.PrivilegeCount = 1;
tkp.Privileges[0].Luid = sedebugnameValue;
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; //
if ( ! AdjustTokenPrivileges( hToken, FALSE, &tkp, sizeof tkp, NULL, NULL ) ) {
printf("提权失败。");
CloseHandle( hToken );
} else {
printf("提权成功!");
return TRUE;
}

}

int main(int argc, char* argv[]) {
//提权
EnableDebugPriv();
DWORD pid = getProcessHandle("douby.exe");
char dll[50] = "DllInject.dll";
if (InjectDll(pid,dll)) {
printf("dll注入成功!\n");
} else {
printf("dll注入失败\n");
}
return 0;
}

以上程序将DllInject.dll这个文件注入到名字为douby.exe这个进程中去

现在我们来写DllInject的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
BOOL APIENTRY DllMain(HMODULE hModule,  
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
MessageBox(NULL,"注入成功!","",MB_OK);
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

DllMain就犹如Main函数作用一样,DllMain函数会在当程序加载完这个Dll时执行。这样,我们就可以在里面写代码

编译后,我们来测试一下。

douby.exe 是我们的目标程序,DllInject.dll是编译后的dll文件,keygen.exe是注入程序

首先,我们运行douby.exe

然后,我们运行keygen.exe将DllInject.dll注入到douby.exe的进程中去

这个弹窗来自我们在dll里写的那个MessageBox,这说明我们的dll被成功加载到douby.exe这个程序中去了。

既然测试成功,那么接下来,我们就可以为所欲为了,

如何在dll中访问宿主的内存呢?

我们可以用强制转换,把地址值转换成指针

比如,我们想访问0x401260处的内容,我们可以

1
2
DWORD *p = (DWORD *)0x401260;  
printf("%d\n",*p);

本题,我们可以直接在dll中利用win api 完成所有功能,不用这么麻烦,

难点在于,如何取得控件窗口的句柄,以及如何接管菜单的事件?

首先是控件句柄的获取

FindWindow函数可以根据窗口名称获取窗口的句柄

EnumChildWindows函数可以枚举窗口中的控件,我们再判断控件的id来确定是不是我们要找的

我们主要是要获取编辑框的句柄,这样我们才能操作编辑器(设置文本,获取文本)

文本框的id是怎么找到的呢?

用ollydbg打开程序,

选择”所有模块间的调用”,找CreateWindiows相关的名称,一个个点进去看看

我们发现了,这个控件带有EDIT字样,就是一个文本框,hMenu=00000001,这个值就是控件的ID ,不明白为什么的可以看看CreateWindow相关函数的定义。

1
2
//获得编辑框  
editor = FindDlgItem(0x1,mainWnd);

于是上述我们就是这样获取编辑框的句柄

我们用SetWindowText函数给它设置测试文本,发现文本成功设置上去,说明这个就是那个编辑框的句柄了

现在,我们要做的就是如何响应Menu事件呢?

SetWindowLongPtr函数可以将窗口的WinProc消息接管到自己的函数里,于是

oriWndProc = SetWindowLongPtr(mainWnd, GWLP_WNDPROC, (LONG_PTR)WndProc);  

这个函数的第一个参数是窗口句柄,我们传入的是主窗口句柄

WndProc是我们自己定义的函数,用来处理事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//事件处理  
LRESULT __stdcall WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg) {
case WM_COMMAND:
switch (wParam) {
//几个菜单事件
case 40001: //Load
loadFile();
return 0;
case 40005: //Save
saveFile();
return 0;
case 40003: //Exit
exitApp();
return 0;
}
break;
}
return CallWindowProcW((WNDPROC)oriWndProc, hWnd, uMsg, wParam, lParam);
}

现在问题又来了,上述的几个值它们分别代表三个选项的ID,它们是怎么来的?

在汇编里应该可以算出来,但感觉有点麻烦,于是我就把wParam所有值都记录下来,点击相应的选项一次,再看看值。最终得到了它们的ID

好了,上述工作都做完了,我们来看看DllInject.cpp的完整代码

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
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
#define _CRT_SECURE_NO_WARNINGS  

#include <iostream>
#include <windows.h>

using std::cout;
using std::endl;

//文本框控件
HWND editor;

//退出程序
void exitApp() {
ExitProcess(0);
}

//加载文件
void loadFile() {
OPENFILENAME ofn;
CHAR szFile[100];
LPCSTR INIT_PATH = ".txt";
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = NULL;
ofn.lpstrFile = szFile;
ofn.lpstrFile[0] = '\0';
ofn.nMaxFile = sizeof(szFile);
ofn.lpstrFilter = INIT_PATH;
ofn.nFilterIndex = 1;
ofn.lpstrFileTitle = NULL;
ofn.nMaxFileTitle = 0;
ofn.lpstrInitialDir = NULL;
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
if (GetOpenFileName(&ofn)) {
HANDLE hFile = CreateFile(ofn.lpstrFile,
GENERIC_READ,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
0
);
if (hFile == INVALID_HANDLE_VALUE)
{
MessageBox(NULL, "创建文件句柄出错", "error", MB_OK);
return;
}
DWORD size = GetFileSize(hFile,NULL);
if (size == -1) {
MessageBox(NULL, "获取文件大小出错", "error", MB_OK);
return;
}
CHAR* data = new CHAR[size];
int filesucc = ReadFile(hFile,
data,
size,//读取文件中多少内容
NULL,
NULL
);
SetWindowText(editor,data);
delete[] data;
CloseHandle(hFile);
}
}

//保存文件
void saveFile() {
OPENFILENAME ofn;
CHAR szFile[100];
LPCSTR TYPE = ".txt";
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = NULL;
ofn.lpstrFile = szFile;
ofn.lpstrFile[0] = '\0';
ofn.nMaxFile = sizeof(szFile);
ofn.lpstrFilter = NULL;
ofn.nFilterIndex = 1;
ofn.lpstrFileTitle = NULL;
ofn.nMaxFileTitle = 0;
ofn.lpstrInitialDir = NULL;
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
if (GetSaveFileName(&ofn)) {
HANDLE hFile = CreateFile(ofn.lpstrFile,
GENERIC_WRITE,
0,
NULL,
CREATE_NEW,
FILE_ATTRIBUTE_NORMAL,
0
);
if (hFile == INVALID_HANDLE_VALUE)
{
MessageBox(NULL, "创建文件句柄出错", "error", MB_OK);
return;
}
int size = GetWindowTextLengthA(editor);
CHAR* buf = new CHAR[size];
GetWindowText(editor,buf,size);
DWORD dwWritenSize = 0;
BOOL bRet = WriteFile(hFile,buf,size, &dwWritenSize, NULL);
CloseHandle(hFile);
delete[] buf;
MessageBox(NULL, "文件保存成功!", "ojbk", MB_OK);
}
}

struct StructFindTaskManagerDlgItem
{
DWORD itemID;//控件ID
HWND hwnd;//该控件的句柄
};

//枚举子控件
BOOL CALLBACK _EnumChildProc(HWND hwnd, LPARAM lParam)
{
StructFindTaskManagerDlgItem* pParam = (StructFindTaskManagerDlgItem*)lParam;

if ((DWORD)GetDlgCtrlID(hwnd) == pParam->itemID)//判断是否为需要的控件
{
pParam->hwnd = hwnd;

return FALSE;
}

return TRUE;
}

//根据控件ID获取控件控件句柄
HWND FindDlgItem(DWORD CtrlId,HWND & mainWnd)
{
StructFindTaskManagerDlgItem param;

param.itemID = CtrlId;
param.hwnd = NULL;

//根据窗口名字,即可获取到它的句柄
//获取主窗口句柄
mainWnd = FindWindow(0x0,"ReverseMe1 - Official practice of the Reverse EngineerZINE");
if (mainWnd == NULL)
{
MessageBox(NULL, "请先运行douby.exe程序!", "", MB_OK);
return NULL;
}
//枚举子窗口,
EnumChildWindows(mainWnd, _EnumChildProc, (LPARAM)& param);
return param.hwnd;
}

LONG_PTR oriWndProc;

//事件处理
LRESULT __stdcall WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg) {
case WM_COMMAND:
switch (wParam) {
//几个菜单事件
case 40001: //Load
loadFile();
return 0;
case 40005: //Save
saveFile();
return 0;
case 40003: //Exit
exitApp();
return 0;
}
break;
}
return CallWindowProcW((WNDPROC)oriWndProc, hWnd, uMsg, wParam, lParam);
}

HWND mainWnd;

void Start()
{
//为窗口设置新的事件处理
oriWndProc = SetWindowLongPtr(mainWnd, GWLP_WNDPROC, (LONG_PTR)WndProc);
}

//初始化一些事
void initSome() {
//获得编辑框
editor = FindDlgItem(0x1,mainWnd);
//创建线程,用于接管窗口事件
CreateThread(NULL, NULL, reinterpret_cast<LPTHREAD_START_ROUTINE>(Start), NULL, NULL, NULL); // start another thread running the hooking stuff
}

BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
initSome();
MessageBox(NULL,"注入成功!","",MB_OK);
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}