资讯详情

浅谈 Windows API 编程

原文地址:http://blog.sina.com.cn/s/blog_46d85b2a01010qpt.html

http://blog.sina.com.cn/s/articlelist_1188584234_0_1.html

WinSDK 这是编程中的传统困难。我曾经听过一个技术不太好的朋友对你说什么 API 每个人都会用它。其实没那么简单。个人写的WinAPI 程序很多,其实每个调用都很难。 API 都包含着 Windows 该操作系统的潜规则或 windows 内部操作机制。

句柄

首先来谈谈 ,初学习 WinSDK朋友刚看到这个词头大了吗? 其实我也是了,我们来看看 programming windows里面是怎么说的,这真的不容易理解。所以我们这么认为,比如你打开它 windows自带计算器。桌面上有很多计算器吗? 当你使用其中一个计算器时,当你按下等于按钮时,计算结果会出现在其他计算机结果栏中吗? 不会,那windows如何知道结果出现在哪里?这就是句柄的作用,

图形设备接口(Graphics Device Interface, 或Graphical Device Interface)

获取句柄的手段也多种多样,但当然是通过调用API函数

MFC 中的 hHandle = GetSafeHandle(); API 编程中的 hBrush = GetStorkObject(BLACK_BRUSH);

很多操作都需要 句柄 添加到参数列表中, 当你不直接定义句柄变量时,你可能会记住很多API间接获取返回类型。

hPen = SelectObject(hdc,GetStockObject(&logicpen)); // SelectObject()设置本设备描述表下的函数GDI设置前的对象将返回到设置前的对象GDI对象句柄 MoveToEx(hdc, pt1.x, pt1.y, &apt); LineTo(hdc, pt2.x,pt2.y); SelectObject(hdc,hPen);

选择自定义的GDI对象的操作。句柄有很多种。掌握一种使用方法后,所有其他人都不自学,WinAPI 句柄总是伴随着编程的元素之一。很重要。因为是浅谈,就到这里。

消息映射

接下来是 windows下的机制,呵呵,窗口过程,刚学的朋友难懂吗? WinSDK编程基于C,但是和 C 概念完全不同,中间的差异,在我看来,最多的是来自这个消息映射,后面吹得很耀眼 (当然和我接下来谈的稍微有点不同)。

首先要明白 的区别,,这里。而 ,但这不是绝对的。我可以用一个简单的例子来解释。我在这里写得越多,就越觉得很难表达清楚。例如,一个例子:一个男人杀人的消息导致了枪杀。 但最重要的区别在于

以上是整个消息循环过程。

#include <windows.h> #include <mmsystem.h>  LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM); //声名消息处理函数(处理windows和接收windows消息)  //hInstance:2和3忘记了系统为窗口分配的实例号.4是显示方式 int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,PSTR szCmdLine, int iCmdShow) {     static TCHAR szAppName[] = TEXT ("HelloWin") ; //窗体名     HWND hwnd;//句柄     MSG msg;//消息体     WNDCLASS wndclass;//     //设置窗体参数     wndclass.style = CS_HREDRAW | CS_VREDRAW ; //样式     wndclass.cbClsExtra   = 0 ;     wndclass.cbWndExtra   = 0 ;     wndclass.hInstance = hInstance ;//窗体实例名,由windows自动分发     wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;//显示上图标titlte     wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;//窗口光标     wndclass.hbrBackground= (HBRUSH) GetStockObject (WHITE_BRUSH) ;//背景刷     wndclass.lpszMenuName=NULL;     wndclass.lpfnWndProc=WndProc;//设置窗体接收windws消息函数     wndclass.lpszClassName= szAppName;///窗体类名     if (!RegisterClass (&wndclass))//注册窗体类     {         MessageBox ( NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ;         return 0 ;     };     //创建窗体。"), szAppName, MB_ICONERROR) ;         return 0 ;     };     //创建窗体。已分配内存。返回窗体句柄     hwnd = CreateWindow( szAppName,      // window class name         TEXT ("The Hello Program"),   // window caption         WS_OVERLAPPEDWINDOW, // window style         CW_USEDEFAULT,// initial x position         CW_USEDEFAULT,// initial y position         CW_USEDEFAULT,// initial x size         CW_USEDEFAULT,// initial y size         NULL, // parent window handle         NULL, // window menu handle         hInstance, // program instance handle         NULL) ;     ShowWindow (hwnd,iCmdShow);//显示窗口     UpdateWindow (hwnd) ;///更新窗体     while(GetMessage(&msg,NULL,0,0))     {         TranslateMessage (&msg);///翻译信息并发送到windows消息队列         DispatchMessage (&msg) ;//接收信息     }     return msg.wParam ; }  LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)//信息处理程序 {     HDC         hdc ;     PAINTSTRUCT ps ;     RECT        rect ;      switch (message)     {      case WM_CREATE:         return 0 ;      case   WM_PAINT:         hdc = BeginPaint (hwnd, &ps) ;         TextOut(hdc,0,0,"Hello",strlen("Hello"));         EndPaint (hwnd, &ps) ;         return 0 ;      case   WM_DESTROY:         PostQuitMessage (0) ;         return 0 ;     }      return DefWinowProc (hwnd, message, wParam, lParam) ;
}

上面大概流程:

LRSULT CALLBACK WndProc(hwnd , uint,wParam,lParam);

int WINAPI WinMain(…)
{
        MSG msg;           // 消息体
        RegisterClass(…);  // 注册窗口类
        CreateWindow(…);   // 创建窗口
        ShowWindow(…);     // 显示窗口
        UpdateWindow(…);
        While(GetMessage(&msg,…)){     // 消息循环
                TranslateMessage(…);
                DispatchMessage(…);
        }
        return msg.wParam ;
}

// 窗口过程函数,用于映射switch语句中各个需要被处理的消息。即 消息处理程序
LRSULT CALLBACK WndProc(hwnd , uint,wParam,lParam);
{
        While((UINT)message)
        {
                Switch(message)
                Case…
                Case…
                ………
                Default……….
        }
}

以上是最基本的 WinAPi 编程的代码结构。其实这里面最重要的结构莫过于 while(GetMessage(&msg)) 和 Winproc 这个函数,这也是传统的C面向过程编程的区别,win编程总等着特定事件触发对应的消息映射函数来完成代码功能,并不是一条代码从头走到尾。关于特殊消息的映射,这里不谈,这里仅是个入门指引。

最后谈一点就是重绘问题。其实在我看来这个东西更多是属于GDI编程里面的东西,说起来其实难度不大,但是处理起来确实是个难点。先拿刚才的代码来说吧,在 函数里面 先添加一条关于 WM_LBUTTONDOWN 的消息映射:

Static int apt[2];
case WM_LBUTTONDOWN:
        hdc = GetDC(hwnd);
        apt[1].x = LOWORD (lParam);
        apt[1].y = HIWORD (lParam);
        hPen = CreatePen(BLACK_PEN,3,RGB(125,125,125));
        SelectObject(hdc,hPen);
        MoveToEx(hdc,apt[0].x,apt[0].y,NULL);
        LineTo(hdc,apt[1].x,apt[1].y);
        apt[0].x = apt[1].x;
        apt[0].y = apt[1].y;
        DeleteObject(hPen);
        ReleaseDC(hwnd,hdc);
        return 0;

这段代码实现一个简单的画线功能,当你在你的客户区胡点一通鼠标后试着拖动一下窗口大小,或者将其最小化或者被其他窗口覆盖一下你都会发现你原来画的线没了,可是其他窗口为什么被覆盖了以后再弹出窗口还会有原来的东西呢? 那就是重绘,要重新绘制整个客户区(准确的说是失效的矩形),以上说的操作都会导致你的客户区失效,这时会产生重绘消息WM_PAINT,我们要想保存这些线那么我们就必须保存这些你用鼠标左键点过的点。当然这是重绘技术中最简单的,当你的客户区上是一个复杂的画面的话,就不仅仅需要保存点,还有各种形状的图形,颜色等等……这里给大家一段我自己写的代码来实现以上的 WM_LBUTTONDOWN消息映射来产生的点。通过单链表来动态添加点来实现重绘。

case WM_PAINT:
        hdc = BeginPaint(hwnd,&ps);
        TextOut(hdc,cxClient/6,cyClient/6,TEXT("图形重绘"),strlen("图形重绘"));
        ReDrawLines(&MyList,hdc);
        EndPaint(hwnd,&ps);
        return 0;
        case WM_LBUTTONDOWN:
        hdc = GetDC(hwnd);
        apt[1].x = LOWORD (lParam);
        apt[1].y = HIWORD (lParam);
        hPen = CreatePen(BLACK_PEN,2,RGB(125,0,0));
        SelectObject(hdc,hPen);
        MoveToEx(hdc,apt[0].x,apt[0].y,NULL);
        LineTo(hdc,apt[1].x,apt[1].y);
        MyList.pCurrent->x = apt[0].x;
        MyList.pCurrent->y = apt[0].y;
        MyList.pCurrent->pNext->x = apt[1].x;
        MyList.pCurrent->pNext->y = apt[1].y;
        MyList.m_iCounter = MyList.m_iCounter+2;
        MyList.pCurrent = MyList.pCurrent->pNext->pNext;
        apt[0].x = apt[1].x;
        apt[0].y = apt[1].y;
        DeleteObject(hPen);
        ReleaseDC(hwnd,hdc);
        return 0;

其中的重绘函数代码如下:

void ReDrawLines(LinkList* pLinkList,HDC hdc)
{
        pMyPoint p = pLinkList->pHead;
        int iSaver =pLinkList->m_iCounter;
        while(iSaver!=0)
        {
                MoveToEx(hdc,p->x,p->y,NULL);
                LineTo(hdc,p->pNext->x,p->pNext->y);
                p=p->pNext->pNext;
                iSaver=iSaver-2;
        }
}

添加了以上的代码你会发现再次拖动窗口大小等等你原来画的线就都能重现出来了。呵呵是不是觉得一个看似简单的东西其实里面需要很多代码实现呢?也许,这就是windows.

好了,WinSDK入门的东西就谈这么多,希望能给初学者一定的帮助,这么多的字,都是我一个一个打出来没有任何借鉴和摘抄的。相信做为一个过来人能更多的理解大家学习中的困难。

Win32环境下动态链接库(DLL)编程原理

比较大应用程序都由很多模块组成,这些模块分别完成相对独立的功能,它们彼此协作来完成整个软件系统的工作。其中可能存在一些模块的功能较为通用,在构造其它软件系统时仍会被使用。在构造软件系统时,如果将所有模块的源代码都静态编译到整个应用程序EXE文件中,会产生一些问题:一个缺点是增加了应用程序的大小,它会占用更多的磁盘空间,程序运行时也会消耗较大的内存空间,造成系统资源的浪费;另一个缺点是,在编写大的EXE程序时,在每次修改重建时都必须调整编译所有源代码,增加了编译过程的复杂性,也不利于阶段性的单元测试。

Windows系统平台上提供了一种完全不同的较有效的编程和运行环境,你可以将独立的程序模块创建为较小的DLL(Dynamic Linkable Library)文件,并可对它们单独编译和测试。在运行时,只有当EXE程序确实要调用这些DLL模块的情况下,系统才会将它们装载到内存空间中。这种方式不仅减少了EXE文件的大小和对内存空间的需求,而且使这些DLL模块可以同时被多个应用程序使用。Microsoft Windows自己就将一些主要的系统功能以DLL模块的形式实现。例如IE中的一些基本功能就是由DLL文件实现的,它可以被其它应用程序调用和集成。

一般来说,DLL是一种磁盘文件(通常带有DLL扩展名),它由全局数据、服务函数和资源组成,在运行时被系统加载到进程的虚拟空间中,成为调用进程的一部分。如果与其它DLL之间没有冲突,该文件通常映射到进程虚拟空间的同一地址上。DLL模块中包含各种导出函数,用于向外界提供服务。Windows 在加载DLL模块时将进程函数调用与DLL文件的导出函数相匹配。

在Win32环境中,每个进程都复制了自己的读/写全局变量。如果想要与其它进程共享内存,必须使用内存映射文件或者声明一个共享数据段。DLL模块需要的堆栈内存都是从运行进程的堆栈中分配出来的。

DLL 现在越来越容易编写。Win32 已经大大简化了其编程模式,并有许多来自 AppWizard 和 MFC类库的支持。

一、导出和导入函数的匹配

DLL文件中包含一个导出函数表。这些导出函数由它们的符号名和称为标识号的整数与外界联系起来。函数表中还包含了DLL中函数的地址。当应用程序加载 DLL模块时时,它并不知道调用函数的实际地址,但它知道函数的符号名和标识号。动态链接过程在加载的DLL模块时动态建立一个函数调用与函数地址的对应表。如果重新编译和重建DLL文件,并不需要修改应用程序,除非你改变了导出函数的符号名和参数序列。

简单的DLL文件只为应用程序提供导出函数,比较复杂的DLL文件除了提供导出函数以外,还调用其它DLL文件中的函数。这样,一个特殊的DLL可以既有导入函数,又有导入函数。这并不是一个问题,因为动态链接过程可以处理交叉相关的情况。

在DLL代码中,必须像下面这样明确声明导出函数:

__declspec(dllexport) int MyFunction(int n);

但也可以在模块定义(DEF)文件中列出导出函数,不过这样做常常引起更多的麻烦。在应用程序方面,要求像下面这样明确声明相应的输入函数:

__declspec(dllimport) int MyFuncition(int n);

仅有导入和导出声明并不能使应用程序内部的函数调用链接到相应的DLL文件上。应用程序的项目必须为链接程序指定所需的输入库(LIB文件)。而且应用程序事实上必须至少包含一个对DLL函数的调用。

二、与DLL模块建立链接

应用程序导入函数与DLL文件中的导出函数进行链接有两种方式:隐式链接和显式链接。所谓的隐式链接是指在应用程序中不需指明DLL文件的实际存储路径,程序员不需关心DLL文件的实际装载。而显式链接与此相反。

采用隐式链接方式,程序员在建立一个DLL文件时,链接程序会自动生成一个与之对应的LIB导入文件。该文件包含了每一个DLL导出函数的符号名和可选的标识号,但是并不含有实际的代码。LIB文件作为DLL的替代文件被编译到应用程序项目中。当程序员通过静态链接方式编译生成应用程序时,应用程序中的调用函数与LIB文件中导出符号相匹配,这些符号或标识号进入到生成的EXE文件中。LIB文件中也包含了对应的DLL文件名(但不是完全的路径名),链接程序将其存储在EXE文件内部。当应用程序运行过程中需要加载DLL文件时,Windows根据这些信息发现并加载DLL,然后通过符号名或标识号实现对DLL函数的动态链接。

显式链接方式对于集成化的开发语言(例如VB)比较适合。有了显式链接,程序员就不必再使用导入文件,而是直接调用Win32 的LoadLibary函数,并指定DLL的路径作为参数。LoadLibary返回HINSTANCE参数,应用程序在调用 GetProcAddress函数时使用这一参数。GetProcAddress函数将符号名或标识号转换为DLL内部的地址。假设有一个导出如下函数的 DLL文件:

extern "C" __declspec(dllexport) double SquareRoot(double d);

下面是应用程序对该导出函数的显式链接的例子:

typedef double(SQRTPROC)(double); HINSTANCE hInstance; SQRTPROC* pFunction; VERIFY(hInstance=::LoadLibrary("c:\\winnt\\system32\\mydll.dll")); VERIFY(pFunction=(SQRTPROC*)::GetProcAddress(hInstance,"SquareRoot")); double d=(*pFunction)(81.0);//调用该DLL函数

在隐式链接方式中,所有被应用程序调用的DLL文件都会在应用程序EXE文件加载时被加载在到内存中;但如果采用显式链接方式,程序员可以决定DLL文件何时加载或不加载。显式链接在运行时决定加载哪个DLL文件。例如,可以将一个带有字符串资源的DLL模块以英语加载,而另一个以西班牙语加载。应用程序在用户选择了合适的语种后再加载与之对应的DLL文件。

三、使用符号名链接与标识号链接

在Win16环境中,符号名链接效率较低,所有那时标识号链接是主要的链接方式。在Win32环境中,符号名链接的效率得到了改善。Microsoft 现在推荐使用符号名链接。但在MFC库中的DLL版本仍然采用的是标识号链接。一个典型的MFC程序可能会链接到数百个MFC DLL函数上。采用标识号链接的应用程序的EXE文件体相对较小,因为它不必包含导入函数的长字符串符号名。

四、编写DllMain函数

DllMain函数是DLL模块的默认入口点。当Windows加载DLL模块时调用这一函数。系统首先调用全局对象的构造函数,然后调用全局函数 DLLMain。DLLMain函数不仅在将DLL链接加载到进程时被调用,在DLL模块与进程分离时(以及其它时候)也被调用。下面是一个框架 DLLMain函数的例子。

HINSTANCE g_hInstance;
extern "C" int APIENTRY DllMain(HINSTANCE hInstance,DWORD dwReason,LPVOID lpReserved)
{
        if(dwReason==DLL_PROCESS_ATTACH)
        {
                TRACE0("EX22A.DLL Initializing!\n");
                //在这里进行初始化
        }
        else if(dwReason=DLL_PROCESS_DETACH)
        {
                TRACE0("EX22A.DLL Terminating!\n");
                //在这里进行清除工作
        }
        return 1;//成功
}

如果程序员没有为DLL模块编写一个DLLMain函数,系统会从其它运行库中引入一个不做任何操作的缺省DLLMain函数版本。在单个线程启动和终止时,DLLMain函数也被调用。正如由dwReason参数所表明的那样。

Exported fn(): send - Ord:0013h
地址         机器码                 汇编代码
:71A21AF4 55                   push ebp //将被HOOK的机器码(第1种方法)
:71A21AF5 8BEC                 mov ebp, esp //将被HOOK的机器码(第2种方法)
:71A21AF7 83EC10                 sub esp, 00000010
:71A21AFA 56                   push esi
:71A21AFB 57                   push edi
:71A21AFC 33FF                 xor edi, edi
:71A21AFE 813D1C20A371931CA271       cmp dword ptr [71A3201C], 71A21C93 //将被HOOK的机器码(第4种方法)
:71A21B08 0F84853D0000             je 71A25893
:71A21B0E 8D45F8                 lea eax, dword ptr [ebp-08]
:71A21B11 50                   push eax
:71A21B12 E869F7FFFF             call 71A21280
:71A21B17 3BC7                 cmp eax, edi
:71A21B19 8945FC                 mov dword ptr [ebp-04], eax
:71A21B1C 0F85C4940000             jne 71A2AFE6
:71A21B22 FF7508                 push [ebp+08]
:71A21B25 E826F7FFFF             call 71A21250
:71A21B2A 8BF0                 mov esi, eax
:71A21B2C 3BF7                 cmp esi, edi
:71A21B2E 0F84AB940000             je 71A2AFDF
:71A21B34 8B4510                 mov eax, dword ptr [ebp+10]
:71A21B37 53                   push ebx
:71A21B38 8D4DFC                 lea ecx, dword ptr [ebp-04]
:71A21B3B 51                   push ecx
:71A21B3C FF75F8                 push [ebp-08]
:71A21B3F 8D4D08                 lea ecx, dword ptr [ebp+08]
:71A21B42 57                   push edi
:71A21B43 57                   push edi
:71A21B44 FF7514                 push [ebp+14]
:71A21B47 8945F0                 mov dword ptr [ebp-10], eax
:71A21B4A 8B450C                 mov eax, dword ptr [ebp+0C]
:71A21B4D 51                   push ecx
:71A21B4E 6A01                 push 00000001
:71A21B50 8D4DF0                 lea ecx, dword ptr [ebp-10]
:71A21B53 51                   push ecx
:71A21B54 FF7508                 push [ebp+08]
:71A21B57 8945F4                 mov dword ptr [ebp-0C], eax
:71A21B5A 8B460C                 mov eax, dword ptr [esi+0C]
:71A21B5D FF5064                 call [eax+64]
:71A21B60 8BCE                 mov ecx, esi
:71A21B62 8BD8                 mov ebx, eax
:71A21B64 E8C7F6FFFF             call 71A21230 //将被HOOK的机器码(第3种方法)
:71A21B69 3BDF                 cmp ebx, edi
:71A21B6B 5B                   pop ebx
:71A21B6C 0F855F940000             jne 71A2AFD1
:71A21B72 8B4508                 mov eax, dword ptr [ebp+08]
:71A21B75 5F                   pop edi
:71A21B76 5E                   pop esi
:71A21B77 C9                   leave
:71A21B78 C21000                 ret 0010

下面用 4 种方法来 HOOK 这个 API:

1,把API入口的第一条指令是PUSH EBP指令(机器码0x55)替换成INT 3(机器码0xcc),然后用WINDOWS提供的调试函数来执行自己的代码,这中方法被SOFT ICE等DEBUGER广泛采用,它就是通过BPX在相应的地方设一条INT 3指令来下断点的。但是不提倡用这种方法,因为它会与WINDOWS或调试工具产生冲突,而汇编代码基本都要调试;

2,把第二条mov ebp,esp指令(机器码8BEC,2字节)替换为INT F0指令(机器码CDF0),然后在IDT里设置一个中断门,指向我们的代码。我这里给出一个HOOK代码:

lea ebp,[esp+12] //模拟原指令mov ebp,esp的功能 pushfd             //保存现场 pushad             //保存现场

//在这里做你想做的事情

popad             //恢复现场 popfd             //恢复现场 iretd             //返回原指令的下一条指令继续执行原函数(71A21AF7地址处)

这种方法很好,但缺点是要在IDT设置一个中断门,也就是要进RING0。

3,更改CALL指令的相对地址(CALL分别在71A21B12、71A21B25、71A21B64,但前面2条CALL之前有一个条件跳转指令,有可能不被执行到,因此我们要HOOK 71A21B64处的CALL指令)。为什么要找CALL指令下手?因为它们都是5字节的指令,而且都是CALL指令,只要保持操作码0xE8不变,改变后面的相对地址就可以转到我们的HOOK代码去执行了,在我们的HOOK代码后面再转到目标地址去执行。

假设我们的HOOK代码在71A20400处,那么我们把71A21B64处的CALL指令改为CALL 71A20400(原指令是这样的:CALL 71A21230) 而71A20400处的HOOK代码是这样的:

71A20400: pushad

//在这里做你想做的事情

popad jmp 71A21230       //跳转到原CALL指令的目标地址,原指令是这样的:call 71A21230

这种方法隐蔽性很好,但是比较难找这条5字节的CALL指令,计算相对地址也复杂。

4,替换71A21AFE地址上的cmp dword ptr [71A3201C], 71A21C93指令(机器码:813D1C20A371931CA271,10字节)成为 call 71A20400 nop nop nop nop nop (机器码:E8 XX XX XX XX 90 90 90 90 90,10字节)

在71A20400的HOOK代码是: pushad mov edx,71A3201Ch               //模拟原指令cmp dword ptr [71A3201C], 71A21C93 cmp dword ptr [edx],71A21C93h       //模拟原指令cmp dword ptr [71A3201C], 71A21C93 pushfd

//在这里做你想做的事

popfd popad ret 这种方法隐蔽性最好,但不是每个API都有这样的指令,要具体情况具体操作。

以上几种方法是常用的方法,值得一提的是很多人都是改API开头的5个字节,但是现在很多杀毒软件用这样的方法检查API是否被HOOK,或其他病毒木马在你之后又改了前5个字节,这样就会互相覆盖,最

APIHook 一直是使大家感兴趣的话题。屏幕取词,内码转化,屏幕翻译,中文平台等等都涉及到了此项技术。有很多文章涉及到了这项技术,但都闪烁其词不肯明明白白的公布。我仅在这里公布以下我用Delphi制作APIHook的一些心得。

通常的APIHOOK有这样几种方法:

1、自己写一个动态链接库,里面定义自己写的想取代系统的API。把这个动态链接库映射到2G以上的系统动态链接库所在空间,把系统动态链接库中的该API的指向修改指向自己的函数。这种方法的好处就是可以取代系统中运行全部程序的该API。但他有个局限,就是只适用于Win9x。(原因是NT中动态链接库不是共享的,每个进程都有自己的一份动态链接库在内存中的映射)

2、自己写一个动态链接库,里面定义自己写得象替代系统的API。把这个动态链接库映射到进程的空间里。将该进程对API的调用指向自己写的动态链接库。这种方法的好处是可以选择性的替代哪个进程的API。而且适用于所有的Windows操作系统。

这里我选用的是第二种方法。

第二种方法需要先了解一点PE文件格式的知识。

        首先是一个实模式的的DOS文件头,是为了保持和DOS的兼容。接着是一个DOS的代理模块。你在纯DOS先运行Win32的可执行文件,看看是不是也执行了,只是显示的的是一行信息大意是说该Windows程序不能在DOS实模式下运行。         然后才是真正意义上的Windows可执行文件的文件头。它的具体位置不是每次都固定的。是由文件偏移$3C决定的。我们要用到的就是它。         如果我们在程序中调用了一个MessageBoxA函数那么它的实现过程是这样的。他先调用在本进程中的MessageBoxA函数然后才跳到动态链接库的MessageBoxA的入口点。即:          call messageBoxA(0040106c)          jmp dword ptr [_jmp_MessageBoxA@16(00425294)] 其中00425294的内容存储的就是就是MessageBoxA函数的入口地址。如果我们做一下手脚,那么......

        那就开始吧!

我们需要定义两个结构 type      PImage_Import_Entry = ^Image_Import_Entry;      Image_Import_Entry = record         Characteristics: DWORD;         TimeDateStamp: DWORD;         MajorVersion: Word;         MinorVersion: Word;         Name: DWORD;         LookupTable: DWORD;      end; type      TImportCode = packed record         JumpInstruction: Word; file: //定义跳转指令jmp         AddressOfPointerToFunction: ^Pointer; file: //定义要跳转到的函数      end;      PImportCode = ^TImportCode; 然后是确定函数的地址。 function LocateFunctionAddress(Code: Pointer): Pointer; var      func: PImportCode; begin      Result := Code;      if Code = nil then exit;      try         func := code;         if (func.JumpInstruction = $25FF) then         begin            Result := func.AddressOfPointerToFunction^;         end;      except         Result := nil;      end; end; 参数Code是函数在进程中的指针,即那条Jmp XXX的指令。$25FF就是跳转指令的机器码。

在这里我将要实现转跳。有人说修改内存内容要进入Ring 0 才可以。可是Windows本身提供了一个写内存的指令WriteProcessMemory。有了这把利器,我们几乎无所不能。如游戏的修改等在这里我们只谈APIHOOK。 function RepointFunction(OldFunc, NewFunc: Pointer): Integer; var     IsDone: TList;     function RepointAddrInModule(hModule: THandle; OldFunc, NewFunc: Pointer): Integer;     var        Dos: PImageDosHeader;        NT: PImageNTHeaders;        ImportDesc: PImage_Import_Entry;        RVA: DWORD;        Func: ^Pointer;        DLL: string;        f: Pointer;        written: DWORD;     begin        Result := 0;        Dos := Pointer(hModule);        if IsDone.IndexOf(Dos) >= 0 then exit;        IsDone.Add(Dos);

       OldFunc := LocateFunctionAddress(OldFunc);

       if IsBadReadPtr(Dos, SizeOf(TImageDosHeader)) then exit;        if Dos.e_magic <> IMAGE_DOS_SIGNATURE then exit;        NT := Pointer(Integer(Dos) + dos._lfanew);

       RVA := NT^.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]           .VirtualAddress;

       if RVA = 0 then exit;        ImportDesc := pointer(integer(Dos) + RVA);        while (ImportDesc^.Name <> 0) do        begin           DLL := PChar(Integer(Dos) + ImportDesc^.Name);           RepointAddrInModule(GetModuleHandle(PChar(DLL)), OldFunc, NewFunc);           Func := Pointer(Integer(DOS) + ImportDesc.LookupTable);           while Func^ <> nil do           begin              f := LocateFunctionAddress(Func^);              if f = OldFunc then              begin                 WriteProcessMemory(GetCurrentProcess, Func, @NewFunc, 4, written);                 if Written > 0 then Inc(Result);              end;              Inc(Func);           end;           Inc(ImportDesc);        end;     end;

begin     IsDone := TList.Create;     try        Result := RepointAddrInModule(GetModuleHandle(nil), OldFunc, NewFunc);     finally        IsDone.Free;     end; end; 有了这两个函数我们几乎可以更改任何API函数。 我们可以先写一个DLL文件。我这里以修改Text相关函数为例: 先定义几个函数: type     TTextOutA = function(DC: HDC; X, Y: Integer; Str: PAnsiChar; Count: Integer): BOOL; stdcall;     TTextOutW = function(DC: HDC; X, Y: Integer; Str: PWideChar; Count: Integer): BOOL; stdcall;     TTextOut = function(DC: HDC; X, Y: Integer; Str: PChar; Count: Integer): BOOL; stdcall;     TDrawTextA = function(hDC: HDC; lpString: PAnsiChar; nCount: Integer; var lpRect: TRect; uFormat: UINT): Integer; stdcall;     TDrawTextW = function(hDC: HDC; lpString: PWideChar; nCount: Integer; var lpRect: TRect; uFormat: UINT): Integer; stdcall;     TDrawText = function(hDC: HDC; lpString: PChar; nCount: Integer; var lpRect: TRect; uFormat: UINT): Integer; stdcall; var     OldTextOutA: TTextOutA;     OldTextOutW: TTextOutW;     OldTextOut: TTextOut;     OldDrawTextA: TDrawTextA;     OldDrawTextW: TDrawTextW;     OldDrawText: TDrawText; ...... function MyTextOutA(DC: HDC; X, Y: Integer; Str: PAnsiChar; Count: Integer): BOOL; stdcall; begin     OldTextOutA(DC, X, Y, ''''ABC'''', length(''''ABC'''')); end;

function MyTextOutW(DC: HDC; X, Y: Integer; Str: PWideChar; Count: Integer): BOOL; stdcall; begin     OldTextOutW(DC, X, Y, ''''ABC'''', length(''''ABC'''')); end;

function MyTextOut(DC: HDC; X, Y: Integer; Str: PChar; Count: Integer): BOOL; stdcall; begin     OldTextOut(DC, X, Y, ''''ABC'''', length(''''ABC'''')); end;

function MyDrawTextA(hDC: HDC; lpString: PAnsiChar; nCount: Integer; var lpRect: TRect; uFormat: UINT): Integer; stdcall; begin     OldDrawTextA(hDC, ''''ABC'''', length(''''ABC''''), lpRect, uFormat); end;

function MyDrawTextW(hDC: HDC; lpString: PWideChar; nCount: Integer; var lpRect: TRect; uFormat: UINT): Integer; stdcall; begin     OldDrawTextW(hDC, ''''ABC'''', length(''''ABC''''), lpRect, uFormat); end;

function MyDrawText(hDC: HDC; lpString: PChar; nCount: Integer; var lpRect: TRect; uFormat: UINT): Integer; stdcall; begin     OldDrawText(hDC, ''''ABC'''', length(''''ABC''''), lpRect, uFormat); end;

调用时我们要把原来的函数地址保存下来:     if @OldTextOutA = nil then        @OldTextOutA := LocateFunctionAddress(@TextOutA);     if @OldTextOutW = nil then        @OldTextOutW := LocateFunctionAddress(@TextOutW);     if @OldTextOut = nil then        @OldTextOut := LocateFunctionAddress(@TextOut);     if @OldDrawTextA = nil then        @OldDrawTextA := LocateFunctionAddress(@DrawTextA);     if @OldDrawTextW = nil then        @OldDrawTextW := LocateFunctionAddress(@DrawTextW);     if @OldDrawText = nil then        @OldDrawText := LocateFunctionAddress(@DrawText); 然后很顺其自然的用自己的函数替换掉原来的函数     RepointFunction(@OldTextOutA, @MyTextOutA);     RepointFunction(@OldTextOutW, @MyTextOutW);     RepointFunction(@OldTextOut, @MyTextOut);     RepointFunction(@OldDrawTextA, @MyDrawTextA);     RepointFunction(@OldDrawTextW, @MyDrawTextW);     RepointFunction(@OldDrawText, @MyDrawText);

在结束时不要忘记恢复原来函数的入口,要不然你会死得很难看哟!好了我们在写一个Demo程序。你会说怎么文字没有变成ABC呀?是呀,你要刷新一下才行。最小化然后在最大化。看看变了没有。             要不然你就写代码刷新一下好了。至于去拦截其他进程的API那就用SetWindowsHookEx写一个其他的钩子将DLL映射进去就行了,我就不再浪费口水了。 掌握了该方法你几乎无所不能。你可以修改其它程序。你可以拦截Createwindow等窗口函数改变其他程序的窗口形状、你还可以入侵其它的程序,你还可以......嘿嘿。干了坏事别招出我来就行了。

标签: 贴片电容cl21b103kacl21b223kb陶瓷电容cl21b331kb陶瓷电容cl21b392kb陶瓷电容

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台