龙龙 发表于 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 ,ax
  jmp word ptr
  这样ip的值就是1234h
  在就是段间转移
  格式
  jmp dword ptr 内存单元地址
  这个dword说明从内存单元开始的一个双字,低字是ip的地址
  高字是cs的地址
  如:mov bx,0
  mov ax,1122h
  mov ,ax
  mov ax,3344h
  mov ,ax
  jmp dword ptr ds:
  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,我们总结下转移命令
  条件转移的是:
  loopcx不为0,则转移
  jcxzcx为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:
  mov cs:,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,
  20BC:0013 2E            CS:
  20BC:0014 8905          MOV   ,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:,ax
  call word ptr ds:
  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   ,AX
  20BD:0013 FF160000      CALL   
  执行到call call word ptr ds:
  这条时,
  首先把下面的地址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:,ax
  mov ds:,cs
  call dword ptr ds:
  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   ,AX
  20BD:000D 8C0E0200      MOV   ,CS
  20BD:0011 FF1E0000      CALL    FAR
  20BD:0015 B8004C      MOV   AX,4C00
  20BD:0018 CD21          INT   21
  当程序执行call dword ptr ds:
  的时间,首先把
  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,ah
  mul byte ptr
  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,dx
  mul word ptr
  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,
  call ww
  mov ,ax
  mov ,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 ,ax处执行,把积的值依次放入第二组数据表中然后si加2,di加4,然后循环上面的操作!
  注意:是十六位相乘,故出现:
  mov ,ax
  mov ,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,
  20BF:0010 E81200      CALL    0025
  20BF:0013 8905          MOV   ,AX
  20BF:0015 895502      MOV   ,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 ,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,
  jcxz ok
  and byte ptr ,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)把积用地址存放
  关于寄存器的冲突问题,相信大家之前也遇到过,往往循环中就出现这种的冲突
  解决办法:大家可以利用栈或者是内存地址空间,把某个寄存器的值先放进去
  然后在重新定义这个寄存器的值,用到的时间,在出栈,或者是调用内存地址的数据,注意栈的出栈顺序!实验十不多讲!都是做过的
页: [1]
查看完整版本: 汇编语言笔记 (第九章)