关于jmp相对跳的一道程序分析
夜深人静,嘿嘿 只有这个时候才有时间静下来看点东西
前几天在看 16 位汇编语言程序设计 王爽 老师写的 写得真的很好 呵呵 不是卖广告哈 资源共享嘛
遇到一个问题 通过这个问题发现能深刻理解到 jmp 指令很具内涵的一些内容
甚欢 乃著此文以记之
程序如下:
assume cs:codesg
codesg segment
mov ax, 4c 00h
int 21h
start :
mov ax,0
s:
nop
nop
mov di,offset s ; 这里应该是计算 s 对于 segment 处的偏移量,赋值给 di
mov si,offset s2 ; 计算 s2 对于 segment 处的偏移,保存到 si
mov ax , cs:[si] ; 将 s 处的 1 个字节指令内容读入 ax (注意,基址是 cs 哦 ~ 不是 ds )
mov cs:[di],ax ; 将 ax 内容写到 s 处,也就是填充上边那 2 个 nop
s0:
jmp short s ;跳到 s ,那么这个时候位于 s 处指令应该是 jmp s1 ;接着执行应该是 mov ax , 0 然后 int 21
s1:
mov ax,0
int 21h
mov ax,0
s2:
jmp short s1
nop
codesg ends
end start
结果发现我推测的跟执行的内容完全不一样 …… 晕厥 ING~
又过了一天 一觉醒来想了想这个结果 哈哈 恍然大悟
书上的例子用的是 windows 自带的 debug 调试 …… 我也只会用这个了 …… 用 windbg 太麻烦 od 好像只能开 32 位的
只能截图看了
jmp 指令在被编译器编译的时候会自动计算跳转时指针与目的地址的偏移量 然后通过加减 ip 这个数值实现跳转的
也就是说 jmp s 这指令实现的是相对位移跳转,跳到哪那是编译器编译的时候就计算好的了
我们来看看 1814 : 0016 这个地方的 jmp 指令 EBF0 这个指令对应的汇编语句应该是 jmp s
S 是在 1814 : 0008 处 所以反编译的结果是 jmp 0008 没错 跟我们的语句没出入
那么我们可以看看机器码 BEF0 BE 是 jmp 指令 F0 代表相对位移 以补码形式保存
那么我们计算下发现 F0 对应的 10 进制是 -16 也就是说要往后跳 16 个字节(注意是字节, 8bit 一个字节哦 ~ )
1814 : 0016 这个是我们执行到 jmp 时 cs : ip 的地址,也就是取指令的地址
注意这个时候我们已经取出 jmp s 这个指令了 那么 IP 指针应该 +2 指向 1814 : 0018 这个位置了
往后跳 16 个字节 1814 : 0018 – 10 ( 10 进制就是 16 咯) = 1814 : 0008
应该是跳到 0008 这个位置上了 正好就是 s 对应的位置
那么我们看看 1814 : 0020 这个指令 EBF 6 F 6 是跳转的相对位移 补码形式存放 换算成 10 进制就是 -10
想象下当程序执行到 s0 那个时候 s 处的那 2 个 nop 指令已经被填充成 EBF6 了 根据相对位移的计算
这个时候程序运行的步骤应该是
通过 jmp s 跳转到 1814 : 0008 地址(这个时候下一个指令对应的机器码是 BEF0 )
取出下一个指令 IP+2 这个时候 IP 指向 1814 : 000a
执行 BEF6 这个指令(向后跳 10 个字节)
也就是应该跳到 1814 : 000a – a ( 10 的 16 进制表示) = 1814 : 0000
也就是说这个时候 jmp 指令应该是 jmp 0000 跳到 segment 开始处而不是跳到 s1 处了
接着应该是执行
mov ax, 4c 00h
int 21h
实际调试的结果也是这样的
如下图示
看到没 执行 jmp 0008 也就是 jmp s 以后然后就是跳到 1814 : 0000 处 而不是跳到 s1 对应的那个偏移处
接着就是跟我们想的一样 执行了 mov ax , 4c 00h 然后就 int 21h 了
也就是说我们 jmp 的地址记录的是相对偏移量 这个程序也说明了 jmp 的地址是在运行时计算出来的而不是编译器一开始就硬编码进去的
不过当然也有硬编码进去的跳转指令啦 ~~ 好像是 jmp far 地址吧 忘了的说 呵呵
只不过小小阐明下 jmp 相对跳的执行流程和细节部分
这样也有个小小启示就是以后用相对跳的时候小心咯 貌似 shellcode 编写或者改内核代码的时候可以注意下 呵呵
跳转的相对地址最好计算出来表直接就来个偏移 一不小心机子就当掉了 哇咔咔 ~