API hook数据包的实现
Windows系统函数都是以DLL封装起来的,应用程序应用到系统函数时,应首先把该DLL加载到当前的进程空间中,调用的系统函数的入口地址,可以通过GetProcAddress函数进行获取。当系统函数进行调用的时候,首先把所必要的信息保存下来(包括参数和返回地址,等一些别的信息),然后就跳转到函数的入口地址,继续执行。其实函数地址,就是系统函数“可执行代码”的开始地址。修改系统函数入口的地方,让他调转到我们的函数的入口点就行了。采用汇编代码就能简单的实现Jmp addr, 其中addr就是要跳转的相对地址。
把系统函数的入口地方的内容替换为一条Jmp指令,目的就是跳到我们的函数进行执行。而Jmp后面要求的是相对偏移,也就是我们的函数入口地址到系统函数入口地址之间的差异,再减去我们这条指令的大小。用公式表达如下:
int addr = your_fun_addr – sys_fun_addr - (我们定制的这条指令的大小);
Jmp addr;
函数里做完必要的处理后,要回调原来的系统函数,然后返回。所以调用原来系统函数之前必须先把原来修改的系统函数入口地方给恢复,否则,系统函数地方被我们改成了Jmp addr就会又跳到我们的函数里,死循环了,最终会造成堆栈溢出。
那么说一下程序执行的过程。
我们的dll注入被hook的进程 -> 保存系统函数入口处的代码 -> 替换掉进程中的系统函数入口指向我们的函数 -> 当系统函数被调用,立即跳转到我们的函数 -> 我们函数进行处理 -> 恢复系统函数入口的代码 -> 调用原来的系统函数 -> 再修改系统函数入口指向我们的函数(为了下次hook)-> 返回。
于是,一次完整的Hook就完成了。
好,这个问题明白以后,讲一下下个问题,就是如何进行dll“注射”?即将我们的dll注射到要Hook的进程中去呢?
这里我们采用调用Windows提供给我们的一些现成的Hook来进行注射。举个例子,鼠标钩子,键盘钩子,大家都知道吧?我们可以给系统装一个鼠标钩子,然后所有响应到鼠标事件的进程,就会“自动”(其实是系统处理了)载入我们的dll然后设置相应的钩子函数。其实我们的目的只是需要让被注射进程载入我们的dll就可以了,我们可以再dll实例化的时候进行函数注射的,我们的这个鼠标钩子什么都不干的。
下面介绍api hook数据包的发送:
编写主程序
新建一个基于对话框的工程One,
在 OnOK()里面调用一些winsock的初始化等等以及send函数:(省略错误判断)
WSAStartup( MAKEWORD( 2, 2 ), &wsaData )
sockaddr_in sai;
sock = socket( AF_INET, type, 0 );
sai.sin_addr.S_un.S_addr = inet_addr( ip );;
sai.sin_family = AF_INET;
sai.sin_port = htons( port );
connect( sock[thread_record], ( LPSOCKADDR ) & sai, sizeof( sai ) );
memcpy( send_buf, “hello server”, 20 );
send( sock, send_buf, 1024, 0 );
////////////////////
第2步,动手Hook
新建一个Hook.dll工程:
添加一个鼠标Hook MouseProc,鼠标hook什么也不做
LRESULT CALLBACK mouse_proc( int code,WPARAM wParam,LPARAM lParam )
{
LRESULT ret_val = CallNextHookEx( hhook, code, wParam, lParam );
return ret_val;
}
添加鼠标钩子的安装和卸载函数:
BOOL install_hook( )
{
//安装一个鼠标钩子,以监视鼠标键盘的使用情况
hhook = SetWindowsHookEx( WH_JOURNALRECORD,
( FARPROC )mouse_proc, app_instance, 0 );
//如果钩子初始化失败
if( NULL == hhook )
{
return FALSE;
}
return TRUE;
}
//卸掉鼠标钩子
void unstall_hook( )
{
UnhookWindowsHookEx( hhook );
}
再实例化中获得一些参数
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)
{
app_instance = hinst;
DWORD pid = GetCurrentProcessId();
h_process = OpenProcess( PROCESS_ALL_ACCESS, 0, pid );
inject( );
//install_hook( );
return 1;
}
好,最重要的注射函数:
void inject( )
{
if( !be_injected )
{
be_injected = TRUE;
}
else
{
return;
}
//open other dll and get the target function
h_winsock_dll = LoadLibrary( "wsock32.dll" );
sys_send = ( send_proc )GetProcAddress( h_winsock_dll,"send" );
pf_sys_send = sys_send;
if( h_winsock_dll == NULL || sys_send == NULL )
{
//error here can write a log
}
//save the add entry function to old_entry_code[]
__asm
{
lea edi, old_entry_code;
mov esi, pf_sys_send;
//clear the DF bit
cld;
//MOVSD指令是从ds:[si]送双字数据到es:[di]单元中,这儿只执行一次
movsd;
movsb;
}
new_entry_code[0] = 0xe9;//实际上0xe9就相当于jmp指令
//获取mysend()的相对地址
//char *p = new_code_entry;
__asm
{
lea eax, hook_send;
mov ebx, pf_sys_send;
sub eax, ebx;
sub eax, 5;
mov dword ptr [new_entry_code + 1],eax;
}
//填充完毕,现在new_code_entry[]里的指令相当于Jmp mysend
hook_on( ); //可以开启钩子了
}
开启钩子的函数
int hook_on()
{
if( h_process == NULL )
{
//here can write log
return -1;
}
DWORD temp = 0;
DWORD old_protect;
//将内存保护模式改为可写,老模式保存入old_protect
VirtualProtectEx( h_process, pf_sys_send, 5, PAGE_READWRITE,&old_protect );
//将所属进程中send()的前5个字节改为Jmp Mysend
WriteProcessMemory( h_process, pf_sys_send, new_entry_code, 5, 0 );
//将内存保护模式改回为old_protect
VirtualProtectEx( h_process, pf_sys_send, 5, old_protect, &temp );
be_hooked = TRUE;
return 0;
}
关闭钩子的函数
//将所属进程中add()的入口代码恢复
int hook_off( )
{
DWORD temp = 0;
DWORD old_protect;
VirtualProtectEx( h_process, pf_sys_send, 5, PAGE_READWRITE, &old_protect );
WriteProcessMemory( h_process, pf_sys_send, old_entry_code, 5, 0 );
VirtualProtectEx( h_process, pf_sys_send, 5, old_protect, &temp );
be_hooked = FALSE;
return 0;
}
然后,写我们自己的send()函数
int hook_send(
SOCKET s,
const char FAR * buf,
int len,
int flags
)
{
//截获了对send()的调用,我们给暂且改变buf进行测试
buf = "your msg has been replaced by me";
hook_off();//关掉send()钩子防止死循环
//调用系统send函数进行发送
int ret = send( s, buf, len, flags );
hook_on();//开启send()钩子
return ret;
}
////////////////////
第3步,我们就可以修改前面的send的测试程序了
增加一个安装钩子的函数/按钮
int do_hook()
{
hinst = LoadLibrary( "hook.dll" );
if( hinst == NULL )
{
return -1;
}
/*
install_hook = ( install_hook_proc )GetProcAddress( hinst, "install_hook" );
if( install_hook )
{
install_hook();
}
*/
return 0;
}
别忘了退出时卸掉钩子
void undo_hook()
{
unstall_hook = ( unstall_hook_proc )GetProcAddress( hinst,"unstall_hook" );
if( unstall_hook )
{
unstall_hook();
}
if( hinst )
{
FreeLibrary( hinst );
}
}
当然,这里只是我们自己的程序进行了hook,如果要hook别的程序,怎么办呢,比如说QQ或者魔兽争霸。下面是我的思路以及实现:
1. CreateProcess,启动进程,并获取进程id。
2. OpenProcess,获取目标进程句柄。
3. VirtualAllocEx,在进程中申请空间并写入一些函数名称待执行。
4. CreateRemoteThread,执行LoadLibraryA即注入。
int start_program( char* exe_file_name, char* dll_path )
{
STARTUPINFO si; //一些必备参数设置
memset(&si, 0, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_SHOW;
PROCESS_INFORMATION pi; //必备参数设置结束
int ret = CreateProcess( NULL, exe_file_name, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi );
if( ret == 0 )
{
return GetLastError();
}
DWORD war_process_id = pi.dwProcessId;
//h_process = OpenProcess( PROCESS_ALL_ACCESS,
//FALSE, war_process_id );
inject_dll( dll_path, war_process_id );
return 0;
}
BOOL inject_dll( const char *dll_path, const DWORD remote_pro_id )
{
HANDLE h_token;
if ( OpenProcessToken( GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &h_token ) )
{
TOKEN_PRIVILEGES tkp;
//修改进程权限
LookupPrivilegeValue( NULL,SE_DEBUG_NAME, &tkp.Privileges[0].Luid );
tkp.PrivilegeCount = 1;
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
//通知系统修改进程权限
AdjustTokenPrivileges( h_token, FALSE, &tkp, sizeof( tkp ), NULL, NULL );
}
HANDLE h_remote_process;
//打开远程线程
if( ( h_remote_process = OpenProcess( PROCESS_CREATE_THREAD | //允许远程创建线程
PROCESS_VM_OPERATION | //允许远程VM操作
PROCESS_VM_WRITE, //允许远程VM写
FALSE, remote_pro_id ) )== NULL )
{
//AfxMessageBox("OpenProcess Error!");
return FALSE;
}
char *lib_func_buf;
//在远程进程的内存地址空间分配DLL文件名缓冲区
lib_func_buf = (char *) VirtualAllocEx( h_remote_process, NULL, lstrlen(dll_path) + 1,
MEM_COMMIT, PAGE_READWRITE);
if( lib_func_buf == NULL )
{
//AfxMessageBox("VirtualAllocEx error! ");
return FALSE;
}
//将DLL的路径名复制到远程进程的内存空间
if( WriteProcessMemory( h_remote_process,
lib_func_buf, ( void * )dll_path, lstrlen( dll_path ) + 1, NULL ) == 0 )
{
//AfxMessageBox("WriteProcessMemory Error");
return FALSE;
}
//计算LoadLibraryA的入口地址
PTHREAD_START_ROUTINE load_start_addr = ( PTHREAD_START_ROUTINE )
GetProcAddress( GetModuleHandle( TEXT("Kernel32") ), "LoadLibraryA");
if( load_start_addr == NULL )
{
//AfxMessageBox("GetProcAddress Error");
return FALSE;
}
HANDLE h_remote_thread;
if( (h_remote_thread = CreateRemoteThread( h_remote_process, NULL, 0,
load_start_addr, lib_func_buf, 0, NULL ) ) == NULL)
{
//AfxMessageBox("CreateRemoteThread Error");
return FALSE;
}
return TRUE;
}