整理复习汇编语言的知识点,以前在学习《Intel汇编语言程序设计 - 第五版》时没有很认真的整理笔记,主要因为当时是以学习理解为目的没有整理的很详细,这次是我第三次阅读此书,每一次阅读都会有新的收获,这次复习,我想把书中的重点,再一次做一个归纳与总结(注:16位汇编部分跳过),并且继续尝试写一些有趣的案例,这些案例中所涉及的指令都是逆向中的重点,一些不重要的我就直接省略了,一来提高自己,二来分享知识,转载请加出处,敲代码备注挺难受的。
本次复习重点在于理解数组中常用的寻址方式以及标志位的测试命令,数组寻址包括了,直接寻址,间接寻址,立即数寻址,基址变址寻址,比例因子寻址,通过ESI内存寻址,通过ESP堆栈寻址,指针寻址。
再次强调:该笔记主要学习的是汇编语言,不是研究编译特性的,不会涉及到编译器的优化与代码还原。
数组取值操作符: 数组取值操作符是对数组操作之前必须要掌握的,以下命令主要实现对数组元素的统计,取偏移值等,后期数组寻址会用到.
.386p
  .model flat,stdcall
  option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
.data
  WordVar1 WORD 1234h
  DwordVar2 DWORD 12345678h
  
  ArrayBT BYTE 1,2,3,4,5,6,7,8,9,0h
  ArrayDW DWORD 1000,2000,3000,4000,5000,6000,7000,8000,9000,0h
  ArrayTP DWORD 30 DUP(?)
.code
  main PROC
    ; 使用 OFFSET 可返回数据标号的偏移地址,单位是字节.
    ; 偏移地址代表标号距DS数据段基址的距离.
    xor eax,eax
    mov eax,offset WordVar1
    mov eax,offset DwordVar2
    
    ; 使用 PTR 可指定默认取出参数的大小(DWORD/WORD/BYTE)
    mov eax,dword ptr ds:[DwordVar2]     ; eax = 12345678h
    xor eax,eax
    mov ax,word ptr ds:[DwordVar2]       ; ax = 5678h
    mov ax,word ptr ds:[DwordVar2 + 2]   ; ax = 1234h
    
    ; 使用 LENGTHOF 可以计算数组元素的数量
    xor eax,eax
    mov eax,lengthof ArrayDW             ; eax = 10
    mov eax,lengthof ArrayBT             ; eax = 10
    
    ; 使用 TYPE 可返回按照字节计算的单个元素的大小.
    xor eax,eax
    mov eax,TYPE WordVar1                ; eax = 2
    mov eax,TYPE DwordVar2               ; eax = 4
    mov eax,TYPE ArrayDW                 ; eax = 4
    
    ; 使用 SIZEOF 返回等于LENGTHOF(总元素数)和TYPE(每个元素占用字节)返回值的乘基.
    xor eax,eax
    mov eax,sizeof ArrayBT               ; eax = 10
    mov eax,sizeof ArrayTP               ; eax = 120
    
    invoke ExitProcess,0
  main ENDP
END main数组直接寻址: 在声明变量名称的后面加上偏移地址即可实现直接寻址,直接寻址中可以通过立即数寻址,也可以通过寄存器相加的方式寻址,如果遇到双字等还可以使用基址变址寻址,这些寻址都属于直接寻址.
.386p
  .model flat,stdcall
  option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
.data
  ArrayB BYTE 10h,20h,30h,40h,50h
  ArrayW WORD 100h,200h,300h,400h
  ArrayDW DWORD 1h,2h,3h,4h,5h,6h,7h,8h,9h
.code
  main PROC
    ; 针对字节的寻址操作
    mov al,[ArrayB]           ; al=10
    mov al,[ArrayB+1]         ; al=20
    mov al,[ArrayB+2]         ; al=30
    ; 针对内存单元字存储操作
    mov bx,[ArrayW]           ; bx=100
    mov bx,[ArrayW+2]         ; bx=200
    mov bx,[ArrayW+4]         ; bx=300
    ; 针对内存单元双字存储操作
    mov eax,[ArrayDW]         ; eax=00000001
    mov eax,[ArrayDW+4]       ; eax=00000002
    mov eax,[ArrayDW+8]       ; eax=00000003
    
    ; 基址加偏移寻址: 通过循环eax的值进行寻址,每次eax递增2
    mov esi,offset ArrayW
    mov eax,0
    mov ecx,lengthof ArrayW
  s1:
    mov dx,word ptr ds:[esi + eax]
    add eax,2
    loop s1
    
    ; 基址变址寻址: 循环取出数组中的元素
    mov esi,offset ArrayDW                 ; 数组基址
    mov eax,0                              ; 定义为元素下标
    mov ecx,lengthof ArrayDW               ; 循环次数
  s2:
    mov edi,dword ptr ds:[esi + eax * 4]   ; 取出数值放入edi
    inc eax                                ; 数组递增
    loop s2
    
    invoke ExitProcess,0
  main ENDP
END main数组间接寻址: 数组中没有固定的编号,处理此类数组唯一可行的方法是用寄存器作为指针并操作寄存器的值,这种方法称为间接寻址,间接寻址通常可通过ESI实现内存寻址,也可通过ESP实现对堆栈的寻址操作.
.386p
  .model flat,stdcall
  option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
.data
  ArrayDW DWORD 1h,2h,3h,4h,5h,6h,7h,8h,9h
.code
  main PROC
    ; 第一种: 通过使用ESI寄存器实现寻址.
    mov esi,offset ArrayDW               ; 取出数组基地址
    mov ecx,lengthof ArrayDW             ; 取出数组元素个数
  s1:
    mov eax,dword ptr ds:[esi]           ; 间接寻址
    add esi,4                            ; 每次递增4
    loop s1
    
    ; 第二种: 通过ESP堆栈寄存器,实现寻址.
    mov eax,100                ; eax=1
    mov ebx,200                ; ebx=2
    mov ecx,300                ; ecx=3
    push eax                   ; push 1
    push ebx                   ; push 2
    push ecx                   ; push 3
    mov edx,[esp + 8]          ; EDX = [ESP+8] = 1
    mov edx,[esp + 4]          ; EDX = [ESP+4] = 2 
    mov edx,[esp]              ; EDX = [ESP] = 3
    
    ; 第三种(高级版): 通过ESP堆栈寄存器,实现寻址.
    push ebp
    mov ebp,esp                      ; 保存栈地址
    lea eax,dword ptr ds:[ArrayDW]   ; 获取到ArrayDW基地址
    ; -> 先将数据压栈
    mov ecx,9                        ; 循环9次
  s2: push dword ptr ss:[eax]          ; 将数据压入堆栈
    add eax,4                        ; 每次递增4字节
    loop s2
    ; -> 在堆栈中取数据
    mov eax,32                       ; 此处是 4*9=36 36 - 4 = 32
    mov ecx,9                        ; 循环9次
  s3: mov edx,dword ptr ss:[esp + eax] ; 寻找栈中元素
    sub eax,4                        ; 每次递减4字节
    loop s3
    
    add esp,36               ; 用完之后修正堆栈
    pop ebp                  ; 恢复ebp
    invoke ExitProcess,0
  main ENDP
END main比例因子寻址: 通过使用比例因子,以下例子每个DWORD=4字节,且总元素下标=0-3,得出比例因子3* type arrayDW.
.386p
  .model flat,stdcall
  option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
.data
  ArrayW  WORD  1h,2h,3h,4h,5h
  ArrayDW DWORD 1h,2h,3h,4h,5h,6h,7h,8h,9h
  TwoArray DWORD 10h,20h,30h,40h,50h
  RowSize = ($ - TwoArray)            ; 每行所占空间 20 字节
     DWORD 60h,70h,80h,90h,0ah
     DWORD 0bh,0ch,0dh,0eh,0fh
.code
  main PROC
  
    ; 第一种比例因子寻址
    mov esi,0                 ; 初始化因子
    mov ecx,9                 ; 设置循环次数
  s1:
    mov eax,ArrayDW[esi * 4]  ; 通过因子寻址,4 = DWORD
    add esi,1                 ; 递增因子
    loop s1
    
    ; 第二种比例因子寻址
    mov esi,0
    lea edi,word ptr ds:[ArrayW]
    mov ecx,5
  s2:
    mov ax,word ptr ds:[edi + esi * type ArrayW]
    inc esi
    loop s2
    
    ; 第三种二维数组寻址
    row_index = 1
    column_index = 2
    
    mov ebx,offset TwoArray            ; 数组首地址
    add ebx,RowSize * row_index        ; 控制寻址行
    mov esi,column_index               ; 控制行中第几个
    mov eax, dword ptr ds:[ebx + esi * TYPE TwoArray]
    
    invoke ExitProcess,0
  main ENDP
END main通过比例因子可以模拟实现二维数组寻址操作,过程也很简单.
.386p
  .model flat,stdcall
  option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
.data
  TwoArray DWORD 10h,20h,30h,40h,50h
  RowSize = ($ - TwoArray)            ; 每行所占空间 20 字节
     DWORD 60h,70h,80h,90h,0ah
     DWORD 0bh,0ch,0dh,0eh,0fh
.code
  main PROC
    lea esi,dword ptr ds:[TwoArray]  ; 取基地址
    mov eax,0                        ; 控制外层循环变量
    mov ecx,3                        ; 外层循环次数
  s1:
    push ecx                         ; 保存外循环次数
    push eax
    
    mov ecx,5                        ; 内层循环数
  s2: add eax,4                        ; 每次递增4
    mov edx,dword ptr ds:[esi + eax] ; 定位到内层循环元素
    loop s2
    
    pop eax
    pop ecx
    add eax,20                       ; 控制外层数组
    loop s1 
    invoke ExitProcess,0
  main ENDP
END main通过比例因子实现对数组求和操作,代码如下:
.386p
  .model flat,stdcall
  option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
.data
  ArrayA DWORD 10h,20h,30h,40h,50h
  ArrayB DWORD 10h,20h,30h,40h,50h
  NewArray DWORD 5 dup(0)
.code
  main PROC
    ; 循环让数组中的每一个数加10后回写
    mov ebx,0
    mov ecx,5
  s1:
    mov eax,dword ptr ds:[ArrayA + ebx * 4]
    add eax,10
    mov dword ptr ds:[ArrayA + ebx * 4],eax
    inc ebx
    loop s1
    
    ; 循环让数组A与数组B相加后赋值到数组NewArray
    mov ebx,0
    mov ecx,5
  s2:
    mov esi,dword ptr ds:[ArrayA + ebx]
    add esi,dword ptr ds:[ArrayB + ebx]
    mov dword ptr ds:[NewArray + ebx],esi
    add ebx,4
    loop s2
    invoke ExitProcess,0
  main ENDP
END main数组指针寻址: 变量地址的变量称为指针变量(pointer variable),Intel处理器使用两种基本类型的指针,即near(近指针)和far(远指针),保护模式下使用Near指针,所以它被存储在双字变量中.
.386p
  .model flat,stdcall
  option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
.data
  ArrayA  WORD  1h,2h,3h,4h,5h
  ArrayB DWORD 1h,2h,3h,4h,5h
  
  PtrA DWORD offset ArrayA     ; 指针 PtrA --> ArrayA
  PtrB DWORD offset ArrayB     ; 指针 PTRB --> ArrayB
.code
  main PROC
  
    mov ebx,0            ; 寻址因子
    mov ecx,5            ; 循环次数
  s1:
    mov esi,dword ptr ds:[PtrA]          ; 将指针指向PtrA
    mov ax,word ptr ds:[esi + ebx * 2]   ; 每次递增2字节
    
    mov esi,dword ptr ds:[PtrB]          ; 将指针指向PtrB
    mov eax,dword ptr cs:[esi + ebx * 4] ; 每次递增4字节
    inc esi              ; 基地址递增
    inc ebx              ; 因子递增
    loop s1
    invoke ExitProcess,0
  main ENDP
END main常见标志位测试: 标志寄存器又称程序状态寄存器,其主要用于存放条件码标志,控制标志和系统标志的寄存器,标志寄存器中存放的有条件标志,也有控制标志,这些标志则会影响跳转的实现,逆向中常见的标志位有如下几种.
.386p
  .model flat,stdcall
  option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
.code
  main PROC
  ; CF 进位标志位: 当执行一个加法(或减法)运算,使最高位产生进位(或借位)时,CF为1;否则为0
    mov ax,0ffffh
    add ax,1    ; cf = 1 af = 1
  ; PF 奇偶标志位: 当运算结果中,所有bit位(例:1001010)中1的个数为偶数时,则PF=1;为基数PF=0
    mov eax,00000000b
    add eax,00000111b  ; pf = 0
    
    mov eax,00000000b
    add eax,00000011b  ; pf = 1
  ; ZF 零标志位: 若当前的运算结果为零,则ZF=1; 否则ZF=0
    mov eax,2
    sub eax,2   ; zf = 1 cf = 0 af = 0
  
  ; SF 符号标志位: 若运算结果为负数,则SF=1;若为非负数则SF=0
    mov eax,3e8h
    sub eax,3e9h  ; sf = 1 cf = 1 af = 1 zf = 0
  
  ; DF 方向标志位: 当DF=0时为正向传送数据(cld),否则为逆向传送数据(std)
    cld
    mov eax,1      ; df = 0
    std
    mov eax,1      ; df = 1
  ; OF 溢出标志位: 记录是否产生了溢出,当补码运算有溢出时OF=1;否则OF=0
    mov al,64h
    add al,64h     ; of = 1 cf = 0 pf = 0 af = 0
  
    invoke ExitProcess,0
  main ENDP
END mainTEST 位与指令: 该指令在对操作数之间执行隐含与运算操作,并设置相应的标志位,与AND指令唯一的不同在于,该指令只会设置相应的标志,并不会替换目的操作数中的数值,常用于测试某些位是否被设置.
TEST指令可以同时检测设置多个标志位的值,该指令执行时总是清除溢出标志和进位标志,它修改符号标志,基偶标志,零标志的方式与AND指令相同.
.386p
  .model flat,stdcall
  option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
.code
  main PROC
    mov al,00001111b
    test al,2            ; zf=0 pf=0
    mov al,00100101b
    test al,00001001b    ; zf=0 pf=0
    
    mov al,00100100b
    test al,00001001b    ; zf=1 pf=1
    
    mov eax,0100h
    test eax,eax         ; zf=0
    
    mov eax,0
    test eax,eax         ; zf=0
    
    or al,80h            ; 设置符号标志 zf=0 pf=0 sf=1
    and al,7fh           ; 清除符号标志 zf=1 pf=1 sf=0
    
    mov al,0
    or al,1              ; 清除符号标志 zf=0 pf=0
    stc                  ; 设置进位标志 cf = 1
    clc                  ; 清除进位标志 cf = 0
    
    mov al,07fh          ; AL = +127
    inc al               ; 设置溢出标志 AL = 80h (-128) of=1 af=1 sf=1
    or eax,0             ; 清除溢出标志
    invoke ExitProcess,0
  main ENDP
END mainCMP 比较指令: 该指令作用是在源操作数和目的操作数中间执行隐含的减法运算,两个操作数都不会被修改,仅会影响标志位的变化,CMP指令是高级语言实现程序逻辑的关键,也是汇编中非常重要的指令常与跳转指令合用.
.386p
  .model flat,stdcall
  option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
.code
  main PROC
    ; 比较5和10
    mov ax,5
    cmp ax,10      ; 5-10 > zf=0 cf=1 pf=0 af=1 sf=1
    
    ; 比较两个相同数
    mov ax,1000
    mov cx,1000
    cmp cx,ax      ; 1000-1000 > zf=1 cf=0 pf=1 sf=0
    
    ; 比较100和0
    mov ax,100
    cmp ax,0       ; 100-0 > zf=0 cf=0 pf=0
    
    ; 比较100和50
    mov eax,100
    mov ebx,50
    cmp eax,ebx    ; 100-50 > zf=0 cf=0 pf=0
    
    ; 比较-100和50
    mov eax,-100
    mov ebx,50
    cmp eax,ebx    ; -100-50 > sf=1 pf=1
    
    ; 比较-100和-50
    mov eax,-100
    mov ebx,-50
    cmp eax,ebx    ; -100--50 > cf=1 af=1 pf=0
  
    invoke ExitProcess,0
  main ENDP
END main标志跳转指令: 跳转指令分为多种,第一种常见的跳转指令就是基于特定CPU的标志寄存器来实现的跳转形式.
.386p
  .model flat,stdcall
  option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
.code
  main PROC
    ; JZ/JE 当ZF置1时 也就是结果为零则跳转 (leftOp - rightOp = 0)
    mov eax,1
    sub eax,1     ; zf=1 pf=1
    je jump
    
    mov eax,1
    mov ebx,1
    cmp eax,ebx   ; zf=1
    jz jump
    
    ; JNZ/JNE 当ZF置0时 也就是结果不为零则跳转 (leftOp - rightOp != 0)
    mov eax,2
    sub eax,1
    jnz jump      ; zf=0 pf=0
    
    mov eax,2
    cmp eax,1
    jne jump      ; zf=0
    
    ; JC/JNC 当 CF=1/0 设置进位标志则跳/未设置进位标志则跳
    mov al,0
    cmp al,1
    jc jump
    jnc jump
    
    ; JO/JNO 当 OF=1/0 设置溢出标志则跳/未设置溢出标志则跳
    mov al,0ffh
    add al,1
    jo jump
    
    ; JS/JNS 当 SF=1/0 设置符号标志则跳/未设置符号标志则跳
    mov eax,1
    cmp eax,1
    js jump       ; cf=0 af=0
    
    ; JP/JNP PF=1/0 设置奇偶标志则跳(偶)/未设置奇偶标志则跳(基)
    mov al,00100100b
    cmp al,0
    jp jump        ; zp=0
  jump:
    xor eax,eax
    xor ebx,ebx
    
    invoke ExitProcess,0
  main ENDP
END main比较跳转标志: 通过使用cmp eax,ebx比较等式两边的值,影响相应的标志寄存器中的值,从而决定是否要跳转,常用的如下:
.386p
  .model flat,stdcall
  option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
.code
  main PROC
    ; JA(无符号)/JG(有符号) 跳转标志: (left > right) 大于则跳转
    mov eax,100
    mov ebx,200
    cmp ebx,eax    ; 无符号 ebx > eax
    ja jump        ; zf=0 pf=0
    
    mov eax,20
    mov ebx,-100
    cmp eax,ebx    ; 有符号 eax > ebx
    jg jump        ; zf=0 cf=1 pf=1 af=1
    
    ; JAE(无符号)/JGE(有符号) 跳转标志: (left >= right) 大于或等于则跳转
    mov eax,50
    mov ebx,20
    cmp eax,ebx    ; 无符号 eax >= ebx
    jae jump       ; jae 被替换成了jnb 小于则跳 (eax<ebx)
    
    mov eax,50
    mov ebx,-20
    cmp eax,ebx    ; 有符号 eax >= ebx
    jge jump       ; zf=0 af=1 pf=0 sf
    
    ; JB(无符号)/JL(有符号) 跳转标志:(left < right) 小于则跳转
    mov eax,10
    mov ebx,20
    cmp eax,ebx     ; 无符号 eax < ebx
    jb jump         ; cf=0 af=0 pf=1
    
    mov eax,-10
    mov ebx,20
    cmp eax,ebx     ; 有符号 eax < ebx
    jl jump
    
    ; JBE(无符号)/JLE(有符号) 跳转标志:(left <= right) 小于或等于则跳转
    mov eax,20
    mov ebx,20
    cmp eax,ebx    ; 无符号 eax <= ebx
    jbe jump       ; zf=1
    
    mov eax,-20
    mov ebx,10
    cmp eax,ebx    ; 无符号 eax,ebx
    jle jump       ; sf=1
    
    ; JNB(不小于则跳 同JAE)/JNA(不大于则跳 同JBE) 跳转标志:(lef !>< right) 无符号
    mov eax,10
    mov ebx,5
    cmp eax,ebx    ; eax !< ebx
    jnb jump
    
    mov eax,5
    mov ebx,10
    cmp eax,ebx    ; eax !> ebx
    jbe jump
    ; JNB(不小于则跳 同JAE)/JNA(不大于则跳 同JBE) 跳转标志:(lef !>< right) 无符号
    mov eax,10
    mov ebx,5
    cmp eax,ebx    ; eax !< ebx
    jnb jump       ; zf=0 cf=0
    
    mov eax,5
    mov ebx,10
    cmp eax,ebx    ; eax !> ebx
    jbe jump       ; cf=1 af=1 zf=0
    
    ; JNL(不小于则跳 同JGE)/JNG(不大于则跳 同JLE) 跳转标志:(lef !>< right) 有符号
    mov eax,10
    mov ebx,-5
    cmp eax,ebx    ; eax !< ebx
    jnl jump       ; sf=0 cf=1 pf=1 af=1 zf=0
    
    mov eax,-10
    mov ebx,5
    cmp eax,ebx    ; eax !> ebx
    jng jump       ; sf=1
  jump:
    xor eax,eax
    xor ebx,ebx
    
    invoke ExitProcess,0
  main ENDP
END mainBT/BSF/BSR 位测试指令: 首先BT系列命令主要用于对特定寄存器进行测试,清除,设置或求反等操作,它会影响CF标志位,而BSF/BSR命令则是对特定位中的值进行正反向扫描操作,它会影响ZF标志位.
.386p
  .model flat,stdcall
  option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
.code
  main PROC
    ; bt 普通的位测试命令
    xor edx,edx
    mov dx,10000001b
    bt dx,7              ; 把DX第7位送入CF = 1
    bt dx,6              ; 把DX第六位送入CF = 0
    
    ; bts 位测试并置位
    mov dx,10000001b
    bts dx,6             ; cf = 0
    bts dx,7             ; cf = 1
    
    ; btr 位测试并复位.在执行BT同时,把操作数的指定位置为0
    mov dx,10000001b
    btr dx,7
    btr dx,6             ; cf = 0
    
    ;btc 位测试并取反.在执行BT同时,把操作数的指定位取反
    mov dx,10000001b
    btc dx,0             ; cf = 1
    btc dx,0             ; cf = 0
    
    ; BSF 执行位扫描 由低->高位 | BSR 由高 -> 到低
    xor edx,edx
    mov dx, 0000111100001100b
    bsf cx,dx            ; 正向扫描,将扫描到1的位置放入CX
    bsr cx,dx            ; 反向扫描 zf=0 pf=0
    
    xor ecx,ecx
    mov cx,0
    mov dx,0
    bsf cx,dx
    lahf
    invoke ExitProcess,0
  main ENDP
END main
版权声明:本博客文章与代码均为学习时整理的笔记,文章 [均为原创] 作品,转载请 [添加出处] ,您添加出处是我创作的动力!









