首页
社区
课程
招聘
[旧帖] [讨论]关于hot patch的问题 0.00雪花
发表于: 2013-6-22 00:30 2937

[旧帖] [讨论]关于hot patch的问题 0.00雪花

2013-6-22 00:30
2937
大家知道win sdk的很多api的入口的汇编代码如下:

nop
nop
nop
nop
nop
mov edi, edi             <---------------API入口
push ebp
mov ebp, esp
....

对于这种形式的api可以采用如下方式进行hook:
l1:jmp 目标地址
jmp l1
push ebp
mov ebp,esp
...



然而还有一些api不是标准此类结构的,比如ws2_32.dll的WSAStartup函数,它的入口形式如下:

nop
nop
nop
nop
nop
push 14h            <---------------API入口
push ws2_32.76c53c08
call ws2_32.76c51370
...

那么对于此函数的hook,该怎么处理呢?需要达到在hook里面知道wsastartup调用返回值是什么。

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 0
支持
分享
最新回复 (25)
雪    币: 496
活跃值: (286)
能力值: ( LV13,RANK:400 )
在线值:
发帖
回帖
粉丝
2
mov指令替换成

近跳转 0xFE
上面5个nop换成远跳到补丁函数所在地址。
2013-6-22 10:50
0
雪    币: 40
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
这就是针对标准的hot patch类型的api的hook;
对与非标准的没有给出方法
2013-6-23 02:55
0
雪    币: 218
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
我个人的处理办法
如果函数有Ansi/Unicode两个版本而且只需要监视Ansi版本的话,自己做字符串转换的操作(如果挂钩Unicode自己执行Ansi的话Ansi版本内部会调用Unicode版本形成死循环)
如果函数很简单(比如GetCommandLine直接就返回内存一块固定区域的地址),自己手动实现一遍
很复杂的函数的话就必须要备份函数头部,这样就跟普通的inline hook没有太大差别了,你可以参阅这方面的文章
最近正好写了点和Hot patch相关的东西,还有什么问题可以向我询问
顺带一提在win64平台上没有对应的Hot patch技术,所以如果对可移植性有要求的话(hook肯定是hook别人的程序,而别人可能用的就是64位程序),建议早点换成一个通用的hook方案有助于提高代码的可维护性
2013-6-23 06:36
0
雪    币: 40
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
好吧,我自己说下实现非标准hot patch的api hook的解决方法,针对wsastartup:

wsastartup的入口点如下:
nop
nop
nop
nop
nop
push 14h            <---------------wsastartup入口
push ws2_32.76c53c08
call ws2_32.76c51370

我的hook函数的实现:

DWORD ver = 0;
DWORD dat = 0;
DWORD regEIP[2] = {0, 0};

PVOID next_step_addr = NULL;

static void __declspec(naked) __MyHook_WSAStartup()
{
    {
        _asm { // 将调用WSAStartup时入栈的参数弹出保存
            pop regEIP[0];
            pop ver;
            pop dat;
            push regEIP[0];
        }

        // 获取下一步的地址
        next_step_addr = CApiHookWSAStartup::hooker.GetNextStepAddress();

        _asm { // 先调用实际的WSAStartup函数,再调用相关的工作函数
            call near orig_routine;
            call begin_lurker_handler;   // 个人的处理函数,希望在real wsastartup返回之后被调用
            ret;

orig_routine:
            pop regEIP[1];
            push dat;
            push ver;
            push regEIP[1];

            push 14h; // 模拟WSAStartup入口点的指令
            jmp next_step_addr;
        }

    }
}

这个实现,正在正常使用,达到了我的目的。但是call有far和near之分,怎么在__MyHook_WSAStartup里面判断WSAStartup被调用的时候是call far的还是call near。希望有兄弟解惑,并给出更好一点的解决方案。
2013-6-23 10:27
0
雪    币: 218
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
不太清楚你是为什么想要知道call是近跳转还是远跳转,这个只对调用者有意义,被调用者是不用管的
如果是我来实现这个函数我大概会采用下面的做法

int WINAPI NewWSAStartup(
  _In_    WORD wVersionRequested,
  _Out_  LPWSADATA lpWSAData
){
int ReturnValue;
_asm
{
    push lpWSAData
    push wVersionRequested
    push 14h
    call next_step_addr
    mov ReturnValue,eax
}
//用C干你想干的事情
return ReturnValue;
}

大致就是这个样子,也许有些地方需要修改,不过应该没错
2013-6-23 13:23
0
雪    币: 40
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
_asm
{
    push lpWSAData
    push wVersionRequested
    push 14h                                      <---------------
    call next_step_addr                      <---------------
    mov ReturnValue,eax
}

楼上的哥们注意看这两个箭头所指的地方:
一、当call的时候,1)如果是far调用,实际上在push 14h和next_step_addr之间多了两个寄存器入栈(CS和EIP);2)如果是near调用,实际上在push 14h和next_step_addr之间多了一个寄存器入栈(EIP)。
二、不考虑call带来的寄存器入栈的副作用,即便是call next_step_addr成功之后,就已经直接ret到调用wsastartup的下一个指定的地方,也即mov returnvalue, eax不可能被调用

鉴于此,楼上的代码是不能成功运行的。

而我指出的判断call是far或者near,正是要解决call指令的入栈操作的问题。楼上的哥们可以仔细体会下我的解决方案的原理。
2013-6-24 23:43
0
雪    币: 40
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
DWORD ver = 0;
DWORD dat = 0;
DWORD regEIP = 0;

PVOID next_step_addr = NULL;

static void __declspec(naked) __MyHook_WSAStartup()
{
    {
        _asm { // 将调用WSAStartup时入栈的参数弹出保存
            pop regEIP; <---------针对call是far或者near,应该有不同的措施
            pop ver;
            pop dat;
            push regEIP;
        }

        // 获取下一步的地址
        next_step_addr = CApiHookWSAStartup::hooker.GetNextStepAddress();

        _asm { // 先调用实际的WSAStartup函数,再调用相关的工作函数
          push dat;  <----移到这里
          push ver;  <----移到这里
          call near orig_routine;
          call begin_lurker_handler;   // 个人的处理函数,希望在real wsastartup返回之后被调用
          ret;

orig_routine:
          push 14h; // 模拟WSAStartup入口点的指令
          jmp next_step_addr;
        }

    }
}

实际上代码可简化成这样
2013-6-25 00:04
0
雪    币: 496
活跃值: (286)
能力值: ( LV13,RANK:400 )
在线值:
发帖
回帖
粉丝
9
在保护模式下的windows程序中flat模型,不存在你说的压段寄存器入栈的问题,事实上在保护模式下段寄存器只是摆设。
2013-6-25 00:05
0
雪    币: 496
活跃值: (286)
能力值: ( LV13,RANK:400 )
在线值:
发帖
回帖
粉丝
10
你所说的非标准指的是什么?
如果你确定目标函数的前五个字可以构成n条完整的指令,那就直接替换前n条指令,吧原始的前五条指令复制到一个内存空间中,然后子在你的hook函数中跳转去执行那5个字节的指令,或者直接把原函数的5个字节的指令硬编码到你的hook函数中。

如果目标函数的前5个字节不能构成完整的n条指令,那就需要你自己去做指令解析,计算出总长度大于5个字节并且能构成n条完整指令的字节数m,然后保存m个字节的内容到一个内存buffer,然后在你的hook函数中跳转去执行那m个字节的指令。

你这个代码写的没有合理性可言。。。等等有空了给你上个徒手hook函数处理大全
2013-6-25 00:12
0
雪    币: 40
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
楼上的可以实际去测试。intel的software developer's manual的6.3 calling procedures using call adn ret对这做了说明
2013-6-25 00:13
0
雪    币: 40
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
这个简单跳转可以这么实现:

static void __declspec(naked) __WSAStartup()
{
        {

// 在这里做私人的事情

                // 获取下一步的地址
                next_step_addr = CApiHookWSAStartup::hooker.GetNextStepAddress();

                _asm {
                        push 14h; // 模拟WSAStartup入口点的指令
                        jmp next_step_addr;
                        // 这里无法到达无法到达无法到达无法到达无法到达
                        ret;   
                }

        }
}

这个跳转是可以正常使用的,但是由于jmp next_step_addr;之后就直接返回到wsastartup调用之后的下一条指令了,所以达不到想要的效果,即:根据实际wsastartup调用结果来判断是否进行私人的处理。
2013-6-25 00:19
0
雪    币: 496
活跃值: (286)
能力值: ( LV13,RANK:400 )
在线值:
发帖
回帖
粉丝
13
知识面要放宽,你看到的只是16位的dos程序的介绍。

windows在平坦内存模式下代码和数据的寻址范围都在4G以内,所以寻址方式全部为NEAR型,何来远跳一说?

你观察一下Call一个系统API之后栈内有没有多出来一个你所谓的段寄存器

去学习学习windows,保护模式,.flat平坦内存模式,关键词就给你列这么多,能3个月内能全部领悟吃透你也很不错了.
2013-6-25 00:23
0
雪    币: 40
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
针对楼上的说法,本人不给回应。先去实际测试再来说问题。
2013-6-25 00:24
0
雪    币: 496
活跃值: (286)
能力值: ( LV13,RANK:400 )
在线值:
发帖
回帖
粉丝
15
如果我写,我会这样写:

DWORD g_pOriginalWSAStartup = 获取原函数地址
void __declspec(naked) WSAStartup_Stub(WORD wVersionRequested, LPWSADATA lpWSAData)
{
        _asm
        {
                mov                edi, edi       //把原函数的前
                push                        ebp            //n跳指令
                mov                ebp, esp    //直接硬编码过来
                mov                eax, g_pOriginalWSAStartup
                add                eax, 5
                jmp                eax   
        }
}

VOID __stdcall fakexxxDispatchMessage(WORD wVersionRequested, LPWSADATA lpWSAData)
{
        //  可以对参数做分析处理

     //  调用原函数
     WSAStartup_Stub(wVersionRequested, lpWSAData);

      // 对返回值进行分析处理(如果有的话)

    // 返回原函数的返回值,或者你想反回的值
    return ;
}
2013-6-25 00:32
0
雪    币: 496
活跃值: (286)
能力值: ( LV13,RANK:400 )
在线值:
发帖
回帖
粉丝
16
期待你的结果。。
我要先睡了。
2013-6-25 00:40
0
雪    币: 496
活跃值: (286)
能力值: ( LV13,RANK:400 )
在线值:
发帖
回帖
粉丝
17
不好意思,上上楼的fake忘了改了
2013-6-25 00:48
0
雪    币: 40
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
感谢楼上的积极参与,可能你对非标准hot patch的api缺乏关注。目测WSAStartup_Stub 这个函数是不能正确运行的,就算正确运行,它也会直接return到wsastartup调用的下一条指令了,根本不会有后续处理的机会。请注意naked关键字。

实际可行的是本人给出的代码,各位可以实际测试。
2013-6-25 00:54
0
雪    币: 496
活跃值: (286)
能力值: ( LV13,RANK:400 )
在线值:
发帖
回帖
粉丝
19
楼主,先把你关于近跳远跳的实验结结果说出来吧,至于我的代码能不能用,无bin无真相,明天放个bin出来咱就知道了。
2013-6-25 01:03
0
雪    币: 218
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
我都说了那个代码只是一个大致的样子,只是证明这个函数不需要像你那样写的那么庞大,我自己都没试着编译这块代码
看了下楼主其他的几个回帖,明显感觉基础有待加强,也懒的多说,花了点时间把代码整理出来,你自己开调试器对照着看吧
只把这里讨论到的函数用C写了一遍
上传的附件:
2013-6-25 04:19
0
雪    币: 496
活跃值: (286)
能力值: ( LV13,RANK:400 )
在线值:
发帖
回帖
粉丝
21
楼主给你bin

// HookWSAStartup.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <Windows.h>

#pragma  comment (lib, "ws2_32")


typedef int (_stdcall * Type_WSAStartup)(WORD, LPWSADATA);

// 全局变量,原始函数地址
Type_WSAStartup g_pOriginalAddress = NULL;

// 原函数前n个字节的完整指令,至于为什么是7,自己研究研究吧,
// 这里只是写个简单的列子,所以直接硬编码了,如果要通用,就需要进行指令分析,计算长度
#define COMPLETE_INSTRUCTION_LENGTH 7


// stub函数,用于跳转到原函数
int _declspec(naked) PASCAL FAR WSAStartup_stub(
									  WORD wVersionRequested,
									  LPWSADATA lpWSAData
									  )
{
	__asm
	{
		_emit 0;
		_emit 0;
		_emit 0;
		_emit 0;
		_emit 0;
		_emit 0;
		_emit 0;

		mov eax, g_pOriginalAddress;
		add eax, COMPLETE_INSTRUCTION_LENGTH;
		jmp eax
	}
}


// hook函数
int PASCAL FAR fake_WSAStartup(
								  WORD wVersionRequested,
								  LPWSADATA lpWSAData
								  )
{
	int iRet = 0;

	MessageBox(NULL, _T("Before original function."), NULL, MB_OK);

	iRet = WSAStartup_stub(wVersionRequested, lpWSAData);

	MessageBox(NULL, _T("After original function."), NULL, MB_OK);

	return iRet;
}


// 下面的函数主要用于hook操作,写的比较乱
#pragma  pack(push)
#pragma  pack(1)
typedef struct __FIVEB_INSTRUCTION
{
	BYTE		jmpCode;
	DWORD_PTR	jmpOffset;
}FIVEB_INSTRUCTION, *PFIVEB_INSTRUCTION;
#pragma  pack(pop)

BOOL DoHook(DWORD_PTR pOriginalAddress, DWORD_PTR pStubProc, DWORD_PTR pFakeProc)
{

	BOOL bRet = FALSE;
	LPVOID targetProc = (LPVOID)pOriginalAddress;

	DWORD dwOldProtectTarget = 0;
	DWORD dwOldProtectHook = 0;
	MEMORY_BASIC_INFORMATION memInfoTarget;
	MEMORY_BASIC_INFORMATION memInfoHook;
	SIZE_T sBytesDone = 0;
	if (VirtualQuery((LPVOID)targetProc, &memInfoTarget, sizeof(memInfoTarget)) > 0 && VirtualQuery((LPVOID)pStubProc, &memInfoHook, sizeof(memInfoHook)) > 0)
	{
		// change the pages to read/write
		bRet = VirtualProtect(memInfoTarget.BaseAddress, 
			memInfoTarget.RegionSize, PAGE_READWRITE, &dwOldProtectTarget);

		bRet = VirtualProtect(memInfoHook.BaseAddress, 
			memInfoHook.RegionSize, PAGE_READWRITE, &dwOldProtectHook);

		bRet = ReadProcessMemory(GetCurrentProcess(),
			targetProc, 
			IntToPtr(pStubProc),
			COMPLETE_INSTRUCTION_LENGTH,
			&sBytesDone);

		if (!bRet)
		{
			return FALSE;
		}

		FIVEB_INSTRUCTION jmpins;
		jmpins.jmpCode = 0xe9;
		jmpins.jmpOffset = pFakeProc - pOriginalAddress - sizeof(jmpins);

		bRet = WriteProcessMemory(GetCurrentProcess(),
			IntToPtr(pOriginalAddress), 
			&jmpins, 
			sizeof(jmpins), 
			&sBytesDone);

		if (!bRet)
		{
			return FALSE;
		}


		bRet = VirtualProtect(memInfoTarget.BaseAddress, 
			memInfoTarget.RegionSize, dwOldProtectTarget, &dwOldProtectTarget);

		bRet = VirtualProtect(memInfoHook.BaseAddress, 
			memInfoHook.RegionSize, dwOldProtectHook, &dwOldProtectHook);
		return TRUE;
	}

	return FALSE;
}

int _tmain(int argc, _TCHAR* argv[])
{
	g_pOriginalAddress = (Type_WSAStartup)GetProcAddress(LoadLibrary(_T("ws2_32.dll")), "WSAStartup");
	if (NULL == g_pOriginalAddress)
	{
		return 0;
	}

	DoHook(PtrToInt(g_pOriginalAddress), PtrToInt(WSAStartup_stub), PtrToInt(fake_WSAStartup));

	WORD wVersionRequested;
	WSADATA wsaData;
	int err;

	wVersionRequested = MAKEWORD( 2, 2 );

	err = WSAStartup(wVersionRequested, &wsaData);
	if ( err != 0 ) 
	{
		return 1;
	}

	return 0;
}


HookWSAStartup.7z
上传的附件:
2013-6-25 21:47
0
雪    币: 1689
活跃值: (379)
能力值: ( LV15,RANK:440 )
在线值:
发帖
回帖
粉丝
22
楼主的目的达到啦,嘿嘿
2013-6-26 05:47
0
雪    币: 218
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
23
写好了程序敢不敢自己运行一下!
    bRet = VirtualProtect(memInfoTarget.BaseAddress, 
      memInfoTarget.RegionSize, PAGE_EXECUTE_READWRITE, &dwOldProtectTarget);

    bRet = VirtualProtect(memInfoHook.BaseAddress, 
      memInfoHook.RegionSize, PAGE_EXECUTE_READWRITE, &dwOldProtectHook);

改成这个就好了,出问题的主要是第二个,因为hook stub和当前指令在同一个页里

第二个问题
你这不是hot patch,只是个普通的inline hook而已...好歹主题是hot patch啊...
2013-6-26 12:00
0
雪    币: 496
活跃值: (286)
能力值: ( LV13,RANK:400 )
在线值:
发帖
回帖
粉丝
24
无伤大雅,只是为了说明naked函数不想楼主所想的那样
2013-6-26 13:48
0
雪    币: 40
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
25
实际上代码是这么写的,谢谢大家讨论

BOOL CApiHookWSAStartup::Hook()
{
        return CApiHook::Hook(_T("ws2_32.dll"), "WSAStartup");
}

typedef int (PASCAL FAR *fnWSAStartup)(WORD wVersionRequired, LPWSADATA lpWSAData);
int PASCAL FAR CApiHookWSAStartup::_WSAStartup(WORD wVersionRequired, LPWSADATA lpWSAData)
{
        DWORD_PTR retv = 0;

        DWORD_PTR _NextStepAddress = (DWORD_PTR)NextStepAddress;     <------------

        _asm
        {
                pushad;

                mov eax, dword ptr [lpWSAData];
                push eax;
                movzx ecx, word ptr [wVersionRequired];
                push ecx;

                call near real_WSAStartup;         <------------------
                mov retv, eax;

                popad;
                jmp func_ok;

real_WSAStartup:
                push 14h;
                jmp _NextStepAddress;
                ret; // 实际上不会执行

func_ok:
                ;
        }

        if (retv == 0)
                begin_lurker_handler();

        //retv = ((fnWSAStartup)NextStepAddress)(wVersionRequired, lpWSAData);

        return retv;
}

static int PASCAL FAR __WSAStartup(WORD wVersionRequired, LPWSADATA lpWSAData)
{
        return CApiHookWSAStartup::hooker._WSAStartup(wVersionRequired, lpWSAData);
}

PVOID CApiHookWSAStartup::GetMyApiAddress()
{
        return (PVOID)__WSAStartup;
}
2013-6-27 14:06
0
游客
登录 | 注册 方可回帖
返回
//