0x01 vitual stdio
基于cpp实现汇编(内敛汇编),注意cpp实现的汇编只支持32位的,不支持64位的, 实现方式如下:

void main(){
__asm {
mov eax, 1原生开发,环境准备
新建空项目


新建源文件, 后缀名使用asm,文件名称必须使用英文
定义项目的入口函数(汇编需要自定义)


编写x86测试代码
.586 .MODEL flat, stdcall .code main proc mov eax,1 main endp END main64位汇编
.code
main proc
mov eax,1
main endp
END main语法格式如下:
mov eax, 1
操作符 被操作数,操作的内容0x02 寄存器
通用寄存器
EAX
EBX
ECX
EDX
E表示32位,R表示64位
EBP:EBP通常用于标记当前函数的栈帧的基址(栈底),这使得在执行过程中可以方便地访问函数的参数和局部变量。与ESP不同,EBP的值在函数内部通常是不变的,
ESP:ESP指向当前堆栈的顶部(栈顶)。栈在内存中通常是向下增长的,也就是说随着数据的压入(push),ESP的值会减小,而随着数据的弹出(pop),ESP的值会增大。
EIP:指向下一步运行的指针
ESI:指向字符串指针
0x03 内存
表现形式
MOV EAX,DWORD PTR DS:[0x03567783](立即数表现形式
MOV EBX,0x03567783
MOV EAX,DWORD PTR DS:[EBX](寄存器表现形式)
MOV EBX,0x03567783
MOV EAX,DWORD PTR DS:[EBX+4](偏移量表现形式1)
MOV EAX,0x03567783
MOV EBX,0x2
MOV ECX,DWORD PTR DS:[EAX+EBX*4](偏移量表现形式2)
0x04运算符
加
ADD destination, source
ADC destination, source
mov eax, 5 ; 将 5 加载到 EAX
add eax, 3 ; EAX = 5 + 3 = 8
;ADC 的作用是带进位加法,除了加法本身,还会将标志寄存器中进位标志 CF 的值加到运算结果中,有符合还是无符号的区别
//结果都在第一个
eax+ebx=eax减
sub destination, source
结果存储在寄存器 EAX 中
sub eax, ebx
sbb eax,ebx乘
MUL operand
用于无符号乘法。当你使用 MUL EBX(或其他寄存器/内存)时,它会执行一个乘法操作,将累加器寄存器的值乘上指定操作数(如 EBX),结果存储在特定的寄存器(EAX)中。
imux operand
mul ebx
imul ebx ;只有一个参数乘以的是eax
mov eax,2
mov ebx, 3
mul ebx
mov eax, eax除
DIV operand
商在eax 余数在edx
两个数字做除法
商也是数字 余数也是数字
占的位数一样
mov eax, 6
mov ebx, 3
cdq
div ebx堆栈操作
PUSH
push eax: 将eax中的数据压入栈中
POP
pop eax,直接弹栈顶内容到eax中
数据移动
MOV
mov eax,ebx ;将ebx中的数据传入eax中
LEA:计算内存地址的有效地址并将其加载到目的寄存器中。它只进行地址计算,不会访问实际的内存。
LEA destination, source
LEA EAX, [EBX + 4]
; 将 EBX 寄存器加上 4 的结果加载到 EAX 中XCHG:(Exchange,交换)指令用于交换两个寄存器或一个寄存器与内存位置中的数据。执行此操作时不需要借助中间寄存器实现数据的交换。
XCHG destination, source
XCHG EAX, EBX
; 将 EAX 和 EBX 寄存器的值交换比较
cmp:用于比较两个操作数。它本质上是进行一次减法操作,但不存储结果,仅根据运算结果更新标志寄存器。
CMP destination, source
mov eax, 5 ; 将 5 加载到 EAX
cmp eax, 3 ; 比较 EAX 和 3(本质是 EAX - 3)
jge greater_or_equal ; 如果 EAX >= 3,跳转到 greater_or_equaltest:指令执行按位的 AND 操作,结果不存储,而是根据按位操作的结果更新标志寄存器。常用于测试特定位是否为 0,以及其他按位逻辑条件检查。
TEST destination, source
mov eax, 3 ; 将 3 加载到 EAX
test eax, eax ; 测试 EAX 是否为非零(EAX & EAX)
jnz not_zero ; 如果 EAX 非零,则跳转到 not_zero跳转
JMP target :target:指跳转地址,可以是一个标号、相对地址、或使用寄存器和内存间接指定的地址。
jmp:无条件跳转
j(n)e:等于(不等于)跳转
j(n)z:等于(不等于)0 跳转
j(n)a: ig无符号的大于跳转
j(n)b:无符号的小于跳转
字符串
movs:指令组用于从源地址复制数据到目标地址。它移动一个字节、字(word)、双字(dword)、或四字(qword)的数据。
MOVS destination, source ; 隐式使用寄存器,只需指定数据大小
MOVSB ; 逐字节(byte)移动
MOVSW ; 逐字(word,2字节)移动
MOVSD ; 逐双字(dword,4字节)移动
MOVSQ ; 逐四字(qword,8字节,64位模式下使用)stos:指令组用于向内存中存储数据,典型场景是初始化一段内存地址或重复填充某个值到内存中。
STOSB ; 存储一个字节
STOSW ; 存储一个字(word,2 字节)
STOSD ; 存储一个双字(dword,4 字节)
STOSQ ; 存储一个四字(qword,8 字节,64 位模式下)mov edi, buffer ; 将目标内存地址加载到 EDI
mov al, 0xFF ; 字节值 0xFF
mov ecx, 10 ; 填充 10 个字节
rep stosb ; 向内存写入 10 次 AL 的值函数
call
指令用于跳转到一个子程序,同时它会保存当前执行点的返回地址,以便子程序完成后能够返回到调用点继续执行。
CALL target
call 函数名称(写汇编的时候是这么写的)
call 地址 (反汇编的时候是这么写的)
当我们call函数的时候如果有参数就得先把参数push进去
; 调用前准备参数
push 5 ; 第一个参数 (5)
push 10 ; 第二个参数 (10)通过栈(Stack)传递参数时,函数参数的压栈顺序与 C 等高级语言中函数调用的书写顺序通常是相反的
int add(int x, int y) {
return x + y;
}
int result = add(5, 10); // 调用函数时,传入的参数为 5 和 10生产汇编代码
push 10 ; 将第二个参数 (y) 压栈
push 5 ; 将第一个参数 (x) 压栈
call add ; 调用函数
add esp, 8 ; 调用者负责清理栈ret
指令用于从子程序返回到调用点。
它会从栈中弹出一个地址(返回地址),并将程序流跳转到该地址继续执行。
RET ; 默认从栈弹出返回地址
RET n ; 返回时,同时调整栈指针,移除 n 个字节(用于清理栈 参数)。ret (写汇编的时候是这么写的)返回
retn(反汇编的时候是这么写的)
0x05 PE结构
PE(Portable Executable,便携式可执行文件)结构 是 Windows 操作系统下的可执行文件格式,例如 .exe 和 .dll。PE 文件基于更通用的 COFF(Common Object File Format,通用目标文件格式),为 Windows 提供了一种组织二进制文件的标准方式。
PE 文件是一个数据结构,它包含了加载和运行应用程序或动态链接库所需的所有信息。它包括了程序指令、全局变量、动态库引用(例如导入表和导出表)、资源(如图标和字符串)、调试信息等内容。
主要组成部分
DOS头
DOS引导
NT Header
PE签名
文件头
可选头
Selection table:PE 文件中各节的目录,每一节都有相应的描述条目。
每节保存不同类型的数据(如代码、变量、资源等)。
常见的区节段包括:
.text:存储可执行代码。.data:存储初始化的全局变量。.rdata:存储只读数据。.reloc:存储重定位信息。.rsrc:存储资源信息(如图标、对话框等)。

selection:Section 是 PE 文件最关键的内容部分,存储了文件实际需要执行或使用的数据:
程序代码(.text 节)。
全局变量(.data 节)。
动态链接库的导入/导出表。
程序所需资源(如图标、对话框布局等)。
PE结构的作用
PE 结构是 Windows 系统加载、执行程序和动态库的基础,其主要作用包括以下几点:
文件的装载与执行
PE 文件包含了所有必要信息,指导操作系统的加载器(Loader)将其正确加载进内存,并运行。
包含程序的入口点地址(Entry Point),即程序从哪条指令开始运行。
动态链接
包括导入表和导出表,记录了程序使用的 DLL 函数或其自身提供给其他文件的接口。
提供了模块化编程的能力,使程序可以动态加载共享库。
虚拟内存映射
PE 文件内的数据并不是直接加载进内存,而是通过节表组织数据并映射到虚拟内存的不同区域。
通过节头部的虚拟地址(VA)来管理文件中逻辑地址与内存位置的映射。
资源管理
.rsrc节存储了文件的资源,例如图标、字符串表、对话框模板等,为程序提供了一种统一读取资源的方式。
调试与维护
PE 文件中可以包含调试符号和代码的映射信息(如
.debug节),方便开发者和调试工具对程序进行调试和跟踪。
安全性
包含各种信息,如数字签名(在特定节内),以验证文件的完整性、防止篡改。
常用的类型
WORD类型,占4个位置,2个字节
DOWRD类型,占8个位置,4个字节
BYTE了类型,占2个位置,1个字节
#include<Windows.h>
_IMAGE_DOS_HEADER;
_IMAGE_NT_HEADERS;
_IMAGE_SECTION_HEADER;
IMAGE_DOS_HEADER;
#endif
typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header
WORD e_magic; // Magic number 这里是需要重点关注的。 以5a 4d开头,表示PE(小端序读法)
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;WORD e_magic; // Magic numb er 标志位(Mz 4d5a 只占2字节)
LONG e_fanew; // File address of new exe header 偏移位置 // (一般接下来就是文件签名)
IMAGE_NT_HEADERS;
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature; // 00 00 45 50 小端序读法
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_FILE_HEADER
typedef struct _IMAGE_FILE_HEADER {
WORD Machine; //表示运行的架构 如0x8664
WORD NumberOfSections; //当前有多少区节段
DWORD TimeDateStamp; //文件是什么时候写好的,远一点的时间可以查ioc,比较近就是新的威胁情报
DWORD PointerToSymbolTable;
DWORD NumberOfSymbols;
WORD SizeOfOptionalHeader;
WORD Characteristics; //相关映射关系见下表,如果没有找到就看看是哪几个加在一起的,比如0x22 就是0x20+0x02
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;Characteristic文件特征详细表
IMAGE_FILE_RELOCS_STRIPPED 0x0001 文件中移除了重定位信息,这表示文件必须装载到它的首选基址,如果基址不可用,加载器会报错。
IMAGE_FILE_EXECUTABLE_IMAGE 0x0002 表明该文件是一个可执行文件(无未解析的外部引用)。
IMAGE_FILE_LINE_NUMS_STRIPPED 0x0004 COFF 行号信息被移除(已经不再使用)。
IMAGE_FILE_LOCAL_SYMS_STRIPPED 0x0008 文件中已移除 COFF 符号表的条目(通常用于减小文件大小)。
IMAGE_FILE_AGGRESIVE_WS_TRIM 0x0010 激进地修剪工作集。这个标志已过时。
IMAGE_FILE_LARGE_ADDRESS_AWARE 0x0020 应用程序可以处理 2GB 以上的地址空间(即支持大地址空间)。
IMAGE_FILE_BYTES_REVERSED_LO 0x0080 低字节的字节顺序是反转的(已过时)。
IMAGE_FILE_32BIT_MACHINE 0x0100 目标机器支持 32 位字(即该文件是为 32 位架构编译的)。
IMAGE_FILE_DEBUG_STRIPPED 0x0200 调试信息被移除(通常用作发布版本)。
IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP 0x0400 如果该文件位于可移动介质上,应将其复制到交换文件再运行。
IMAGE_FILE_NET_RUN_FROM_SWAP 0x0800 如果该文件是从网络运行的,运行前需要将其复制到交换文件。
IMAGE_FILE_SYSTEM 0x1000 该文件是一个系统文件。
IMAGE_FILE_DLL 0x2000 文件是一个 DLL 文件,不能直接运行,但可供其他程序动态加载和调用。
IMAGE_FILE_UP_SYSTEM_ONLY 0x4000 文件只能在单处理器系统上运行(不支持多线程/多处理器)。
IMAGE_FILE_BYTES_REVERSED_HI 0x8000 高字节的字节顺序是反转的(已过时)。IMAGE_OPTIONAL_HEADER32 可选头
typedef struct _IMAGE_OPTIONAL_HEADER {
//
// Standard fields.
//
WORD Magic; //文件类型标识,0x10B表示32位镜像文件,0x107表示一个ROM镜像,0x20B表示64位镜像文件
BYTE MajorLinkerVersion; //链接器的主版本号
BYTE MinorLinkerVersion; // 链接器副版本号
DWORD SizeOfCode; //text总大小
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;WORD Magic; //文件类型标识,0x10B表示32位镜像文件,0x107表示一个ROM镜像,0x20B表示64位镜像文件
DWORD SizeOfCode; //text存储可执行代码的总大小
DWORD SizeOfInitializedData; 存储初始化的全局变量
DWORD SizeOfUninitializedData; bss总大小。
bss:BSS段通常是指用来存放程序中未初始化的或者初始化为0的全局变量和静态变量的一块内存区域。特点是可读写的,在程序执行之前BSS段会自动清0。
下面三个都是以偏移量表示(非常重要,需要记住)
DWORD AddressOfEntryPoint;程序的虚拟入口地址(运行起来的第一行代码的位置)
BaseofCode;代码基址DWORD,写代码的第一行
BaseOfData;数据基址DWORD
以上三个的值都是以偏移量的形式表示,最终的值是需要加上下面的imageBase
DWORD ImageBase;入口点,当加载进内存时镜像的第1个字节的首选地址。它必须是64K的倍数。
DLL默认是10000000H。WindowsCE的EXE默认是00010000H。Windows 系列的EXE默认是00400000H。
SectionAlignment;当加载进内存时节的对齐值(以字节计)(内存)DWORD
DWORD FileAlignment;用来对齐镜像文件的节中的原始数据的对齐因子(以字节计)(磁盘)
对齐
一个pe文件无论在磁盘和内存中存放都会进行对齐,但他们的对齐值会不相同。PE 文件头里边的FileAligment 定义了磁盘区块的对齐值。每一个区块从对齐值的倍数的偏移位置开始存放。而区块的实际代码或数据的大小不一定刚好是这么多,所以在多余的地方一般以00h 来填充,这就是区块间的间隙。PE文件头里边的SectionAligment 定义了内存中区块的对齐值。PE文件被映射到内存中时,区块总是至少从一个页边界开始。
WORD MajorOperatingSystemVersion;
WORDMinorOperatingSystemVersion;
WORDMajorlmageVersion;
WORDMinorlmageVersion;
WORDMajorSubsystemVersion;
WORDMinorSubsystemVersion;
以上全是版本号
DWORD Win32VersionValue;win32版本(必须为0)
DWORD SizeOflmage;总大小
DWORD SizeOfHeaders; 头的大小
DWORD CheckSum; 校验和,用来检测文件是否被修改
WORD Subsystem; 子系统,下面为子系统表
WORD DllCharacteristics; dll特征
#define IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE 0X0040 // DLL can move. aslr随机地址关掉
#define IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY 0x0080 // Code Integrity Image
#define IMAGE_DLLCHARACTERISTICS_NX_COMPAT 0x0100 // Image is NX compatible
#define IMAGE_DLLCHARACTERISTICS_NO_ISOLATION Ox0200 // Image understands isolatio
#define IMAGE_DLLCHARACTERISTICS_NO_SEH 0x0400 // Image does not use SEH. N
//seh的保护机制
#define IMAGE_DLLCHARACTERISTICS_NO_BIND 0x0800 // Do not bind this image.
//0x1000 Reserved.
#define IMAGE_DLLCHARACTERISTICS_WDM_DRIVER 0x2000 // Driver uses WDM model
//大部分的d11都有这个
// 0x4000 Reserved.
#define IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE 0X8000
DWORDSizeOfStackReserve;栈保留大小
DWORD SizeOfStackCommit;栈申请大小
DWORDSizeOfHeapReserve;堆保留大小
DWORDSizeOfHeapCommit;堆申请大小
DWORDLoaderFlags;标志位(必须为0)
DWORD NumberOfRvaAndSizes;数据目录(16)
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; API导入表存在的位置,
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;BYTE NaMe[IMAGE SIZEOF SHORT NAME];本节的名字DWORD PhysicalAddress;本节的物理地址
DWORD Virtualsize;本节的实际大小
为什么有union大括号:2选1,通常选后面,因为不知道具体的物理地址
DWORD VirtualAddress;本节的RVA
DWORD SizeOfRawData;本节在磁盘中的大小
DWORD PointerToRawData;本节在磁盘中的偏移
DWORD PointerToRelocations;exe文件无意义
DWORD PointerToLinenumbers;行号表的位置(调试用,不过基本没用)
WORD NumberOfRelocations;重定位数量
WORD NumberOfLinenumbers;行号表数量
DWORD Characteristics;特征,特征类型如下
IMAGE_SCN_TYPE_NO_PAD 0x00000008 节数据不会对齐到文件边界,此标志已被弃用。
IMAGE_SCN_CNT_CODE 0x00000020 节包含可执行代码。
IMAGE_SCN_CNT_INITIALIZED_DATA 0x00000040 节包含已初始化的数据。
IMAGE_SCN_CNT_UNINITIALIZED_DATA 0x00000080 节包含未初始化的数据(BSS 段)。
IMAGE_SCN_LNK_OTHER 0x00000100 保留,不用于实际用途。
IMAGE_SCN_LNK_INFO 0x00000200 节包含注释或其他信息(仅在目标文件中使用)。
IMAGE_SCN_LNK_REMOVE 0x00000800 从最终的 PE 文件中移除该节(仅在目标文件中使用)。
IMAGE_SCN_LNK_COMDAT 0x00001000 节包含 COMDAT 数据(即重复节的消除,通常用于目标文件)。
IMAGE_SCN_GPREL 0x00008000 节中的数据是全局指针相对的。
IMAGE_SCN_MEM_PURGEABLE 0x00020000 保留标志。
IMAGE_SCN_MEM_LOCKED 0x00040000 保留标志。
IMAGE_SCN_MEM_PRELOAD 0x00080000 保留标志,指示节应该预先加载到内存中。
IMAGE_SCN_LNK_NRELOC_OVFL 0x01000000 节包含扩展重定位项,NumberOfRelocations 为 0xFFFF 表示超出 16 位字段限制。
IMAGE_SCN_MEM_DISCARDABLE 0x02000000 节可以被丢弃,例如在运行时不需要的初始化数据。
IMAGE_SCN_MEM_NOT_CACHED 0x04000000 节不可被缓存。
IMAGE_SCN_MEM_NOT_PAGED 0x08000000 节不可被分页 OUT(总是保持在内存中)。
IMAGE_SCN_MEM_SHARED 0x10000000 节可以在进程之间共享。
IMAGE_SCN_MEM_EXECUTE 0x20000000 节包含可执行代码。
IMAGE_SCN_MEM_READ 0x40000000 节包含可读取的数据。
IMAGE_SCN_MEM_WRITE 0x80000000 节包含可写入的数据。实际用处
当我们遇到一些代码会释放文件这种操作的时候很重要,释放的位置是内存,我们不太好直接分析,这个时候就需要用到我们今天的知识来判断是否是pe,如果是,那么就下载这段内存重命名(判断exe还是dll),如果不是,就当shellcode处理
小端序
小端序(Little Endian) 是一种存储数据的字节序方式。在这种方式中,多字节数据的 低字节(最低有效字节, Least Significant Byte, LSB) 存放在内存的低地址,而 高字节(最高有效字节, Most Significant Byte, MSB) 存放在内存的高地址。
相对的,另一种方式叫 大端序(Big Endian),它将高字节存储在低地址,将低字节存储在高地址
小端序的特点:
小端序是 Intel 体系结构(如 x86/x64) 的默认字节序。
数据按从小到大的顺序存储在内存中,例如从最低有效字节开始。
常用在现代 PC、嵌入式设备中,特别是基于 Intel 和 ARM 处理器(ARM 默认是小端序,但可以配置为大端序)。
例如,你写了一条汇编指令:
mov eax, 0x12345678这一条指令会将 0x12345678 加载到寄存器 eax 中。然而在小端序的内存中,指令编码表示的内容可能是:
78 56 34 12虽然你写的数值是 0x12345678,但小端序处理器将以从低到高的字节顺序存储它。
工具
XDPViewer
010 Editor
python -> pefile