在PWN中,永远离不开汇编,包括汇编指令,寄存器等
至于为什么要区分大小端,这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为 8bit。
但是在C语言中除了8bit的char之外,还有16bit的short型,32bit的long型(要看具体的编译器),另外,对于位数大于 8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。
例如一个16bit的short型x,在内存中的地址为0x0010,x的值为0x1122,那么0x11为高字节,0x22为低字节。对于 大端模式,就将0x11放在低地址中,即0x0010中,0x22放在高地址中,即0x0011中。
小端模式,刚好相反。我们常用的X86结构是小端模式,而KEIL C51则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以随时在程序中(在ARM Cortex 系列使用REV、REV16、REVSH指令 [1] )进行大小端的切换。
所谓的大端模式是指数据的高字节,保存在内存的低地址中,而数据的低字节, 保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处 理:地址由小向大增加,而数据从高位往低位放;
例如:对于0x12345678的大端模式存储如下
低地址 | —— | ——> | 高地址 |
---|---|---|---|
0x12 | 0x34 | 0x56 | 0x78 |
所谓的小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权 有效地结合起来,高地址部分权值高,低地址部分权值低,和我们的逻 辑方法一致。
例如:对于0x12345678的小端模式存储如下
低地址 | —— | ——> | 高地址 |
---|---|---|---|
0x78 | 0x56 | 0x34 | 0x12 |
寄存器是中央处理器(CPU)内的组成部 分。寄存器是有限存贮容量的高速存贮部件, 它们可用来暂存指令、数据和地址。在中央处理器的控制部件中,包含的寄存器有指令寄存器(IR)和程序计数器(PC)。在中央处理器的算 术及逻辑部件中,寄存器有累加器(ACC)。
32位寄存器 | 16位寄存器 | 8位寄存器 | 描述 |
---|---|---|---|
EAX | AX | AH | AL | 累加寄存器 |
EBX | BX | BH | BL | 基址寄存器 |
ECX | CX | CH | CL | 计数寄存器 |
EDX | DX | DH | DL | 数据指针寄存器 |
ESP | SP | 栈顶指针寄存器 | |
EBP | BP | 扩展基址指针寄存器 | |
ESI | SI | 源变址寄存器 | |
EDI | DI | 目的变址寄存器 | |
EIP | IP | 指令指针寄存器 | |
EFLAGS | 标志寄存器 |
这是一个16位的存放条件标志、控制标志寄存器,主要用于反映处理器的状态和ALU运算结果的某些特征及控制指令的执行。
CF(进位标志位)
若上一个运算产生进位,则其值为1,否则其值为0。
ZF(零标志位)
若上一个运算结果为 0,则其值为1,否则其值为0。
OF(溢出标志位)
若上一个运算产生溢出,则其值为1,否则其值为0。
百度百科解释
标志位(外语缩写) | 标志位名称及外语全称 | =1 | =0 |
---|---|---|---|
CF | 进位标志/Carry Flag | CY/Carry/进位 | NC/No Carry/无进位 |
PF | 奇偶标志/Parity Flag | PE/Parity Even/偶 | PO/Parity Odd/奇 |
AF | 辅助进位标志/Auxiliary Carry Flag | AC/Auxiliary Carry/进位 | NA/No Auxiliary Carry/无进位 |
ZF | 零标志/Zero Flag | ZR/Zero/等于零 | NZ/Not Zero/不等于零 |
SF | 符号标志/Sign Flag | NG/Negative/负 | PL/Positive/非负 |
TF | 跟踪标志/Trace Flag | ||
IF | 中断标志/Interrupt Flag | EI/Enable Interrupt/允许 | DI/Disable Interrupt/禁止 |
DF | 方向标志/Direction Flag | DN/Down/减少 | UP/增加 |
OF | 溢出标志/Overflow Flag | OV/Overflow/溢出 | NV/Not Overflow/未溢出 |
这些标志位,在我们执行一些比较和跳转的汇编指令时,会被用到。
• 在8086CPU中 ,寄存器的寻址范围为0~0xFFFF,程序可用空间十分有限,于是出现了段寄存器。
• 段寄存器相当于一个基址,段寄存器 * 0x10000 + 偏移 = 寻址地址
• 不同的段寄存器寻址功能不同
代码段寄存器CS(Code Segment)
存放当前正在运行的程序代码所在段的段基址,表示当前使用的指令代码可以从 该段寄存器指定的存储器段中取得,相应的偏移量则由IP提供。
数据段寄存器DS(Data Segment)
指出当前程序使用的数据所存放段的最低地址,即存放数据段的段基址。
堆栈段寄存器SS(Stack Segment)
指出当前堆栈的底部地址,即存放堆栈段的段基址。
附加段寄存器ES(Extra Segment)
指出当前程序使用附加数据段的段基址,该段是串操作指令中目的串所在的段。
程序执行前,硬盘中的数据(可以使用winhex查看)
程序执行后(可以使用IDA查看)
未开启 ASLR(地址随机化)时
地址 = 基址 + 程序在硬盘中的数据的偏移
由图可知,该程序的基址为:0x400000
栈本质上是一块内存,每块内存有对应的地址编号,只不过这块内存被程序用来作为一 个特殊的存储区,主要功能是暂时存放数据或地址,通常用来保护断点和现场
函数参数在栈上按照从右至左的顺序依次压入栈中,返回值 存放在 EAX 中。
可以继续参考这个文章中的案例
Windows下参数1、参数2、参数3、参数4分别保存在 RCX、RDX、R8D、R9D这四个通用寄存器中,剩下的 参数从右往左依次入栈,被调用者实现栈平衡,返回值存放在 RAX 中。
Linux下前6个参数分别被存放在RDI,RSI,RDX,RCX,R8,R9寄存器中,剩下的参数从右往左依次入栈,被调用 者实现栈平衡,返回值存放在 RAX 中。
内核提供的一组实现特殊功能的子程序供程序员在编写自己的程序时调用,以减轻编程的工作 量。汇编语言中系统调用的功能有很多,涉及屏幕显示、文件管理、I/O管理等等,每个子程序都有一个功能号,所有 的功能调用的格式都是一致的。
调用的步骤大致如下:
1)系统功能号送到寄存器AX中;
2)入口参数送到指定的寄存器中;
3)执行相关系统调用指令;
4)根据出口参数分析功能调用执行情况。
32位程序 的系统调用由0x80
中断完成(int 0x80
指令),各个通用寄存器用于传递参数,EAX寄存器用于表示系统 调用的接口号,比如:
每个系统调用都对应于内核源代码中的一个函数,它们都是 以“sys_”开头的,比如exit调用对应内核中的sys_exit函数。
根据系统调用参数数量的不同,依次将参数放入EBX、ECX、EDX、ESI、EDI和EBP这6个寄存器中传递。注(如 今的32位程序多用SYSENTER快速系统调用来实现)
64位程序 的系统调用由syscall
指令完成,系统调用号存放在rax
寄存器中,参数和64位函数调用存放一致。
具体的函数调用表,参考这里