zoukankan      html  css  js  c++  java
  • 一、汇编

    • 进制01

      可以通过修改约定俗成的进制符号,起到对外加密的作用

      比如8进制默认的是

      0 1 2 3 4 5 6 7

      我们可以将其打乱成

      5 2 3 0 1 4 6 7

      这里的符号‘5’对应默认的0 , 当然不仅是打乱,耶可以替换成其他其他字符

       非十进制的计算,先手算出1+1~n+n,乘法通过查加法表完善,之后的大数相乘就和十进制类似列竖式

      

    • 进制02

      进制说了许多感觉没有意义的东西,目的就是想让我们了解进制的含义,逢N进一,抛开传统的十进制和数字作为符号,扳手指头计算。

    • 数据宽度_逻辑运算

    重要的计量单位
        BYTE       字节      8-bit
        WORD       字        16-bit
        DWORD      双字      32-bit

      可逆的逻辑运算:^(异或,相同为0,不同为1)、!(非,取反)

      CPU对加法的本质运算

      

    • 通用寄存器_内存读写

      寄存器结构: EAX - AX - AH - AL

      其中EAX为32位寄存器,AX为16位寄存器,AH与AL位8位寄存器,它们之间为包含关系

     

      

      总结一下就是:立即数复制到寄存器,多余的直接扔掉。只有大小相同的寄存器之间才能mov

      add和sub运算可以是目标操作数的大小大于源操作数,比如 " add r/16,r/8 "

        内存mov到内存需要用寄存器中转一下。

    • 内存地址_堆栈

      大小端:大端模式即数据的高字节保存在内存的低地址中,小端模式即数据的高字节保存在内存的高地址中

        比如0x12345678, 以16进制呈现,在大端模式中为 " 12 34 56 78 ",在小端模式中为" 78 56 34 12 "

        很多时候逆向解密的时候需要细心处理大小端的问题

      内存的寻址:

        1. [立即数]    eg:" dword prt ds:[0xff865f8] "

        2..[寄存器]    eg:“ dword prt ds:[eax] ”    寄存器需要是8个通用寄存器之一

        3.[寄存器+立即数]     eg:“ dword prt ds:[eax+0x10] ”

        4.[寄存器+寄存器*{1,2,4,8}]     eg:" dword prt ds:[eax+exc*2]  "

        5.[寄存器+寄存器*{1,2,4,8}+立即数]     eg:" dword prt ds:[eax+exc*2+0x10]  "

      堆栈内数据查询可通过ebp(栈底)+offset(偏移),也可通过esp(栈顶)+offset(偏移)实现

    •  标志寄存器

      1.进位标志CF (Carry Flag) :如果运算结果的最高位产生了-一个进位或借位,那么,其值为1,否则其值为0。

      2.奇偶标志PF (Parity Flag):奇偶标志PF用于反映运算结果的最低有效字节(后8位)中“1”的个数的奇偶性。如果“1”的个数为偶数,则PF的值为1,否则其值为0。

      3.辅助进位标志AF (Auxiliary Carry Flag) :在发生下列情况时,辅助进位标志AF的值被置为1,否则其值为0:

        (1)、在字操作时,发生低字节向高字节进位或借位时; 

        (2)、在字节操作时,发生低4位向高4位进位或借位时。

      4.零标志ZF (Zero Flag): 零标志ZF用来反映运算结果是否为0。如果运算结果为0,则其值为1,否则其值为0。在判断运算结果是否为0时,可使用此标志位。

      5.符号标志SF Flag):符号标志SF用来反映运算结果的符号位,它与运算结果的最高位相同。

      6.溢出标志OF (Overflow Flag):溢出标志0F用于反映有符号数加减运算所得结果是否溢出。如果运算结果超过当前运算位数所能表示的范围,则称为溢出,OF的值被置为1,否则,OF的值被清为0

        注意溢出标志位是给有符号的运算使用的,有以下规律:

          正 + 正 = 正      如果结果是负数,说明有溢出

          负 + 负 = 负      如果结果是正数,说明有溢出

          正 + 负              永远不会溢出

        而计算机最终是这样计算OF的:

          首先做加法(减法可以看作是加法),设x=0/1(符号位无进位/有进位),y=0/1(最高有效位无进位/有进位),那么OF = x ^ y

      

      7.方向标志DF (Direction Flag):决定执行某些语句后,寄存器值的 ‘ 增长 ’ 方向

        eg : STOS DWORD PTR ES:[EDI]    当DF为0时候,EDI加上DWORD宽度,当DF为1时候,EDI减去DWORD宽度

      ps:mov指令不修改标志寄存器的值

        进位标志表示无符号数运算结果是否超出范围,溢出标志表示有符号数运算结果是否超出范围.

      ADC指令:带进位加法,用法和ADD一样,只不过会额外加上CF标志位,并将其还原

      SBB指令:带错位减法,用法和SUB一样,只不过会额外减去CF标志位,并将其还原

      XCHG指令:交换指令,交换宽度相同的寄存器或内存(不包括内存和内存之间交换)

      MOVS指令:移动数据,相当于MOV,并且支持内存之间的移动!!!

      STOS指令:将AL/AX/EAX的值存储到[EDI]指定的内存单元,由宽度决定

      REP指令:根据ECX的值决定重复次数。

        eg:REP STOS DWORD PTR ES:[EDI]          就是将后面的句子重复ECX次

    •  JCC

      CMP:按照SUB运算,结果不保存,只修改标志位(比如ZF为0的时候说明两数相等)

      TEST:两数进行与(&)操作,结果不保存,只修改标志位(一般用于判断该数是否为0)

      常见的简单JCC:

      JE/JZ:结果为零则跳转( 相等时跳转,ZF=1 )

      JNE/JNZ:结果不为零跳转( 不相等跳转,ZF=0 )

      JS:结果为负则跳转( SF=1 )

      JNS:结果为非负则跳转( SF=0 )

      JP/JPE:结果(低8位)中1的个数为偶数则跳转( PF=1 )

      JNP/JPO:结果(低8位)中1的个数为奇数则跳转( PF=0 )

      JO:结果溢出了则跳转( OF=1 )

      JNO:结果没有溢出则跳转 ( OF=0 )

    • 堆栈图

      默认给每个函数都有分配一个栈缓存区,可以控制栈缓存区的大小,也可以取消分配。每次申请栈缓存的时候,里面都是历史数据,也就是垃圾内容,所以栈缓存区一般全部分配为CC,硬编码就是 'int8' 终止 当程序异常执行到栈的时候终止程序。

    • C语言基础

      就算一个函数内没有任何c语句,程序默认要申请缓存区并为其赋处置CC。使用以下函数,编译器不会对空函数做任何处理:

    void __declspec(naked) Fuction()
    {
              
    }

      既然这样,就可以自己控制如何进行堆栈调整,比如

    int __declspec(naked) Fuction(int a,int b)
    {//实现a+b
        _asm
        {
            push ebp
            mov ebp,esp
            sub esp,0x10
            mov eax,dword ptr ds:[ebp+0x8]
            add eax,dword ptr ds:[ebp+0xc]
            mov esp,ebp
            pop ebp
            ret
        }
    }

        此函数属于__cdecl,即调用者调正栈帧

    • 数据类型

      有符号与无符号变量,在内存中的存储一模一样,是由使用者来决定有无符号。

        比如 char a=0xff; char b=1; 则a<b

      浮点数的二进制储存:

     

        有一个符号位,整数部分除以二取余数,从下往上列,小数部分乘以二取整数部分,从上往下列(小数部分可能存在精度问题),指数部分可以使用127±3(左移加,右移减)

      https://www.bilibili.com/video/av36036005?t=738&p=13               111:34 关于float在内存中的存储

      变量的储存:全局变量程序开始就分配内存,地址固定。局部变量在用到的时候分配内存,分配到堆栈内。

      数据类型的转换:

        小到大:MOVSX 符号扩展,即高位补符号位

            MOVZX 零扩展,即高位补零

        大到小:仅截取低位

    • 函数

      常见的几个约定调用:

        __cdecl:从右至左入栈,调用者平衡栈

    push 2
    push 1
    call fuction
    add esp,0x8

        __stdcall:从右至左入栈,被调用函数平衡栈

    push 2
    push 1
    call fuction
    ------------------------------
    fuction:
    push ebp mov ebp,esp sub esp,0x40 ..... mov esp,ebp pop ebp ret 8
    // 这里的ret 8 == pop eip
    //         add esp,8

        __fastcall:ECX/EDX传递前两个参数(从左至右),剩下的参数从右至左入栈,被调用函数调整栈

    Fuction(1,3,7,9):
    
    push 9
    push 7
    mov edx,3
    mov ecx,1
    call fuction

           既然想要变得fast,最好就是在只有2个一下参数的时候使用,不然意义不大

      函数的返回值:返回1字节保存在al寄存器,2字节( short )保存在ax寄存器,4字节( int )保存在wax寄存器,8个字节( __int64 ) 高32位保存在edx寄存器,低32位保存在eax寄存器。

      参数的传递:4字节以下的参数均以4字节传递,使用的时候仅取要用的部分。8字节的参数拆分成两个4字节,由高位到低位入栈。

      栈空间的分配:遵循对齐本机位数的原则。定义任意一个4字节以下的变量,都会为其分配4字节。但是定义一个数组,会向上对齐到4的倍数,然后从左到右,从低地址到高地址存储。比如char[6]= 会分配 ( (6*4/)+1 )*4 = 8字节的空间。

        多维数组的分布按照维度由低到高,栈地址由小到大匹配,仍然存在对齐4字节的规则

    • 结构体

      结构体的储存和数组相似,各个变量的地址都连续,不一样的是结构体内可以有不同的变量类型。结构体仍然存在内存对齐,不过并不是每个内部类型分别对齐,而是全部空间加起来对4向上取整。如下:

      如图可见,结构体内定义了byte,word,dword各一个,但是总空间只有3c-38=8字节。因为他将byte和word放在了一个4字节的空间里了。

      可以看作是依次为变量分配空间,每次分配前先检查上一次分配后剩下的空间是否够该变量类型分配,如果可以就利用上一次的,否则就新开辟一块。

     

      此外,因为编译器默认的对齐参数是4,我们可以修改其对齐参数(1,2,4,8),使用#prgma pack(n),将结构体包起来,如下

    #pragma pack(8)
    struct A
    {
        char a;
        int b;
        long long c;
    }
    #pragma pack() // 后面这里不需要数字

      每次对齐的值应该是对齐参数和变量类型中的较小值,比如一个结构体中一次定义了 int a;char b;char c;  对齐参数是8,但实际分配是这样的

    a a a a
    b c 0 0

      最终的空间大小一定是最大变量类型的整数倍。

      具体还是参考滴水的视频把。。。

      https://www.bilibili.com/video/BV1yt41127Cd       48:37

    • Switch语句

      怎样编译都看编译器,一般来说有以下规律:

        较少的时候按照if else编译

        较多且比较连续的时候,生成大表,大表内连续,但是可能存在很多浪费

        较多且不比较连续的时候,生成大表和小表,小表的目的是确定大表内的偏移,大表不存在空间浪费的情况

    • 指针

      理解为一种带*的变量,赋值只能使用“完整写法” 比如 

    char* a=(char*)1

      带*变量的宽度永远是4字节,不论何种类型带*,也不论带多少个*

      自增、自减、加减一个整数,要根据去掉一个*之后的类型判断其宽度。比如 char* 自增自减1  char**自增自减4

      两个完全相同的带*变量可以相减,不能相加,结果应该除以去掉一个*后变量的宽度,也就得到了偏移量

       & 可以取任何一个变量的地址,返回的类型为 变量类型+* 。比如

    char a;
    //&a 的类型为 char*
    char*** b;
    //&b 的类型为 char****

      * 可以取任意一个地址的值,返回类型为 变量类型-* 。 比如

    char* a;
    //*a 的类型为 char
    char*** b;
    //*b 的类型为 char** 

      所以变量类型没有带*的变量不能在前面加*

      这样一看,*和&就是两个逆运算,前者是取地址的值,后者是取变量的地址。意思是

    int a=10;
    int* x = &a;
    // 则 *x = a 即类型和值完全相同

      指针类型可以用于遍历数组,只需要把数组的某个值的地址传给指针即可,数组名就相当于数组第一个变量的地址。

    char a[5];
    char *p ;
    p = a  //
    p = &a[0]
    //意义相同

      特别的char* 还可以直接指向一个string。

    char* p="abcdef";

        此时的"abcdef"是一个全局变量。即有固定内存地址。

       数组指针:实质就是把指针指向数组第一个变量的地址,使用指针来替代变量名,方便遍历数组。此外这两种定义有区别

    int* a[5]
    int (*b)[5]
    b = (int (*)[5])a;//b point to a

        第一个是定义了5个指针,第二个是定义了一个数组指针,可以指向一个数组,并且自增,自减,加减都是以5个int为单位一,即b++就是加上20。特殊的,**b才能取值,b和*b的值一样,都指向数组的首地址,但是类型不同,所以进行加减相同立即数的结果不同。b的类型为int*[2] 自增移动2*4个字节.*b的类型为int* 自增移动4个字节。

        总结一下:数组指针是为了修改自增,自减的幅度而自行设计的。这里的数组指针是一维数组指针。相当于把一维数组分成,以5个为一维的二维数组。

        n维数组指针类似,要用n+1个*才是取值,每次使用一个*,相当于降一维,从前到后降。

       函数指针:把指向的数据段执行。如下

    • 位运算

      算数移位指令(SAL,SAR):移出去的那一位都记录在CF位,左移补零,右移补符号位

      

      逻辑移位指令(SHL,SHR):移出去的那一位都记录在CF位,左移右移都补零

       循环位移指令(ROL,ROR):顾名思义,最左端左移成为最右端,最右端右移成为最左端

      带进位的循环位移指令(RCL,RCR):循环过程中,增添了CF位,溢出的部分进入CF,CF以前的补上需要补的地方

      

  • 相关阅读:
    es5预览本地文件、es6练习代码演示案例
    Java实现 LeetCode 838 推多米诺(暴力模拟)
    Java实现 LeetCode 838 推多米诺(暴力模拟)
    Java实现 LeetCode 838 推多米诺(暴力模拟)
    Java实现 LeetCode 837 新21点(DP)
    Java实现 LeetCode 837 新21点(DP)
    Java实现 LeetCode 837 新21点(DP)
    Java实现 LeetCode 836 矩形重叠(暴力)
    Subversion under Linux [Reprint]
    Subversion how[Reprint]
  • 原文地址:https://www.cnblogs.com/lxy8584099/p/12520741.html
Copyright © 2011-2022 走看看