Pwn

看雪学院(汇编学习)

Posted on 2020-01-02,10 min read
<!-- more -->

eax:累加器,操作数和结果数据累加器,返回值运算结果一般都存储在这里
ebx,基地址,DS段的数据指针,在内存寻址的时候存放基地址
ecx:计数器,字符串和循环操作的计数器
edx:用于存储部分乘法结果和部分除法被除数
32位:                          EBX
16位:                           |       BX
8位:                           |BH      |        BL
32位的EBX,后16位可以分为BX,而BX又可以分为BH,BL
ebp:基址指针,SS段的数据指针   
esp:栈顶指针,一般指向栈顶,所以也被称为栈顶指针
edi:字符串操作的目标指针,ES段的数据指针
esi:字符串操作的源指针,SS段的数据指针
32位    EDI
16位        |DI
内存寻址范围:
32位系统内存寻址范围是0x00000000-0xFFFFFFFF
最大寻址范围为0xFFFFFFFF+1(4294967296)
1Byte=8bit
1KB=1024Byte
1MB=1024KB
1GB=1024MB
4294967296Byte=4GB
64位内存的寻址范围是0x0000000000000000-0xFFFFFFFFFFFFFFFF
内存的五种表现形式
立即数:
MOV EAX,DWORD PTR DS:[0x????]
#DWORD表示双字,4个字节,PTR是指针,指向一个内存地址    将DS[0x???]地址的值(4字节)放入EAX中
寄存器:
MOV EBX,0X?????
MOV EAX,DWORD PTR DS:[EBX]
寄存器+立即数:
MOV EBX,0X?????
MOV EAX,DWORD PTR DS:[EBX+4]
比例因子:[REG+REG*{1,2,4,8}]
数组元素地址=数组首地址+元素索引*数组元素占用空间
MOV EAX,0X?????
MOV EBX,0X2
MOV ECX,DWORD PTR DS:[EAX+EBX*4]
比例因子+立即数:
MOV EAX,0X????
MOV EBX,OX2
MOV ECX,DWORD PTR DS:[EAX+EBX*4+1]
数据存储模式:
分为
大端序:数据高位在内存地位,地位在内存高位
小端序:数据高位在内存高位,数据地位在内存地位
0x77665544
大端序:77665544
小端序:44556677    (栈)
无符号数乘法指令MUL
MUL OPRD
带符号数乘法指令
IMUL OPRD
乘法操作,本指令影响标志位CF及OF
MUL EBX
EAX=EBX*EAX
将EAX与EBX相乘,结果存储在EAX中
除法:
无符号数除法指令DIV
DIV OPRD
实现两个无符号二进制除法运算
16bit的被除数,分存2个8bit寄存器AH:AL,商放在AL,余放在AH
32bit的被除数,分存2个16bit寄存器DX:AX,商放在AX,余数在DX
64bit的被除数,分存2个32bit寄存器EDX:EAX,商放在EAX,余数在EDX
128bit的被除数,分存2个64bit寄存器RDX:RAX,商放在RAX,余数在RDX
自增:
INC OPRD
OPRD=OPRD+1
自减:
dec OPRD
OPRD=OPRD-1
什么是堆栈:
先入后出,SS段寄存器描述的就是堆栈段的段地址
栈的数据出口位于栈顶,也就是ESP寄存器指向的地址
栈顶是低位,ebp寄存器指向栈底,不会改变
PUSH:压栈
32位汇编首先会ESP-4,流出空间,然后压入数据
出栈:
POP:出栈
32位汇编首先弹出数据,然后ESP+4
栈的作用:
1.存储少量的数据
2.保存寄存器环境
3.传递参数
MOV OPRD1,OPRD2
OPRD1:目的操作数,寄存器,存储器,累加器
OPRD2:源操作数,寄存器,存储器,累加器,立即数
LEA OPRD1,
lea eax,dword ptr ss:[esp-4]
有效地址传送指令
将源操作数给出的有效地址,传送到指定的寄存器中
XCHG
将两个操作数数据交换
XCHG OPRD1,OPRD2
CMP指令
CMP OPRD1,OPRD2
对两个数,进行相减,比较

如果OPRD1>OPRD2,结果如下

如果OPRD2>OPRD1,结果如下

如果OPRD1=OPRD2,结果如下

TEST指令
TEST OPRD1,OPRD2
按位与
jcc 指令
JMP 无条件跳转      无论怎么样。直接跳转
JZ/JE               ZF=1或者=0  第一个参数等于第二个参数,跳转
JNZ/JNE             ZF=0或者!=0 第一个参数等于第二个参数,跳转
JBE/JNA             CF=1/ZF=1 第一个参数小于第二个参数,跳转
JNBE/JA             CF=0/ZF=0 第一个参数大于第二个参数,跳转
JL/JNGE             SF!=OF  小于/不大于等于,跳转
JNL/JGE             SF=OF不小于/大于等于,跳转
cmp OPRD1,OPRD2
相同
OV = 0 UP = 0 EI = 1 PL = 0 ZR = 1 AC = 0 PE = 1 CY = 0 
不同
OV = 0 UP = 0 EI = 1 PL = 0 ZR = 0 AC = 0 PE = 0 CY = 0 
相同情况下,ZR=1,jz就会跳转
    mov edi,esp
	mov dword ptr ss:[esp],0
	mov esi,ebp
	movs dword ptr es:[edi],dword ptr ds:[esi]
	mov eax,eax
    movs,字符串的替换
    mov eax,11223344
	mov edi,esp
	stos dword ptr es:[edi]
	mov eax,eax
    stos,字符串替换,将eax的值,存入es:[edi],小段存储
CALL指令
CALL OPRD
相当于push eip  jmp OPRD
为什么要压入eip呢,是为了RETN指令能够返回
RETN指令   
CALL和retn流程:
0X004014D0    call 0X004014ED       
EIP(偏移)=004014D0    EBP(栈底地址)=0019FF94    ESP(栈顶)=0019FF84
0x004013D5
0x004013D4
......
0x004014ED     mov ax,1
EIP(偏移)=004014ED    EBP(栈底地址)=0019FF94    ESP(栈顶)=0019FF80  栈中的FF80,存放着返回地址,也就是call下面一条指令的地址,当程序PUSH进去,当然也要POP出去,不然ESP(返回地址)上面就存放着push进去的数据。导致程序不能正常返回
0x004014F2     retun
总结一下:
当程序call的时候,会把下面一条语句的地址,push到栈内,放在ESP中。
然后执行完程序,返回的时候,会POP,retn返回
过程调用-函数
function proc
    code
function endp
参数传递:
寄存器传参
堆栈传参
.586
.MODEL flat, stdcall
.code

addx proc
	mov eax,[esp+4]
	mov ebx,[esp+8]
	add eax,ebx
	ret
addx ENDP

main proc
	push 1
	push 2
	call addx
	mov eax,eax
main ENDP
END main
首先push1,push2,将数值入栈,位置分别为ESP[0],ESP[+4]
call addx,返回地址入栈了,所以此时栈内分布如下
esp[0]      call addx address
esp[4]      PUSH 1
esp[8]      PUSH 2
            EBP
返回时,直接pop addx address,返回
C:typedef
typedef int uint32
给类型取别名
uint32 aaa=1;
而define,是替换,两者有区别
数组寻址
int Num[5]={1,2,3,4,5};
int * a = Num;
Num指向的首地址
Num+1=Num数组的第一个值,+1是int类型的四字节,而不是单纯的+1
1|      2|      3|      4|      5
Num+1  Num+2    
函数指针:
int add(int a,int b);
int(*Myadd)(int a,int b);
Myadd=add;
函数指针。就是给函数取了个别名。将函数地址,指向了我们取的别名
	Myadd = add;
010216EE  mov         dword ptr [Myadd],offset _add (01021104h)  
将0x01021104h(跳转表)赋值给了Myadd
int c=Myadd(1,2)
Myadd->跳转表->真实地址
成功调用
指针函数:
#include <stdio.h>
int * add(int *a);
//传入数组地址,a是地址,*a是地址上的值
int * add(int * a) {
    //返回的是地址,而不是值,因为不是* a
	return a;
}

int main() {
	int arr[5] = { 1,2,3,4,5 };
    //将arr的地址传入add函数,* ret表示的是值,ret表示地址
	int * ret = add(arr);
    //ret[2]=地址[2]=arr[2]
	int num = ret[2];
    //将num,输出
	printf("%d", num);
	getchar();
	return 0;
}
字符串的声明
#include <stdio.h>
int main() {
	char Str1[] = { "My name is guoke" };
	char Str2[] = { 'M','y',' ','N','a','m','e','\0'};
	char * Str3 = "My name is guoke3";
	printf("%s\n%s\n%s\n", Str1, Str2, Str3);
	getchar();
	return 0;
}
字符串输入
char Str[50];
scanf("%s",&Str);           不会接收\n
fgets(Str,50,stdin);        会接收\n
字符串输出
char Str[]={"Hello world!"};
puts(Str);                  自动会添加换行,printf不会自动添加
字符串长度:
char Str[]="Hello world";
字符串是以\00结尾的,字符串真实长度=数出来的长度+1
int Length=strlen(Str);
返回整数字符串有效长度,真实长度为有效长度+1
字符串拼接
char Str1[]="Hello";
char Str2[]="Guoke";
strcat(Str1,Str2);              结果存储在Str1
strncat(Str1,Str2,2)            只复制第二个字符串的2个字节
char Str1[]="Hello World!";
char Str2[]="Hello";
char Str3[]="Hello World!";
int ret = strcmp(Str1,Str2);                     一样返回0,不一样返回1
int reg = strncmp(Str1,Str2,5);                 判断前五个
字符拷贝
char Str1[50]={0};
char Str2[]="Hello";
strcpy(Str1,Str2);
所有复制,可能会造成溢出
strncpy(Str1,Str2,2);
只复制了两个,但是没有00结尾。函数会一直找到00为止,在char Str1后面={0}就好了
动态内存管理
char * Str;
//在堆上面申请内存,返回的void *
Str=(char *)mallo(200 * sizeof(char));
//memset把一段内存,全部刷值
memset(Str,0,200 * sizeof(char));
将申请的内存地址,200个字节全部刷成0

下一篇: 汇编语言笔记(三)→