精华内容
下载资源
问答
  • Windows DLL基本原理 Windows系统平台上,你可以将独立的程序模块创建为较小的DLL(Dynamic Linkable Library)文件,并可对它们单独编译和测试。在运行时,只有当EXE程序确实要调用这些DLL模块的情况下,系统才会将...

    Windows DLL基本原理

             Windows系统平台上,你可以将独立的程序模块创建为较小的DLL(Dynamic Linkable Library)文件,并可对它们单独编译和测试。在运行时,只有当EXE程序确实要调用这些DLL模块的情况下,系统才会将它们装载到内存空间中。这种方式不仅减少了EXE文件的大小和对内存空间的需求,而且使这些DLL模块可以同时被多个应用程序使用。Microsoft Windows自己就将一些主要的系统功能以DLL模块的形式实现。例如IE中的一些基本功能就是由DLL文件实现的,它可以被其它应用程序调用和集成。一般来说,DLL是一种磁盘文件(通常带有DLL扩展名,是标准win32可执行文件-“PE”格式),它由全局数据、服务函数和资源组成,在运行时被系统加载到进程的虚拟空间中,成为调用进程的一部分,进程中所有线程都可以调用其中的函数。如果与其它DLL之间没有冲突,该文件通常映射到进程虚拟空间的同一地址上。DLL模块中包含各种导出函数,用于向外界提供服务。Windows在加载DLL模块时将进程函数调用与DLL文件的导出函数相匹配。

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

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

    简单的DLL文件只为应用程序提供导出函数,比较复杂的DLL文件除了提供导出函数以外,还调用其它DLL文件中的函数。

             每个DLL都有一个入口函数(DLLMain),系统在特定环境下会调用DLLMain。在下面的事件发生时会调用dll入口函数:1.进程装载DLL2.进程卸载DLL3.DLL在被装载之后创建了新线程。4. DLL在被装载之后一个线程被终止了。

             应用程序导入函数与DLL文件中的导出函数进行链接有两种方式:隐式链接和显式链接。

             隐式链接(load-time dynamic linking)是指在应用程序中不需指明DLL文件的实际存储路径,程序员不需关心DLL文件的实际装载(由编译器自动完成地址分配)。采用隐式链接方式,程序员在建立一个DLL文件时,链接程序会自动生成一个与之对应的LIB导入文件。该文件包含了每一个DLL导出函数的符号名和可选的标识号,但是并不含有实际的代码。LIB文件作为DLL的替代文件被编译到应用程序项目中。当程序员通过静态链接方式编译生成应用程序时,应用程序中的调用函数与LIB文件中导出符号相匹配,这些符号或标识号进入到生成的EXE文件中。LIB文件中也包含了对应的DLL文件名(但不是完全的路径名),链接程序将其存储在EXE文件内部。当应用程序运行过程中需要加载DLL文件时,Windows根据这些信息发现并加载DLL,然后通过符号名或标识号实现对DLL函数的动态链接。我们使用的大部分系统Dll就是通过这样的方式链接的。若找不到需要的Dll则会给出一个Dll缺少的错误消息。

             显式链接(run-time dynamic linking)与此相反。用户程序在编译的时候并没有指明需要哪些Dll,而是在运行起来之后调用Win32 LoadLibary()函数,去装载Dll。若没有找到Dll则这个函数就会返回一个错误。在用LoadLibary()函数装载Dll之后,应用程序还需要用GetProcAdress()函数去获得Dll输出函数的地址。显式链接方式对于集成化的开发语言比较适合。有了显式链接,程序员就不必再使用导入文件,而是直接调用Win32 LoadLibary()函数,并指定DLL的路径作为参数。还要说明一点的就是Known Dlls就是保证在通过LoadLibary()去装载系统Dll的时候,只从特定的系统目录去装载,防止装载错。装载的时候会去看注册表下是否有一样的注册表键名。如果是装载windows/system32/目录下的对应的Dll

             Dll的搜索顺序,在Windows上有个注册表键值决定了Dll的搜索顺序:HKLM/System/CurrentControlSet/SessionManager/SafeDllSearchMode。在vista,server2003,xp sp2中这个值为1,在xp,2000 sp4中为01值时的搜素顺序为:1.可执行文件所在目录,2.系统目录windows/system32/3. 16位系统目录,4.windows目录,5.当前进程目录。6.环境变量PATH中的目录。0值时的搜素顺序为:1.可执行文件所在目录,2. 当前进程目录。3.系统目录windows/system32/4. 16位系统目录,5.windows目录,6.环境变量PATH中的目录。

    DLL的加载与连接

        Windows DLL装入(ntdll.dll)和连接是通过ntdll.dll中一个函数LdrInitializeThunk实现的。先对LdrInitializeThunk()这个函数名作些解释“Ldr显然是“Loader”的缩写。而“Thunk”意为“翻译”、“转换”、或者某种起着“桥梁”作用的东西。这个词在一般的字典中是查不到的,但却是个常见于微软的资料、文档中术语。这个术语起源于编译技术,表示一小片旨在获取某个地址的代码,最初用于函数调用时“形参”和“实参”结合。后来这个术语有了不少新的特殊含义和使用,但是DLL的动态连接与函数调用时“形实结合”确实有着本质的相似。

             由于Windows没有公开这个函数的代码,所以学习起来比较困难,只能通过查阅一些资料来大概猜测这个函数的实现。这个过程中也参看了很多ReactOSReactOS是一个免费而且完全兼容 Microsoft Windows XP 的操作系统。ReactOS 旨在通过使用类似构架和提供完整公共接口实现与 NT 操作系统二进制下的应用程序和驱动设备的完全兼容。)的LdrInitializeThunk()函数实现源代码。

             在进入这个函数之前,目标 EXE映像已经被映射到当前进程的用户空间,系统DLL ntdll.dll的映像也已经被映射,但是并没有在EXE映像与ntdll.dll映像之间建立连接 (实际上 EXE映像未必就直接调用ntdll.dll中的函数)LdrInitializeThunk()ntdll.dll中不经连接就可进入的函数,实质上就是ntdll.dll的入口。除ntdll.dll以外,别的 DLL都还没有被装入(映射)。此外,当前进程(除内核中的“进程控制块”EPROCESS等数据结构外)在用户空间已经有了一个“进程环境块”PEB,以及该进程的第一个“线程环境块”TEB。这就是进入 LdrInitializeThunk()前的“当前形势”。

             PEB中有一个字段Ldr是个PEB_LDR_DATA结构指针,所指向的数据结构用来为本进程维持三个“模块”队列、即InLoadOrderModuleListInMemoryOrderModuleList、和InInitializationOrderModuleList。这里所谓“模块”就是PE格式的可执行映像,包括EXE映像和DLL映像。前两个队列都是模块队列,第三个是初始化队列。两个模块队列的不同之处在于排列的次序,一个是按装入的先后,一个是按装入的位置。每当为本进程装入一个模块、即.exe映像或DLL映像时,就要为其分配,创建一个LDR_DATA_TABLE_ENTRY数据结构,并将其挂入InLoadOrderModuleList。然后,完成对这个模块的动态连接以后,就把它挂入InInitializationOrderModuleList队列,以便依次调用它们的初始化函数。相应地,LDR_DATA_TABLE_ENTRY数据结构中有三个队列头,因而可以同时挂在三个队列中。在我做的小实验当中就是通过查找这三个队列,来将当前进程的Dll加载信息显示出来的。具体的实例请见我的实验说明文档。

             LdrInitializeThunk()中,最开始为做的事情就是将加载的模块信息存放在PEB中的ldr字段,如上面一段文字中所述。之后,LdrInitializeThunk()函数又调用了一个叫LdrPEStartup()的函数。LdrPEStartup()函数首先判断了“期望地址”是否可用,PE映像的NtHeader(peb中有个ImageBaseAddress的地址,代表exe映像在用户空间的位置,在这个地址指向的数据结构中就有NtHeader的结构)中有个指针,指向一个OptionalHeader。在OptionalHeader中有个字段ImageBase,是具体映像建议、或者说希望被装入的地址,我们称之为“愿望地址”。在装入一个映像时,只要相应的区间(取决于它的期望地址和大小)空闲,就总正常装入。但是如果与已经被占用的区间相冲突,就只好利用LdrPerformRelocations()换个地方。

             那么映像的愿望地址有着什么物理的或者逻辑的意义呢?我们知道,软件在编译以后有个连接的过程,即为函数的调用者落实被调用函数的入口地址、为全局变量(按绝对地址)的使用者落实变量地址的过程。连接有静态和动态两种,静态连接是在“制造”软件时进行的,而动态连接则是在使用软件时进行的。尽管EXE模块和DLL模块之间的连接是动态连接,但是EXEDLL模块内部的连接却是静态连接。既是静态连接,就必须为模块的映像提供一个假定的起点地址。如果以此假定地址为基础进行连接以后就不可变更,使用时必须装入到这个地址上,那么这个地址就是固定的“指定地址”了。早期的静态连接往往都是使用指定地址的。但是,如果允许按假定地址连接的映像在实际使用时进行“重定位”,那么这假定地址就是可浮动的“愿望地址”了。可“重定位”的静态连接当然比固定的静态连接灵活。事实上,要是没有可“重定位”的静态连接技术,DLL的使用就无法实现,因为根本就不可能事先为所有可能的DLL划定它们的装入位置和大小。至于可“重定位”静态连接的实现,则一般都是采用间接寻址,通过指针来实现。

             所谓重定位,就是计算出实际装入地址与建议装入地址间的位移a,然后调整每个重定位块中的每一个重定位项、即指针,具体就是在指针上加a。而映像中使用的所有绝对地址(包括函数入口、全局量数据的位置)实际上用的都是间接寻址,每个这样的地址都有个指针存在于某个重定位块中。

             完成了可能需要的EXE映像重定位以后,下一个主要的操作就是LdrFixupImports()了。实际上这才是关键所在,它所处理的就是当前模块所需DLL模块的装入和连接。各DLL的程序入口记录在它们的LDR_DATA_TABLE_ENTRY数据结构中借助InInitializationOrderModuleList队列就可依次调用所有DLL的初始化函数。

             NtHeaderOptionalHeader中有个数组DataDirectory[],其中之一是重定位目录。除此之外,数组中还有“(普通)引入(import)”、“绑定引入(bound import)”以及其它多种目录,但是我们在这里只关心“引入”和“绑定引入”。这两个目录都是用于库函数的引入,但是作用不同,目录项的数据结构也不同。每个引入目录项都代表着一个被引入模块,其模块名、即文件名在dwRVAModuleNameReactOS中的名字,下同)所指的地方。需要从同一个被引入模块引入的函数通常有很多个,dwRVAFunctionNameList指向一个字符串数组,数组中的每一个字符串都是一个函数名;与此相对应,dwRVAFunctionAddressList则指向一个指针数组。这两个数组是平行的,同一个函数在两个数组中具有相同的下标。从一个被引入模块中引入一个函数的过程大体上就是:根据函数名在被引入模块的引出目录中搜索,找到目标函数以后就把它实际装入后的入口地址填写到指针数组中的相应位置上。但是,这个过程可能是个开销相当大、速度比较慢的过程。为此,又发展起一种称为“绑定”的优化。

    所谓绑定,就是在软件的编译,连接过程中先对使用时的动态连接来一次预演,预演时假定所有的DLL都被装入到它们的愿望地址上,然后把预演中得到的被引入函数的地址直接记录在引入者模块中相应引入目录下的指针数组中。这样,使用软件时的动态连接就变得很简单快捷,因为实际上已经事先连接好了。其实“绑定引入”和静态连接并无实质的不同。但是,各模块的版本配套就成为一个问题,因为万一使用的某个DLL不是当初绑定时的版本,而且其引出目录又发生了变化,就有可能引起混乱。为此,PE格式增加了一种“绑定引入”目录,相关的机制会进行判断。但是,“绑定引入”毕竟不是很可靠的,万一发现版本不符就不能使用原先的绑定了。所以“绑定引入”不能单独存在,而必须有普通引入作为后备。如果不符就不能按“绑定引入”目录处理引入,而只好退而求其次,改成按普通“引入”目录处理引入。另一方面,所谓“绑定”是指当被引入模块装入在预定位置上时的地址绑定,如果被引入模块的装入位置变了,就得对原先所绑定的地址作相应的调整、即“重定位”。

    LdrFixupImports()函数首先从映像头部获取指向“引入”目录和“绑定引入”目录的指针。若存在“绑定引入”目录,则先通过LdrpGetOrLoadModule()找到或装入(映射)被引入模块的映像。首先当然是在模块队列中寻找,找不到就从被引入模块的磁盘文件装入。之后检查绑定版本是否一致,如果不一致就退而求其次,通过LdrpProcessImportDirectory()处理引入。当然,那样一来效率就要降低了。如果一致,则返回(因为在“预演”中已经连接好,效率当然高了)。而LdrpProcessImportDirectory()才是真正意义上的动态连接!!(说了这么多原来才开始……)。

    LdrpProcessImportDirectory()首先根据目录项中的两个位移量取得分别指向函数名字符串数组和函数指针数组的指针。这两个数组是平行的(前面有介绍),然后对字符串数组中的元素计数,得到该数组的大小IATSize。显然,函数指针数组的大小也是IATSize。这里IAT是“引入地址表(Imported Address Table)”的缩写,其实就是函数指针数组。这个数组在映像内部,其所在的页面在装入映像时已被加上写保护,而下面要做的事正是要改变这些指针的值,所以先要通过NtProtectVirtualMemory()把这些页面的访问模式改成可读可写。做完这些准备之后,下面就是连接的过程了,那就是根据需要把被引入模块所引出的函数入口(地址)填写到引入者模块的IAT中。与当前模块中的两个数组相对应,在被引入模块的“引出”目录中也有两个数组,说明本模块引出函数的名称和入口地址(在映像中的位移)。当然,这两个数组也是平行的。要获取被引入模块中的函数入口有两种方法,即按序号(Ordinal)引入和按函数名引入。从而分别调用LdrGetExportByOrdinal()LdrGetExportByName()。这两个函数都返回目标函数在本进程用户空间中的入口地址,把它填写入当前模块引入目录函数指针数组中的相应元素,就完成了一个函数的连接。当然,同样的操作要循环实施于当前模块需要从给定模块引入的所有函数,并且(在上一层)循环实施于所有的被引入模块。完成了对一个被引入模块的连接之后,又调用NtProtectVirtualMemory()恢复当前模块中给定目录项内函数指针数组所在页面的保护。

    到此,我们大概的清楚Windows Dll的加载与连接过程。

     

    总结与感想

             以上的Dll加载与连接过程有点抽象与混乱,在这里进行总结,绝体的函数加载关系如下:

    展开全文
  • Windows提供的API(LoadLibrary, LoadLibraryEx)只支持从文件系统上加载DLL文件,我们无法使用这些API从内存中加载DLL。 但是有些时候,我们的确需要从内存中加载DLL,比如: 对发布的文件数量有限制。我们可以...

    Windows提供的API(LoadLibrary, LoadLibraryEx)只支持从文件系统上加载DLL文件,我们无法使用这些API从内存中加载DLL。

    但是有些时候,我们的确需要从内存中加载DLL,比如:

    1. 对发布的文件数量有限制。我们可以将DLL打包到exe的资源中,程序运行时从调用LoadResource等API读取DLL文件到内存中,然后从内存中加载DLL。
    2. 需要对DLL进行压缩或加密等。解压和解密之后的内容首先都是存放在内存之中的,我们从内存中加载DLL会更加便捷。

    本文主要介绍如何实现从内存中加载DLL,并调用DLL提供接口函数(必须是纯C接口)。

    虽然“从内存中加载DLL”和“Windows的注入与拦截”之间没有直接关系,但还是选择放在《Windows注入与拦截》系列文章之中,主要是为了后面介绍的“无痕注入”(也叫反射注入)作铺垫。

    一. PE格式

    从内存中加载DLL就是解析PE格式并将DLL内容按照该格式要求存放到进程的虚拟地址空间的过程。所以对PE格式的了解对理解整个加载过程比较重要。建议对照《PE文件格式》中的PE格式图来阅读本文内容和代码。

    PE文件大致由下面几部分组成,本文不会详细的介绍PE格式的每一个细节,只会针对“从内存中加载DLL”所需要掌握的PE知识来进行介绍。若需要详细了解PE格式,可以参考:《Windows PE权威指南》

    +----------------+
    | DOS header     |
    |                |
    | DOS stub       |
    +----------------+
    | PE header      |
    +----------------+
    | Section header |
    +----------------+
    | Section 1      |
    +----------------+
    | Section 2      |
    +----------------+
    | . . .          |
    +----------------+
    | Section n      |
    +----------------+

    1.1 DOS header、stub

    DOS头的存在主要是为了向后兼容,它位于dos stub的前面,通常用于显示一个“该程序不能允许在DOS模式”的错误提示。
    我们用16进制工具打开任意一个exe文件就可以看到如下图的字符串常量:
    这里写图片描述

    DOS头的结构体定义如下:

    typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
        WORD   e_magic;                     // Magic number
        WORD   e_cblp;                      // Bytes on last page of file
        WORD   e_cp;                        // Pages in file
        WORD   e_crlc;                      // Relocations
        WORD   e_cparhdr;                   // Size of header in paragraphs
        WORD   e_minalloc;                  // Minimum extra paragraphs needed
        WORD   e_maxalloc;                  // Maximum extra paragraphs needed
        WORD   e_ss;                        // Initial (relative) SS value
        WORD   e_sp;                        // Initial SP value
        WORD   e_csum;                      // Checksum
        WORD   e_ip;                        // Initial IP value
        WORD   e_cs;                        // Initial (relative) CS value
        WORD   e_lfarlc;                    // File address of relocation table
        WORD   e_ovno;                      // Overlay number
        WORD   e_res[4];                    // Reserved words
        WORD   e_oemid;                     // OEM identifier (for e_oeminfo)
        WORD   e_oeminfo;                   // OEM information; e_oemid specific
        WORD   e_res2[10];                  // Reserved words
        LONG   e_lfanew;                    // File address of new exe header
      } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

    我们只需要关注e_lfanew字段,它表示PE头的偏移位置,我们用这个字段来定位PE头的起始地址。

    1.2 PE header

    PE头的结构体定义如下:

    typedef struct _IMAGE_NT_HEADERS {
        DWORD Signature;
        IMAGE_FILE_HEADER FileHeader;
        IMAGE_OPTIONAL_HEADER32 OptionalHeader;
    } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

    Signature字段为IMAGE_NT_SIGNATURE常量,可以用来检查PE内容是否合法。
    FileHeader字段包含了可执行文件的物理格式或属性,如符号信息,所需CPU,文件信息标志(dll还是exe),文件创建时间等,结构体定义如下:

    typedef struct _IMAGE_FILE_HEADER {
        WORD    Machine;
        WORD    NumberOfSections;
        DWORD   TimeDateStamp;
        DWORD   PointerToSymbolTable;
        DWORD   NumberOfSymbols;
        WORD    SizeOfOptionalHeader;
        WORD    Characteristics;
    } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

    OptionalHeader字段包含一些逻辑上的信息,如操作系统版本、入口点、基地址、映像大小等,结构体定义如下:

    typedef struct _IMAGE_OPTIONAL_HEADER64 {
        WORD        Magic;
        BYTE        MajorLinkerVersion;
        BYTE        MinorLinkerVersion;
        DWORD       SizeOfCode;
        DWORD       SizeOfInitializedData;
        DWORD       SizeOfUninitializedData;
        DWORD       AddressOfEntryPoint;
        DWORD       BaseOfCode;
        ULONGLONG   ImageBase;
        DWORD       SectionAlignment;
        DWORD       FileAlignment;
        WORD        MajorOperatingSystemVersion;
        WORD        MinorOperatingSystemVersion;
        WORD        MajorImageVersion;
        WORD        MinorImageVersion;
        WORD        MajorSubsystemVersion;
        WORD        MinorSubsystemVersion;
        DWORD       Win32VersionValue;
        DWORD       SizeOfImage;
        DWORD       SizeOfHeaders;
        DWORD       CheckSum;
        WORD        Subsystem;
        WORD        DllCharacteristics;
        ULONGLONG   SizeOfStackReserve;
        ULONGLONG   SizeOfStackCommit;
        ULONGLONG   SizeOfHeapReserve;
        ULONGLONG   SizeOfHeapCommit;
        DWORD       LoaderFlags;
        DWORD       NumberOfRvaAndSizes;
        IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
    } IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;

    OptionalHeader最后的DataDirectory包含了16(IMAGE_NUMBEROF_DIRECTORY_ENTRIES)个IMAGE_DATA_DIRECTORY逻辑组件,每个组件的功能分别如下:

    ===== ==========================
    Index Description
    ===== ==========================
    0     Exported functions
    ----- --------------------------
    1     Imported functions
    ----- --------------------------
    2     Resources
    ----- --------------------------
    3     Exception informations
    ----- --------------------------
    4     Security informations
    ----- --------------------------
    5     Base relocation table
    ----- --------------------------
    6     Debug informations
    ----- --------------------------
    7     Architecture specific data
    ----- --------------------------
    8     Global pointer
    ----- --------------------------
    9     Thread local storage
    ----- --------------------------
    10    Load configuration
    ----- --------------------------
    11    Bound imports
    ----- --------------------------
    12    Import address table
    ----- --------------------------
    13    Delay load imports
    ----- --------------------------
    14    COM runtime descriptor
    ===== ==========================

    对于从内存中加载DLL,我们只需要关注Index为0,1,5的组件。

    1.3 Section header

    Section头存储在OptionalHeader的后面,Section头包含n个IMAGE_SECTION_HEADER结构体,具体的个数可以通过PEHeader.FileHeader.NumberOfSections字段得到。

    微软提供了IMAGE_FIRST_SECTION宏来获取第一个IMAGE_SECTION_HEADER结构体的地址,这样我们就可以遍历到所有Section.

    IMAGE_SECTION_HEADER结构体定义如下:

    typedef struct _IMAGE_SECTION_HEADER {
        BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];
        union {
                DWORD   PhysicalAddress;
                DWORD   VirtualSize;
        } Misc;
        DWORD   VirtualAddress;
        DWORD   SizeOfRawData;
        DWORD   PointerToRawData;
        DWORD   PointerToRelocations;
        DWORD   PointerToLinenumbers;
        WORD    NumberOfRelocations;
        WORD    NumberOfLinenumbers;
        DWORD   Characteristics;
    } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

    二. DLL文件的加载步骤

    我们要模拟PE加载器从内存中加载DLL,我们首先要知道Windows加载DLL文件的步骤,以及需要准备那些结构体等。
    当我们调用LoadLibrary时,windows主要执行了下面的一些步骤:

    1. 检测DOS和PE头的合法性。
    2. 尝试在PEHeader.OptionalHeader.ImageBase位置分配PEHeader.OptionalHeader.SizeOfImage字节的内存区域。
    3. 解析Section header中的每个Section,并将它们的实际内容拷贝到第2步分配的地址空间中。拷贝的目的地址的计算方法为:IMAGE_SECTION_HEADER.VirtualAddress偏移 + 第二步分配的内存区域的起始地址
    4. 检查加载到进程地址空间的位置和之前PE文件中指定的基地址是否一致,如果不一致,则需要重定位。重定位就需要用到1.2节中的IMAGE_OPTIONAL_HEADER64.DataDirectory[5].
    5. 加载该DLL依赖的其他dll,并构建"PEHeader.OptionalHeader.DataDirectory.Image_directory_entry_import"导入表.
    6. 根据每个Section的"PEHeader.Image_Section_Table.Characteristics"属性来设置内存页的访问属性; 如果被设置为”discardable”属性,则释放该内存页。
    7. 获取DLL的入口函数指针,并使用DLL_PROCESS_ATTACH参数调用。

    三. 代码实现

    本代码参考了fancycode/MemoryModule,修复原有代码的若干BUG,扩充了部分功能,并针对第二节介绍的步骤添加了详细的注释。

    3.1 接口定义

    #ifndef __MEMORY_MODULE_HEADER
    #define __MEMORY_MODULE_HEADER
    
    #include <Windows.h>
    
    typedef void *HMEMORYMODULE;
    
    #ifdef __cplusplus
    extern "C" {
    #endif
    
    HMEMORYMODULE MemoryLoadLibrary(const void *);
    
    FARPROC MemoryGetProcAddress(HMEMORYMODULE, const char *);
    
    void MemoryFreeLibrary(HMEMORYMODULE);
    
    #ifdef __cplusplus
    }
    #endif
    
    #endif  // __MEMORY_MODULE_HEADER

    HMEMORYMODULE是一个自定义结构体,该结构体分配在进程的默认堆上面,调用者需要保存该结构体指针,在后面获取接口地址和释放DLL时需要传入该指针。

    typedef struct {
        PIMAGE_NT_HEADERS headers;
        unsigned char *codeBase;
        HMODULE *modules;
        int numModules;
        int initialized;
    } MEMORYMODULE, *PMEMORYMODULE;

    3.2 MemoryLoadLibrary函数

    HMEMORYMODULE MemoryLoadLibrary(const void *data)
    {
        PMEMORYMODULE result;
        PIMAGE_DOS_HEADER dos_header; // DOS头
        PIMAGE_NT_HEADERS old_header; // PE头
        unsigned char *code, *headers;
        SIZE_T locationDelta;
        DllEntryProc DllEntry;
        BOOL successfull;
    
        // 获取DOS头指针,并检查DOS头
        dos_header = (PIMAGE_DOS_HEADER)data;
        if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) {
    #if DEBUG_OUTPUT
            OutputDebugStringA("Not a valid executable file.\n");
    #endif
            return NULL;
        }
    
        // 获取PE头指针,并检查PE头
        old_header = (PIMAGE_NT_HEADERS)&((const unsigned char *)(data))[dos_header->e_lfanew];
        if (old_header->Signature != IMAGE_NT_SIGNATURE) {
    #if DEBUG_OUTPUT
            OutputDebugStringA("No PE header found.\n");
    #endif
            return NULL;
        }
    
        // 在"PEHeader.OptionalHeader.ImageBase"处预定"PEHeader.OptionalHeader.SizeOfImage"字节的空间
        code = (unsigned char *)VirtualAlloc((LPVOID)(old_header->OptionalHeader.ImageBase),
            old_header->OptionalHeader.SizeOfImage,
            MEM_RESERVE,
            PAGE_READWRITE);
    
        if (code == NULL) {
            // try to allocate memory at arbitrary position
            code = (unsigned char *)VirtualAlloc(NULL,
                old_header->OptionalHeader.SizeOfImage,
                MEM_RESERVE,
                PAGE_READWRITE);
            if (code == NULL) {
    #if DEBUG_OUTPUT
                OutputLastError("Can't reserve memory");
    #endif
                return NULL;
            }
        }
    
        // 在进程的默认堆上分配"sizeof(MEMORYMODULE)"字节的空间用于存放MEMORYMODULE结构体
        // 方便函数末尾将该结构体指针当作返回值返回
        result = (PMEMORYMODULE)HeapAlloc(GetProcessHeap(), 0, sizeof(MEMORYMODULE));
        result->codeBase = code;
        result->numModules = 0;
        result->modules = NULL;
        result->initialized = 0;
    
    
        // 一次性从code地址处将整个映像所需的内存区域都分配
        VirtualAlloc(code,
            old_header->OptionalHeader.SizeOfImage,
            MEM_COMMIT,
            PAGE_READWRITE);
    
        // 原作者的代码中此处会再次调用VirtualAlloc从code处分配SizeOfHeaders大小的内存,
        // 但这步操作属于多余的,因为上一步已经在code处分配了所需的整个内存区域了,
        // 所以直接将此处更改为 headers = code;
        //
        //headers = (unsigned char *)VirtualAllocEx(process, code,
        //  old_header->OptionalHeader.SizeOfHeaders,
        //  MEM_COMMIT,
        //  PAGE_READWRITE);
        headers = code;
    
        // 拷贝DOS头 + DOS STUB + PE头到headers地址处
        memcpy(headers, dos_header, dos_header->e_lfanew + old_header->OptionalHeader.SizeOfHeaders);
        result->headers = (PIMAGE_NT_HEADERS)&((const unsigned char *)(headers))[dos_header->e_lfanew];
    
        // 更新"MEMORYMODULE.PIMAGE_NT_HEADERS"结构体中的基地址
        result->headers->OptionalHeader.ImageBase = (POINTER_TYPE)code;
    
        // 从dll文件内容中拷贝每个section(节)的数据到新的内存区域
        CopySections(data, old_header, result);
    
        // 检查加载到进程地址空间的位置和之前PE文件中指定的基地址是否一致,如果不一致,则需要重定位
        locationDelta = (SIZE_T)(code - old_header->OptionalHeader.ImageBase);
        if (locationDelta != 0) {
            PerformBaseRelocation(result, locationDelta);
        }
    
        // 加载依赖dll,并构建"PEHeader.OptionalHeader.DataDirectory.Image_directory_entry_import"导入表
        if (!BuildImportTable(result)) {
            goto error;
        }
    
        // 根据每个Section的"PEHeader.Image_Section_Table.Characteristics"属性来设置内存页的访问属性;
        // 如果被设置为"discardable"属性,则释放该内存页
        FinalizeSections(result);
    
        // 获取DLL的入口函数指针,并调用
        if (result->headers->OptionalHeader.AddressOfEntryPoint != 0) {
            DllEntry = (DllEntryProc) (code + result->headers->OptionalHeader.AddressOfEntryPoint);
            if (DllEntry == 0) {
    #if DEBUG_OUTPUT
                OutputDebugStringA("Library has no entry point.\n");
    #endif
                goto error;
            }
    
            // notify library about attaching to process
            successfull = (*DllEntry)((HINSTANCE)code, DLL_PROCESS_ATTACH, 0);
            if (!successfull) {
    #if DEBUG_OUTPUT
                OutputDebugStringA("Can't attach library.\n");
    #endif
                goto error;
            }
            result->initialized = 1;
        }
    
        return (HMEMORYMODULE)result;
    
    error:
        // cleanup
        MemoryFreeLibrary(result);
        return NULL;
    }

    完整的示例代码见:https://gitee.com/china_jeffery/MemoryModule

    另外,Stephen Fewer 的ReflectiveDLLInjection提供了反射注入的完整解决方案,其中的LoadLibraryR也实现了和本文类似的功能。

    展开全文
  • Windows DLL基本原理 Windows系统平台上,你可以将独立的程序模块创建为较小的DLL(Dynamic Linkable Library)文件,并可对它们单独编译和测试。在运行时,只有当EXE程序确实要调用这些DLL模块的情况下,系统才会将...

    Windows DLL基本原理

             Windows系统平台上,你可以将独立的程序模块创建为较小的DLL(Dynamic Linkable Library)文件,并可对它们单独编译和测试。在运行时,只有当EXE程序确实要调用这些DLL模块的情况下,系统才会将它们装载到内存空间中。这种方式不仅减少了EXE文件的大小和对内存空间的需求,而且使这些DLL模块可以同时被多个应用程序使用。Microsoft Windows自己就将一些主要的系统功能以DLL模块的形式实现。例如IE中的一些基本功能就是由DLL文件实现的,它可以被其它应用程序调用和集成。一般来说,DLL是一种磁盘文件(通常带有DLL扩展名,是标准win32可执行文件-“PE”格式),它由全局数据、服务函数和资源组成,在运行时被系统加载到进程的虚拟空间中,成为调用进程的一部分,进程中所有线程都可以调用其中的函数。如果与其它DLL之间没有冲突,该文件通常映射到进程虚拟空间的同一地址上。DLL模块中包含各种导出函数,用于向外界提供服务。Windows在加载DLL模块时将进程函数调用与DLL文件的导出函数相匹配。

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

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

    简单的DLL文件只为应用程序提供导出函数,比较复杂的DLL文件除了提供导出函数以外,还调用其它DLL文件中的函数。

             每个DLL都有一个入口函数(DLLMain),系统在特定环境下会调用DLLMain。在下面的事件发生时会调用dll入口函数:1.进程装载DLL2.进程卸载DLL3.DLL在被装载之后创建了新线程。4. DLL在被装载之后一个线程被终止了。

             应用程序导入函数与DLL文件中的导出函数进行链接有两种方式:隐式链接和显式链接。

             隐式链接(load-time dynamic linking)是指在应用程序中不需指明DLL文件的实际存储路径,程序员不需关心DLL文件的实际装载(由编译器自动完成地址分配)。采用隐式链接方式,程序员在建立一个DLL文件时,链接程序会自动生成一个与之对应的LIB导入文件。该文件包含了每一个DLL导出函数的符号名和可选的标识号,但是并不含有实际的代码。LIB文件作为DLL的替代文件被编译到应用程序项目中。当程序员通过静态链接方式编译生成应用程序时,应用程序中的调用函数与LIB文件中导出符号相匹配,这些符号或标识号进入到生成的EXE文件中。LIB文件中也包含了对应的DLL文件名(但不是完全的路径名),链接程序将其存储在EXE文件内部。当应用程序运行过程中需要加载DLL文件时,Windows根据这些信息发现并加载DLL,然后通过符号名或标识号实现对DLL函数的动态链接。我们使用的大部分系统Dll就是通过这样的方式链接的。若找不到需要的Dll则会给出一个Dll缺少的错误消息。

             显式链接(run-time dynamic linking)与此相反。用户程序在编译的时候并没有指明需要哪些Dll,而是在运行起来之后调用Win32 LoadLibary()函数,去装载Dll。若没有找到Dll则这个函数就会返回一个错误。在用LoadLibary()函数装载Dll之后,应用程序还需要用GetProcAdress()函数去获得Dll输出函数的地址。显式链接方式对于集成化的开发语言比较适合。有了显式链接,程序员就不必再使用导入文件,而是直接调用Win32 LoadLibary()函数,并指定DLL的路径作为参数。还要说明一点的就是Known Dlls就是保证在通过LoadLibary()去装载系统Dll的时候,只从特定的系统目录去装载,防止装载错。装载的时候会去看注册表下是否有一样的注册表键名。如果是装载windows/system32/目录下的对应的Dll

             Dll的搜索顺序,在Windows上有个注册表键值决定了Dll的搜索顺序:HKLM/System/CurrentControlSet/SessionManager/SafeDllSearchMode。在vista,server2003,xp sp2中这个值为1,在xp,2000 sp4中为01值时的搜素顺序为:1.可执行文件所在目录,2.系统目录windows/system32/3. 16位系统目录,4.windows目录,5.当前进程目录。6.环境变量PATH中的目录。0值时的搜素顺序为:1.可执行文件所在目录,2. 当前进程目录。3.系统目录windows/system32/4. 16位系统目录,5.windows目录,6.环境变量PATH中的目录。

    DLL的加载与连接

        Windows DLL装入(ntdll.dll)和连接是通过ntdll.dll中一个函数LdrInitializeThunk实现的。先对LdrInitializeThunk()这个函数名作些解释“Ldr显然是“Loader”的缩写。而“Thunk”意为“翻译”、“转换”、或者某种起着“桥梁”作用的东西。这个词在一般的字典中是查不到的,但却是个常见于微软的资料、文档中术语。这个术语起源于编译技术,表示一小片旨在获取某个地址的代码,最初用于函数调用时“形参”和“实参”结合。后来这个术语有了不少新的特殊含义和使用,但是DLL的动态连接与函数调用时“形实结合”确实有着本质的相似。<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

             由于Windows没有公开这个函数的代码,所以学习起来比较困难,只能通过查阅一些资料来大概猜测这个函数的实现。这个过程中也参看了很多ReactOSReactOS是一个免费而且完全兼容 Microsoft Windows XP 的操作系统。ReactOS 旨在通过使用类似构架和提供完整公共接口实现与 NT 操作系统二进制下的应用程序和驱动设备的完全兼容。)的LdrInitializeThunk()函数实现源代码。

             在进入这个函数之前,目标 EXE映像已经被映射到当前进程的用户空间,系统DLL ntdll.dll的映像也已经被映射,但是并没有在EXE映像与ntdll.dll映像之间建立连接 (实际上 EXE映像未必就直接调用ntdll.dll中的函数)LdrInitializeThunk()ntdll.dll中不经连接就可进入的函数,实质上就是ntdll.dll的入口。除ntdll.dll以外,别的 DLL都还没有被装入(映射)。此外,当前进程(除内核中的“进程控制块”EPROCESS等数据结构外)在用户空间已经有了一个“进程环境块”PEB,以及该进程的第一个“线程环境块”TEB。这就是进入 LdrInitializeThunk()前的“当前形势”。

             PEB中有一个字段Ldr是个PEB_LDR_DATA结构指针,所指向的数据结构用来为本进程维持三个“模块”队列、即InLoadOrderModuleListInMemoryOrderModuleList、和InInitializationOrderModuleList。这里所谓“模块”就是PE格式的可执行映像,包括EXE映像和DLL映像。前两个队列都是模块队列,第三个是初始化队列。两个模块队列的不同之处在于排列的次序,一个是按装入的先后,一个是按装入的位置。每当为本进程装入一个模块、即.exe映像或DLL映像时,就要为其分配,创建一个LDR_DATA_TABLE_ENTRY数据结构,并将其挂入InLoadOrderModuleList。然后,完成对这个模块的动态连接以后,就把它挂入InInitializationOrderModuleList队列,以便依次调用它们的初始化函数。相应地,LDR_DATA_TABLE_ENTRY数据结构中有三个队列头,因而可以同时挂在三个队列中。在我做的小实验当中就是通过查找这三个队列,来将当前进程的Dll加载信息显示出来的。具体的实例请见我的实验说明文档。

             LdrInitializeThunk()中,最开始为做的事情就是将加载的模块信息存放在PEB中的ldr字段,如上面一段文字中所述。之后,LdrInitializeThunk()函数又调用了一个叫LdrPEStartup()的函数。LdrPEStartup()函数首先判断了“期望地址”是否可用,PE映像的NtHeader(peb中有个ImageBaseAddress的地址,代表exe映像在用户空间的位置,在这个地址指向的数据结构中就有NtHeader的结构)中有个指针,指向一个OptionalHeader。在OptionalHeader中有个字段ImageBase,是具体映像建议、或者说希望被装入的地址,我们称之为“愿望地址”。在装入一个映像时,只要相应的区间(取决于它的期望地址和大小)空闲,就总正常装入。但是如果与已经被占用的区间相冲突,就只好利用LdrPerformRelocations()换个地方。

             那么映像的愿望地址有着什么物理的或者逻辑的意义呢?我们知道,软件在编译以后有个连接的过程,即为函数的调用者落实被调用函数的入口地址、为全局变量(按绝对地址)的使用者落实变量地址的过程。连接有静态和动态两种,静态连接是在“制造”软件时进行的,而动态连接则是在使用软件时进行的。尽管EXE模块和DLL模块之间的连接是动态连接,但是EXEDLL模块内部的连接却是静态连接。既是静态连接,就必须为模块的映像提供一个假定的起点地址。如果以此假定地址为基础进行连接以后就不可变更,使用时必须装入到这个地址上,那么这个地址就是固定的“指定地址”了。早期的静态连接往往都是使用指定地址的。但是,如果允许按假定地址连接的映像在实际使用时进行“重定位”,那么这假定地址就是可浮动的“愿望地址”了。可“重定位”的静态连接当然比固定的静态连接灵活。事实上,要是没有可“重定位”的静态连接技术,DLL的使用就无法实现,因为根本就不可能事先为所有可能的DLL划定它们的装入位置和大小。至于可“重定位”静态连接的实现,则一般都是采用间接寻址,通过指针来实现。

             所谓重定位,就是计算出实际装入地址与建议装入地址间的位移a,然后调整每个重定位块中的每一个重定位项、即指针,具体就是在指针上加a。而映像中使用的所有绝对地址(包括函数入口、全局量数据的位置)实际上用的都是间接寻址,每个这样的地址都有个指针存在于某个重定位块中。

             完成了可能需要的EXE映像重定位以后,下一个主要的操作就是LdrFixupImports()了。实际上这才是关键所在,它所处理的就是当前模块所需DLL模块的装入和连接。各DLL的程序入口记录在它们的LDR_DATA_TABLE_ENTRY数据结构中借助InInitializationOrderModuleList队列就可依次调用所有DLL的初始化函数。

             NtHeaderOptionalHeader中有个数组DataDirectory[],其中之一是重定位目录。除此之外,数组中还有“(普通)引入(import)”、“绑定引入(bound import)”以及其它多种目录,但是我们在这里只关心“引入”和“绑定引入”。这两个目录都是用于库函数的引入,但是作用不同,目录项的数据结构也不同。每个引入目录项都代表着一个被引入模块,其模块名、即文件名在dwRVAModuleNameReactOS中的名字,下同)所指的地方。需要从同一个被引入模块引入的函数通常有很多个,dwRVAFunctionNameList指向一个字符串数组,数组中的每一个字符串都是一个函数名;与此相对应,dwRVAFunctionAddressList则指向一个指针数组。这两个数组是平行的,同一个函数在两个数组中具有相同的下标。从一个被引入模块中引入一个函数的过程大体上就是:根据函数名在被引入模块的引出目录中搜索,找到目标函数以后就把它实际装入后的入口地址填写到指针数组中的相应位置上。但是,这个过程可能是个开销相当大、速度比较慢的过程。为此,又发展起一种称为“绑定”的优化。

    所谓绑定,就是在软件的编译,连接过程中先对使用时的动态连接来一次预演,预演时假定所有的DLL都被装入到它们的愿望地址上,然后把预演中得到的被引入函数的地址直接记录在引入者模块中相应引入目录下的指针数组中。这样,使用软件时的动态连接就变得很简单快捷,因为实际上已经事先连接好了。其实“绑定引入”和静态连接并无实质的不同。但是,各模块的版本配套就成为一个问题,因为万一使用的某个DLL不是当初绑定时的版本,而且其引出目录又发生了变化,就有可能引起混乱。为此,PE格式增加了一种“绑定引入”目录,相关的机制会进行判断。但是,“绑定引入”毕竟不是很可靠的,万一发现版本不符就不能使用原先的绑定了。所以“绑定引入”不能单独存在,而必须有普通引入作为后备。如果不符就不能按“绑定引入”目录处理引入,而只好退而求其次,改成按普通“引入”目录处理引入。另一方面,所谓“绑定”是指当被引入模块装入在预定位置上时的地址绑定,如果被引入模块的装入位置变了,就得对原先所绑定的地址作相应的调整、即“重定位”。

    LdrFixupImports()函数首先从映像头部获取指向“引入”目录和“绑定引入”目录的指针。若存在“绑定引入”目录,则先通过LdrpGetOrLoadModule()找到或装入(映射)被引入模块的映像。首先当然是在模块队列中寻找,找不到就从被引入模块的磁盘文件装入。之后检查绑定版本是否一致,如果不一致就退而求其次,通过LdrpProcessImportDirectory()处理引入。当然,那样一来效率就要降低了。如果一致,则返回(因为在“预演”中已经连接好,效率当然高了)。而LdrpProcessImportDirectory()才是真正意义上的动态连接!!(说了这么多原来才开始……)。

    LdrpProcessImportDirectory()首先根据目录项中的两个位移量取得分别指向函数名字符串数组和函数指针数组的指针。这两个数组是平行的(前面有介绍),然后对字符串数组中的元素计数,得到该数组的大小IATSize。显然,函数指针数组的大小也是IATSize。这里IAT是“引入地址表(Imported Address Table)”的缩写,其实就是函数指针数组。这个数组在映像内部,其所在的页面在装入映像时已被加上写保护,而下面要做的事正是要改变这些指针的值,所以先要通过NtProtectVirtualMemory()把这些页面的访问模式改成可读可写。做完这些准备之后,下面就是连接的过程了,那就是根据需要把被引入模块所引出的函数入口(地址)填写到引入者模块的IAT中。与当前模块中的两个数组相对应,在被引入模块的“引出”目录中也有两个数组,说明本模块引出函数的名称和入口地址(在映像中的位移)。当然,这两个数组也是平行的。要获取被引入模块中的函数入口有两种方法,即按序号(Ordinal)引入和按函数名引入。从而分别调用LdrGetExportByOrdinal()LdrGetExportByName()。这两个函数都返回目标函数在本进程用户空间中的入口地址,把它填写入当前模块引入目录函数指针数组中的相应元素,就完成了一个函数的连接。当然,同样的操作要循环实施于当前模块需要从给定模块引入的所有函数,并且(在上一层)循环实施于所有的被引入模块。完成了对一个被引入模块的连接之后,又调用NtProtectVirtualMemory()恢复当前模块中给定目录项内函数指针数组所在页面的保护。

    到此,我们大概的清楚Windows Dll的加载与连接过程。

     

    总结与感想

             以上的Dll加载与连接过程有点抽象与混乱,在这里进行总结,绝体的函数加载关系如下:

     

    展开全文
  • 浅析Windows程序输入法工作原理

    千次阅读 2018-12-23 22:28:56
    浅析Windows程序输入法工作原理一,输入法结构介绍二,Windows消息循环捕捉键盘消息三,总结 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;输入法程序也是一个进程,但是我们在任务管理器中却找不到该进程,...


         输入法程序也是一个进程,但是我们在任务管理器中却找不到该进程,找到输入法的安装目录会发现一个后缀是.ime的文件,就是一个dll的模块,当一个进程打开输入法要输入汉字的时候其实是加载了这个模块。

    一,输入法结构介绍

    1),IMM-输入法管理器
    是windows的内在组成部分,是应用程序与IME之间的接口,用来管理每一个IME,维护IME与应用程序的通信。
    2),IME-输入法编辑器
    用来从系统中接收键盘信息,经过输入法所定义的转换产生汉字,相当于转换引擎,由系统提供。
    3),Input context-输入上下文
    是一个内置结构,由IME来维护,它包含了IME窗口所使用的有关IME状态的信息。默认情况下,系统会为每一个线程创建并指定一个输入上下文。在线程中,这个默认的输入上下文是一个共享资源,并且将会与每个新建窗口关联,供线程中的所有窗口共享。
    从用户敲击键盘进行输入到应用程序接收到字符输入消息,大概流程如下:
    在这里插入图片描述

    二,Windows消息循环捕捉键盘消息

    windows程序的消息循环为:

    while ((r = GetMessageW(&msg, 0, 0, 0)) != -1)
    	{
    			if (r == 0)break;
    			
    			TranslateMessage(&msg);   //消息预处理
    		
    			DispatchMessageW(&msg);	//分发消息
    			
    	}
    

         一般情况下GetMessage得到WM_KEYDOWN的wParam是虚拟键码,如果我们开了输入法,即中文输入法下,WM_KEYDOWN的wParam虚拟码是VK_PROCESSKEY,TranslateMessage对一般的虚拟键码是产生WM_CHAR,WM_SYSCHAR之类的,但是当TranslateMessage遇到了VK_PROCESSKEY,就会有特殊处理。这时候TranslateMessage就会把控制权交给DefWindowProc这个缺省的消息处理例程,在DefWindowProc中根据中文字符按下收到的WM_IME_COMPOSITION消息,产生一个WM_IME_CHAR,然后再发送给本地线程,实际上也就是以WM_IME_CHAR为参数,递归调用了一次WINPROC,在这次调用的WINPROC中产生WM_CHAR。
         如果你没有开输入法WM_CHAR直接由 TranslateMessage产生。备注:TranslateMessage在此前会进行一些键盘消息预处理操作,从WM_KEYDOWN、WM_SYSKEYDOWN中产生‘字符消息’(WM_CHAR、WM_DEADCHAR、WM_SYSCHAR或WM_SYSDEADCHAR)。典型的一组击键消息过程为WM_KEYDOWN——WM_CHAR——WM_KEYUP

    三,总结

         将上面的两点结合在一起,就是整个输入法的处理流程:IMM通过IME的转换接口将键盘消息发送到IME,此函数对键盘消息进行筛选处理,用以判断该消息是否是发送给IME还是直接发送给应用程序。IME接收到键盘消息后,进行处理,并最终转换为输出的字符串。 IME使用WM_IME_COMPOSITION/GCS_RESULT或WM_IME_CHAR消息把组合好的字符发送给应用程序,如果应用程序没有处理这些消息,DefWindowProc函数会把它们翻译成一条或多条WM_CHAR消息。

    展开全文
  • Windows DLL基本原理

    千次阅读 2014-06-09 06:14:55
    Windows DLL基本原理 Windows系统平台上,你可以将独立的程序模块创建为较小的DLL(Dynamic Linkable Library)文件,并可对它们单独编译和测试。在运行时,只有当EXE程序确实要调用这些DLL模块的情况下,系统才...
  • Windows程序内部运行原理

    千次阅读 2016-11-14 16:11:43
    为了理解Visual C++应用程序开发过程,先要理解Windows程序的运行机制。因为 Visual C++是 Windows 开发语言,需要明白在Windows 环境下编程和在其它环境下编程的一些根本性的差别。全面地讨论 Windows 的内部...
  • Windows程序运行原理

    2012-04-20 16:01:35
    Windows操作系统版本 MicrosoftWindows NT...DLL(动态链接库)指一组可调用的子例程,合起来被链接成一个二进制文件,使用这些子例程的应用程序可以动态地加载此二进制文件。例如Msvcrt.dll(C运行库)和Kernel32
  • dll文件32位64位检测工具以及Windows文件夹SysWow64的坑 自从操作系统升级到64位以后,就要不断的需要面对32位、64位的问题。相信有很多人并不是很清楚32位程序与64位程序的区别,以及Program Files (x86),Program...
  • 修改PE加载dll

    千次阅读 2016-04-16 16:16:52
    创建远程线程(CreateRemoteThread()...通过修改exe的格式,我们可以实现静态注入,也就是对程序的机器字节进行修改,使程序运行时自动加载dll。 下面我们就来详细的介绍一下这种方法。exe,dll等文件统称为可执行文
  • 通过修改PE加载DLL

    千次阅读 2018-06-24 10:51:58
    基本概念除了DLL动态注入技术外,还可以通过手工修改PE文件的方式来加载DLL,这种方式只要应用过一次之后,每当进程开始运行时便会自动加载指定的DLL。整体思路如下:1、查看IDT是否有充足的空间,若无则移动IDT至...
  • WindowsDLL文件基本原理及修改方法 天极blog 2005-12-01 文/
  •  Windows1.0到3.0中使用分段内存模式,在分段内存模式下,内存地址分为两部分—— 一个16位段指针和一个16为偏移指针。其中带来了long或far指针(包括段地址和偏移量地址)和short或near指针
  • 从int 3探索Windows应用程序调试原理

    千次阅读 2015-05-14 09:45:55
    【系统篇】从int 3探索Windows应用程序调试原理 探索调试器下断点的原理  在Windows上做开发的程序猿们都知道,x86架构处理器有一条特殊的指令——int 3,也就是机器码0xCC,用于调试所用,当程序执行到int 3的...
  • Dll原理和使用

    2010-08-31 23:18:00
    讲的实在是好,必须收藏的 如果想要与其它进程共享内存,必须使用内存映射文件或者声明一个共享数据段 MFC中的DLL Non-MFC DLL Regular DLL Extension DLL
  • WinMain(windows程序的运行原理以及VC++的实现过程)操作系统以消息机制把输入设备的变化传递给我们的应用程序,操作系统所扮演的角色是神经末梢 APP系统调用API OS 输出 输入 HARDWARE操作系统之所以成为操作...
  • 编程实现感染PE文件加载DLL 这篇文章是在网上看到的,貌似我找不到原文了。但是网上的排版非常乱,而且觉得此篇此篇文章对学习pe文件有很大帮助。 首先发张图让大家对PE文件有个整体认识 PE文件是Windows...
  • 关于java加载dll文件

    千次阅读 2016-12-13 10:21:06
    最近项目中用到了Jni,需要用java调用Opencv生成的dll文件完成图像处理。 问题描述:UnsatisfiedLinkError:no msvcp120d in java.library.path dll文件是同事提供的,他同时提供了一个demo project。
  • 从前面的《Windows内存体系》系列文章中我们可以知道,在Windows系统中,每个进程都有自己私有的地址空间。当我们用指针来引用内存的时候,指针的值表示的是进程自己的地址空间的一个虚拟的内存地址。进程不能通过...
  • DLL劫持原理&防御方法

    万次阅读 2019-05-18 14:35:01
    DLL劫持指的是,病毒通过一些手段来劫持或者替换正常的DLL,欺骗正常程序加载预先准备好的恶意DLL。 如下图,LPK.dll是应用程序运行所需加载的DLL,该系统文件默认在C:\Windows\system32路径下,但由于windows优先...
  • 从这篇文章开始,作者将带着大家来...第一篇文章主要包括两部分内容,开发环境(VS、编译设置)、基础技术、运行单一实例(互斥对象示例)、DLL延迟加载(skin++换皮肤示例)、资源释放(MFC示例)。希望对您有所帮助~
  • DLL原理

    2014-06-09 06:19:54
    一,DLL原理  1,动态链接程序库  动态链接程序库,全称:Dynamic Link Library,简称:DLL,作用在于为应用程序提供扩展功能。应用程序想要调用DLL文件,需要跟其进行“动态链接”;从编程的角度,应用程序...
  • DLL原理与使用

    千次阅读 2004-10-26 16:01:00
    引言 调用方式 MFC中的DLL DLL入口函数 关于约定 关于DLL的函数 模块定义文件(.DEF) DLL程序和调用其输出函数的程序的关系 作者引言比较大的应用程序都由很多模块组成,这些模块分别完成相对独立的功能,它们彼此...
  • Windows服务程序原理及实现

    千次阅读 2010-01-28 15:48:00
    今天给大家讲下怎样做一个服务程序...本来是想详细讲的,不过写着写着累得要命..很多 地方就没详细...不过代码我加了点注...如果还有一些不明白的自己查下MSDN......便宜 环境,,VC++6.0...代码有俩段,一段是服务程序...
  • windowsDLL

    2015-01-04 22:12:17
    windowsDLL的基本原理浅谈,其实主要是读《windows核心编程》后的一些笔记而已!由于是第一次写博客!希望大家多多谅解!
  • DLL劫持原理

    2020-01-03 10:27:05
    系统有一套标准的搜索DLL路径的规则,这套规则又分为两种搜索模式,安全搜索模式,非安全搜索模式。 默认情况下启用安全DLL搜索模式。要禁用此功能,需创建HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\...
  • 给.NET中的Windows窗体加载Xp样式 作者:Heath Stewart翻译:郑佐 介绍 当Windows XP 带着他特有的可视化样式或者主题发布时,许多人为他拥有的华丽界面而兴奋。然而,当.NET1.0正式版本发布的时候,许多人包括我...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 20,859
精华内容 8,343
关键字:

windows程序加载dll原理