《Windows PE》4.1导入表
导入表顾名思义,就是记录外部导入函数信息的表。这些信息包括外部导入函数的序号、名称、地址和所属的DLL动态链接库的名称。Windows程序中使用的所有API接口函数都是从系统DLL中调用的。当然也可能是自定义的DLL动态链接库。对于调用方,我们称之为导入函数,而对于DLL动态链接库而言,则称之为导出函数。我们将在下一章中再详细讲述导出表。
本节必须掌握的知识点:
导入表数据结构
PE中的导入表
IAT函数地址表
手工重构导入表
4.1.1 导入表数据结构
导入表(Import Table)是PE(可执行文件、动态链接库等)文件中的一个数据结构,用于记录该模块所依赖的外部函数和库。在运行时,操作系统根据导入表中的信息来解析和加载这些外部函数和库。导入表数据是一组导入表描述符结构。每组为20个字节。导入表的位置和大小信息保存在数据目录项的第1项(从第0项开始)。
■导入表包含以下重要的信息:
●导入描述符表(Import Descriptor Table):记录每个被导入的模块的信息,如模块名称、导入地址表的位置等。
●导入地址表(Import Address Table):记录外部函数的地址,在加载时会被填充为实际函数的地址。
●导入名称表(Import Name Table):记录外部函数的名称。
■导入表描述符的结构如下:
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; //特征值,对于终止的空导入描述符为0
DWORD OriginalFirstThunk; //指向原始未绑定的导入地址表的RVA
//(PIMAGE_THUNK_DATA)
} DUMMYUNIONNAME;
DWORD TimeDateStamp; //时间戳,如果未绑定则为0。
//绑定时为-1,并在IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT中记录实际日期\时间戳(新的BIND中),否则为绑定到的DLL的日期/时间戳(旧的BIND中)
DWORD ForwarderChain; //正向链表索引,如果没有正向链表则为-1
DWORD Name; //DLL模块名称的RVA
DWORD FirstThunk; //导入地址表(IAT)的RVA,如果已绑定,则IAT包含实际地址
} IMAGE_IMPORT_DESCRIPTOR, *PIMAGE_IMPORT_DESCRIPTOR;
●OriginalFirstThunk字段的作用是记录导入函数的信息。对于每个导入模块,OriginalFirstThunk字段是一个指针,指向一个由IMAGE_THUNK_DATA类型的结构体构成的表。每个IMAGE_THUNK_DATA结构体存储着导入函数的信息,包括函数名称或序号。
OriginalFirstThunk指向的表中的每个IMAGE_THUNK_DATA结构体有两种可能的形式:
- 如果导入函数是通过名称进行导入,IMAGE_THUNK_DATA结构体中的值是一个指向函数名称的指针。
- 如果导入函数是通过序号进行导入,IMAGE_THUNK_DATA结构体中的值是函数的序号。
通过解析OriginalFirstThunk指向的表,可以获取导入模块中所有导入函数的名称或序号。这些信息后续可以用于将导入的函数地址与对应的函数名称或序号进行关联。
注意
1.OriginalFirstThunk字段在已绑定的PE文件中可能会被覆盖,此时可以通过FirstThunk字段来获取已绑定的导入地址表。
2.ForwarderChain用于指示导入函数的转发链(Forwarder Chain)。
在PE文件中,导入表描述符用于描述导入函数的相关信息,包括导入模块的名称、导入地址表的位置等。而转发链是指导入函数在一个模块中被转发到另一个模块的一种机制。
转发链的作用是将一个导入函数的调用转发到另一个模块中的函数。当一个导入函数被调用时,如果它在当前模块中没有找到,就会根据转发链找到另一个模块,并在那个模块中寻找匹配的函数来处理该调用。
ForwarderChain字段在导入表描述符中记录了转发链的信息,它的值表示下一个导入函数描述符在导入表中的索引。如果ForwarderChain字段的值为-1,表示没有转发链,即没有导入函数被转发到其他模块。
需要注意的是,转发链是可选的,不是所有的导入函数都会使用转发链。只有在某些特定的情况下,如跨模块调用或模块间的函数重定向时,才会使用转发链来实现导入函数的转发。
●下面是winnt.h头文件中IMAGE_THUNK_DATA结构体的定义:
#include "pshpack8.h" // Use align 8 for the 64-bit IAT.
typedef struct _IMAGE_THUNK_DATA64 {
union {
ULONGLONG ForwarderString; // PBYTE
ULONGLONG Function; // PDWORD
ULONGLONG Ordinal;
ULONGLONG AddressOfData; // PIMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA64;
typedef IMAGE_THUNK_DATA64 * PIMAGE_THUNK_DATA64;
#include "poppack.h" // Back to 4 byte packing
typedef struct _IMAGE_THUNK_DATA32 {
union {
DWORD ForwarderString; // PBYTE
DWORD Function; // PDWORD
DWORD Ordinal;
DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA32;
typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;
IMAGE_THUNK_DATA结构体的字段含义如下:
1.ForwarderString:如果存在转发导入(forwarding import),则该字段保存转发字符串的指针;否则,该字段为0。
2.Function:导入函数的地址。如果导入函数是通过名称进行导入,则该字段保存函数的地址;如果导入函数是通过序号进行导入,则该字段保存函数的序号。
3.Ordinal:如果导入函数是通过序号进行导入,则该字段保存函数的序号;否则,该字段为0。
4.AddressOfData:如果导入函数是通过名称进行导入,则该字段保存一个指向IMAGE_IMPORT_BY_NAME结构体的指针,该结构体包含函数的名称;否则,该字段保存一个指向函数的地址的指针。
IMAGE_THUNK_DATA结构体用于描述PE文件中的导入地址表(Import Address Table,IAT)或原始未绑定导入地址表(Original Unbound Import Address Table)中的每个导入函数的信息。
通过解析IMAGE_THUNK_DATA结构体的字段,可以获取导入函数的地址或序号,以及函数的名称(如果是通过名称进行导入)。
注意
1.MAGE_THUNK_DATA结构体在64位的PE文件中使用_IMAGE_THUNK_DATA64。
2.对于32位的PE文件,相应的结构体是_IMAGE_THUNK_DATA32。它们之间的区别在于64位PE文件使用ULONGLONG表示64位指针类型,而32位PE文件使用DWORD表示32位指针类型。
●IMAGE_IMPORT_BY_NAME结构体:
IMAGE_IMPORT_BY_NAME是Windows可执行文件(PE文件)中的一个结构体,用于描述通过名称导入函数的信息。
以下是IMAGE_IMPORT_BY_NAME结构体的定义:
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint;
BYTE Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
IMAGE_IMPORT_BY_NAME结构体的字段含义如下:
1.Hint:导入函数的提示值(Hint),是一个16位的无符号整数。它提供了关于导入函数的一些附加信息,例如函数的重载情况等。在导入描述符中,通过OriginalFirstThunk指向的表中的IMAGE_THUNK_DATA结构体的Ordinal字段存储了函数的序号,而Hint字段则提供了与该序号对应的提示值。
2.Name:导入函数的名称,是一个以NULL结尾的字符串。
IMAGE_IMPORT_BY_NAME结构体用于描述PE文件中的导入地址表(Import Address Table,IAT)或原始未绑定导入地址表(Original Unbound Import Address Table)中,通过名称进行导入的函数的信息。
通过解析IMAGE_IMPORT_BY_NAME结构体的字段,可以获取导入函数的名称以及与之对应的提示值(如果有的话)。
4.1.2 PE中的导入表
接下来我们通过具体实例分析PE中的导入表。
实验二十三:如何定位导入表?
●我们以32位汇编版HelloWorld.exe为例,将其拖入WinHex,如下所示:
00000000 4D 5A 90 00 03 00 00 00 04 00 00 00 FF FF 00 00 MZ............
00000010 B8 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 ?......@.......
00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000030 00 00 00 00 00 00 00 00 00 00 00 00 C8 00 00 00 ............?..
000000C0 00 00 00 00 00 00 00 00 50 45 00 00 4C 01 04 00 ........PE..L...
000000D0 E4 68 03 66 00 00 00 00 00 00 00 00 E0 00 02 01 鋒.f........?..
000000E0 0B 01 0E 10 00 02 00 00 00 06 00 00 00 00 00 00 ................
000000F0 00 10 00 00 00 10 00 00 00 20 00 00 00 00 40 00 ......... ....@.
00000100 00 10 00 00 00 02 00 00 06 00 00 00 00 00 00 00 ................
00000110 06 00 00 00 00 00 00 00 00 50 00 00 00 04 00 00 .........P......
00000120 00 00 00 00 02 00 40 81 00 00 10 00 00 10 00 00 ......@.........
00000130 00 00 10 00 00 10 00 00 00 00 00 00 10 00 00 00 ................
00000140 00 00 00 00 00 00 00 00 DC 20 00 00 3C 00 00 00 ........?..<...
00000150 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000160 00 00 00 00 00 00 00 00 00 40 00 00 10 00 00 00 .........@......
00000170 10 20 00 00 1C 00 00 00 00 00 00 00 00 00 00 00 . ..............
00000180 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000190 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000001A0 00 20 00 00 10 00 00 00 00 00 00 00 00 00 00 00 . ..............
000001B0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
●导入表数据目录项
DOS头的IMAGE_DOS_HEADER. e_lfanew字段000000C8H指向IMAGE_NT_HEADERS32。
IMAGE_NT_HEADERS32.OptionalHeader.DataDirectory[1*8]指向导入表数据目录项00000148H地址处。
导入表的RVA地址为000020DCH,大小为0000003C。
●定位导入表
000001C0 2E 74 65 78 74 00 00 00 26 00 00 00 00 10 00 00 .text...&.......
000001D0 00 02 00 00 00 04 00 00 00 00 00 00 00 00 00 00 ................
000001E0 00 00 00 00 20 00 00 60 2E 72 64 61 74 61 00 00 .... ..`.rdata..
000001F0 5E 01 00 00 00 20 00 00 00 02 00 00 00 06 00 00 ^.... ..........
00000200 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 40 ............@..@
00000210 2E 64 61 74 61 00 00 00 1B 00 00 00 00 30 00 00 .data........0..
00000220 00 02 00 00 00 08 00 00 00 00 00 00 00 00 00 00 ................
00000230 00 00 00 00 40 00 00 C0 2E 72 65 6C 6F 63 00 00 ....@..?reloc..
00000240 10 00 00 00 00 40 00 00 00 02 00 00 00 0A 00 00 .....@..........
00000250 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 42 ............@..B
将导入表数据目录项的RVA地址转换为FOA文件偏移地址。
RVA地址000020DCH位于.rdata节区00002000H~0000215EH区间。
FOA地址=000020DCH-00002000H+00000600H=000006DCH。
目录项目的第1个结构就是导入表
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress; //RVA 指向导入表结构
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
将RVA转换成FOA
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
DWORD OriginalFirstThunk;
};
DWORD TimeDateStamp;
DWORD ForwarderChain;
DWORD Name;
DWORD FirstThunk;
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED
*PIMAGE_IMPORT_DESCRIPTOR;
000006D0 1B 00 00 00 2E 64 61 74 61 00 00 00 20 21 00 00 .....data... !..
000006E0 00 00 00 00 00 00 00 00 36 21 00 00 08 20 00 00 ........6!... ..
000006F0 18 21 00 00 00 00 00 00 00 00 00 00 50 21 00 00 .!..........P!..
00000700 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 . ..............
00000710 00 00 00 00 00 00 00 00 42 21 00 00 00 00 00 00 ........B!......
00000720 28 21 00 00 00 00 00 00 B1 01 4D 65 73 73 61 67 (!......?Messag
00000730 65 42 6F 78 41 00 75 73 65 72 33 32 2E 64 6C 6C eBoxA.user32.dll
00000740 00 00 9B 00 45 78 69 74 50 72 6F 63 65 73 73 00 ..?ExitProcess.
00000750 6B 65 72 6E 65 6C 33 32 2E 64 6C 6C 00 00 00 00 kernel32.dll....
总结
HelloWorld.exe程序中共有3个导入表描述符结构,每个导入表描述符为20个字节,分别用于描述user32.dll和kernel32.dll两个动态链接库中的导入函数。此外还有一个为空的导入表描述符作为结束标志。
Windows程序中的导入表描述符是一个双桥结构:
1.描述符的第一个字段OriginalFirstThunk又被称为桥1,是一个32位的RVA地址,示例中的值为00002120H,指向INT表,最高位为0表示按序号导入,最高位为1表示按名称导入。
导入表中的INT表(IMAGE_THUNK_DATA32. AddressOfData)指的是导入函数中的函数指针表中的一个特定部分,用于存储导入函数名的RVA地址。通过解析导入描述符中的INT表,可以获取导入函数的入口地址,从而在程序运行时能够正确地调用这些导入函数。
将OriginalFirstThunk存储的RVA地址转换为FOA地址:
INT表的FOA地址=00002120H-00002000H+600H=720H。
在00000720H地址处存储的值为IMAGE_THUNK_DATA32. AddressOfData(00002128H),同样是一个RVA地址,指向导入函数名。
导入函数名的FOV地址=00002128H-00002000H+600H=728H。
在00000728H地址处存储的值就是导入函数名”MessagBoxA”。注意函数名之前的01B1H为与导入函数序号对应的提示值。
2.导入表描述符的TimeDateStamp和ForwarderChain字段为空。
3.导入表描述符的Name字段(00002136H)为DLL模块名称的RVA地址。将此RVA地址转换为FOA地址为736H,该地址处存储的值就是” user32.dll”。
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
DWORD OriginalFirstThunk;// 00002120H,转换后的FOA地址为720H。
};
DWORD TimeDateStamp; //为空
DWORD ForwarderChain; //为空
DWORD Name; // 00002136H,转换后的FOA地址为736H” user32.dll”
DWORD FirstThunk;
} IMAGE_IMPORT_DESCRIPTOR;
typedef struct _IMAGE_THUNK_DATA32 {
union {
DWORD ForwarderString; // PBYTE
DWORD Function; // PDWORD
DWORD Ordinal;
DWORD AddressOfData; //00002128H ,转换后的FOA地址为728H。
} u1;
} IMAGE_THUNK_DATA32;
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint; //01B1H
BYTE Name[1]; // “MessagBoxA”
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
4.最后一个字段FirstThunk指向IAT表。我们将在下一小节中讲解。
DLL名称
遍历OriginalFirstThunk
注意
1.导入函数名字符串和DLL模块名字符串均为ASCII码字符串。
2.导入函数名字符串和DLL模块名字符串均为’\0’结尾字符串,并且以偶数字节对齐。例如函数名”MessagBoxA”字符串结尾处为1个’\0’,而” user32.dll”字符串结尾处有两个’\0’。
●我们再以64位汇编版HelloWorld64.exe为例,将其拖入WinHex,如下所示:
00000180 00 00 00 00 00 00 00 00 64 27 00 00 B4 00 00 00 ........d'..?..
00000190 00 50 00 00 E0 01 00 00 00 40 00 00 68 01 00 00 .P..?...@..h...
000001A0 00 00 00 00 00 00 00 00 00 60 00 00 1C 00 00 00 .........`......
000001B0 30 22 00 00 70 00 00 00 00 00 00 00 00 00 00 00 0"..p...........
000001C0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000001D0 A0 22 00 00 00 01 00 00 00 00 00 00 00 00 00 00 ?..............
000001E0 00 20 00 00 80 01 00 00 00 00 00 00 00 00 00 00 . ..€...........
000001F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000200 2E 74 65 78 74 00 00 00 38 0D 00 00 00 10 00 00 .text...8.......
00000210 00 0E 00 00 00 04 00 00 00 00 00 00 00 00 00 00 ................
00000220 00 00 00 00 20 00 00 60 2E 72 64 61 74 61 00 00 .... ..`.rdata..
00000230 A0 0D 00 00 00 20 00 00 00 0E 00 00 00 12 00 00 ?... ..........
00000240 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 40 ............@..@
00000250 2E 64 61 74 61 00 00 00 48 06 00 00 00 30 00 00 .data...H....0..
00000260 00 02 00 00 00 20 00 00 00 00 00 00 00 00 00 00 ..... ..........
00000270 00 00 00 00 40 00 00 C0 2E 70 64 61 74 61 00 00 ....@..?pdata..
00000280 68 01 00 00 00 40 00 00 00 02 00 00 00 22 00 00 h....@......."..
00000290 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 40 ............@..@
000002A0 2E 72 73 72 63 00 00 00 E0 01 00 00 00 50 00 00 .rsrc...?...P..
000002B0 00 02 00 00 00 24 00 00 00 00 00 00 00 00 00 00 .....$..........
000002C0 00 00 00 00 40 00 00 40 2E 72 65 6C 6F 63 00 00 ....@..@.reloc..
000002D0 1C 00 00 00 00 60 00 00 00 02 00 00 00 26 00 00 .....`.......&..
000002E0 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 42 ............@..B
首先我们找到数据目录项的第1项,位于地址00000188H处,导入表的RVA地址为00002764H,大小是000000B4H。
我们遍历节表可知,RVA地址00002764H位于.rdata节区。
导入表FOA地址=00002764H-00002000H+00001200H=00001964H。
如下所示:
00001960 01 00 00 00 A0 28 00 00 00 00 00 00 00 00 00 00 ....?..........
00001970 A6 29 00 00 88 20 00 00 B0 28 00 00 00 00 00 00 ?..?..?......
00001980 00 00 00 00 D4 29 00 00 98 20 00 00 F8 28 00 00 ....?..?..?..
00001990 00 00 00 00 00 00 00 00 7E 2B 00 00 E0 20 00 00 ........~+..?..
000019A0 E8 28 00 00 00 00 00 00 00 00 00 00 A0 2B 00 00 ?..........?..
000019B0 D0 20 00 00 80 29 00 00 00 00 00 00 00 00 00 00 ?..€)..........
000019C0 C0 2B 00 00 68 21 00 00 D8 28 00 00 00 00 00 00 ?..h!..?......
000019D0 00 00 00 00 E0 2B 00 00 C0 20 00 00 C8 28 00 00 ....?..?..?..
000019E0 00 00 00 00 00 00 00 00 02 2C 00 00 B0 20 00 00 .........,..?..
000019F0 18 28 00 00 00 00 00 00 00 00 00 00 92 2D 00 00 .(..........?..
00001A00 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 . ..............
00001A10 00 00 00 00 00 00 00 00 E2 2C 00 00 00 00 00 00 ........?......
00001A20 7E 2D 00 00 00 00 00 00 6C 2D 00 00 00 00 00 00 ~-......l-......
00001A30 58 2D 00 00 00 00 00 00 42 2D 00 00 00 00 00 00 X-......B-......
00001A40 28 2D 00 00 00 00 00 00 12 2D 00 00 00 00 00 00 (-.......-......
00001A50 FC 2C 00 00 00 00 00 00 22 2C 00 00 00 00 00 00 ?......",......
00001A60 C6 2C 00 00 00 00 00 00 B2 2C 00 00 00 00 00 00 ?......?......
00001A70 9E 2C 00 00 00 00 00 00 80 2C 00 00 00 00 00 00 ?......€,......
00001A80 64 2C 00 00 00 00 00 00 50 2C 00 00 00 00 00 00 d,......P,......
00001A90 36 2C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 6,..............
00001AA0 98 29 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ?..............
00001AB0 CA 29 00 00 00 00 00 00 B2 29 00 00 00 00 00 00 ?......?......
00001AC0 00 00 00 00 00 00 00 00 0C 2B 00 00 00 00 00 00 .........+......
00001AD0 00 00 00 00 00 00 00 00 F6 2A 00 00 00 00 00 00 ........?......
00001AE0 00 00 00 00 00 00 00 00 08 2A 00 00 00 00 00 00 .........*......
00001AF0 00 00 00 00 00 00 00 00 B4 2A 00 00 00 00 00 00 ........?......
00001B00 72 2B 00 00 00 00 00 00 48 2B 00 00 00 00 00 00 r+......H+......
00001B10 2C 2B 00 00 00 00 00 00 9E 2A 00 00 00 00 00 00 ,+......?......
00001B20 96 2A 00 00 00 00 00 00 88 2A 00 00 00 00 00 00 ?......?......
00001B30 7C 2A 00 00 00 00 00 00 58 2A 00 00 00 00 00 00 |*......X*......
00001B40 36 2A 00 00 00 00 00 00 1C 2A 00 00 00 00 00 00 6*.......*......
00001B50 64 2B 00 00 00 00 00 00 F8 29 00 00 00 00 00 00 d+......?......
00001B60 E6 29 00 00 00 00 00 00 C8 2A 00 00 00 00 00 00 ?......?......
00001B70 BE 2A 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ?..............
00001B80 1C 2B 00 00 00 00 00 00 A6 2A 00 00 00 00 00 00 .+......?......
00001B90 00 00 00 00 00 00 00 00 8A 02 4D 65 73 73 61 67 ........?Messag
00001BA0 65 42 6F 78 57 00 55 53 45 52 33 32 2E 64 6C 6C eBoxW.USER32.dll
00001BB0 00 00 08 00 5F 5F 43 5F 73 70 65 63 69 66 69 63 ....__C_specific
00001BC0 5F 68 61 6E 64 6C 65 72 00 00 3E 00 6D 65 6D 73 _handler..>.mems
00001BD0 65 74 00 00 56 43 52 55 4E 54 49 4D 45 31 34 30 et..VCRUNTIME140
00001BE0 2E 64 6C 6C 00 00 40 00 5F 73 65 68 5F 66 69 6C .dll..@._seh_fil
00001BF0 74 65 72 5F 65 78 65 00 42 00 5F 73 65 74 5F 61 ter_exe.B._set_a
00001C00 70 70 5F 74 79 70 65 00 09 00 5F 5F 73 65 74 75 pp_type...__setu
00001C10 73 65 72 6D 61 74 68 65 72 72 00 00 18 00 5F 63 sermatherr...._c
00001C20 6F 6E 66 69 67 75 72 65 5F 6E 61 72 72 6F 77 5F onfigure_narrow_
00001C30 61 72 67 76 00 00 33 00 5F 69 6E 69 74 69 61 6C argv..3._initial
00001C40 69 7A 65 5F 6E 61 72 72 6F 77 5F 65 6E 76 69 72 ize_narrow_envir
00001C50 6F 6E 6D 65 6E 74 00 00 2B 00 5F 67 65 74 5F 6E onment..+._get_n
00001C60 61 72 72 6F 77 5F 77 69 6E 6D 61 69 6E 5F 63 6F arrow_winmain_co
00001C70 6D 6D 61 6E 64 5F 6C 69 6E 65 00 00 36 00 5F 69 mmand_line..6._i
00001C80 6E 69 74 74 65 72 6D 00 37 00 5F 69 6E 69 74 74 nitterm.7._initt
00001C90 65 72 6D 5F 65 00 55 00 65 78 69 74 00 00 23 00 erm_e.U.exit..#.
00001CA0 5F 65 78 69 74 00 54 00 5F 73 65 74 5F 66 6D 6F _exit.T._set_fmo
00001CB0 64 65 00 00 16 00 5F 63 65 78 69 74 00 00 15 00 de...._cexit....
00001CC0 5F 63 5F 65 78 69 74 00 3D 00 5F 72 65 67 69 73 _c_exit.=._regis
00001CD0 74 65 72 5F 74 68 72 65 61 64 5F 6C 6F 63 61 6C ter_thread_local
00001CE0 5F 65 78 65 5F 61 74 65 78 69 74 5F 63 61 6C 6C _exe_atexit_call
00001CF0 62 61 63 6B 00 00 08 00 5F 63 6F 6E 66 69 67 74 back...._configt
00001D00 68 72 65 61 64 6C 6F 63 61 6C 65 00 16 00 5F 73 hreadlocale..._s
00001D10 65 74 5F 6E 65 77 5F 6D 6F 64 65 00 01 00 5F 5F et_new_mode...__
00001D20 70 5F 5F 63 6F 6D 6D 6F 64 65 00 00 34 00 5F 69 p__commode..4._i
00001D30 6E 69 74 69 61 6C 69 7A 65 5F 6F 6E 65 78 69 74 nitialize_onexit
00001D40 5F 74 61 62 6C 65 00 00 3C 00 5F 72 65 67 69 73 _table..<._regis
00001D50 74 65 72 5F 6F 6E 65 78 69 74 5F 66 75 6E 63 74 ter_onexit_funct
00001D60 69 6F 6E 00 1E 00 5F 63 72 74 5F 61 74 65 78 69 ion..._crt_atexi
00001D70 74 00 67 00 74 65 72 6D 69 6E 61 74 65 00 61 70 t.g.terminate.ap
00001D80 69 2D 6D 73 2D 77 69 6E 2D 63 72 74 2D 72 75 6E i-ms-win-crt-run
00001D90 74 69 6D 65 2D 6C 31 2D 31 2D 30 2E 64 6C 6C 00 time-l1-1-0.dll.
00001DA0 61 70 69 2D 6D 73 2D 77 69 6E 2D 63 72 74 2D 6D api-ms-win-crt-m
00001DB0 61 74 68 2D 6C 31 2D 31 2D 30 2E 64 6C 6C 00 00 ath-l1-1-0.dll..
00001DC0 61 70 69 2D 6D 73 2D 77 69 6E 2D 63 72 74 2D 73 api-ms-win-crt-s
00001DD0 74 64 69 6F 2D 6C 31 2D 31 2D 30 2E 64 6C 6C 00 tdio-l1-1-0.dll.
00001DE0 61 70 69 2D 6D 73 2D 77 69 6E 2D 63 72 74 2D 6C api-ms-win-crt-l
00001DF0 6F 63 61 6C 65 2D 6C 31 2D 31 2D 30 2E 64 6C 6C ocale-l1-1-0.dll
00001E00 00 00 61 70 69 2D 6D 73 2D 77 69 6E 2D 63 72 74 ..api-ms-win-crt
00001E10 2D 68 65 61 70 2D 6C 31 2D 31 2D 30 2E 64 6C 6C -heap-l1-1-0.dll
00001E20 00 00 D3 04 52 74 6C 43 61 70 74 75 72 65 43 6F ..?RtlCaptureCo
00001E30 6E 74 65 78 74 00 DA 04 52 74 6C 4C 6F 6F 6B 75 ntext.?RtlLooku
00001E40 70 46 75 6E 63 74 69 6F 6E 45 6E 74 72 79 00 00 pFunctionEntry..
00001E50 E1 04 52 74 6C 56 69 72 74 75 61 6C 55 6E 77 69 ?RtlVirtualUnwi
00001E60 6E 64 00 00 BC 05 55 6E 68 61 6E 64 6C 65 64 45 nd..?UnhandledE
00001E70 78 63 65 70 74 69 6F 6E 46 69 6C 74 65 72 00 00 xceptionFilter..
00001E80 7B 05 53 65 74 55 6E 68 61 6E 64 6C 65 64 45 78 {.SetUnhandledEx
00001E90 63 65 70 74 69 6F 6E 46 69 6C 74 65 72 00 1D 02 ceptionFilter...
00001EA0 47 65 74 43 75 72 72 65 6E 74 50 72 6F 63 65 73 GetCurrentProces
00001EB0 73 00 9A 05 54 65 72 6D 69 6E 61 74 65 50 72 6F s.?TerminatePro
00001EC0 63 65 73 73 00 00 89 03 49 73 50 72 6F 63 65 73 cess..?IsProces
00001ED0 73 6F 72 46 65 61 74 75 72 65 50 72 65 73 65 6E sorFeaturePresen
00001EE0 74 00 50 04 51 75 65 72 79 50 65 72 66 6F 72 6D t.P.QueryPerform
00001EF0 61 6E 63 65 43 6F 75 6E 74 65 72 00 1E 02 47 65 anceCounter...Ge
00001F00 74 43 75 72 72 65 6E 74 50 72 6F 63 65 73 73 49 tCurrentProcessI
00001F10 64 00 22 02 47 65 74 43 75 72 72 65 6E 74 54 68 d.".GetCurrentTh
00001F20 72 65 61 64 49 64 00 00 F0 02 47 65 74 53 79 73 readId..?GetSys
00001F30 74 65 6D 54 69 6D 65 41 73 46 69 6C 65 54 69 6D temTimeAsFileTim
00001F40 65 00 6C 03 49 6E 69 74 69 61 6C 69 7A 65 53 4C e.l.InitializeSL
00001F50 69 73 74 48 65 61 64 00 82 03 49 73 44 65 62 75 istHead.?IsDebu
00001F60 67 67 65 72 50 72 65 73 65 6E 74 00 D7 02 47 65 ggerPresent.?Ge
00001F70 74 53 74 61 72 74 75 70 49 6E 66 6F 57 00 7E 02 tStartupInfoW.~.
00001F80 47 65 74 4D 6F 64 75 6C 65 48 61 6E 64 6C 65 57 GetModuleHandleW
00001F90 00 00 4B 45 52 4E 45 4C 33 32 2E 64 6C 6C 00 00 ..KERNEL32.dll..
notepad64.exe导入表描述符:
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
DWORD OriginalFirstThunk; 000028A0H,转换后的FOA地址为1AA0H。
};
DWORD TimeDateStamp; //为空。
DWORD ForwarderChain; //为空。
DWORD Name; //000029A6H,转换后的FOA地址为1BA6H,” USER32.dll”。
DWORD FirstThunk; //00002088H,转换后的FOA地址为1288H。
} IMAGE_IMPORT_DESCRIPTOR;
typedef struct _IMAGE_THUNK_DATA64 {
union {
ULONGLONG ForwarderString; // PBYTE
ULONGLONG Function; // PDWORD
ULONGLONG Ordinal;
ULONGLONG AddressOfData; //0000000000002998H,转换后的FOA地址为1B98H。
} u1;
} IMAGE_THUNK_DATA64;
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint; //028AH
BYTE Name[1]; // “MessagBoxW”
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
练习
1.继续完成实验二十三剩下导入表描述符的分析。
实验二十四:遍历导入表。
我们以32位和64位记事本程序为例:
/*------------------------------------------------------------------------
FileName:PrintImportDescriptor.c
实验24:遍历导入表(支持32位和64位PE)
(c) bcdaren, 2024
-----------------------------------------------------------------------*/
#include <stdio.h>
#include <windows.h>
#include <winnt.h>
#define WIN64
PBYTE creatfilemap(LPCWSTR szFile);
VOID import32(PBYTE lpvResult);
VOID import64(PBYTE lpvResult);
DWORD RvaToFoa(PIMAGE_NT_HEADERS ntHeaders, DWORD rva);
int main(int argc, char* argv[])
{
LPCWSTR szFileName = TEXT("c:\\notepad64.exe");
PBYTE lpAddress = NULL; //PE文件内存映射文件地址
lpAddress = creatfilemap(szFileName);
if (lpAddress)
{
printf("%ls\n", szFileName);
#ifdef WIN64
import64(lpAddress);
#else
import32(lpAddress);
#endif
}
system("pause");
return 0;
}
//创建PE文件映射对象
PBYTE creatfilemap(LPCWSTR szFile)
{
HANDLE hFile;
HANDLE hMapFile = NULL;
PBYTE lpMemory = NULL; //PE文件内存映射文件地址
char buffer[16] = { 0 };
DWORD dwFileSize;
DWORD dwBytesRead = 0;
PIMAGE_DOS_HEADER lpstDOS = NULL;
PIMAGE_NT_HEADERS lpstNT = NULL;
hFile = CreateFile(szFile,
GENERIC_READ, // 只读打开
FILE_SHARE_READ, // 允许其他进程以读取方式打开文件
NULL, // 默认安全属性
OPEN_EXISTING, // 打开已存在的文件
FILE_ATTRIBUTE_NORMAL, // 普通文件
NULL);
if (hFile == INVALID_HANDLE_VALUE)
printf("打开文件失败!\n");
else
{
dwFileSize = GetFileSize(hFile, 0);//获得文件大小可通过结构体获取
//创建内存映射文件
if (dwFileSize)
{
if (hMapFile = CreateFileMapping(hFile,
NULL, PAGE_READONLY, 0, 0, NULL))
{
//获得文件在内存的映象起始位置
lpMemory = MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, 0);
if (!lpMemory)
printf("获取映像起始地址失败!\n");
//检查PE文件是否有效
lpstDOS = (PIMAGE_DOS_HEADER)lpMemory;
if (lpstDOS->e_magic != IMAGE_DOS_SIGNATURE)
printf("非PE格式文件!\n");
lpstNT = (PIMAGE_NT_HEADERS)(lpMemory + lpstDOS->e_lfanew);
if (lpstNT->Signature != IMAGE_NT_SIGNATURE)
printf("非PE格式文件!\n");
}
}
}
return lpMemory;
}
//32位PE文件
VOID import32(PBYTE lpvResult)
{
PIMAGE_DOS_HEADER pImageDOSHeader = (PIMAGE_DOS_HEADER)lpvResult;
PIMAGE_NT_HEADERS32 psImageNTHeader =
(PIMAGE_NT_HEADERS32)(lpvResult + pImageDOSHeader->e_lfanew);
//导入表RVA
DWORD ImportRVA =
psImageNTHeader->OptionalHeader.DataDirectory[1].VirtualAddress;
DWORD dwTemp = 0;
//获取导入表FOA地址
PIMAGE_IMPORT_DESCRIPTOR pImageImportDescriptor =
(PIMAGE_IMPORT_DESCRIPTOR)RvaToFoa((PIMAGE_NT_HEADERS)psImageNTHeader,
ImportRVA);
dwTemp = (DWORD)pImageDOSHeader + (DWORD)pImageImportDescriptor;
pImageImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)dwTemp;
//遍历导入表描述符
for (DWORD i = 0; *(DWORD*)(pImageImportDescriptor + i) != 0; i++)
{
//打印模块名
DWORD NameRVA = (pImageImportDescriptor + i)->Name;
DWORD NameFOA = RvaToFoa((PIMAGE_NT_HEADERS)psImageNTHeader, NameRVA) +
(DWORD)pImageDOSHeader;
printf("%s:\n", (PBYTE)NameFOA);
//获取桥1 RVA地址
DWORD OriginalFirstThunkRVA = (pImageImportDescriptor +
i)->OriginalFirstThunk;
DWORD TimeDate = (pImageImportDescriptor + i)->TimeDateStamp;
if (TimeDate == -1)
{
printf("已绑定导入函数。\n");
}
//IMAGE_ORDINAL_FLAG32用于表示32位的PE文件中导入函数的序号(Ordinal)
if (OriginalFirstThunkRVA & IMAGE_ORDINAL_FLAG32)
{
printf("OriginalFirstThunk最高位为1,按序号导入函数。\n");
}
else
{
//转换为FOA地址
PIMAGE_THUNK_DATA32 pImageFirstThunkData =
(PIMAGE_THUNK_DATA32)RvaToFoa((PIMAGE_NT_HEADERS)psImageNTHeader,
OriginalFirstThunkRVA);
dwTemp = (DWORD)pImageDOSHeader + (DWORD)pImageFirstThunkData;
pImageFirstThunkData = (PIMAGE_THUNK_DATA32)dwTemp;
for (DWORD j = 0; *((DWORD*)pImageFirstThunkData + j) != 0; j++)
{
//INT表
DWORD AddressOfDataRVA = (pImageFirstThunkData +
j)->u1.AddressOfData;
PIMAGE_IMPORT_BY_NAME pImageImportByName =
(PIMAGE_IMPORT_BY_NAME)RvaToFoa((PIMAGE_NT_HEADERS)psImageNTHeader, AddressOfDataRVA);
dwTemp = (DWORD)pImageDOSHeader + (DWORD)pImageImportByName;
pImageImportByName = (PIMAGE_IMPORT_BY_NAME)dwTemp;
//输出函数名
printf("0x%04X : %s\n", pImageImportByName->Hint,
pImageImportByName->Name);
}
printf("\n");
}
}
}
//64位PE文件
VOID import64(PBYTE lpvResult)
{
PIMAGE_DOS_HEADER pImageDOSHeader = (PIMAGE_DOS_HEADER)lpvResult;
PIMAGE_NT_HEADERS64 psImageNTHeader = (PIMAGE_NT_HEADERS64)(lpvResult +
pImageDOSHeader->e_lfanew);
//导入表RVA
DWORD ImportRVA =
psImageNTHeader->OptionalHeader.DataDirectory[1].VirtualAddress;
ULONGLONG dwTemp = 0;
//获取导入表FOA地址
PIMAGE_IMPORT_DESCRIPTOR pImageImportDescriptor =
(PIMAGE_IMPORT_DESCRIPTOR)RvaToFoa((PIMAGE_NT_HEADERS)psImageNTHeader, ImportRVA);
dwTemp = (ULONGLONG)pImageDOSHeader + (ULONGLONG)pImageImportDescriptor;
pImageImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)dwTemp;
//遍历导入表描述符
for (DWORD i = 0; *(DWORD*)(pImageImportDescriptor + i) != 0; i++)
{
//打印模块名
DWORD NameRVA = (pImageImportDescriptor + i)->Name;
ULONGLONG NameFOA = RvaToFoa((PIMAGE_NT_HEADERS)psImageNTHeader,
NameRVA) + (ULONGLONG)pImageDOSHeader;
printf("%s:\n", (PBYTE)NameFOA);
//获取桥1 RVA地址
DWORD OriginalFirstThunkRVA = (pImageImportDescriptor +
i)->OriginalFirstThunk;
DWORD TimeDate = (pImageImportDescriptor + i)->TimeDateStamp;
if (TimeDate == -1)
{
printf("已绑定导入函数。\n");
}
//IMAGE_ORDINAL_FLAG64用于表示64位的PE文件中导入函数的序号(Ordinal)
if (OriginalFirstThunkRVA & IMAGE_ORDINAL_FLAG64)
{
printf("OriginalFirstThunk最高位为1,按序号导入函数。\n");
}
else
{
//转换为FOA地址
PIMAGE_THUNK_DATA64 pImageFirstThunkData =
(PIMAGE_THUNK_DATA64)RvaToFoa((PIMAGE_NT_HEADERS)psImageNTHeader, OriginalFirstThunkRVA);
dwTemp = (ULONGLONG)pImageDOSHeader +
(ULONGLONG)pImageFirstThunkData;
pImageFirstThunkData = (PIMAGE_THUNK_DATA64)dwTemp;
for (ULONGLONG j = 0; *((ULONGLONG*)pImageFirstThunkData + j) != 0; j++)
{
//INT表
ULONGLONG AddressOfDataRVA = (pImageFirstThunkData +
j)->u1.AddressOfData;
PIMAGE_IMPORT_BY_NAME pImageImportByName =
(PIMAGE_IMPORT_BY_NAME)RvaToFoa((PIMAGE_NT_HEADERS)psImageNTHeader, (DWORD)AddressOfDataRVA);
dwTemp = (ULONGLONG)pImageDOSHeader +
(ULONGLONG)pImageImportByName;
pImageImportByName = (PIMAGE_IMPORT_BY_NAME)dwTemp;
//输出函数名
printf("0x%04X : %s\n", pImageImportByName->Hint,
pImageImportByName->Name);
}
printf("\n");
}
}
}
//RVA转FOA
DWORD RvaToFoa(PIMAGE_NT_HEADERS ntHeaders, DWORD rva) {
//ntHeaders+4+sizeof(IMAGE_FILE_HEADER)+FileHeader.SizeOfOptionalHeader(32或64位PE)
PIMAGE_SECTION_HEADER sectionHeader = IMAGE_FIRST_SECTION(ntHeaders);
WORD numberOfSections = ntHeaders->FileHeader.NumberOfSections;
for (WORD i = 0; i < numberOfSections; i++) {
DWORD sectionStartRva = sectionHeader->VirtualAddress;
DWORD sectionEndRva = sectionStartRva + sectionHeader->SizeOfRawData;
if (rva >= sectionStartRva && rva < sectionEndRva) {
DWORD foa = sectionHeader->PointerToRawData + (rva –
sectionStartRva);
return foa;
}
sectionHeader++;
}
return 0; // RVA not found
}
总结
1.遍历导入表的过程:
首先稍等数据目录项的第1项,即导入表项,获取导入表RVA地址,将其转换为FOA地址。
然后根据导入表FOA地址,在PE文件中找到导入表描述符的起始地址,循环遍历获取导入表描述符的OriginalFirstThunk字段中保存的RVA地址,将此RVA地址转换为FOA地址,即INT表IMAGE_THUNK_DATA的起始地址。
最后循环遍历获取INT表内的AddressOfData指向的IMAGE_IMPORT_BY_NAME结构体的RVA地址,将其转换为FOA地址,该地址存储导入函数序号提示值和函数名字符串。
遍历过程如图4-1所示。
图4-1 遍历导入表
2.在遍历导入表时,32位和64位PE文件的差异在于下面两个结构的不同:
typedef struct _IMAGE_NT_HEADERS64 {
DWORD Signature; //PE文件的签名(PE特征码)
IMAGE_FILE_HEADER FileHeader; //文件头
IMAGE_OPTIONAL_HEADER64 OptionalHeader; //64位PE文件扩展头
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature; //PE文件的签名(PE特征码)
IMAGE_FILE_HEADER FileHeader; //文件头
IMAGE_OPTIONAL_HEADER32 OptionalHeader; //32位PE文件扩展头
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
typedef struct _IMAGE_THUNK_DATA64 {
union {
ULONGLONG ForwarderString; // PBYTE
ULONGLONG Function; // PDWORD
ULONGLONG Ordinal;
ULONGLONG AddressOfData; // PIMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA64;
typedef IMAGE_THUNK_DATA64 * PIMAGE_THUNK_DATA64;
typedef struct _IMAGE_THUNK_DATA32 {
union {
DWORD ForwarderString; // PBYTE
DWORD Function; // PDWORD
DWORD Ordinal;
DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA32;
typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;
3.OriginalFirstThunk为导入表描述符IMAGE_IMPORT_DESCRIPTOR结构的桥1字段,最高位为0表示按序号导入,最高位为1表示按名称导入。32位PE中取掩码IMAGE_ORDINAL_FLAG32,64位PE文件取掩码IMAGE_ORDINAL_FLAG64。
4.导入表描述符的TimeDateStamp字段的值如果为0xFFFFFFFF,表示导入函数已绑定,我们将在4.2节绑定导入表中详细讲解。
5.导入表描述符以一个空导入表描述符作为结束标志。
6.INT表中,不同DLL模块中的导入函数名以一个空的AddressOfData字段作为分隔符。
7.读者也可以尝试参考实验19中 ReadPe1.c中使用的方法,按照内存映像文件的方法实现,这样可以省略RVA转换为FOA地址的过程。
相关文章:

《Windows PE》4.1导入表
导入表顾名思义,就是记录外部导入函数信息的表。这些信息包括外部导入函数的序号、名称、地址和所属的DLL动态链接库的名称。Windows程序中使用的所有API接口函数都是从系统DLL中调用的。当然也可能是自定义的DLL动态链接库。对于调用方,我们称之为导入函…...

计算机专业大学生应该如何规划大学四年?
计算机专业的大学生在学习过程中应该注重以下几个方面,以确保他们在快速变化的技术领域中保持竞争力: 基础知识: 数学基础:离散数学、线性代数、概率论等数学课程对于理解算法和数据结构至关重要。编程基础:学习至少一…...

R知识图谱1—tidyverse玩转数据处理120题
以下是本人依据张老师提供的tidyverse题库自行刷题后的tidyverse Rmd文件,部分解法参考张老师提示,部分解法我本人灵感提供 数据下载来源https://github.com/zhjx19/tidyverse120/tree/main/data 参考https://github.com/MaybeBio/R_cheatsheet/tree/mai…...

【赵渝强老师】K8s中的有状态控制器StatefulSet
在K8s中,StatefulSets将Pod部署成有状态的应用程序。通过使用StatefulSets控制器,可以为Pod提供持久存储和持久的唯一性标识符。StatefulSets控制器与Deployment控制器不同的是,StatefulSets控制器为管理的Pod维护了一个有粘性的标识符。无论…...

机器学习笔记(持续更新)
使用matplotlib绘图: import matplotlib.pyplot as plt fig, axplt.subplots() #创建一个图形窗口 plt.show() #不绘制任何内容,直接显示空图 重复值处理: 重复值处理代码: import pandas as pd data pd.DataFrame({学号: [1…...

Nginx 配置之server块
在 Nginx 配置中使用两个 server 块是为了处理 HTTP 和 HTTPS 请求的不同需求。具体来说: 第一个 server 块: 监听 80 端口(HTTP)。将所有 HTTP 请求重定向到 HTTPS(443 端口)。 第二个 server 块ÿ…...

魅族Lucky 08惊艳亮相:极窄四等边设计引领美学新风尚
在这个智能手机设计趋于同质化的时代,魅族以其独特的设计理念和创新技术,再次为市场带来了一股清新之风。 近日,魅族全新力作——Lucky 08手机正式曝光,其独特的“极窄物理四等边”设计瞬间吸引了众多消费者的目光,而…...

自动化的抖音
文件命名 main.js var uiModule require("ui_module.js"); if (!auto.service) {toast("请开启无障碍服务");auto.waitFor();} var isRunning true; var swipeCount 0; var targetSwipeCount random(1, 10); var window uiModule.createUI(); uiMo…...

无人机之巡航控制篇
一、巡航控制的基本原理 无人机巡航控制的基本原理是通过传感器检测无人机的飞行状态和环境信息,并将其反馈给控制器。控制器根据反馈信息和任务需求,计算出无人机的控制指令,并将其发送给执行机构。执行机构根据控制器的控制指令,…...

面试必问的7大测试分类!一文说清楚!
在日常测试工作中,我们经常会听到“单元测试,集成测试,系统测试”之类的词汇,大家都知道这是按照开发阶段进行测试活动的划分。 这种划分完整的分类,其实是分为四种“单元测试,集成测试,系统测…...

深信服上网行为管理AC无法注销在线用户
下图用户认证成功后无法注销 很多入网的用户都是使用的这个账号 针对单个IP强制注销也不生效 解决步骤: 接入管理-用户管理-用户绑定管理-用户绑定 删除绑定免认证的配置 删除后所有用户会强制注销掉,重新登录即可 可添加主页联系方式帮忙远程解决问…...

使用GitLab CI构建持续集成案例
1. 部署GitLab (1)基础准备 解压软件包并导入镜像: [rootmaster ~]# curl -O http://mirrors.douxuedu.com/competition/Gitlab-CI.tar.gz [rootmaster ~]# tar -zxvf Gitlab-CI.tar.gz [rootmaster ~]# ctr -n k8s.io image import gitla…...

WSL2环境下Ubuntu的Docker安装与配置
检查是否存在安装残留,移除可能会造成冲突的组件。 for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done从apt Docker仓库中安装官方GPG key: sudo apt-get update …...

使用vscode调试wails项目(golang桌面GUI)
文章目录 安装 Golang 环境安装 NPM安装 VSCode安装 Go 插件安装 Go 插件依赖工具安装 Wails系统检查 准备项目Visual Studio Code 配置安装和构建步骤参考资料 安装 Golang 环境 访问 golang 官网下载环境安装包:https://go.dev/dl/ 安装 NPM 从 Node 下载页面 …...

Java中注解与反射的详细介绍
注解和反射 一、注解 什么是注解?Annotation Override :表示一个方法声明打算重写超类的另一个方法声明Deprecated:表示不鼓励程序员使用这样的元素,(此注释可以用于修饰方法,属性,类…...

Redis 过期时间删除策略详解
在使用 Redis 时,设置键的过期时间是一项非常有用的功能。它可以帮助我们自动清理不再需要的数据,节省内存空间。那么,Redis 是如何处理这些带有过期时间的键呢?这就涉及到 Redis 的过期时间删除策略。 一、Redis 过期键的判定 R…...

C语言_内存函数
内存函数是 C 标准库中的一组函数,用于管理和操作内存。使用时需要包含头文件<string.h>。 1. memcpy的使用和模拟实现 函数形式如下: void* memcpy(void* destination, const void* source, size_tnum);函数解析和注意事项: memcp…...

基于s32ds平台指定变量储存位置
开启一段ram空间 打开ld文件 配置一段ram空间 开辟一个段 .shareable_data1 : {. ALIGN(4);KEEP(*(.mcal_shared_data1)) . ALIGN(4);} > int_sram_shareable1定义一个变量并将变量指定在段中 volatile uint32 u32SbcmGenState __attribute__((section (".mcal_s…...

什么是快充协议、支持多协议的USB Type-C受电端取电芯片
随着快充技术的不断发展,传统的慢充模式已经满足不了消费者对充电效率的要求。有了快充技术的支持很大程度的缩短了我们的充电时间,给我们的生活带来了很多便利。 什么是快充协议 快充协议是快充技术的核心,现如今市面上已经有很多种快充协议…...

在Vue CLI项目中使用ECharts:详细指南
在Vue CLI项目中使用ECharts:详细指南 ECharts 是一款由百度开源的数据可视化图表库,它提供了丰富的图表类型和高度的可定制性。在Vue CLI项目中集成ECharts,可以帮助我们快速地将数据以图形的方式展示给用户。以下是在Vue CLI项目中使用ECh…...

第二阶段:mysql(学完就隐藏版)
第一章:部署数据库系统(注意关闭防火墙,selinux安装) 安装mysql配置的相关文件:yum install https://dev.mysql.com/get/mysql80-community-release-el7-5.noarch.rpm(centos9:yum install http…...

Spring Cloud微服务
引言 在过去的几十年中,软件架构的发展经历了从单体应用到微服务的演变。微服务架构是一种将应用程序分解为小的、独立的服务的方法,每个服务可以独立地部署和扩展。Spring Cloud为开发和部署基于Spring的微服务提供了一系列的工具和框架,使…...

后端复习资料
后端面试资料 语雀-图灵面试题 语雀-javaGuide 小林coding:https://www.xiaolincoding.com/ 面试鸭: https://www.mianshiya.com/ codehot香菜哥:https://codehot.cn/markdown/welfare 面试题: vizard自行整理 牛客Golang社招面…...

C++和OpenGL实现3D游戏编程【连载14】——VBO、VAO和EBO应用
🔥C和OpenGL实现3D游戏编程【目录】 1、本节实现的内容 我们从一开始学OpenGL到现在,OpenGL的图形绘图必须在glBegin()和glEnd()函数之间完成,在此基础之上,才能进行后续操作功能。但是我们今天要讨论一下OpenGL图形绘制的模式&a…...

AI + 智能互助平台(一点杂想)
随着人工智能的火爆,各种AI产品的兴起,我发现在解决人们日常需求的AI工具还是比较少的,还是比较依赖上一代的搜索功能。 是不是可以有这样的一款产品,需求方在平台上发布需求,提供方在平台上发布能力,AI自动…...

其他浏览器可以联网,但edge不能联网
问题描述: 今早edge无法上网,检测网络连接正常,而且其他chrome,Firefox和360浏览器都可以上网。 解决方案: 注意:为防止是代理问题,可以在扩展中禁用后再试试 如果没有代理或者禁用代理也不…...

Redis 缓存淘汰策略:LRU 和 LFU 的缺点及解决方案详解
引言 Redis 是一款高性能的内存数据库,它的缓存淘汰机制是保障内存使用效率和应用性能的关键。为了在内存有限的情况下保证缓存数据的有效性,Redis 提供了多种缓存淘汰策略,其中 LRU(Least Recently Used,最近最少使用…...

软件工程pipeline梳理
文章目录 软件工程pipeline梳理为什么需要梳理软件工程的pipeline软件工程pipeline的概念与注意点软件工程pipeline中的最大挑战rethink相关资料 软件工程pipeline梳理 为什么需要梳理软件工程的pipeline 反思自己日常工作中的认知和行为。以算法/软件工程师为代表的技术工种往…...

npm运行时出现npm ERR! builtins is not a function报错!
项目场景: 项目运行时什么都没动都没改突然运行不起来了,报错 TypeError: builtins is not a function 代码什么都没动,不是代码问题,排查后只有可能是node和npm的问题,所以卸载掉node重装重启 解决方案: …...

2024年软件设计师中级(软考中级)详细笔记【5】软件工程基础知识上(分值10+)
第5章软件工程 目录 前言第5章 软件工程基础知识(上)(分值10)5.1 软件工程概述5.1.4 软件过程 5.2 软件过程模型5.2.1 瀑布模型 (Waterfall Model)5.2.2 增量模型5.2.3 演化模型5.2.4 喷泉模型(Water Fountain Model&a…...