PE

新手——010纯手工编辑打造PE文件

Weaving https://mp.weixin.qq.com/s/M6vBxRrLnSzPD5ZxAxKhpQ
4月27日

工具


1.010Editor :填充16进制码

2.lordPE     :检查PE文件出错原因


知识背景


1.  PE知识   :(PE中结构体的字段分布,PE加载器的原理)

2.  汇编知识 :(汇编调用MessageBoxA个ExitProcess)

扫描二维码关注公众号,回复: 1710613 查看本文章


目标:构造一个程序(CUI).exe


程序功能:

1. 调用MessageBoxA弹出消息框

2. 调用ExitProcess退出程序


思路


1.在Windows下PE文件格式的程序如何运行?

答:符合PE格式的文件+系统加载器,如何成功运行程序,失败,报错。


2.那么系统加载的原理或者执行逻辑又是什么呢?

答:

加载器:简单执行流程如下

  • 1.内存映射

  • 2.修复IAT

  • 3.修复重定位

tip:本次程序并没有重定位,只需要考虑前2步即可。


3.PE文件如何构建?还可以做哪些优化呢?

答:

构建:

1.Dos头:IMAGE_DOS_HEADER;

1.1->WORD   e_magic; //标识符:MZ

1.2->LONG   e_lfanew;  //Dos头部的大小


2.NT头:IMAGE_NT_HEADERS32

2.1->DWORD Signature;

2.2->IMAGE_FILE_HEADER FileHeader;

2.3->IMAGE_OPTIONAL_HEADER32 OptionalHeader;


3.区段头:IMAGE_SECTION_HEADER

3.1 ->.text

3.2 ->.rdata


4.区段数据:数据

4.1 ->代码数据(OpCode) == 200+

4.2 ->导入数据(IAT、导入表、INT、HitName) == 200+


优化:

1.DosStub是个历史遗留问题,在一定情况下也可在该区域填写代码,但在本次程序可以将该区域删除。

2.区段减少到2个,.text、.rdata。(ps:其实还可以减少到只有一个区段.text)

3.对程序运行无影响的字段用0填充。



构造PE步骤


1.整理以上思路大致可以得到以下思维导图

仔细观察PE文件的格式,就不难发现到处都体现出了头部+身体,目录+内容的管理数据的思想,那么对于我们自己手工建立PE文件,同样也可以以此作为思路开始构建PE文件。(在构建时最好同时去做这两件事,有助于加快进度)


1.组成头部:

1.1-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;



关键字段:
 1.WORD   e_magic; ==> MZ  => 4D 5A (标识符)
 2.LONG   e_lfanew; ==> 40h => 40 00 00 00(DosStub去除后,IMAGE_DOS_HEADER 的结构体大小就是Dos头大小)


4D 5A
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00
00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
40 00 00 00


1.2-NT头


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


关键字段:

1.2.1.DWORD Signature;                           ==> PE ==>50 45 00 00 (标识符)

1.2.2IMAGE_FILE_HEADER FileHeader; 

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;


关键字段


2.1 WORD    Machine;                          ==> x86  ==>   4c 01 (0x014c 应用的机器型号)

2.2 WORD    NumberOfSections;         ==> 2个  ==>   02 00 (1.text 2.rdata)

2.3 WORD    SizeOfOptionalHeader;   ==> E0    ==>    E0 00 (NT扩展头大小)

2.4 WORD    Characteristics;               ==> 10F  ==>     0F 01 (可以自定义)

1.2.3. IMAGE_OPTIONAL_HEADER32 OptionalHeader;

typedef struct _IMAGE_OPTIONAL_HEADER {
   //
   // Standard fields.
   //
   WORD    Magic;
   BYTE    MajorLinkerVersion;
   BYTE    MinorLinkerVersion;
   DWORD   SizeOfCode;
   DWORD   SizeOfInitializedData;
   DWORD   SizeOfUninitializedData;
   DWORD   AddressOfEntryPoint;
   DWORD   BaseOfCode;
   DWORD   BaseOfData;
   //
   // NT additional fields.
   //
   DWORD   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;
   DWORD   SizeOfStackReserve;
   DWORD   SizeOfStackCommit;
   DWORD   SizeOfHeapReserve;
   DWORD   SizeOfHeapCommit;
   DWORD   LoaderFlags;
   DWORD   NumberOfRvaAndSizes;
   IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;


*PIMAGE_OPTIONAL_HEADER32;


关键字段


3.1 WORD    Magic;                                            ==> 10B    ==>0B 01 (文件类型:PE32文件)


3.2 DWORD   AddressOfEntryPoint;                  ==> 1000   ==>00 10 00 00(.text在文件中200h位置,转内存偏移后为1000h)


3.2 DWORD   ImageBase;                                 ==> 40000 ==>00 00 04 00 (PE文件在内存的优先加载起始地址VA)


3.3 DWORD   SectionAlignment;                       ==> 1000h ==>00 10 00 00 (内存对齐)


3.4 DWORD   FileAlignment;                             ==> 200h   ==>00 02 00 00 (文件对齐)


3.5 WORD    MajorOperatingSystemVersion;   ==> 6         ==>06 00


3.6 WORD    MajorSubsystemVersion;             ==> 6         ==>06 00


3.7 DWORD   SizeOfImage;                              ==>207E   ==> 7E 20 00 00 (加载到内存中镜像大小)


3.8 DWORD   SizeOfHeaders;                          ==>200h   ==> 00 02 00 00  (Dos+NT+Section :在文件中未超过200h)


3.9 WORD    Subsystem;                                ==> 3        ==>03 00        (使用界面的子系统:(CUI) subsystem)   


3.10 WORD    DllCharacteristics;                    ==> 8100  ==>00 81        (DLL文件属性)


3.11 DWORD   NumberOfRvaAndSizes;        ==> 10h    ==>10 00        (表示数据目录结构的数量.默认16个)


3.12 IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];

          ==>Import Table                                   ==>  10 20 00 00  (.rdata 在400h处,rva: 2000h+10h(IAT字节数)) 

                                                                       ==>  3C 00 00 00    (有2个DLL,即2个导入表结构体(结尾为20个00),即3Ch个字节)

——————————————–

50 45 00 00


4c 01 
02 00
00 00 00 00 00 00 00 00 00 00 00 00
E0 00
0F 01


0B 01
00
00
00 00 00 00
00 00 00 00
00 00 00 00
00 10 00 00
00 00 00 00
00 00 00 00

00 00 40 00
00 10 00 00 
00 02 00 00

06 00 
00 00 
00 00 
00 00 
06 00 
00 00
00 00 00 00
7E 20 00 00
00 02 00 00
00 00 00 00
03 00
00 81

00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
10 00 00 00

00 00 00 00 00 00 00 00
10 20 00 00 3C 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00

——————————————–

1.3-区段头

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;

关键字段


1.text

1.1 BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];      ==>.text            ==>   2E 74 65 78 74 00 00 00(区段名称)

1.2 DWORD   VirtualSize;                                                  ==>22h             ==>   22 00 00 00 (有效的OpCode数目)

1.3 DWORD   VirtualAddress;                                            ==>1000h         ==>   00 10 00 00 (区段rva:200h映射到内存中为1000h)

1.4 DWORD   SizeOfRawData;                                          ==>200h           ==>   00 02 00 00 (在文件中的对齐后大小)

1.5 DWORD   PointerToRawData;                                     ==>200h           ==>   00 02 00 00 (在文件中相对与0的偏移)

1.6 DWORD   Characteristics;                                            ==>E00000E0  ==>  E0 00 00 E0 (区段属性可以自定义)


2.rdata

1.1 BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];      ==>.rdata          ==>   2E 72 64 61 74 61 00 00(区段名称)

1.2 DWORD   VirtualSize;                                                  ==>7Eh             ==>   7E 00 00 00 (有效的字节数目)

1.3 DWORD   VirtualAddress;                                            ==>2000h         ==>   00 20 00 00 (区段rva:400h映射到内存中为2000h)

1.4 DWORD   SizeOfRawData;                                          ==>200h           ==>   00 02 00 00 (在文件中的对齐后大小)

1.5 DWORD   PointerToRawData;                                     ==>400h           ==>   00 04 00 00 (在文件中相对与0的偏移)

1.6 DWORD   Characteristics;                                            ==>E00000E0  ==>  E0 00 00 E0 (区段属性可以自定义)

——————————————–

2E 74 65 78 74 00 00 00
22 00 00 00
00 10 00 00
00 02 00 00
00 02 00 00
00 00 00 00
00 00 00 00
00 00
00 00
E0 00 00 E0

2E 72 64 61 74 61 00 00
7E 00 00 00
00 20 00 00
00 02 00 00
00 04 00 00
00 00 00 00
00 00 00 00
00 00
00 00
E0 00 00 E0

——————————————–

tip:200对齐其余填0


2.组成身体:


2.1-区段1.text

伪代码如下:

<b>push 0x0;</b>
<b>push 0x0;</b>
<b>push 0x0;</b>
<b>push 0x0;</b>
<b>call MessageBoxA;</b>
<b>push 0x0;</b>
<b>call ExitProcess;</b>

vs程序IAT调用为:FF 15 XXXXXXXX(IAT地址)(有2个dll)

IAT1:FOA:400h ==> RVA: 2000h ==>VA:400000h + 2000h==>402000h

IAT2:FOA:408h ==> RVA: 2008h ==>VA:400000h + 2008h==>402008h

——————————————–

6A 00
6A 00
6A 00
6A 00
FF 15 00 20 40 00
6A 00
FF 15 08 20 40 00

——————————————–

tip:200对齐其余填0


2.2-区段2.rdata




1.IAT:

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;


关键字段:
 1.WORD   e_magic; ==> MZ  => 4D 5A (标识符)
 2.LONG   e_lfanew; ==> 40h => 40 00 00 00(DosStub去除后,IMAGE_DOS_HEADER 的结构体大小就是Dos头大小)


4D 5A
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00
00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
40 00 00 00


1.2-NT头


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


关键字段:

1.2.1.DWORD Signature;                           

==> PE ==>50 45 00 00 (标识符)


1.2.2IMAGE_FILE_HEADER FileHeader; 


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;

关键字段


2.1 WORD    Machine;                          ==> x86  ==>   4c 01 (0x014c 应用的机器型号)

2.2 WORD    NumberOfSections;         ==> 2个  ==>   02 00 (1.text 2.rdata)

2.3 WORD    SizeOfOptionalHeader;   ==> E0    ==>    E0 00 (NT扩展头大小)

2.4 WORD    Characteristics;               ==> 10F  ==>     0F 01 (可以自定义)


1.2.3. IMAGE_OPTIONAL_HEADER32 OptionalHeader;

typedef struct _IMAGE_OPTIONAL_HEADER {
   //
   // Standard fields.
   //
   WORD    Magic;
   BYTE    MajorLinkerVersion;
   BYTE    MinorLinkerVersion;
   DWORD   SizeOfCode;
   DWORD   SizeOfInitializedData;
   DWORD   SizeOfUninitializedData;
   DWORD   AddressOfEntryPoint;
   DWORD   BaseOfCode;
   DWORD   BaseOfData;
   //
   // NT additional fields.
   //
   DWORD   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;
   DWORD   SizeOfStackReserve;
   DWORD   SizeOfStackCommit;
   DWORD   SizeOfHeapReserve;
   DWORD   SizeOfHeapCommit;
   DWORD   LoaderFlags;
   DWORD   NumberOfRvaAndSizes;
   IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

关键字段


3.1 WORD    Magic;                                            ==> 10B    ==>0B 01 (文件类型:PE32文件)

3.2 DWORD   AddressOfEntryPoint;                  ==> 1000   ==>00 10 00 00(.text在文件中200h位置,转内存偏移后为1000h)

3.2 DWORD   ImageBase;                                 ==> 40000 ==>00 00 04 00 (PE文件在内存的优先加载起始地址VA)

3.3 DWORD   SectionAlignment;                       ==> 1000h ==>00 10 00 00 (内存对齐)
3.4 DWORD   FileAlignment;                             ==> 200h   ==>00 02 00 00 (文件对齐)

3.5 WORD    MajorOperatingSystemVersion;   ==> 6         ==>06 00

3.6 WORD    MajorSubsystemVersion;             ==> 6         ==>06 00

3.7 DWORD   SizeOfImage;                              ==>207E   ==> 7E 20 00 00 (加载到内存中镜像大小)

3.8 DWORD   SizeOfHeaders;                          ==>200h   ==> 00 02 00 00  (Dos+NT+Section :在文件中未超过200h)

3.9 WORD    Subsystem;                                ==> 3        ==>03 00        (使用界面的子系统:(CUI) subsystem)   

3.10 WORD    DllCharacteristics;                    ==> 8100  ==>00 81        (DLL文件属性)

3.11 DWORD   NumberOfRvaAndSizes;        ==> 10h    ==>10 00        (表示数据目录结构的数量.默认16个)

3.12 IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];

          ==>Import Table                                   ==>  10 20 00 00  (.rdata 在400h处,rva: 2000h+10h(IAT字节数)) 

                                                                       ==>  3C 00 00 00    (有2个DLL,即2个导入表结构体(结尾为20个00),即3Ch个字节)

——————————————–

50 45 00 00


4c 01 
02 00
00 00 00 00 00 00 00 00 00 00 00 00
E0 00
0F 01


0B 01
00
00
00 00 00 00
00 00 00 00
00 00 00 00
00 10 00 00
00 00 00 00
00 00 00 00

00 00 40 00
00 10 00 00 
00 02 00 00

06 00 
00 00 
00 00 
00 00 
06 00 
00 00
00 00 00 00
7E 20 00 00
00 02 00 00
00 00 00 00
03 00
00 81

00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
10 00 00 00

00 00 00 00 00 00 00 00
10 20 00 00 3C 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00

——————————————–

1.3-区段头

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;



关键字段


1.text

1.1 BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];      ==>.text            ==>   2E 74 65 78 74 00 00 00(区段名称)

1.2 DWORD   VirtualSize;                                                  ==>22h             ==>   22 00 00 00 (有效的OpCode数目)

1.3 DWORD   VirtualAddress;                                            ==>1000h         ==>   00 10 00 00 (区段rva:200h映射到内存中为1000h)

1.4 DWORD   SizeOfRawData;                                          ==>200h           ==>   00 02 00 00 (在文件中的对齐后大小)

1.5 DWORD   PointerToRawData;                                     ==>200h           ==>   00 02 00 00 (在文件中相对与0的偏移)

1.6 DWORD   Characteristics;                                            ==>E00000E0  ==>  E0 00 00 E0 (区段属性可以自定义)


2.rdata

1.1 BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];      ==>.rdata          ==>   2E 72 64 61 74 61 00 00(区段名称)

1.2 DWORD   VirtualSize;                                                  ==>7Eh             ==>   7E 00 00 00 (有效的字节数目)

1.3 DWORD   VirtualAddress;                                            ==>2000h         ==>   00 20 00 00 (区段rva:400h映射到内存中为2000h)

1.4 DWORD   SizeOfRawData;                                          ==>200h           ==>   00 02 00 00 (在文件中的对齐后大小)

1.5 DWORD   PointerToRawData;                                     ==>400h           ==>   00 04 00 00 (在文件中相对与0的偏移)

1.6 DWORD   Characteristics;                                            ==>E00000E0  ==>  E0 00 00 E0 (区段属性可以自定义)

——————————————–

2E 74 65 78 74 00 00 00
22 00 00 00
00 10 00 00
00 02 00 00
00 02 00 00
00 00 00 00
00 00 00 00
00 00
00 00
E0 00 00 E0

2E 72 64 61 74 61 00 00
7E 00 00 00
00 20 00 00
00 02 00 00
00 04 00 00
00 00 00 00
00 00 00 00
00 00
00 00
E0 00 00 E0

——————————————–

tip:200对齐其余填0



结语


以上将16进制码复制到010Editor中保存为.exe格式即可运行,运行环境为win10,并未考虑兼容性的问题,发帖的本意为0到1质变,1往后的变化希望大家自行发挥,例如:添加新的区段,在弹窗中添加文字,将代码复杂化等等。



猜你喜欢

转载自blog.csdn.net/dong1528313271/article/details/80593853
PE