当我们在gdb中break OFFSET的时候,此时的这个OFFSET是一个address.gdb首先会把这个地址的一字节的value单独自己记下来(为了之后替换回original),然后把这个字节的value设置成INT3(0xcc).假设原来在OFFSET的指令是0x8345fc01(addl $0x1,-0x8(*ebp))), gdb会把这个最后一个字节的0x01记下来,然后替换成0x8345fccc.这并不是一个这种的instruction.
OFFSET 01 #original
OFFSET+1 fc
OFFSET+2 45
OFFSET+3 83
最终替换成
OFFSET cc #breakpoint instered
OFFSET+1 fc
OFFSET+2 45
OFFSET+3 83
于是当我们继续进行我们这个debugged program,当遇到INT3的时候,debugger program就会trap进kernel,随后kernel就会通过信 来告知对应的gdb.gdb随后就会把这个OFFSET上的字节用他之前存储的那个original value来替换,并且把EIP的instruction potiner移动到OFFSET,并且重新开始执行OFFSET所在位置的指令。之所以要移动EIP,是因为当CPU执行了INT3之后,此时的EIP已经自动加了1个字节了。
OFFSET cc # after breakpoint
OFFSET+1 fc # <—- EIP points here
OFFSET+2 45
OFFSET+3 83
OFFSET 01 # <— EIP points here, after gdb restores instruction and EIP
OFFSET+1 fc
OFFSET+2 45
OFFSET+3 83
这个时候我们可以看到此时的指令已经重新编程了0x8345fc01.
这里提一下,为什么IN3应该是1个字节。首先在x86环境下,1个字节是最小的指令长度。如果INT3比1个字节来的长,我们就会因为上面的操作覆写超过1个以上的指令,这就会产生问题。考虑如下的场景:(在Cortex-M CPUs里面,instructions要么是2个字节要么是4个字节,因此breakpoint instruction在这个CPU体系下最小长度取2个字节
OFFSET <instruction 1, one byte> OFFSET+1 <instruction 2, one byte>
如果假设我们要在instruction 1的地方设置断点,那么整个OFFSET就会被我们改成
OFFSET <INT3……………….
OFFSET+1 ………………….>
如果此时一旦有指令想要跳到OFFSET+1的位置,这个位置注意并不是INT 3,而是INT 3的一部分,这就是undefined behavoir,如果我们的INT 3本身就是x86最短的1个字节的话就不会有这个问题。
有同学肯定会问,为什么是替换字节而不是插入字节?我就不能插入这个breakpoint然后把接下来的指令都往后移吗?如果你真要做这个过程是非常复杂的,因为他会扰乱所有指令的offset,并且让所有的j*(jump)指令会跳到错误的位置。
看下面的例子:
比如我们本来有指令为
OFFSET <instruction 1>
OFFSET+1 <instruction 2>
当我们用插入的方式来设breakpoints
OFFSET INT3
OFFSET+1 <instruction 1> OFFSET+2 <instruction 2>
假设我们想在instruction 1的地方加上breakpoints,如果我们沿用上面的假设改用插入,那么所有的指令往后移,那么当其他的code想要跳到OFFSET+1的时候,他本来想跳到instruction 2,但是在这里他就跳到了instruction 1,错误就产生了
PS:
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!