C# API-Hook / x86

11 篇文章 0 订阅

        API-Hook是拦截或改变API函数执行的结果技术,百度百

科上有不少注解 但却是IAT-Hook,与本文的Inline-Hook不是

一至,但是本质作用是相同的 而两种Hook都有各自的优势

与劣势、那么下面我们开始了解什么是Inline-Hook它的原理又

是如何 当然你可以变通的试试(Mem-Hook)内存挂钩 当然我希

望观摩过本帖的开发人员 可以告别EasyHook

       Inline-Hook是在Ring3(应用层)一种最为有效的Hook方案,

是修改底层函数(API)的入口处汇编(ASM)、

       通常有两种方式的汇编挂钩,一为JMP(相对跳转) 二为

CALL(相对调用),当然从性能上两者相差不多 但是非要挑刺

我认为是JMP的方式可能会快上一些 因会少执行一些代码

       那么会有人疑问为什么CALL方式会多上一些指令呢?仔

细观察下图 你会发现什么?如果有发现说明你天赋足够好 若

努力觅道以后或可成为一位优秀的开发人员

       或许有自持懂的人会批评我上图做法错误,因为上面的汇

编如果修改到底层函数入口,一定会发生错误,但如果侥幸调

用通过,那么_tmain函数中局部变量value中的值应该是4,因

为上方让被调用add的函数转向到cb_add它的确调用了 但是返

回值被add重置,

       有必要弄清楚函数返回到底把值压入到什么CPU寄存器中

       在上图中可以看清楚调用函数并把返回值放到value中的汇

编代码,CALL(调用函数)的指令,ADD(相加指令)不可能是它

们,PUSH(压入堆栈)更加不可能 代码不可能倒着执行 是}(右

花括号)吗?也不可能已经超出调用add函数的代码范围 所以

只有mov dword ptr[value],eax是最有嫌疑也是最有可能的

我们都知道代码是从右向左赋值 那么计算器在执行代码时也不

可能从左向右赋值,所以我们找到那个寄存器叫做 EAX(32位元

加寄存器)

       EAX是Win32应用代码中利用最多的一个寄存器 它被使用

的次数出奇高,它过度繁忙,所以会拉低应用代码执行的效率

当然我们不必去思虑那些问题 只需所了解即可

       如果我们需要使用CALL的方式,那么势必我们需要手动

清除函数的堆栈,所以CALL的挂钩的汇编应如下编写

       在_tmain中调用add函数后,返回的值不是3而是4那么说明

add函数被正确的挂钩了,也就是俗称的Inline-Hook通过修过底

层函数入口点汇编过而实现的一种技术

       但需要注意的事是 为什么有返回值没有被重置为3的原因,

在于在执行add函数中的return x + y;之前返回了值,注意__asm

中嵌入的ret(返回)汇编指令,但pop(移出堆栈)汇编指令没有移出

eax中的值 所以可以正常调用并返回

       上面所言的Inline-Hook或许是各种方法里面最过于复杂的一

种但却是最佳的 如果可以理解上面的内容 那么JMP方式的挂钩

的原理理解起来就容易不少

       在C#代码编写中我使用的是那种方式挂钩的呢?不是如上方

所言的CALL方式,而是JMP方式 那么看看汇编是怎样的?

	__asm
	{
		jmp cb_add
	}

       然后没有了,是不是感到很奇怪?为什么只有一句话但却不

会报错 其实并有想象中的那么困难 这是CALL与JMP汇编指令两

个不同的一个地方

      在上图中可以看出,JMP是可以转移到函数且不清理堆栈

属于流程控制一类的汇编指令,它不会造成函数的调用错误,

所以是安全可靠的,而且重点在于它所需修改的汇编指令足够

短小,可以有效提升应用的代码执行效率

      但是要在C#中对底层函数进行挂钩,那么该如何去做呢?

      先把项目创建好后,打开项目属性在生成选项卡中,选择目

标平台,将其修改为x86

      我们并不需要过度担心,在C++中如何去做的那么在C#中

我们则怎么做

      首先修改需要被挂钩函数入口点汇编,而我们所用的汇编

代码是jmp rva但是需要被换算到机器代码而汇编语言标示符

C#与电脑都无法认识,但却可以使用认识机器代码 虽然会麻

烦一些,我们把jmp rva换算到机器代码是233 00 00 00 00的

形式,233则表示jmp,00 00 00 00为缺省rva,共计5个字节

      下面我们备份底层函数的入口点五个字节用于必要时恢复

被挂钩的函数

      或许有人疑惑jmp rva,相对转移到RVA那么RVA到底是什

么?RVA(相对虚拟地址) 它是一个地址,但却是一个相对性的

地址,是一个不确定性的内存地址

      我们把相对地址计算并转换到字节数组(二进制),然后在把

233(0xE9)相连在一起,形成一条汇编机器代码相对转移代码,

那么我们可以把形成的汇编机器代码写到函数的入口点,修改

成功后,我们所需要的挂接的函数就被挂接上了 那么我们现在

该测试挂钩有没有效了

      经过调试可以确定我们编写对底层函数挂钩的代码成功了

没有任何问题,但是在我的开源代码中包含了其他的几个公开

方法下面是我对它们的一些注解

public void Install(IntPtr oldMethodAddress, IntPtr newMethodAddress) // 安装(原函数, 新函数)

public void Suspend() // 挂起

public void Resume() // 恢复

public void Uninstall() // 卸载

public IntPtr GetProcAddress(Delegate d) // 取函数地址(委托)

public IntPtr GetProcAddress(string strLibraryName, string strMethodName) // 取函数地址(DLL, 方法)

      下面是如何使用NetHook的一个示例代码,它只是简单的对

消息框进行的一个挂钩

    public static class Program
    {
        [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Auto)]
        public delegate int MessageBoxW(int hWnd, string Msg, string Title, int Flags); 

        [STAThread]
        static void Main()
        {
            NetHook NetHook = new NetHook();
            MessageBoxW DMsgBoxW = CB_MessageBoxW;
            NetHook.Install(NetHook.GetProcAddress("user32.dll", "MessageBoxW"), NetHook.GetProcAddress(DMsgBoxW));

            MessageBox.Show("难道你不知道,现在流行年下吗?");
        }

        static int CB_MessageBoxW(int hWnd, string Msg, string Title, int Flags)
        {
            Console.WriteLine(Msg);
            return Console.ReadKey(false).KeyChar;
        }
    }

      下面是整个NetHook的源代码,希望有仁兄可以指出源码中

足之处,小弟感激不尽

using System;
using System.Runtime.InteropServices;

public partial class NetHook // E9 00 00 00 00
{
    private int mOldMemoryProtect;
    private IntPtr mOldMethodAddress;
    private IntPtr mNewMethodAddress;
    private byte[] mOldMethodAsmCode;
    private byte[] mNewMethodAsmCode;

    private abstract partial class Win32Native
    {
        public const int PAGE_EXECUTE_READWRITE = 64;
        public static readonly IntPtr NULL = IntPtr.Zero;

        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr GetModuleHandle(string lpModuleName);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr LoadLibrary(string lpLibFileName);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool VirtualProtect(IntPtr lpAddress, int dwSize, int flNewProtect, out int lpflOldProtect);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool FreeLibrary([In] IntPtr hLibModule);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, uint nSize, IntPtr lpNumberOfBytesWritten);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr GetCurrentProcess();
    }
}

public partial class NetHook
{
    public void Install(IntPtr oldMethodAddress, IntPtr newMethodAddress)
    {
        if (oldMethodAddress == Win32Native.NULL || newMethodAddress == Win32Native.NULL)
            throw new Exception("The address is invalid.");
        if (!this.VirtualProtect(oldMethodAddress, Win32Native.PAGE_EXECUTE_READWRITE))
            throw new Exception("Unable to modify memory protection.");
        this.mOldMethodAddress = oldMethodAddress;
        this.mNewMethodAddress = newMethodAddress;
        this.mOldMethodAsmCode = this.GetHeadCode(this.mOldMethodAddress);
        this.mNewMethodAsmCode = this.ConvetToBinary((int)this.mNewMethodAddress - ((int)this.mOldMethodAddress + 5));
        this.mNewMethodAsmCode = this.CombineOfArray(new byte[] { 0xE9 }, this.mNewMethodAsmCode);
        if (!this.WriteToMemory(this.mNewMethodAsmCode, this.mOldMethodAddress, 5))
            throw new Exception("Cannot be written to memory.");
    }

    public void Suspend()
    {
        if (this.mOldMethodAddress == Win32Native.NULL)
            throw new Exception("Unable to suspend.");
        this.WriteToMemory(this.mOldMethodAsmCode, this.mOldMethodAddress, 5);
    }

    public void Resume()
    {
        if (this.mOldMethodAddress == Win32Native.NULL)
            throw new Exception("Unable to resume.");
        this.WriteToMemory(this.mNewMethodAsmCode, this.mOldMethodAddress, 5);
    }

    public void Uninstall()
    {
        if (this.mOldMethodAddress == Win32Native.NULL)
            throw new Exception("Unable to uninstall.");
        if (!this.WriteToMemory(this.mOldMethodAsmCode, this.mOldMethodAddress, 5))
            throw new Exception("Cannot be written to memory.");
        if (!this.VirtualProtect(this.mOldMethodAddress, this.mOldMemoryProtect))
            throw new Exception("Unable to modify memory protection.");
        this.mOldMemoryProtect = 0;
        this.mOldMethodAsmCode = null;
        this.mNewMethodAsmCode = null;
        this.mOldMethodAddress = Win32Native.NULL;
        this.mNewMethodAddress = Win32Native.NULL;
    }
}

public partial class NetHook
{
    private byte[] GetHeadCode(IntPtr ptr)
    {
        byte[] buffer = new byte[5];
        Marshal.Copy(ptr, buffer, 0, 5);
        return buffer;
    }

    private byte[] ConvetToBinary(int num)
    {
        byte[] buffer = new byte[4];
        IntPtr ptr = Marshal.AllocHGlobal(4);
        Marshal.WriteInt32(ptr, num);
        Marshal.Copy(ptr, buffer, 0, 4);
        Marshal.FreeHGlobal(ptr);
        return buffer;
    }

    private byte[] CombineOfArray(byte[] x, byte[] y)
    {
        int i = 0, len = x.Length;
        byte[] buffer = new byte[len + y.Length];
        while (i < len)
        {
            buffer[i] = x[i];
            i++;
        }
        while (i < buffer.Length)
        {
            buffer[i] = y[i - len];
            i++;
        }
        return buffer;
    }

    private bool WriteToMemory(byte[] buffer, IntPtr address, uint size)
    {
        IntPtr hRemoteProcess = Win32Native.GetCurrentProcess();
        return Win32Native.WriteProcessMemory(hRemoteProcess, address, buffer, size, Win32Native.NULL);
    }
}

public partial class NetHook
{
    public IntPtr GetProcAddress(Delegate d)
    {
        return Marshal.GetFunctionPointerForDelegate(d);
    }

    public IntPtr GetProcAddress(string strLibraryName, string strMethodName)
    {
        IntPtr hRemoteModule;
        if ((hRemoteModule = Win32Native.GetModuleHandle(strLibraryName)) == Win32Native.NULL)
            hRemoteModule = Win32Native.LoadLibrary(strLibraryName);
        return Win32Native.GetProcAddress(hRemoteModule, strMethodName);
    }
}

public partial class NetHook
{
    private bool VirtualProtect(IntPtr ptr, int flNewProtect)
    {
        return Win32Native.VirtualProtect(ptr, 5, flNewProtect, out this.mOldMemoryProtect);
    }
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值