开始拿到本题我也一脸茫然,也没有游戏说明。于是就随便看了看,发现菜单上的三个选项都提示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,要一致才能成功,这个坑了我好久
- //注入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的代码
- 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处的内容,我们可以
- DWORD *p = (DWORD *)0x401260;
- printf("%d\n",*p);
本题,我们可以直接在dll中利用win api 完成所有功能,不用这么麻烦,
难点在于,如何取得控件窗口的句柄,以及如何接管菜单的事件?
首先是控件句柄的获取
FindWindow函数可以根据窗口名称获取窗口的句柄
EnumChildWindows函数可以枚举窗口中的控件,我们再判断控件的id来确定是不是我们要找的
我们主要是要获取编辑框的句柄,这样我们才能操作编辑器(设置文本,获取文本)
文本框的id是怎么找到的呢?
用ollydbg打开程序,
选择”所有模块间的调用”,找CreateWindiows相关的名称,一个个点进去看看
我们发现了,这个控件带有EDIT字样,就是一个文本框,hMenu=00000001,这个值就是控件的ID ,不明白为什么的可以看看CreateWindow相关函数的定义。
- //获得编辑框
- editor = FindDlgItem(0x1,mainWnd);
于是上述我们就是这样获取编辑框的句柄
我们用SetWindowText函数给它设置测试文本,发现文本成功设置上去,说明这个就是那个编辑框的句柄了
现在,我们要做的就是如何响应Menu事件呢?
SetWindowLongPtr函数可以将窗口的WinProc消息接管到自己的函数里,于是
oriWndProc = SetWindowLongPtr(mainWnd, GWLP_WNDPROC, (LONG_PTR)WndProc);
这个函数的第一个参数是窗口句柄,我们传入的是主窗口句柄
WndProc是我们自己定义的函数,用来处理事件
- //事件处理
- 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的完整代码
- #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;
- }
我们最终的测试结果