找回密码
 立即注册

扫一扫,访问微社区

QQ登录

只需一步,快速开始

查看: 1507|回复: 0

汇编语言笔记 (第九章)

[复制链接]
  • TA的每日心情
    开心
    2018-10-31 14:26
  • 签到天数: 18 天

    [LV.4]偶尔看看III

    78

    主题

    109

    回帖

    1019

    积分

    高级会员

    积分
    1019

    猴年

    龙龙实名认证 发表于 2015-6-22 02:59:59 | 显示全部楼层 |阅读模式
      汇编语言笔记 (第九章)
      第九章转移指令
      呵呵!转移相对于逆向软件还是非常重要的
      其实我们之前学过的loop就是一个转移指令,可以说是一个条件转移
      或者是循环转移的指令
      先说offset操作符
      他的作用就是取得标号的偏移地址!
      比如:offset sb
      如果sb标号所在的偏移地址为6,那么这个offset就取得6
      例如:
      assume cs:qq
      qq segment
      mov ax,5
      sb:
      mov bx,ax
      mov cx,offset sb
      s1:
      add ax,10
      loop s1
      mov ax,4c00h
      int 21h
      qq ends
      end
      上面的cx,他的值就是3了,因为sb所在的偏移地址是3,mov ax,5这句占了3个地址,即0,1,2
      下面说下jmp指令了
      jmp为无条件转移指令,他的功能可以分为很多种,我们一样一样说
      首先是短转移
      格式是: jmp short 标号
      既然短转移,可以看出肯定非常短,所以他只能修改ip的值,范围呢
      就是-128到127之间,就是往前可以移动128个字节,往后可以移动127个字节
      例如:
      assume cs:qq
      qq segment
      mov ax,5
      add ax,10
      jmp short s
      add ax,10
      s:mov ax,4c00h
      int 21h
      qq ends
      end
      那么这样的话,就只执行一次加10的操作,而跳过一个加10的操作
      把上面程序,载入debug,用u指令查看
      20BC:0000 B80500        MOV     AX,0005
      20BC:0003 83C00A        ADD     AX,+0A
      20BC:0006 EB03          JMP     000B
      20BC:0008 83C00A        ADD     AX,+0A
      20BC:000B B8004C        MOV     AX,4C00
      20BC:000E CD21          INT     21
      可以看出,其他立即数都显示在汇编指令中,如,MOV     AX,0005
      显示的是B80500 ,其他也有对应的数值,但jmp命令却没有 是:EB03
      在观察可以发现,jmp 000b其实是给出的内存地址的位移数,即我们需要跳转到
      MOV     AX,4C00  这里,而这里的偏移地址正好是000b,而jmp指令后的的
      下一个地址是0008,相减正好是03,即我们得出结论
      jmp short 标号,的地址是,标号处的地址减去jmp指令执行后的第一个字节的地址
      还有一直和jmp short 指令很像的一条指令,我们称为近转移
      他的格式是 jmp near ptr标号,他也是只能修改ip的值,范围是-32768到32767之间
      是16位的位移!其他和jmp short一样!
      在就是jmp的段间转移了
      格式是jmp far ptr标号
      他是远转移,可以修改cs和ip的值,如:
      assume cs:qq
      qq segment
      mov ax,5
      add ax,10
      jmp far ptr s
      add ax,10
      s:mov ax,4c00h
      int 21h
      qq ends
      end
      载入debug使用U命令查看:
      20BC:0000 B80500        MOV     AX,0005
      20BC:0003 83C00A        ADD     AX,+0A
      20BC:0006 EA0E00BC20    JMP     20BC:000E
      20BC:000B 83C00A        ADD     AX,+0A
      20BC:000E B8004C        MOV     AX,4C00
      20BC:0011 CD21          INT     21
      可以看出,jmp命令,已经把cs和ip的值都给了出来,而且机器码也显示出来
      再说下一个jmp命令
      就是jmp 寄存器(16位寄存器)
      他的作用是修改ip的,范围和jmp near ptr一样
      最后说一下,地址在内存的转移
      这个有两种,也是段内转移,和段间转移
      先说段内转移:
      格式
      jmp word ptr 内存单元地址
      这个word说明从内存单元开始的一个字,是ip的地址
      如:mov ax,1234h
      mov [bx],ax
      jmp word ptr [bx]
      这样ip的值就是1234h
      在就是段间转移
      格式
      jmp dword ptr 内存单元地址
      这个dword说明从内存单元开始的一个双字,低字是ip的地址
      高字是cs的地址
      如:mov bx,0
      mov ax,1122h
      mov [bx],ax
      mov ax,3344h
      mov [bx+2],ax
      jmp dword ptr ds:[0]
      add ax,10
      这样ip的值就是1122h,cs就是3344h
      下面就是条件转移指令了
      jcxz指令
      他和loop差不多,都是拿cx来判断的
      也是短转移,所以修改的也只是ip的值,同样也是8位的位移
      所以修改范围还是—128——127之间
      格式:
      jcxz XX(如果cx=0就执行跳转)
      这个XX就是标号,这个命令和loop相反
      loop是cx不等于就执行跳转
      jcxz的例子:
      assume cs:qq
      qq segment
      start:
      mov cx,0
      jcxz s
      mov ax,10
      s: add ax,10
      mov ax,4c00h
      int 21h
      qq ends
      end start
      这个例子中,ax的结果就是A,也就是十进制的10
      而不执行mov ax,10这条指令
      OK,我们总结下转移命令
      条件转移的是:
      loop  cx不为0,则转移
      jcxz  cx为0,则转移
      都是修改ip,都是8位位移
      无条件转移:
      jmp short 标号
      短转移,ip,8位位移
      jmp near ptr 标号
      近转移,ip,16位位移
      jmp far ptr 标号
      远转移,同时修改cs和ip
      jmp 寄存器
      16位ip位移
      jmp word ptr 内存单元地址
      段内转移,修改ip,16位位移
      jmp dword ptr 内存单元地址
      段间转移,修改cs和ip的值
      第九章下半部分了
      呵呵!其实第九章基本前面已经说的差不多了,这里就是个总结和补充
      说下jmp指令为什么不是全部直接给出cs和ip的值,比如jmp short 标号
      就是给出偏移地址
      原因很简单,就是为了程序的移植新行和,在内存中的浮动配置,以及后期程序的修改
      比如:
      我要死限定jmp跳转到某一个地址如2000:100  指令为mov ax,bx
      但是如果放到别人的电脑上,就有可能2000:100是别的指令,特别是循环语句loop,如果
      循环语句给出的是cs和ip的准确值,那么修改源码时,就要重新计算地址...这个大家对内
      存地址稍微了解些,就明白了
      关于内存地址跳转时的指令的范围,大家可以字节实验一下
      比如测试jmp short 标号 这个,大家可以在跳转命令和符号地址中间填充一些空白数据测
      试,如这个命令只能向后移动127个字节,那么你就中间填充130个或更多的字节,自己编
      译看看!当然编译器会报错,这个是由编译器自己检测的
      我分析下实验八,其实这个就是有点绕,大家单步走走就明白了
      源代码:
      assume cs:codesg
      codesg segment
      mov ax,4c00h
      int 21h
      start: mov ax,0         
      s: nop               
      nop                 
      mov di,offset s   
      mov si,offset s2   
      mov ax,cs:[si]
      mov cs:[di],ax     
      s0: jmp short s         
      s1: mov ax,0
      int 21h
      mov ax,0
      s2: jmp short s1      
      nop
      codesg ends
      end start
      加载debug用u指令查看:
      20BC:0005 B80000        MOV     AX,0000
      20BC:0008 90            NOP
      20BC:0009 90            NOP
      20BC:000A BF0800        MOV     DI,0008
      20BC:000D BE2000        MOV     SI,0020
      20BC:0010 2E            CS:
      20BC:0011 8B04          MOV     AX,[SI]
      20BC:0013 2E            CS:
      20BC:0014 8905          MOV     [DI],AX
      20BC:0016 EBF0          JMP     0008
      20BC:0018 B80000        MOV     AX,0000
      20BC:001B CD21          INT     21
      20BC:001D B80000        MOV     AX,0000
      20BC:0020 EBF6          JMP     0018
      20BC:0022 90            NOP
      我一步一步说下:
      先说地址:
      start上面的两段代码暂居了0—4地址
      首先
      根据start找入口,执行mov ax,0   ax=0
      然后从s标号开始执行,也就是两个nop
      然后执行mov di,offset s   
      mov si,offset s2
      就是把di设置为8h,应为s标号的地址是8h
      把si设置为20h,因为s2标号地址是20h
      然后ip为20h的指令放入ax中,即s2的指令放入ax中,s2的指令是jmp short s1   (两个
      字节)
      然后又把ax中的值放入ip值是di的地方,前面说过di是8h,也就是把ax的指令放入ip地址
      是8h中 正好把两个nop填充了
      然后执行s0: jmp short s  命令,就跳转到8h处,也就是标号s处
      这时标号s的地址指令已经是EBF6   这条指令在地址20h处,他指向了18h,也就是向上编
      译了10个地址可以得知 F6是补码-10然后在8h地址执行,占两个字节,也就是8+2-10,得出跳转到地址0处然后执mov ax,4c00hint 21h....
      呵呵!感觉有点饶,关键是考验补码和jmp short命令的位移问题!大家好好思考就OK了关于实验九,方法很多,但为了练习大家,还是希望多用jmp指令,这样简单些!代码就不写了,自己地下写,很简单!
      第十章上
      呵呵!已经第十章了,很快!这几章呢,都是说的转移指令,都是和cs:ip打交道所以大家一定要对cs和ip的原理烂透于心
      先说下ret指令
      ret指令有两个
      一个是ret指令,一个是retf指令
      可以这样说,一个是近转移,一个是远转移
      一个修改ip一个同时修改cs和ip
      ret指令就是
      ip=ss*16+sp
      retf就是
      ip=ss*16+sp
      sp+2
      cs=ss*16+sp
      拿ret做个无限循环的例子:
      assume cs:qq,ds:ee
      ee segment
      db 16 dup (0)
      ee ends
      qq segment
      mov ax,4c00h
      int 21h
      start:
      mov ax,ee
      mov ss,ax
      mov sp,16
      mov ax,5
      push ax
      ret
      qq ends
      end start
      上面的程序,我把ax设置为5,然后入栈,这样的话,
      每次执行到ret的时间,ip都是指向5,就是指向了mov ax,ee这句
      导致程序不能正常退出!
      如果那retf做的话,也是一样,程序如下:
      assume cs:qq,ds:ee
      ee segment
      db 16 dup (0)
      ee ends
      qq segment
      mov ax,4c00h
      int 21h
      start:
      mov ax,ee
      mov ss,ax
      mov sp,16
      mov ax,5
      push cs
      push ax
      retf
      qq ends
      end start
      无非是,retf指令,是远转移,需要指定cs的值,我把cs本身的值入栈,
      就是cs=cs,ip还是5,所有效果和上面是一样的!
      在写给例子吧
      例如我们让程序从2000:1000处执行
      程序就是:
      assume cs:qq,ds:ee
      ee segment
      db 16 dup (0)
      ee ends
      qq segment
      mov ax,4c00h
      int 21h
      start:
      mov ax,ee
      mov ss,ax
      mov sp,16
      mov ax,200
      push ax
      mov ax,2000h
      push ax
      mov ax,1000h
      push ax
      retf
      qq ends
      end start
      执行retf后,cs就是2000,ip就是1000
      下面就是说下call指令了
      首先是位移的call指令  格式:call 标号
      范围呢是十六位的位移
      这个刚开始有点绕,光看这一个call很难理解,我说下和ret的合用
      举个例子:
      使程序正常退出:
      assume cs:code,ss:ee
      ee segment
      db 16 dup(0)
      ee ends
      code segment
      start:mov ax,ee
      mov ss,ax
      mov sp,16
      call s
      mov ax,4c00h
      int 21h
      s:add ax,1
      ret
      code ends
      end start
      上面的程序,载入debug,用u查看机器指令:
      20BD:0000 B8BC20        MOV     AX,20BC
      20BD:0003 8ED0          MOV     SS,AX
      20BD:0005 BC1000        MOV     SP,0010
      20BD:0008 E80500        CALL    0010
      20BD:000B B8004C        MOV     AX,4C00
      20BD:000E CD21          INT     21
      20BD:0010 83C001        ADD     AX,+01
      20BD:0013 C3            RET
      单步走的话,执行到call s这句的时间,进行了如下操作
      首先把此时ip的值,也就是下句:mov ax,4c00h 的ip值
      入栈,就是000B入栈,然后跳转到位移是0005的地方,也就是
      000B+0005就是0010处,就是add ax,1
      现在接着执行下面的ret
      ret执行的时间,从栈中获取到0005当作ip的值,这是就跳转到
      mov ax,4c00h处,从而实现了正常退出
      下面就是段间的跳转,用call指令
      格式:call far ptr 标号
      他是在进行跳转的时间,同时把下面指令的cs和ip的值入栈
      我们举个例子:
      使程序正常退出:
      assume cs:code,ss:ee
      ee segment
      db 16 dup(0)
      ee ends
      code segment
      start:mov ax,ee
      mov ss,ax
      mov sp,16
      call far ptr s
      mov ax,4c00h
      int 21h
      s:mov ax,0
      retf
      code ends
      end start
      上面的程序,载入debug,用u查看机器指令:
      20BD:0000 B8BC20        MOV     AX,20BC
      20BD:0003 8ED0          MOV     SS,AX
      20BD:0005 BC1000        MOV     SP,0010
      20BD:0008 9A1200BD20    CALL    20BD:0012
      20BD:000D B8004C        MOV     AX,4C00
      20BD:0010 CD21          INT     21
      20BD:0012 B80000        MOV     AX,0000
      20BD:0015 CB            RETF
      当程序执行到call far ptr s的时间,首先把cs和ip的值入栈
      即把20BD 000D 先是cs入栈,后ip入栈
      然后跳转到cs=20bd,ip=0012处,就是mov ax,0000这里
      然后执行retf这句
      当执行这句的时间,会用栈顶获取cs和ip的值,首先是ip=0012
      再就是cs=20bd,这个时间就跳转到mov ax,4c00h这句,从而实现
      正常退出
      再说下call 寄存器的命令
      格式:call 16位寄存器
      还是举个例子说:
      同样是实现正常退出程序:
      assume cs:code,ss:ee
      ee segment
      db 16 dup(0)
      ee ends
      code segment
      start:mov ax,ee
      mov ss,ax
      mov sp,16
      mov ax,12h
      call ax
      mov ax,4c00h
      int 21h
      s:mov ax,0
      ret
      code ends
      end start
      上面的程序,载入debug,用u查看机器指令:
      20BD:0000 B8BC20        MOV     AX,20BC
      20BD:0003 8ED0          MOV     SS,AX
      20BD:0005 BC1000        MOV     SP,0010
      20BD:0008 B81200        MOV     AX,0012
      20BD:000B FFD0          CALL    AX
      20BD:000D B8004C        MOV     AX,4C00
      20BD:0010 CD21          INT     21
      20BD:0012 B80000        MOV     AX,0000
      20BD:0015 C3            RET
      当程序执行到call ax时,首先OD入栈,然后根据ax=12
      所以跳转到mov ax,0 处,然后执行ret命令,ret从栈中获取
      ip的值,就是ip=od,即跳转到mov ax,4c00处,从而实现正常
      退出程序最后说下call在内存地址中的指令和jmp一样,分2种
      先说 call word ptr 内存单元地址
      例子:
      正常退出:
      assume cs:code,ss:ee
      ee segment
      db 16 dup(0)
      ee ends
      code segment
      mov ax,4c00h
      int 21h
      start:mov ax,ee
      mov ss,ax
      mov sp,16
      mov ax,0
      mov ds:[0],ax
      call word ptr ds:[0]
      code ends
      end start
      载入debug,用U命令查看:
      20BD:0000 B8004C        MOV     AX,4C00
      20BD:0003 CD21          INT     21
      20BD:0005 B8BC20        MOV     AX,20BC
      20BD:0008 8ED0          MOV     SS,AX
      20BD:000A BC1000        MOV     SP,0010
      20BD:000D B80000        MOV     AX,0000
      20BD:0010 A30000        MOV     [0000],AX
      20BD:0013 FF160000      CALL    [0000]
      执行到call call word ptr ds:[0]
      这条时,
      首先把下面的地址ip入栈,然后跳转到ip为0处执行,这个最简单!
      下面再说下,call dword ptr 内存单元地址这个命令
      他和上面不同的是,他执行跳转时,同时把cs和ip的值都入栈了
      举个例子:
      还是正常退出:
      assume cs:code,ss:ee
      ee segment
      db 16 dup(0)
      ee ends
      code segment
      retf
      start:
      mov ax,ee
      mov sp,16
      mov ax,0
      mov ds:[0],ax
      mov ds:[2],cs
      call dword ptr ds:[0]
      mov ax,4c00h
      int 21h
      code ends
      end start
      载入debug查看,用U指令:
      20BD:0000 CB            RETF
      20BD:0001 B8BC20        MOV     AX,20BC
      20BD:0004 BC1000        MOV     SP,0010
      20BD:0007 B80000        MOV     AX,0000
      20BD:000A A30000        MOV     [0000],AX
      20BD:000D 8C0E0200      MOV     [0002],CS
      20BD:0011 FF1E0000      CALL    FAR [0000]
      20BD:0015 B8004C        MOV     AX,4C00
      20BD:0018 CD21          INT     21
      当程序执行call dword ptr ds:[0]
      的时间,首先把
      cs是20bd和ip是0015入栈了,然后根据ds:0的数据进行跳转
      就是本段的0处,所以跳转到retf处,retf又根据栈中的数据
      设置了cs=20bd,ip=0015,所有跳转到mov ax,4c00h处
      导致程序正常退出!
      关于call和ret的综合运用,我就不说了,上面全部赛综合运用了
      OK 第十章上半部分就先说到这!
      说下乘法指令mul
      两数相乘:
      都是八位的话,一个默认放在al中
      另一个在内容单元或者8位寄存器中
      结果默认是放在ax
      格式:
      mul 内存单元
      mul 8位寄存器
      由于是八位,这样乘数就必须小于255
      大于的话,就用16位相乘
      寄存器例如:
      计算20*30
      20的十六进制是14H
      30的十六进制是1EH
      assume cs:qq
      qq segment
      mov al,14h
      mov ah,1eh
      mul ah
      mov ax,4c00h
      int 21h
      qq ends
      end
      结果AX=0258
      内存单元例如:
      assume cs:qq
      qq segment
      mov al,14h
      mov ah,1eh
      mov bx,0
      mov byte ptr[bx],ah
      mul byte ptr[bx]
      mov ax,4c00h
      int 21h
      qq ends
      end
      结果还是AX=0258
      都是十六位的话,一个默认让在ax中一个默认放在另一个在内容单元或者16位寄存器中结果默认是高位放dx中,地位放ax中
      mul 内存单元
      mul 16位寄存器
      内存单元例如:
      计算500*400
      500的十六进制是1F4H
      400的十六进制是190H
      assume cs:qq
      qq segment
      mov ax,1F4h
      mov bx,190h
      mul bx
      mov ax,4c00h
      int 21h
      qq ends
      end
      结果:dx=0003
      ax=0d40
      即结果是30d40 即200000
      拿内存单元来说:
      assume cs:qq
      qq segment
      mov ax,1F4h
      mov dx,190h
      mov word ptr[bx],dx
      mul word ptr[bx]
      mov ax,4c00h
      int 21h
      qq ends
      end
      结果一样!
      注意:word ptr 和byte ptr的使用
      下面我再说个,混合运用的例子:
      计算:
      (8+12-10)*100/20  的结果
      思路:先算8+12的和,然后拿和减去10,乘以100,然后除于20
      assume cs:qq
      qq segment
      mov ah,8h
      mov al,0ch
      mov bh,0ah
      add ah,al
      sub ah,bh
      mov al,ah
      mov ah,64h
      mul ah
      mov bx,14h
      div bx
      mov ax,4c00h
      int 21h
      qq ends
      end
      结果是al=32h 即50
      下面就是重要的,关于模块化程序的设计
      这个和C语言很想象,呵呵!思想很类似与api
      我写给例子给大家说明:
      计算第一组数据的三次方,然后把结果放到第二组里面
      assume cs:qq,ds:ee
      ee segment
      dw 2,4,6,8,10,12,15
      dd 0,0,0,0,0,0,0,0
      ee ends
      qq segment
      start:
      mov ax,ee
      mov ds,ax
      mov si,0
      mov di,16
      mov cx,8
      s:
      mov bx,[si]
      call ww
      mov [di],ax
      mov [di+2],dx
      add si,2
      add di,4
      loop s
      mov ax,4c00h
      int 21h
      ww:
      mov ax,bx
      mul bx
      mul bx
      ret
      qq ends
      end start
      程序执行到s表好处,把地址ds:si处的数据给了bx然后call 跳转到ww标号处,并把ip值入栈
      程序执行ww标号处指令,也就算出三次方的值,题目为16位相乘ret根据栈中数据地址,跳转到mov [di],ax处执行,把积的值依次放入第二组数据表中然后si加2,di加4,然后循环上面的操作!
      注意:是十六位相乘,故出现:
      mov [di],ax
      mov [di+2],dx
      理解add di,4
      载入debug用U命令查看:
      20BF:0000 B8BC20        MOV     AX,20BC
      20BF:0003 8ED8          MOV     DS,AX
      20BF:0005 BE0000        MOV     SI,0000
      20BF:0008 BF1000        MOV     DI,0010
      20BF:000B B90800        MOV     CX,0008
      20BF:000E 8B1C          MOV     BX,[SI]
      20BF:0010 E81200        CALL    0025
      20BF:0013 8905          MOV     [DI],AX
      20BF:0015 895502        MOV     [DI+02],D
      20BF:0018 83C602        ADD     SI,+02
      20BF:001B 83C704        ADD     DI,+04
      20BF:001E E2EE          LOOP    000E
      20BF:0020 B8004C        MOV     AX,4C00
      20BF:0023 CD21          INT     21
      20BF:0025 8BC3          MOV     AX,BX
      20BF:0027 F7E3          MUL     BX
      20BF:0029 F7E3          MUL     BX
      20BF:002B C3            RET
      批量数据的传递,返回值的传递大家有这样的感受,如果再不用内存地址的时间只有寄存器来传递,大家会感觉到寄存器不够用呵呵!从开始的那么多寄存器不知道用那个到现在的不够用,代表进步了很多,同时大家要注意对内存地址的使用,因为这个是很大利用空间啊说下,用内存地址来传递数据
      如书上例题一样:
      把一个字符串转换为大写
      assume cs:qq,ds:ee
      ee segment
      db 'hellofishc'
      ee ends
      qq segment
      start:
      mov ax,ee
      mov ds,ax
      mov di,0
      mov cx,10
      call ww
      mov ax,4c00h
      int 21h
      ww: and byte ptr [di],11011111b
      inc di
      loop ww
      ret
      qq ends
      end start
      这个思路说下:
      call到ww标号处,and命令转换大写,然后inc di转换下面的字母,cx=10,就是转换10次,所以字母转换完后,ret跳转到mov ax,4c00h处,正常退出程序
      这个问题,用jcxz和jmp来做,很有意思
      assume cs:qq,ds:ee
      ee segment
      db 'hellofishc'
      ee ends
      qq segment
      start:
      mov ax,ee
      mov ds,ax
      mov di,0
      call ww
      mov ax,4c00h
      int 21h
      ww:
      mov cl,0
      mov ch,[di]
      jcxz ok
      and byte ptr [di],11011111b
      inc di
      jmp ww
      ok:ret
      qq ends
      end start
      呵呵!先把cl设置为0,然后把di的值放入ch中,如果cx=0则不运算,直接返回!
      关于多个值的传递,大家下面可以测试下,同时计算几个数的积,然后把所有的积相加
      如:(2*4)+(4*5)+(3*6)+(7*8)把积用地址存放
      关于寄存器的冲突问题,相信大家之前也遇到过,往往循环中就出现这种的冲突
      解决办法:大家可以利用栈或者是内存地址空间,把某个寄存器的值先放进去
      然后在重新定义这个寄存器的值,用到的时间,在出栈,或者是调用内存地址的数据,注意栈的出栈顺序!实验十不多讲!都是做过的
    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|虫虫联盟 ( 备案号:蜀ICP备15018121号-1 )

    GMT+8, 2024-6-25 14:31 , Processed in 0.400923 second(s), 27 queries .

    Powered by Discuz! X3.5 Licensed

    © 2001-2024 Discuz! Team.

    快速回复 返回顶部 返回列表