用户层下拦截系统api的原理与实现

http://www.leftworld.net/wenzhang/show/2251.html

写这篇文章是为了复习一些知识,最近在做毕业设计,之中大量地使用了这种技术,主要是用在拦截 winsock 函数,对于其他系统 api,其效果也是一样的。

 前段时间写了一个 HookSend 的小程序,其实就是用的这里所说的方法.只不过这里讲得更详细一点,熟悉的就不用看了,另外欢迎大虾指出错误,不胜感激@_@

拦截 api 的技术有很多种,大体分为用户层和内核层的拦截.这里只说说用户层的拦截.而用户层也分为许多种:修改PE文件导入表,直接修改要拦截的 api 的内存(从开始到最后,使程序跳转到指定的地址执行)。不过大部分原理都是修改程序流程,使之跳转到你要执行的地方,然后再返回到原地址.原来 api 的功能必须还能实现.否则拦截就失去作用了.修改文件导入表的方法的缺点是如果用户程序动态加载(使用 LoadLibrary 和 GetProcAddress 函数),拦截将变得复杂一些.所以这里介绍一下第二种方法,直接修改 api,当然不是全局的。(后面会说到)

需要了解的一些知识:

1、windows 内存的结构属性和进程地址空间

2、函数堆栈的一些知识

一、win2000 和 xp 的内存结构和进程地址空间

windows 采用 4GB 平坦虚拟地址空间的做法。即每个进程单独拥有 4GB 的地址空间。每个进程只能访问自己的这 4GB 的虚拟空间,而对于其他进程的地址空间则是不可见的。这样保证了进程的安全性和稳定性。但是,这 4GB 的空间是一个虚拟空间,在使用之前,我们必须先保留一段虚拟地址,然后再为这段虚拟地址提交物理存储器。可是我们的内存大部分都还没有 1GB,那么这 4GB 的地址空间是如何实现的呢?事实上 windows 采用的内存映射这种方法,即把物理磁盘当作内存来使用,比如我们打开一个可执行文件的时候,操作系统会为我们开辟这个 4GB 的地址空间:0x00000000--0xffffffff。其中 0x00000000--0x7fffffff 是属于用户层的空间,0x80000000--0xffffffff 则属于共享内核方式分区,主要是操作系统的线程调度,内存管理,文件系统支持,网络支持和所有设备驱动程序。对于用户层的进程,这些地址空间是不可访问的。任何访问都将导致一个错误。开辟这4GB的虚拟地址空间之后,系统会把磁盘上的执行文件映射到进程的地址空间中去(一般是在地址 0x00400000,可以通过修改编译选项来修改这个地址)而一个进程运行所需要的动态库文件则一般从 0x10000000 开始加载。但是如果所有的动态库都加载到这个位置肯定会引起冲突。因此必须对一些可能引起冲突的 dll 编译时重新修改基地址。但是对于所有的操作系统所提供的动态库 windows 已经定义好了映射在指定的位置。这个位置会随着版本的不同而会有所改变,不过对于同一台机器上的映射地址来说都是一样的。即在 a 进程里映射的kernel32.dll 的地址和在进程 b 里的 kernel32.dll 的地址是一样的。对于文件映射是一种特殊的方式,使得程序不需要进行磁盘 I/O 就能对磁盘文件进行操作,而且支持多种保护属性。对于一个被映射的文件,主要是使用CreateFileMapping 函数,利用他我们可以设定一些读写属性:PAGE_READONLY、PAGE_READWRITE、PAGE_WRITECOPY。第一参数指定只能对该映射文件进行读操作。任何写操作将导致内存访问错误。第二个参数则指明可以对映射文件进行读写。这时候,任何对文件的读写都是直接操作文件的。而对于第三个参数 PAGE_WRITECOPY 顾名思义就是写入时拷贝,任何向这段内存写入的操作(因为文件是映射到进程地址空间的,对这段空间的读写就相当于对文件进行的直接读写)都将被系统捕获,并重新在你的虚拟地址空间重新保留并分配一段内存,你所写入的一切东西都将在这里,而且你原先的指向映射文件的内存地址也会实际指向这段重新分配的内存,于是在进程结束后,映射文件内容并没有改变,只是在运行期间在那段私有拷贝的内存里面存在着你修改的内容。windows 进程运行所需要映射的一些系统 dll 就是以这种方式映射的,比如常用的 ntdll.dll、kernel32.dll、gdi32.dll。几乎所有的进程都会加载这三个动态库。如果你在一个进程里修改这个映射文件的内容,并不会影响到其他的进程使用他们。你所修改的只是在本进程的地址空间之内的。事实上原始文件并没有被改变。

这样,在后面的修改系统 api 的时候,实际就是修改这些动态库地址内的内容。前面说到这不是修改全局 api 就是这个原因,因为他们都是以写入时拷贝的方式来映射的。不过这已经足够了,windows 提供了 2 个强大的内存操作函数ReadProcessMemory 和 WriteProcessMemory。利用这两个函数我们就可以随便对任意进程的任意用户地址空间进行读写了。但是,现在有一个问题,我们该写什么,说了半天,怎么实现跳转呢?现在来看一个简单的例子:

MessageBox(NULL, "World", "Hello", 0);

我们在执行这条语句的时候,调用了系统 api MessageBox,实际上在程序中我没有定义 UNICODE 宏,系统调用的是MessageBox 的 ANSI 版本 MessageBoxA,这个函数是由 user32.dll 导出的。下面是执行这条语句的汇编代码:

0040102A   push         0

0040102C   push         offset string "Hello" (0041f024)

00401031   push         offset string "World" (0041f01c)

00401036   push         0

00401038   call         dword ptr [__imp__MessageBoxA@16 (0042428c)]

前面四条指令分别为参数压栈,因为 MessageBoxA 是 __stdcall 调用约定,所以参数是从右往左压栈的。最后再CALL 0x0042428c

看看 0042428c 这段内存的值:

0042428C   0B 05 D5 77 00 00 00

可以看到这个值 0x77d5050b,正是 user32.dll 导出函数 MessageBoxA 的入口地址。

这是 0x77D5050B 处的内容,

77D5050B 8B FF                mov         edi, edi

77D5050D 55                   push        ebp

77D5050E 8B EC                mov         ebp, esp

理论上只要改变 api 入口和出口的任何机器码,都可以拦截该 api。这里我选择最简单的修改方法,直接修改 api 入口的前十个字节来实现跳转。为什么是十字节呢?其实修改多少字节都没有关系,只要实现了函数的跳转之后,你能把他们恢复并让他继续运行才是最重要的。在 CPU 的指令里,有几条指令可以改变程序的流程:JMP,CALL,INT,RET,RETF,IRET等指令。这里我选择 CALL 指令,因为他是以函数调用的方式来实现跳转的,这样可以带一些你需要的参数。到这里,我该说说函数的堆栈了。

总结:windows 进程所需要的动态库文件都是以写入时拷贝的方式映射到进程地址空间中的。这样,我们只能拦截指定的进程。修改目标进程地址空间中的指定 api 的入口和出口地址之间的任意数据,使之跳转到我们的拦截代码中去,然后再恢复这些字节,使之能顺利工作。

 

二、函数堆栈的一些知识

正如前面所看到MessageBoxA函数执行之前的汇编代码,首先将四个参数压栈,然后 CALL MessageBoxA,这时候我们的线程堆栈看起来应该是这样的:

|        |     <---ESP

|返回地址|

|参数1   |

|参数2   |

|参数3   |

|参数4   |

|......  |

我们再看 MessageBoxA 的汇编代码,

77D5050B 8B FF                mov         edi, edi

77D5050D 55                   push        ebp

77D5050E 8B EC                mov         ebp, esp

注意到堆栈的操作有 push ebp,这是保存当前的基址指针,以便一会儿恢复堆栈后返回调用线程时使用,然后再有mov ebp, esp就是把当前 esp 的值赋给 ebp,这时候我们就可以使用 ebp + 偏移 来表示堆栈中的数据,比如参数 1 就可以表示成 [ebp + 8],返回地址就可以表示成 [ebp + 4]... 如果我们在拦截的时候要对这些参数和返回地址做任何处理,就可以使用这种方法。如果这个时候函数有局部变量的话,就通过减小 ESP 的值的方式来为之分配空间。接下来就是保存一些寄存器:EDI、ESI、EBX。要注意的是,函数堆栈是反方向生长的。这时候堆栈的样子:

|...     |

|EDI     |   <---ESP

|ESI     |

|EBX     |

|局部变量|

|EBP     |

|返回地址|

|参数1   |

|参数2   |

|参数3   |

|参数4   |

|...     |

在函数返回的时候,由函数自身来进行堆栈的清理,这时候清理的顺序和开始入栈的顺序恰恰相反,类似的汇编代码可能是这样的:

pop   edi

pop   esi

pop   ebx

add   esp, 4

pop   ebp

ret   0010

先恢复那些寄存器的值,然后通过增加 ESP 的值的方式来释放局部变量。这里可以用 mov esp, ebp 来实现清空所有局部变量和其他一些空闲分配空间。接着函数会恢复 EBP 的值,利用指令 POP EBP 来恢复该寄存器的值。接着函数运行 ret 0010 这个指令。该指令的意思是,函数把控制权交给当前栈顶的地址的指令,同时清理堆栈的 16 字节的参数。如果函数有返回值的话,那在 EAX 寄存器中保存着当前函数的返回值。如果是 __cdecl 调用方式,则执行 ret 指令,对于堆栈参数的处理交给调用线程去做。如 wsprintf 函数。

这个时候堆栈又恢复了原来的样子。线程得以继续往下执行...

在拦截 api 的过程之中一个重要的任务就是保证堆栈的正确性。你要理清每一步堆栈中发生了什么。

三、形成思路

呵呵,不知道你现在脑海是不是有什么想法。怎么去实现拦截一个api?

这里给出一个思路,事实上拦截的方法真的很多,理清了一个,其他的也就容易了。而且上面所说的2个关键知识,也可以以另外的形式来利用。

我以拦截 CreateFile 这个 api 为例子来简单说下这个思路吧:

首先,既然我们要拦截这个 api 就应该知道这个函数在内存中的位置吧,至少需要知道从哪儿入口。CreateFile 这个函数是由 kernel32.dll 这个动态库导出的。我们可以使用下面的方法来获取他映射到内存中的地址:

  HMODULE hkernel32 = LoadLibrary("Kernel32.dll");

  PVOID dwCreateFile = GetProcAddress(hkernei32, "CreateFileA");

这就可以得到 createfile 的地址了,注意这里是获取的 createfile 的 ansic 版本。对于 UNICODE 版本的则获取 CreateFileW。这时 dwCreateFile 的值就是他的地址了。对于其他进程中的 createfile 函数也是这个地址,前面说过 windows 指定了他提供的所有的 dll 文件的加载地址。

接下来,我们该想办法实现跳转了。最简单的方法就是修改这个 api 入口处的代码了。但是我们该修改多少呢?修改的内容为什么呢?前面说过我们可以使用 CALL 的方式来实现跳转,这种方法的好处是可以为你的拦截函数提供一个或者多个参数。这里只要一个参数就足够了。带参数的函数调用的汇编代码是什么样子呢,前面也已经说了,类似与调用 MessageBoxA 时的代码:

PUSH   参数地址

CALL   函数入口地址(这里为一个偏移地址)

执行这 2 条指令就能跳转到你要拦截的函数了,但是我们该修改成什么呢。首先,我们需要知道这 2 条指令的长度和具体的机器代码的值。其中 PUSH 对应 0x68,而 CALL 指令对应的机器码为 0xE8,而后面的则分别对应拦截函数的参数地址和函数的地址。注意第一个是一个直接的地址,而第二个则是一个相对地址。当然你也可以使用 0xFF0x15 这个 CALL 指令来进行直接地址的跳转。

下面就是计算这 2 个地址的值了,

对于参数和函数体的地址,要分情况而定,对于对本进程中 api 的拦截,则直接取地址就可以了。对于参数,可以先定义一个参数变量,然后取变量地址就 ok 了。

如果是想拦截其他进程中的 api,则必须使用其他一些方法,最典型的方法是利用 VirtualAllocEx 函数来在其他进程中申请和提交内存空间。然后用 WriteProcessMemory 来分别把函数体和参数分别写入申请和分配的内存空间中去。然后再生成要修改的数据,最后用 WriteProcessMemory 来修改 api 入口,把入口的前 10 字节修改为刚刚生成的跳转数据。比如在远程进程中你写入的参数和函数体的内存地址分别为 0x00010000 和 0x00011000,则生成的跳转数据为 68 00 00 01 00 E8 00 10 01 00(PUSH 00010000   CALL 00011000),这样程序运行 createfile 函数的时候将会先运行 PUSH 00010000   CALL 00011000,这样就达到了跳转的目的。此刻我们应该时刻注意堆栈的状态,对于 CreateFile 有

HANDLE CreateFile(
            LPCTSTR   lpFileName,
            DWORD     dwDesiredAccess,
            DWORD     dwShareMode, 
            LPSECURITY_ATTRIBUTES   lpSecurityAttributes,
            DWORD    dwCreationDisposition,
            DWORD    dwFlagsAndAttributes,
            HANDLE   hTemplateFile
            );

可以看到其有 7 个参数,于是在调用之前,堆栈应该已经被压入了这 7 个参数,堆栈的样子:

|... |     <---ESP

|createfile执行后的下一条指令地址|

|参数1|

|参数2|

|参数3|

|参数4|

|参数5|

|参数6|

|参数7|

|...  |

这是执行到我们的跳转语句:PUSH 00010000,于是堆栈又变了:

|.... |     <---ESP

|00010000|

|createfile执行后的下一条指令地址|

|参数1|

|参数2|

|参数3|

|参数4|

|参数5|

|参数6|

|参数7|

|.... |

接着执行 CALL 00011000,堆栈变为:

|...|   <---ESP

|api入口之后的第11个字节的指令的地址|

|00010000|

|createfile执行后的下一条指令地址|

|参数1|

|参数2|

|参数3|

|参数4|

|参数5|

|参数6|

|参数7|

|...|

接下来就到了我们的拦截函数中拉,当然,函数肯定也会做一些类似动作,把EBP压栈,为局部变量分配空间等。这时候堆栈的样子又变了:

|EDI|   <---ESP

|ESI|

|EBX|

|局部变量|

|EBP|     <---EBP

|api入口之后的第11个字节的指令的地址|

|00010000|

|createfile执行后的下一条指令地址|

|参数1|

|参数2|

|参数3|

|参数4|

|参数5|

|参数6|

|参数7|

|...|

这时候,你想做什么就尽情地做吧,获取参数信息,延缓执行 CreateFile 函数等等。拿获取打开文件句柄的名字来说吧,文件名是第一个参数,前面说过我们可以用 [EBP + 8] 来获取参数,但是对照上面的堆栈形状,中间又加了另外一些数据,所以我们用 [EBP + 16] 来获取第一个参数的地址。比如:

char* PFileName = NULL;

__asm{

        MOV EAX, [EBP + 16]

        MOV [szFileName], EAX

}

比如我们用一个 messagebox 来弹出一个信息,说明该程序即将打开一个某谋路径的文件句柄。但是有一个要注意的是,如果你想拦截远程进程的话,对于那个拦截函数中所使用到的任何函数或者以任何形式的相对地址的调用都要停止。因为每个进程中的地址分配都是独立的,比如上面的 CALL MessageBoxA 改成直接地址的调用。对于使用 messagebox,我们应该定义一个函数指针,然后把这个指针的值赋值为 user32.dll 中导出该函数的直接地址。然后利用这个指针来进行函数调用。对于 messagebox 函数的调用可以这样,在源程序中定义一个参数结构体,参数中包含一个导出函数的地址,把这个地址设为 MessageBoxA 的直接地址,获取地址的方法就不说了。然后把这个参数传给拦截函数,就可以使用拉。这也是利用一个参数的原因。类似代码如下:

typedef struct _RemoteParam {
        DWORD dwMessageBox;
    } RemoteParam, * PRemoteParam;

 

typedef int (__stdcall * PFN_MESSAGEBOX)(HWND, LPCTSTR, LPCTSTR, DWORD);    // 定义一个函数指针

// 拦截函数
    void HookCreateFile(LPVOID lParam)
    {
        RemoteParam* pRP = (RemoteParam *)lParam;    // 获取参数地址
        char* PFileName = NULL;    // 定义一个指针
        __asm {
            MOV EAX,[EBP + 16]
            MOV [szFileName], EAX     // 把 CreateFile 第一个参数的值,文件的路径的地址传给 szFileName
        }

        // 定义一个函数指针
        PFN_MESSAGEBOX pfnMessageBox = (PFN_MESSAGEBOX)pRP->dwMessageBox;
        pfnMessageBox(NULL, PFileName, PFileName, MB_ICONINformATION | MB_OK);

        // 输出要打开的文件的路径
        // .....
    }

对于你要使用的其他函数,都是使用同样的方式,利用这个参数来传递我们要传递的函数的绝对地址,然后定义这个函数指针,就可以使用了。

好了,接下来我们该让被拦截的 api 正常工作了,这个不难,把他原来的数据恢复一下就可以了。那入口的 10 个字节。我们在改写他们的时候应该保存一下,然后也把他放在参数中传递给拦截函数,呵呵,参数的作用可多了。接着我们就可以用 WriteProcessMemory 函数来恢复这个 api 的入口了,代码如下:

PFN_GETCURRENTPROCESS pfnGetCurrentProcess = (PFN_GETCURRENTPROCESS)pRP->dwGetCurrentProcess;

PFN_WRITEPROCESSMEMORY pfnWriteProcessMemory = (PFN_WRITEPROCESSMEMORY)pRP->dwWriteProcessMemory;

  

if( !pfnWriteProcessMemory(pfnGetCurrentProcess(),

                                          (LPVOID)pfnConnect,

                                          (LPCVOID)pRP->szOldCode,

                                          10,

                                          NULL))

pfnMessageBox(NULL, pRP->szModuleName1, pRP->szModuleName2,   MB_ICONINformATION | MB_OK);

其中这些函数指针的定义和上面的类似。

而参数中的szoldcode则是在源程序中在修改api之前保存好,然后传给拦截函数,在源程序中是用ReadProcessMemory函数来获取他的前10个字节的:

ReadProcessMemory(GetCurrentProcess(),

                                (LPCVOID)RParam.dwCreateFile,

                                oldcode,

                                10,

                                &dwPid)

strcat((char*)RParam.szOldCode, (char*)oldcode);

接下来如果你还继续保持对该 api 的拦截,则又该用 WriteProcessMemory 来修改入口了,跟前面的恢复入口是一样的,只不过把 szOldCode 换成了 szNewCode 了而已。这样你又能对 CreateFile 继续拦截了。

好了,接下来该进行堆栈的清理了,也许你还要做点其他事情,尽管做去。但是清理堆栈是必须要做的,在函数结束的时候,因为在我们放任 api 恢复执行之后,他又 return 到我们的函数中来了,这个时候的堆栈是什么样子呢?

|EDI|   <---ESP

|ESI|

|EBX|

|局部变量|  

|EBP|     <---EBP

|api入口之后的第11个字节的指令的地址|    

|00010000|

|createfile执行后的下一条指令地址|

|参数1|

|参数2|

|参数3|

|参数4|

|参数5|

|参数6|

|参数7|

|..|

我们的目标是把返回值记录下来放到 EAX 寄存器中去,把返回地址记录下来,同时把堆栈恢复成原来的样子。

首先我们恢复那些寄存器的值,接着释放局部变量,可以用 mov esp, ebp。因为我们不清楚具体的局部变量分配了多少空间。所以使用这个方法。

__asm
    {
        POP EDI
        POP ESI
        POP EBX                    // 恢复那些寄存器
        MOV EDX, [NextIpAddr]      // 把返回地址放到 EDX 中,因为待会儿 EBX 被恢复
                                   // 后,线程中的所有局部变量就不能正常使用了。
        MOV EAX, [Retvalue]        // 返回值放到 EAX 中,当然也可以修改这个返回值
        MOV ESP, EBP               // 清理局部变量
        POP EBP                    // 恢复 EBP 的值
        ADD ESP, 28H               // 清理参数和返回地址,注意一共 (7 + 1 + 1 + 1) * 4
        PUSH EDX                   // 把返回地址压栈,这样栈中就只有这一个返回地址了,返回之后栈就空了
        RET
    }

这样,一切就完成了,堆栈恢复了应该有的状态,而你想拦截的也拦截到了。

四、后记

  拦截的方式多种多样,不过大体的思路却都相同。要时刻注意你要拦截的函数的堆栈状态以及在拦截函数中的对数据的引用和函数的调用(地址问题)。

  

//////////////////////////////////////////////////////////////////////

附录:一个拦截CreateFile函数的简单实现

//////////////////////////////////////////////////////////////////////

#include <stdio.h>
#include <windows.h>
#include <Psapi.h>
#pragma comment(lib, "psapi.lib") 
typedef struct _RemoteParam {
            DWORD             dwCreateFile;
            DWORD             vdwMessageBox;
            DWORD             dwGetCurrentProcess;
            DWORD             dwWriteProcessMemory;
            unsigned char     szOldCode[10];
            DWORD             FunAddr;
} RemoteParam, * PRemoteParam;

typedef HANDLE (__stdcall * PFN_CREATEFILE)(LPCTSTR,DWORD,DWORD,LPSECURITY_ATTRIBUTES,DWORD,DWORD,HANDLE);
typedef int (__stdcall * PFN_MESSAGEBOX)(HWND, LPCTSTR, LPCTSTR, DWORD);
typedef BOOL (__stdcall * PFN_WRITEPROCESSMEMORY)(HANDLE, LPVOID, LPCVOID, SIZE_T, SIZE_T*);
typedef HANDLE (__stdcall * PFN_GETCURRENTPROCESS)(void);

#define PROCESSNUM 128
#define MYMESSAGEBOX "MessageBoxW"
#define MYCREATEFILE "CreateFileW"

void HookCreateFile(LPVOID lParam)
{
    RemoteParam* pRP = (RemoteParam*)lParam;
    DWORD NextIpAddr = 0;
    DWORD dwParamaAddr = 0;
    HANDLE RetFpHdl = INVALID_HANDLE_value;
    LPCTSTR lpFileName;
    DWORD dwDesiredAccess;
    DWORD dwShareMode;
    LPSECURITY_ATTRIBUTES lpSecurityAttributes;
    DWORD dwCreationDisposition;
    DWORD dwFlagsAndAttributes;
    HANDLE hTemplateFile;

    PFN_CREATEFILE pfnCreatefile = (PFN_CREATEFILE)pRP->dwCreateFile;

    __asm
    {
        MOV EAX, [EBP+8]
        MOV [dwParamaAddr], EAX
        MOV EAX, [EBP+12]
        MOV [NextIpAddr], EAX
        MOV EAX, [EBP+16]
        MOV [lpFileName], EAX
        MOV EAX,[EBP+20]
        MOV [dwDesiredAccess], EAX
        MOV EAX, [EBP+24]
        MOV [dwShareMode], EAX
        MOV EAX, [EBP+28]
        MOV [lpSecurityAttributes], EAX
        MOV EAX, [EBP+32]
        MOV [dwCreationDisposition], EAX
        MOV EAX, [EBP+36]
        MOV [dwFlagsAndAttributes], EAX
        MOV EAX, [EBP+40]
        MOV [hTemplateFile], EAX
    }
    PFN_MESSAGEBOX pfnMessageBox = (PFN_MESSAGEBOX)pRP->dwMessageBox;
    int allowFlag = pfnMessageBox(NULL, lpFileName, NULL, MB_ICONINformATION | MB_YESNO);

    if(allowFlag == IDYES)
    {
        unsigned char szNewCode[10];
        int PramaAddr = (int)dwParamaAddr;
        szNewCode[4] = PramaAddr >> 24;
        szNewCode[3] = (PramaAddr << 8) >> 24;
        szNewCode[2] = (PramaAddr << 16) >> 24;
        szNewCode[1] = (PramaAddr << 24) >> 24;
        szNewCode[0] = 0x68;

        int funaddr = (int)pRP->FunAddr - (int)pfnCreatefile - 10;
        szNewCode[9] = funaddr >> 24;
        szNewCode[8] = (funaddr << 8) >> 24;
        szNewCode[7] = (funaddr << 16) >> 24;
        szNewCode[6] = (funaddr << 24) >> 24;
        szNewCode[5] = 0xE8;

        PFN_GETCURRENTPROCESS pfnGetCurrentProcess = (PFN_GETCURRENTPROCESS)pRP->dwGetCurrentProcess; 
        PFN_WRITEPROCESSMEMORY pfnWriteProcessMemory = (PFN_WRITEPROCESSMEMORY)pRP->dwWriteProcessMemory;
        pfnWriteProcessMemory (pfnGetCurrentProcess(),
                            (LPVOID)pfnCreatefile,
                            (LPCVOID)pRP->szOldCode,
                            10,
                            NULL);
        RetFpHdl = pfnCreatefile (lpFileName,
                            dwDesiredAccess,
                            dwShareMode,
                            lpSecurityAttributes, 
                            dwCreationDisposition, 
                            dwFlagsAndAttributes, 
                            hTemplateFile);
        pfnWriteProcessMemory(pfnGetCurrentProcess(),
                            (LPVOID)pfnCreatefile,
                            (LPCVOID)szNewCode,
                            10,
                            NULL);
    }

    __asm
    {
        POP EDI
        POP ESI
        POP EBX
        MOV EDX, [NextIpAddr]
        MOV EAX, [RetFpHdl]
        MOV ESP, EBP
        POP EBP
        ADD ESP, 28H  
        PUSH EDX
        RET
    }

}

BOOL AdjustProcessPrivileges(LPCSTR szPrivilegesName)
{
    HANDLE hToken;
    TOKEN_PRIVILEGES tkp;
    if(!OpenProcessToken(GetCurrentProcess(),
        TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) 
    {
        return FALSE;
    }

    if(!LookupPrivilegeValue(NULL, szPrivilegesName, &tkp.Privileges[0].Luid))
    {
        CloseHandle(hToken);
        return FALSE;
    }

    tkp.PrivilegeCount = 1;
    tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

    if(!AdjustTokenPrivileges(hToken, FALSE, &tkp, sizeof(tkp), NULL, NULL))
    {
        CloseHandle(hToken);
        return FALSE;
    }

    CloseHandle(hToken);
    return TRUE;
}

void printProcessNameByPid( DWORD ProcessId )
{
    HANDLE pHd;
    HMODULE pHmod;
    char ProcessName[MAX_PATH] = "unknown";
    DWORD cbNeeded;
    pHd = OpenProcess( PROCESS_QUERY_INformATION | PROCESS_VM_READ, FALSE, ProcessId );
    if(pHd == NULL) return;

    if(!EnumProcessModules( pHd, &pHmod, sizeof(pHmod), &cbNeeded)) return;
    if(!GetModuleFileNameEx( pHd, pHmod, ProcessName, MAX_PATH)) return;

    printf( "%dt%s\n", ProcessId, ProcessName);
    CloseHandle( pHd );
    return;
}

int main(void)
{
    if(!AdjustProcessPrivileges(SE_DEBUG_NAME))
    {
        printf("AdjustProcessPrivileges Error!\n");
        return -1;
    }
    DWORD Pids[PROCESSNUM];
    DWORD dwProcessNum = 0;
    if(!EnumProcesses(Pids, sizeof(Pids), &dwProcessNum))
    {
        printf("EnumProcess Error!\n");
        return -1;
    }

    for( DWORD num = 0; num < (dwProcessNum / sizeof(DWORD)); num++)
        printProcessNameByPid(Pids[num]);
    printf("\nAll %d processes running.\n", dwProcessNum / sizeof(DWORD));
    DWORD dwPid = 0;
    printf("\n请输入要拦截的进程id:");
    scanf("%d", &dwPid);

    HANDLE hTargetProcess = OpenProcess(
            PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ,
            FALSE,
            dwPid
            );
    if(hTargetProcess == NULL)
    {
        printf("OpenProcess Error!\n");
        return -1;
    }
    DWORD dwFunAddr = (DWORD)VirtualAllocEx(hTargetProcess, NULL, 8192,
                                MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

    if((LPVOID)dwFunAddr == NULL)
    {
        printf("申请线程内存失败!\n");
        CloseHandle(hTargetProcess);
        return -1;
    }

    DWORD dwPramaAddr = (DWORD)VirtualAllocEx(hTargetProcess, NULL, sizeof(RemoteParam),
                                    MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    if((LPVOID)dwPramaAddr == NULL)
    {
        printf("申请参数内存失败!\n");
        CloseHandle(hTargetProcess);
        return -1;
    }

    printf("\n线程内存地址:%.8x\n 参数内存地址:%.8x\n", dwFunAddr, dwPramaAddr);

    RemoteParam RParam;
    ZeroMemory(&RParam, sizeof(RParam));

    HMODULE hKernel32 = LoadLibrary("kernel32.dll");
    HMODULE hUser32 = LoadLibrary("user32.dll");

    RParam.dwCreateFile = (DWORD)GetProcAddress(hKernel32, MYCREATEFILE);
    RParam.dwGetCurrentProcess = (DWORD)GetProcAddress(hKernel32, "GetCurrentProcess");
    RParam.dwWriteProcessMemory = (DWORD)GetProcAddress(hKernel32, "WriteProcessMemory");
    RParam.dwMessageBox = (DWORD)GetProcAddress(hUser32, MYMESSAGEBOX);

    unsigned char oldcode[10];
    unsigned char newcode[10];
    int praadd = (int)dwPramaAddr;
    int threadadd = (int)dwFunAddr;

    newcode[4] = praadd >> 24;
    newcode[3] = (praadd << 8) >> 24;
    newcode[2] = (praadd << 16) >> 24;
    newcode[1] = (praadd << 24) >> 24;
    newcode[0] = 0x68;

    offsetaddr = threadadd - (int)RParam.dwCreateFile - 10 ;
    newcode[9] = offsetaddr >> 24;
    newcode[8] = (offsetaddr << 8) >> 24;
    newcode[7] = (offsetaddr< < 16) >> 24;
    newcode[6] = (offsetaddr << 24) >> 24;
    newcode[5] = 0xE8;
    printf("NewCode:");
    for(int j = 0; j < 10; j++)
        printf("0x%.2x ",newcode[j]);
    printf("\n\n");
    if(!ReadProcessMemory(GetCurrentProcess(),
                        (LPCVOID)RParam.dwCreateFile,
                        oldcode,
                        10,
                        &dwPid))
    {
        printf("read error");
        CloseHandle(hTargetProcess);
        FreeLibrary(hKernel32);
        return -1;
    }

    strcat((char*)RParam.szOldCode, (char*)oldcode);
    RParam.FunAddr = dwFunAddr;

    printf(
        "RParam.dwCreate文件:%.8x\n"
        "RParam.dwMessageBox:%.8x\n"
        "RParam.dwGetCurrentProcess:%.8x\n"
        "RParam.dwWriteProcessMemory:%.8x\n"
        "RParam.FunAddr:%.8x\n",
        RParam.dwCreateFile,
        RParam.dwMessageBox,
        RParam.dwGetCurrentProcess,
        RParam.dwWriteProcessMemory,
        RParam.FunAddr);

    printf("RParam.szOldCode:"); 

    for( int i = 0; i < 10; i++)
        printf("0x%.2x ", RParam.szOldCode); 

    printf("\n"); 

    if(!WriteProcessMemory(hTargetProcess, (LPVOID)dwFunAddr, (LPVOID)&HookCreateFile, 8192, &dwPid))
    {
        printf("WriteRemoteProcessesMemory Error!\n");
        CloseHandle(hTargetProcess);
        FreeLibrary(hKernel32);
        return -1;
    }
    if(!WriteProcessMemory(hTargetProcess, (LPVOID)dwPramaAddr,
                        (LPVOID)&RParam, sizeof(RemoteParam), &dwPid))
    {
        printf("WriteRemoteProcessesMemory Error!\n");
        CloseHandle(hTargetProcess);
        FreeLibrary(hKernel32);
        return -1;
    }

    if(!WriteProcessMemory(hTargetProcess, (LPVOID)RParam.dwCreateFile, (LPVOID)newcode, 10, &dwPid))
    {
        printf("WriteRemoteProcessesMemory Error!\n");
        CloseHandle(hTargetProcess);
        FreeLibrary(hKernel32);
        return -1;
    }
    printf("\nThat's all, good luck :)\n");
    CloseHandle(hTargetProcess);
    FreeLibrary(hKernel32);
    return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章