写这篇文章的时候,已经是一周后了,因为考试的原因在学校逗留了很久,4级可能又要跪了,听力听到最后竟然发现多了一题,What the kuck!
不过所幸编译原理,抄到了同学的,很happy的玩了会wow,考考古钓钓鱼,发现踏风武僧PK果断很弱,不过熊猫人囧囧的眼神还是很可爱的,平安夜前夕嗓子无故疼痛
没有和大家去唱歌,抱着2.5L的大瓶农夫山泉在电脑前一顿猛喝,默默想哥是不是得铁线虫了,世界末日都熬过来了,却要惨死在平安夜里,不禁心中泛起一丝悲凉,于是乎
把一个月前答应别人的明信片依次寄了出去,杀了杀部落小号,打了一局LOL拿了首胜,看了泰囧,洗漱完毕,觉得生无可恋躺到床上等死,灵魂正游荡于乌有之乡,忽然想起还有这么一篇博客没有写,陡然惊醒,爬起来写博客,后人写诗赞曰:垂死病中惊坐起,芙蓉帐暖写博客。
------------------------------------------我是莫名其妙的分割线---------------------------------------------------------------------------------
几个月后被问到,怎样dll注入的问题,竟然没答出个大概,这里补充一下DLL注入前面的一部分,怎样把写好的DLL注入到指定进程的地址空间。
这里大概需要几个Win API函数,
(1) 利用Windows API OpenProcess打开宿主进程
(2) 利用Windows API VirtualAllocEx函数在远程线程的VM中分配DLL完整路径宽字符所需的存储空间
(3) 利用Windows API WriteProcessMemory函数将完整路径写入该存储空间
(4) 利用Windows API GetProcAddress取得Kernel32模块中LoadLibraryW函数的地址
(5)利用Windows API CreateRemoteThread启动远程线程,将LoadLibraryW的地址作为远程线程的入口函数地址,将宿主进程里被分配空间中存储的完整DLL路径作为线程入口函数的参数以另其启动指定的DLL
粘了一下别人的程序
//打开目标进程 HANDLE hProcess=OpenProcess(PROCESS_ALL_ACCESS,FALSE,g_Pid); if (!hProcess) { AfxMessageBox(TEXT("打开进程失败")); return; } //在目标进程申请一段内存区域 来存放DLL路径 LPVOID pRemoteBase=VirtualAllocEx(hProcess,NULL,0x1000,MEM_COMMIT,PAGE_READWRITE); if (pRemoteBase==NULL) { AfxMessageBox(TEXT("申请内存区域失败")); return; } //在目标进程中写入DLL路径 写入的长度要+1 字符串终止符 if (!WriteProcessMemory(hProcess,pRemoteBase,(LPTSTR)(LPCTSTR)DllPath,DllPath.GetLength()+1,NULL)) { AfxMessageBox(TEXT("进程写入失败")); //失败就释放原先申请的内存区域 撤销内存页的提交状态 VirtualFreeEx(hProcess,pRemoteBase,0x1000,MEM_DECOMMIT); return; } //得到LoadLibraryA的函数地址 因为Kernel32的加载地址在每个应用程序中都一样 LPTHREAD_START_ROUTINE pfn=(LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle("Kernel32.dll"),"LoadLibraryA"); //创建远程线程 执行加载 HANDLE hRemoteThread = CreateRemoteThread(hProcess,NULL,0,pfn,pRemoteBase,0,NULL); if (hRemoteThread==NULL) { AfxMessageBox(TEXT("创建远程线程失败")); //释放原先申请的内存区域 撤销内存页的提交状态 VirtualFreeEx(hProcess,pRemoteBase,0x1000,MEM_DECOMMIT); return; } AfxMessageBox(TEXT("成功注入")); //等待线程退出 WaitForSingleObject(hRemoteThread,-1); //释放原先申请的内存区域 撤销内存页的提交状态 VirtualFreeEx(hProcess,pRemoteBase,0x1000,MEM_DECOMMIT); //关闭句柄 CloseHandle(hRemoteThread); CloseHandle(hProcess);
//================================================割鸡鸡========================================================
言归正传,实习过程中要实现一个DLL HOOK的功能,大概是还原Stuxent攻击过程的一个小模块,目的是HOOK s7otbxdx.dll 这个动态连接库中的s7blk_write这个函数,
Stuxent在入侵过程中,会下载一个s7otbxdx.dll 劫持掉 SIMATIC STEP 7 所使用的真正的DLL,当PC机连接PLC的时候,会拦截正常指令换成恶意的代码,并且拦截了STEP 7
的系统警告,与运行状态监控,从中剔除掉危险的信息,这招极其阴毒。不过所幸的,我只需要实现读出 s7blk_write这个函数中的某些数据就可以,应该比较简单。
这是Stuxent劫持DLL的详细报告http://www.symantec.com/connect/blogs/stuxnet-1
通过伟哥给的信息和百度,大概总结了DLL HOOK的两种主要方式,DLL劫持,DLL注入。
DLL劫持的还比较简单,因为应用程序在寻找DLL是会先在本目录下寻找,再去系统目录下寻找,微软为了不让系统目录中的DLL看起来乱七八糟而采用了这种做法,
而我们只要用一个与原DLL有相同导出函数的DLL替换掉原DLL就可以实现HOOK,简单方便。关于这个DLL的导出函数,使用IDA的Hex-Rays.Decompiler插件可以把DLL
翻译成c代码,尽管有一些会不准确,或者使用AheadLib这个工具直接可以生成可以使用的DLL c代码,不过依然是代码不够准确需要修改,在原DLL有几百个导出函数时,
并不是一种可行的方法。
关于DLL劫持的结构大概如下,声明函数不需要包含参数,因为在调用前参数已经被push,直接JMP到真实函数地址即可运行,当然在DLL初始化的时候需要load原DLL
#pragma comment(linker, "/EXPORT:FuncationName=MyFuncationName,@1")
MyFuncationName(void)
{
GetAddress("FuncationName");
__asm JMP EAX;
}
DLL注入的方法,实现的途径也比较多,微软提供了一个小型的库detour来实现,detour的编译过程比较蛋疼,网上的教程良莠不齐,大体过程是这样,
(1) 下载Detour并安装,一路下一步 (2) 把nmake配置到PATH中,或者运行vc\bin目录下的vcvars32.bat也可以 (3) 把Microsoft Research\Detours Express 2.1\src 整个移动到 vc文件夹下 (4) 把\Microsoft Research\Detours Express 3.0下的system.mak移动到 vc 文件夹下 (5) cmd到vc目录下执行nmake,会在lib目录下生成detours.lib,使用detour这个库只需要这一个lib文件 (6) \Microsoft Research\Detours Express 3.0\sample 是一些小工具这次我们要用到withdll (7)直接进入,sample\withdll 文件夹下 nmake,之前最好先编译setdll和syslog两个文件
程序实现就比较简单了,使用withdll /d:detour.dll main.exe 就可以看到结果,注意detour.dll中的Hook函数一定要是导出的,这样才能被withdll捕捉到
main.exe
typedef void (*func)(); int _tmain(int argc, _TCHAR* argv[]) { HMODULE h = LoadLibrary(_T("target.dll")); if (h == NULL) { printf("Load target.dll failed.\n"); return 1; } func f = (func)GetProcAddress(h, "test"); if (f == NULL) { printf("Load function failed.\n"); } f(); getchar(); }
target.dll
BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { printf("target.dll loaded.\n"); return TRUE; } #ifdef _MANAGED #pragma managed(pop) #endif void test() { printf("This is test function in target module.\n"); }
detour.dll
typedef void (*func)(); void my_test() { printf("Func replaced in detour.dll!!\n"); } func old = NULL; BOOL WINAPI DllMain(HINSTANCE hinst, DWORD dwReason, LPVOID reserved) { LONG error; (void)hinst; (void)reserved; if (dwReason == DLL_PROCESS_ATTACH) { //加载 printf("detour.dll: Starting.\n"); fflush(stdout); printf("DLLs:\n"); //共调试用, 列出当前加载的模块 for (HMODULE hModule = NULL; (hModule = DetourEnumerateModules(hModule)) != NULL;) { wchar_t szName[MAX_PATH]; GetModuleFileName(hModule, szName, sizeof(szName)); printf(" %p: %S\n", hModule, szName); } PVOID f = DetourFindFunction("target.dll", "test"); //要提花的函数是target.dll中的test if (f == NULL) { //没找到, 直接返回 printf("func == null.\n"); return TRUE; } old = (func)f; //保存旧函数 DetourRestoreAfterWith(); //开始准备替换工作 DetourTransactionBegin(); DetourUpdateThread(GetCurrentThread()); DetourAttach(&f, my_test); //替换!! 替换成my_test() 函数 error = DetourTransactionCommit(); //提交修改, 并检查返回值 if (error == NO_ERROR) { printf("detor.dll: Detoured function replaced!\n"); } else { printf("detor.dll: Error detouring function: %d\n", error); } } else if (dwReason == DLL_PROCESS_DETACH) { //程序退出的时候回复原来的函数 DetourTransactionBegin(); DetourUpdateThread(GetCurrentThread()); DetourDetach(&(PVOID&)old, my_test); error = DetourTransactionCommit(); fflush(stdout); } return TRUE; }
关于detour的原理稍微说一下,他用到了一个很巧妙的Trampoline函数,来实现跳回原函数
具体可以看这里http://www.cnblogs.com/flying_bat/archive/2008/04/18/1159996.html
关于怎么建立一个dll的教程http://blog.csdn.net/XXKKFF/article/details/1522632
写到这里觉得胜利只是一步之遥了,但是在实际的SIMATIC STEP 7的注入以及HOOk中,detour却频繁报错,使我不得不换另外一种方法。
手动写HOOK所用的DLL,在通过DLL注入工具注入到进程中,以达到HOOK函数目的,
这里伟哥给出了一个类似的Demo
APIHook.cpp
// #include "APIHook.h" #pragma comment(lib, "Wininet.lib") CAPIHook::CAPIHook(LPSTR pszModName, LPSTR pszFuncName, PROC pfnHook) { // 生成新的执行代码 //0xB8, 0x0F, 0x10, 0x40, 0x00, 0xFF, 0xE0 BYTE btNewBytes[8] = { 0x0B8, 0x0, 0x0, 0x40, 0x0, 0x0FF, 0x0E0, 0 }; memcpy(m_btNewBytes, btNewBytes, 8); *(DWORD *)(m_btNewBytes + 1) = (DWORD)pfnHook; // 加载指定模块 //m_hModule = GetModuleHandle(pszModName); //if(m_hModule==NULL) //{ m_hModule = ::LoadLibrary(pszModName); //} if(m_hModule == NULL) { m_pfnOrig = NULL; return; } m_pfnOrig = ::GetProcAddress(m_hModule, pszFuncName); // 修改原API函数执行代码的前8个字节,使它跳向我们的函数 if(m_pfnOrig != NULL) { DWORD dwOldProtect; MEMORY_BASIC_INFORMATION mbi; VirtualQuery( m_pfnOrig, &mbi, sizeof(mbi) ); VirtualProtect(m_pfnOrig, 8, PAGE_EXECUTE_READWRITE, &dwOldProtect); // 保存原来的执行代码 memcpy(m_btOldBytes, m_pfnOrig, 8); // 写入新的执行代码 ::WriteProcessMemory(::GetCurrentProcess(), (void *)m_pfnOrig, m_btNewBytes, sizeof(DWORD)*2, NULL); VirtualProtect(m_pfnOrig, 8, mbi.Protect, 0); } } CAPIHook::~CAPIHook() { Unhook(); if(m_hModule != NULL) ::FreeLibrary(m_hModule); } void CAPIHook::Rehook() { // 修改原API函数执行代码的前8个字节,使它跳向我们的函数 if(m_pfnOrig != NULL) { DWORD dwOldProtect; MEMORY_BASIC_INFORMATION mbi; VirtualQuery( m_pfnOrig, &mbi, sizeof(mbi) ); VirtualProtect(m_pfnOrig, 8, PAGE_EXECUTE_READWRITE, &dwOldProtect); // 写入新的执行代码 ::WriteProcessMemory(::GetCurrentProcess(), (void *)m_pfnOrig, m_btNewBytes, sizeof(DWORD)*2, NULL); VirtualProtect(m_pfnOrig, 8, mbi.Protect, 0); } } void CAPIHook::Unhook() { if(m_pfnOrig != NULL) { DWORD dwOldProtect; MEMORY_BASIC_INFORMATION mbi; VirtualQuery(m_pfnOrig, &mbi, sizeof(mbi)); VirtualProtect(m_pfnOrig, 8, PAGE_EXECUTE_READWRITE, &dwOldProtect); // 写入原来的执行代码 ::WriteProcessMemory(::GetCurrentProcess(), (void *)m_pfnOrig, m_btOldBytes, sizeof(DWORD)*2, NULL); VirtualProtect(m_pfnOrig, 8, mbi.Protect, 0); } }
APIHook.h
////////////////////////////////////////////////////////// // ApiHook.h #ifndef __APIHOOK_H__ #define __APIHOOK_H__ #include <windows.h> class CAPIHook { public: CAPIHook(LPSTR pszModName, LPSTR pszFuncName, PROC pfnHook); ~CAPIHook(); void Unhook(); void Rehook(); public: PROC m_pfnOrig; // 那个函数的真正地址 protected: BYTE m_btNewBytes[8]; BYTE m_btOldBytes[8]; HMODULE m_hModule; }; #endif // __APIHOOK_H__
只要再写一个DLL就可以了,这里注意三点,
(1) DLLMain一定要返回true,这是dll是否加载成功的标志,不然DLL会被秒卸载掉
(2) 在Hook的时候尽量使用WinAPI函数,可以减少不必要的出错
(3) 在使用ollydbg调试的时候,设置断点的行,会把对该行的修改改成中断,所以设置断点的行不会被修改。
// step7inject.cpp : Defines the entry point for the DLL application. // #include "stdafx.h" #include "APIHook.h" #include <stdlib.h> #include <stdio.h> #define MAXSIZE 512 void DumpData(int a7, int a8) { DWORD dwOldProtect; MEMORY_BASIC_INFORMATION mbi; VirtualQuery((PROC)a7, &mbi, sizeof(mbi)); VirtualProtect((PROC)a7, a8, PAGE_EXECUTE_READWRITE, &dwOldProtect); BYTE data[MAXSIZE]; char strData[MAXSIZE]; memcpy(data,(void*)a7,a8); char length[10] = {0}; //sprintf("%08x", l) DWORD nBytes; HANDLE hOpenFile = (HANDLE)CreateFile(LPCSTR("c:\\dumpBin.txt"), GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, NULL, NULL); WriteFile(hOpenFile, &a8, 4 ,&nBytes,NULL); WriteFile(hOpenFile, data, a8 ,&nBytes,NULL); CloseHandle(hOpenFile); hOpenFile = (HANDLE)CreateFile(LPCSTR("c:\\dumpHex.txt"), GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, NULL, NULL); if (hOpenFile != INVALID_HANDLE_VALUE){ for(int i=0;i<a8;i++){ if (data[i]<16) sprintf(strData,"0%x ",data[i]); else sprintf(strData,"%x ",data[i]); WriteFile(hOpenFile,strData, strlen(strData) ,&nBytes,NULL); if (i%16==0 && i!=0) WriteFile(hOpenFile,"\n", strlen("\n") ,&nBytes,NULL); } CloseHandle(hOpenFile); } VirtualProtect((PROC)a7, a8, mbi.Protect, 0); } /* a[9..c] = 58 = length a[4..5] = 02 = 序号 a[2..3] = 0C = type a[2a..] = type description a[50..54] = 00 00 10 00 */ char oldData[2048] = {0}; int op; CAPIHook * HookWrite; typedef int ( __stdcall *my_s7blk_write)(int a1, int a2, int a3, int a4, __int16 a5, __int16 a6, int buffer, int length); extern "C" int __stdcall s7blk_write_0(int a1, int a2, int a3, int a4, __int16 a5, __int16 a6, int a7, int a8) { int ret; if(op == 0) { MessageBox(NULL,"DumpGo","title",MB_OK); DumpData(a7,a8); MessageBox(NULL,"DumpDone","title",MB_OK); HookWrite->Unhook(); ret = ((my_s7blk_write)HookWrite->m_pfnOrig)(a1,a2,a3,a4,a5,a6,a7,a8); HookWrite->Rehook(); } else if(op == 1) { char * data = NULL; DWORD nBytes; HANDLE hOpenFile = (HANDLE)CreateFile(LPCSTR("c:\\dumpBin.txt"), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_ALWAYS, NULL, NULL); char length[10] = {0}; ReadFile(hOpenFile, length, 4 ,&nBytes,NULL); int l = ((int *)length)[0]; data = (char *)malloc(l); ReadFile(hOpenFile, data, l, &nBytes,NULL); CloseHandle(hOpenFile); HookWrite->Unhook(); if(memcmp(data, (char *)a7, 6) == 0) { memcpy(oldData, &a8, 4); memcpy(&oldData[4], (char *)a7, a8); ret = ((my_s7blk_write)HookWrite->m_pfnOrig)(a1,a2,a3,a4,a5,a6,(int)data,l); } else ret = ((my_s7blk_write)HookWrite->m_pfnOrig)(a1,a2,a3,a4,a5,a6,a7,a8); HookWrite->Rehook(); if(data) delete data; } return ret; } CAPIHook * HookRead; typedef int ( __stdcall *my_s7blk_read)(int a2, int a3, int a4, int a5, __int16 a6, __int16 a7, int offset, int length, int a10); //(char **); (char *)((*(char*)offset)) extern "C" int __stdcall s7blk_read_0(int a2, int a3, int a4, int a5, __int16 a6, __int16 a7, int a8, int a9, int a10) { int ret; HookRead->Unhook(); ret = ((my_s7blk_read)HookRead->m_pfnOrig)(a2,a3,a4,a5,a6,a7,a8,a9,a10); char * newdata = (char *)(((int*)a8)[0]); if(memcmp(&oldData[4], newdata, 6) == 0) { ((int *)a8)[0] = int(&oldData[4]); } HookRead->Rehook(); return ret; } BOOL WINAPI DllMain(HINSTANCE hinst, DWORD dwReason, LPVOID reserved){ if (dwReason == DLL_PROCESS_ATTACH) { HookWrite = new CAPIHook("s7otbxdx.dll", "s7blk_write",(PROC)s7blk_write_0); HookRead = new CAPIHook("s7otbxdx.dll", "s7blk_read",(PROC)s7blk_read_0); op = 1; } else if (dwReason == DLL_PROCESS_DETACH) { if(HookWrite) HookWrite->Unhook(); if(HookRead) HookRead->Unhook(); } return true; }
DLL HOOK手动编写的教程
http://www.programfan.com/club/showpost.asp?id=13644