首页
论坛
课程
招聘
[原创]PE头解析-字段说明
2022-9-22 22:32 3119

[原创]PE头解析-字段说明

2022-9-22 22:32
3119

PE头解析-字段说明

一、什么是可执行文件

1、可执行文件(executable file)指的是可以由操作系统进行加载执行的文件。

2、可执行文件的格式:

  • Windows平台:
    • PE(Portable Executable)文件结构
  • Linux平台:
    • ELF(Executable and Linking Format)文件结构

二、如何识别PE文件

1、PE文件的特征(PE指纹)

分别打开.exe .dll .sys等文件,观察特征前2个字节,都是4D5A,变成字母是MZ,是DOS系统开发人员的一个名字,再看3C字节是什么,3C存的是一个地址,这个地址对应的5045,变为字母就是PE。

2、不要仅仅通过文件的后缀名来认定PE文件

三、对齐

对齐是为了读写速度,找起来快,用空间换时间

1、硬盘对齐(200h)

早期计算机硬盘水平比较低,只有很小的几个G,节与节之间的空隙比较小,硬盘对齐为200H,后来随着硬件水平提高了,硬盘对齐与内存对齐一样了为1000H

2、内存对齐(1000h)

四、为什么要分节

1、节省硬盘空间.(这个不是决定的,由编译器决定)

硬盘间隔小,内存间隔大,这是老的编译器

 

任何一个exe程序都会有一个自己独立的4G内存空间,虚拟内存

 

2G是平时写应用程序用的,2G是给操作系统用的

 

这里注意:还有一些exe程序 当我们用winhex打开时

 

它在硬盘上和内存中是一样的

 

这个时候我们要有两个概念 就是硬盘对齐(200h字节)和内存对齐(1000h字节),它是为了增加读写速度

2、一个应用程序多开

比如一个qq程序有两个部分,数据1(可读),数据2(可读可写) ,都是100M,我运行一个账号就需要200M,我再开一个,数据1(可读)已经有了,就没必要在复制一份丢在内存中,只需要复制一份数据2就可以了

 

img

 

PE磁盘文件与内存映像图

 

img

 

节表(块表): PE文件中所有节的属性都被定义在节表中

 

PE文件头/DOS头:对当前整个exe程序做概要性描述

DOS头(64个字节)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
typedef struct IMAGE_DOS_HEADER{
      WORD e_magic;            //DOS头的标识,为4Dh5Ah。分别为字母MZ
      WORD e_cblp;
      WORD e_cp;
      WORD e_crlc;
      WORD e_cparhdr;
      WORD e_minalloc;
      WORD e_maxalloc;
      WORD e_ss;
      WORD e_sp;
      WORD e_csum;
      WORD e_ip;
      WORD e_cs;
      WORD e_lfarlc;
      WORD e_ovno;
      WORD e_res[4];
      WORD e_oemid;
      WORD e_oeminfo;
      WORD e_res2[10];
      DWORD e_lfanew;           //指向IMAGE_NT_HEADERS的所在(PE头相对于文件的偏移,用于定位PE文件)
}IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
0x00 WORD e_magic; //5A4D * MZ标记用于判断是否为可执行文件
0x02 WORD e_cblp; //0090
0x04 WORD e_cp; //0003
0x06 WORD e_crlc; //0000
0x08 WORD e_cparhdr; //0004
0x0a WORD e_minalloc; //0000
0x0c WORD e_maxalloc; //FFFF
0x0e WORD e_ss; //0000
0x10 WORD e_sp; //00B8
0x12 WORD e_csum; //0000
0x14 WORD e_ip; //0000
0x16 WORD e_cs; //0000
0x18 WORD e_lfarlc; //0040
0x1a WORD e_ovno; //0000
0x1c WORD e_res[4]; //0000000000000000
0x24 WORD e_oemid; //0000
0x26 WORD e_oeminfo; //0000
0x28 WORD e_res2[10]; //20
0x3c DWORD e_lfanew; //00000080 * PE头相对于文件的偏移,用于定位PE文件

从文件开始的地方算,过80个字节,就是PE文件真正开始的地方

 

image-20220112125230743

 

中间这一部分大小是不确定的

 

留了一块空间,可以放一些随意的数据

 

image-20220112125355815

PE文件头(NT头)

1
2
3
4
5
typedef struct IMAGE_NT_HEADERS{
0x00      DWORD Signature;                    //PE标识
0x04      IMAGE_FILE_HEADER FileHeader;        //标准PE头(20字节)
0x18      IMAGE_OPTIONAL_HEADER32 OptionalHeader;  //扩展PE头(大小不确定)
}IMAGE_NT_HEADERS,*PIMAGE_NT_HEADERS;

标准PE头(20字节)

1
2
3
4
5
6
7
0x00 WORD Machine;  //014C * 程序运行的CPU型号,0x0任何处理器/0x14C Intel 386及后续处理器
0x02 WORD NumberOfSections; //0008 * 文件中存在的节的总数,除了头,还有几节数据,如果要新增节或者合并节就要修改这个值.
0x04 DWORD TimeDateStamp; //3E22F0DF * 时间戳:文件的创建时间(和操作系统的创建时间无关),编译器填写的
0x08 DWORD PointerToSymbolTable; //00000000
0x0c DWORD NumberOfSymbols; //00000000
0x10 WORD SizeOfOptionalHeader; //00E0 * 可选PE头的大小,32位PE文件默认E0h=16*1464位PE文件默认为F0h,大小可以自定义
0x12 WORD Characteristics; //010E * 每个位有不同的含义,可执行文件值为10F 0 1 2 3 8位置1

TimeDateStamp

 

.map文件是对.exe文件中函数的描述,对.exe文件的说明

 

.map文件和.exe文件 不同步时

 

就是检查 时间戳是否 一致

 

Characteristics

 

image-20220112153714023

 

打勾的 即为1

 

把所有值 对应起来 0 1 0 E

 

(第六位是标志保留位,但是没有显示出来)

1
0000 0001 0000 1110

image-20220112153733828

可选PE头(大小是不确定的)

程序入口 + 内存镜像基址 才是真正的地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
0x00 WORD Magic; * 说明文件类型:10B->32位下的PE文件 20B->64位下的PE文件
0x02 BYTE MajorLinkerVersion;
0x03 BYTE MinorLinkerVersion;
0x04 DWORD SizeOfCode; * 代码大小/所有代码节的和,必须是FileAlignment的整数倍 编译器填的 没用
0x08 DWORD SizeOfInitializedData; * 已初始化数据大小的和,必须是 FileAlignment的整数倍 编译器填的 没用
0x0c DWORD SizeOfUninitializedData; * 未初始化数据大小的和,必须是 FileAlignment的整数倍 编译器填的 没用
0x10 DWORD AddressOfEntryPoint; * 程序入口
0x14 DWORD BaseOfCode; * 代码开始的基址,编译器填的 没用
0x18 DWORD BaseOfData; * 数据开始的基址,编译器填的 没用
0x1c DWORD ImageBase; * 内存镜像基址
0x20 DWORD SectionAlignment; * 内存对齐,区段对齐
0x24 DWORD FileAlignment; * 文件对齐
0x28 WORD MajorOperatingSystemVersion;
0x2a WORD MinorOperatingSystemVersion;
0x2c WORD MajorImageVersion;
0x2e WORD MinorImageVersion;
0x30 WORD MajorSubsystemVersion;
0x32 WORD MinorSubsystemVersion;
0x34 DWORD Win32VersionValue;
0x38 DWORD SizeOfImage; *镜像大小/ 内存中整个PE文件的映射的尺寸,可以比实际的值大,但必须是SectionAlignment的整数倍,是拉伸之后的大小,,,
0x3c DWORD SizeOfHeaders; * 所有头+节表,技照‘文件对齐 FileAlignment’后的大小,否则加载会出错
0x40 DWORD CheckSum; * 校验和,一些系统文件有要求,用来判断文件是否被修改
0x44 WORD Subsystem;
0x46 WORD DllCharacteristics;
0x48 DWORD SizeOfStackReserve; * 初始化时保留的堆栈大小
0x4c DWORD SizeOfStackCommit; * 初始化时实际提交的大小
0x50 DWORD SizeOfHeapReserve; * 初始化时保留的堆大小
0x54 DWORD SizeOfHeapCommit; * 初始化时实践提交的大小
0x58 DWORD LoaderFlags;
0x5c DWORD NumberOfRvaAndSizes; * 目录项数目,RVA数目和大小
0x60 _IMAGE_DATA_DIRECTORY DataDirectory[16]; 16个结构体,每个结构体是8个字节

进行了拉伸,完成之后完全遵守操作系统,就可以执行了

 

程序入口 + 内存镜像基址 才是真正的入口点地址

 

image-20220828181443181

编写程序读取一个.exe文件,输出所有的PE头信息.

第一种通过指针偏移方式实现
 

代码有个极大的缺点,就是只能加载打印特定固定的notepad.exe文件,而且在
不同的操作系统下面会有异常,可移植性差

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
int* OpenFile()
{
    FILE* PointToFile = NULL;
    int FileSize = 0;
    int* StrBuffer = NULL;
    int Num = 0;
 
    //打开文件
    if ((PointToFile = fopen("C:\\notepad.exe","rb")) == NULL) {
        printf("打开文件失败!\n");
        exit(1);
    }
 
    //获取文件大小
    fseek(PointToFile,0,2);
    FileSize = ftell(PointToFile);
 
    //重定位指针
    fseek(PointToFile,0,0);
 
    //buffer指向申请的堆
    StrBuffer = (int*)(malloc(FileSize));
    if (!StrBuffer)
    {
        printf("堆空间分配失败!\n");
        free(StrBuffer);
        return 0;
    }
 
    //读取文件内容
    Num = fread(StrBuffer,FileSize,1,PointToFile);
    if (!Num)
    {
        printf("读取文件内容失败!\n");
        free(StrBuffer);
        return 0;
    }
 
    //关闭文件
    fclose(PointToFile);
 
    //将缓冲区内的文件内容的地址返回到调用函数的地方
    return StrBuffer;
}
 
int* FileSizes = OpenFile();
 
int PrintfNtHeaders()
{
    //文件指针
    unsigned int* PointBuffer = (unsigned int*)FileSizes;
    unsigned short* pBuffer = (unsigned short*)PointBuffer;
    unsigned char* pcBuffer = (unsigned char*)PointBuffer;
 
    //判断MZ和PE的标志
    unsigned short Cmp1 = 0x5A4D;
    unsigned int Cmp2 = 0x00004550;
 
    //判断文件是否读取成功
    if(!PointBuffer)
    {
        printf("文件读取失败!\n");
        free(PointBuffer);
        return 0;
    }
 
    //判断是否为MZ标志
    if (*pBuffer != Cmp1)
    {
        printf("不是有效MZ标志!\n");
        printf("%X\n",*pBuffer);
        free(PointBuffer);
        return 0;
    }
    printf("*********打印DOS头*********\n");
    printf("e_magic:\t\t\t%X\n",*(pBuffer));
    printf("e_ifanew:\t\t\t%08X\n\n\n",*(PointBuffer+15));
 
    //判断是否为PE标志
    if (*(PointBuffer+56) != Cmp2)
    {
        printf("不是有效的PE标志!\n");
        printf("%X\n",*(PointBuffer+56));
        free(PointBuffer);
        return 0;
    }
 
    printf("*********打印标准PE文件头*********\n");
 
    printf("PE标志:\t\t\t\t%X\n",*(PointBuffer+56));
 
    printf("Machine:\t\t\t%04X\n",*(pBuffer+114));
    printf("NumberOfSection:\t\t%04X\n",*(pBuffer+115));
    printf("TimeDateStamp:\t\t\t%08X\n",*(PointBuffer+58));
    printf("PointerToSymbolTable:\t\t%08X\n",*(PointBuffer+59));
    printf("NumberOfSymbols:\t\t%08X\n",*(PointBuffer+60));
    printf("SizeOfOptionalHeader:\t\t%04X\n",*(pBuffer+122));
    printf("Chrarcteristics:\t\t%04X\n\n\n",*(pBuffer+123));
 
    printf("*********打印标准可选PE头*********\n");
 
    printf("Magic:\t\t\t\t%04X\n", *(pBuffer+124));
    printf("MajorLinkerVersion:\t\t%02X\n", *(pcBuffer+250));
    printf("MinorLinkerVersion:\t\t%02X\n", *(pcBuffer+251));
    printf("SizeOfCode:\t\t\t%08X\n", *(PointBuffer+63));
    printf("SizeOfInitializedData:\t\t%08X\n", *(PointBuffer+64));
    printf("SizeOfUninitializedData:\t%08X\n", *(PointBuffer+65));
    printf("AddressOfEntryPoint:\t\t%08X\n", *(PointBuffer+66));
    printf("BaseOfCode:\t\t\t%08X\n", *(PointBuffer+67));
    printf("BaseOfData:\t\t\t%08X\n", *(PointBuffer+68));
    printf("ImageBase:\t\t\t%08X\n", *(PointBuffer+69));
    printf("SectionAlignment:\t\t%08X\n", *(PointBuffer+70));
    printf("FileAlignment:\t\t\t%08X\n", *(PointBuffer+71));
    printf("MajorOperatingSystemVersion:\t%04X\n", *(pBuffer+144));
    printf("MinorOperatingSystemVersion:\t%04X\n", *(pBuffer+145));
    printf("MajorImageVersion:\t\t%04X\n", *(pBuffer+146));
    printf("MinorImageVersion:\t\t%04X\n", *(pBuffer+147));
    printf("MajorSubsystemVersion:\t\t%04X\n", *(pBuffer+148));
    printf("MinorSubsystemVersion:\t\t%04X\n", *(pBuffer+149));
    printf("Win32VersionValue:\t\t%08X\n", *(PointBuffer+75));
    printf("SizeOfImage:\t\t\t%08X\n", *(PointBuffer+76));
    printf("SizeOfHeaders:\t\t\t%08X\n", *(PointBuffer+77));
    printf("CheckSum:\t\t\t%08X\n", *(PointBuffer+78));
    printf("Subsystem:\t\t\t%04X\n", *(pBuffer+158));
    printf("DllCharacteristics:\t\t%04X\n", *(pBuffer+159));
    printf("SizeOfStackReserve:\t\t%08X\n", *(PointBuffer+80));
    printf("SizeOfStackCommit:\t\t%08X\n", *(PointBuffer+81));
    printf("SizeOfHeapReserve:\t\t%08X\n", *(PointBuffer+82));
    printf("SizeOfHeapCommit:\t\t%08X\n", *(PointBuffer+83));
    printf("LoaderFlags:\t\t\t%08X\n", *(PointBuffer+84));
    printf("NumberOfRvaAndSizes:\t\t%08X\n", *(PointBuffer+85));
 
    free(PointBuffer);
    return 0;
}
 
int main()
{
    PrintfNtHeaders();
    OpenFile();
    return 0;
}
第二种写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <malloc.h>
 
#define F_PATH "C:\\cntflx\\ipmsg.exe"
 
FILE* open_file(char* file_path,char* open_mode);
int compute_file_size(FILE* file_address);
char* allocate_buffer(int file_size);
char* readfile2memory(char* file_buffer,int file_size,FILE* file_address);
void analysis_PE_head(char* File_buffer);
 
VOID PrintNTHeaders()
{
    // 初始化
    //char file_path[] = "C:\\Windows\\System32\\notepad.exe";
    char file_path[] = F_PATH;
    char open_mode[] = "rb";
    // 打开文件,返回文件指针
    FILE* file_address = open_file(file_path,open_mode);
    // 计算文件长度
    int file_size = compute_file_size(file_address);
    // 分配内存
    char* File_buffer = allocate_buffer(file_size);
    // 写入内存,返回内存地址
    File_buffer = readfile2memory(File_buffer,file_size,file_address);
    // 打印PE头部信息
    analysis_PE_head(File_buffer);
    // 释放内存、关闭文件流
    free(File_buffer);
    fclose(file_address);
}
 
FILE* open_file(char* file_path,char* open_mode)
{
    FILE* file_address = fopen(file_path,open_mode);  // fopen() 参数是字符串也就是常量指针类型
    if(!file_address)
    {
        printf("打开文件失败!\r\n");
        return 0;
    }
    return file_address;
}
 
int compute_file_size(FILE* file_address)
{
    int size = 0;
    fseek(file_address,0,SEEK_END);
    size = ftell(file_address);
    fseek(file_address,0,SEEK_SET);
    return size;
}
 
char* allocate_buffer(int file_size)
{
    char* file_buffer = (char*)malloc(file_size);
    if(!file_buffer)
    {
        printf("申请内存失败!\r\n");
        return 0;
    }
    memset(file_buffer,0,file_size);
    return file_buffer;
}
 
char* readfile2memory(char* file_buffer,int file_size,FILE* file_address)
{
    if(!(fread(file_buffer,file_size,1,file_address)))
    {
        printf("从文件向内存中读取数据失败!\r\n");
        return 0;
    }
    return file_buffer; // 如果写入内存成功,则返回内地址
}
 
void analysis_PE_head(char* File_buffer)
{
    // 实例化PE文件头几个结构体
    PIMAGE_DOS_HEADER pDosHeader = NULL;
    PIMAGE_NT_HEADERS pNTHeader = NULL;
    PIMAGE_FILE_HEADER pPEHeader = NULL;
    PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
    PIMAGE_SECTION_HEADER pSectionHeader = NULL;
    // 强制类型转换
    pDosHeader = (PIMAGE_DOS_HEADER)File_buffer;
    // 判断是不是有效的MZ标志
    if(*((PWORD)pDosHeader) != IMAGE_DOS_SIGNATURE)
    {
        printf("不是有效的MZ标志!\r\n");
        free(File_buffer);
        return;
    }
    // 强制类型转换 PIMAGE_DOS_HEADER结构体
    pDosHeader = (PIMAGE_DOS_HEADER)File_buffer;
    // 打印DOS头
    printf("=============================DOS头信息如下=============================\r\n");
    printf("MZ标志:\t\t\t%04X\r\n",pDosHeader->e_magic);
    printf("PE偏移:\t\t\t%08X\r\n",pDosHeader->e_lfanew);
 
    // 判断是不是有效的PE标志
    if(*((PDWORD)((DWORD)File_buffer+pDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE)
    {
        printf("不是有效的PE标志!\r\n");
        free(File_buffer);
        return;
    }
    // 强制类型转换 PIMAGE_NT_HEADERS结构体
    pNTHeader = PIMAGE_NT_HEADERS((DWORD)File_buffer+pDosHeader->e_lfanew);
    // 打印NT头
    printf("=============================NT头信息如下===============================\r\n");
    printf("NT:\t\t\t\t%04X\r\n",pNTHeader->Signature);
    // 强制类型转换 PIMAGE_FILE_HEADER结构体
    pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader+4);
    // 打印标准PE文件头
    printf("=============================标准PE头信息如下============================\r\n");
    printf("PE_machine:\t\t\t%04X\r\n",pPEHeader->Machine);
    printf("NumberOfSections:\t\t%04X\n",pPEHeader->NumberOfSections);
    printf("SizeOfOptionalHeader:\t\t%04X\r\n",pPEHeader->SizeOfOptionalHeader);
    // 强制类型转换
    pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader+IMAGE_SIZEOF_FILE_HEADER);//
    // 打印可选PE头
    printf("==============================可选PE头信息如下==============================\r\n");
    printf("Magic:\t\t\t\t%04X\r\n",pOptionHeader->Magic);
    printf("AddressOfEntryPoint:\t\t%08X\r\n",pOptionHeader->AddressOfEntryPoint);
    printf("ImageBase:\t\t\t%08X\r\n",pOptionHeader->ImageBase);
    printf("SizeOfImage:\t\t\t%08X\r\n",pOptionHeader->SizeOfImage);
    printf("SizeOfHeaders:\t\t\t%08X\r\n",pOptionHeader->SizeOfHeaders);
    printf("SectionAlignment:\t\t%08X\r\n",pOptionHeader->SectionAlignment);
    printf("FileAlignment:\t\t\t%08X\r\n",pOptionHeader->FileAlignment);
    // 强制类型转换
    pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader+pPEHeader->SizeOfOptionalHeader);
    printf("==============================节表信息如下===============================\n");
    //printf("name:%s\n",pSectionHeader->Misc);
    DWORD dwNumberOfSection = pPEHeader->NumberOfSections;
    /*
    printf("%x\n",pPEHeader->NumberOfSections);
    printf("IMAGE_SIZEOF_SHORT_NAME:%x\n",IMAGE_SIZEOF_SHORT_NAME);
    printf("option_add:%x\n",pOptionHeader);
    printf("psection_add:%x\n",pSectionHeader);
    printf("==============================================================\n");*/
    for(DWORD i = 0;i<dwNumberOfSection;i++,pSectionHeader++)
    {
        printf("========================第%d个节信息:===============================\n",i+1);
        printf("section_name:");
        for(DWORD j = 0;j<IMAGE_SIZEOF_SHORT_NAME;j++)
        {
            printf("%c",pSectionHeader->Name[j]);
        }
        printf("\r\n");
        printf("Misc:\t\t\t\t%08X\r\n",pSectionHeader->Misc);
        printf("VirtualAddress:\t\t\t%08X\r\n",pSectionHeader->VirtualAddress);
        printf("SizeOfRawData:\t\t\t%08X\r\n",pSectionHeader->SizeOfRawData);
        printf("PointerToRawData:\t\t%08X\r\n",pSectionHeader->PointerToRawData);
        printf("Characteristics:\t\t%08X\r\n",pSectionHeader->Characteristics);
    }
}
 
int main(int argc, char* argv[])
{
    PrintNTHeaders();
    //getchar();
    return 0;
}

看雪招聘平台创建简历并且简历完整度达到90%及以上可获得500看雪币~

收藏
点赞0
打赏
分享
最新回复 (2)
雪    币: 6313
活跃值: 活跃值 (1103)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
chengdrgon 活跃值 2022-9-23 11:48
2
0
感谢分享
雪    币: 43
活跃值: 活跃值 (26)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
傲雪丶三生 活跃值 2022-9-25 21:32
3
0
楼主能请教一个问题吗?一个pe文件运行后,写内存破坏MZ标记是不是会导致无法启动新的线程(调用NtCreateThread启动)?如果是有什么办法在MZ标记被破坏的情况下(不手动修复)启动线程吗?
游客
登录 | 注册 方可回帖
返回