csapp attack lab phase4
每个gadget由一系列指令字节组成,最后一个字节为0xc3,编码为ret指令。
举个例子:
48 89 c7 是指令 movq %rax, %rdi, 对应的地址是0x400f15 + 0x3 也就是0x400f18, 是开始的指令位置。
例如:
ret编码为0xc3,nop编码为0x90。
0x4019a7 + 0x4 得到地址0x4019ab
48 89 c7 对应的汇编指令是 mov %rax, %rdi
0x4019c3 + 0x2 得到地址 0x4019c5
rsp 存储 0x59b997fa 是cookie 的值。
rip存储的值是retq时候,rsp存储mov %rax, %rdi 对应的地址.
汇编
ret指令
当执行汇编指令ret时,计算机会进行以下操作:
- 将栈顶的值弹出,并将其赋值给指令指针寄存器rip,这样程序控制流会返回到调用函数的地方。
- 将栈顶指针rsp增加一个合适的值,以便指向上一个函数的栈帧。这样可以清除当前函数的栈帧,为返回到上一个函数做准备。
总的来说,ret指令会对rsp和rip寄存器进行操作,以实现函数返回的功能。
在执行ret
指令时,栈顶指针rsp
的增加值是由调用约定(calling convention)和编译器生成的代码决定的。在一般情况下,当函数调用结束时,栈需要被恢复到调用函数之前的状态,这就需要栈指针rsp
回到上一个函数的栈帧。
当使用ret
指令从函数返回时,它会弹出栈顶的地址并将程序计数器设置为这个地址,以便继续执行代码。一旦ret
指令执行完成,弹出的地址就不再处于栈顶位置,而是成为了程序计数器所指向的位置,因此在普通情况下无法直接通过栈来访问这个地址。
如果在函数中保存了返回地址的拷贝,或者在调用ret
之前将返回地址拷贝到其他位置,那么在函数返回后仍然可以通过这些拷贝来访问返回地址。但是直接通过栈来访问ret
弹出的地址是不可能的。
stack frame
在x86-64架构中,一般的调用约定是将栈帧中的参数和局部变量保存在栈上,然后通过rsp
指针来访问这些参数和局部变量。当函数调用结束时,栈指针rsp
需要回到上一个函数的栈帧,这个值是根据当前函数的栈帧大小来计算的。
编译器会在编译时根据函数的参数、局部变量和其他需要保存在栈上的信息来计算栈帧的大小,然后在ret
指令中使用适当的偏移值来恢复栈指针rsp
。这个偏移值会确保rsp
指向上一个函数的栈帧,从而实现函数返回的功能。
在大多数操作系统和体系结构中,栈帧的大小都是有限制的。这个限制通常由硬件和操作系统的设计决定。
在x86-64架构中,栈帧的大小受到寄存器的位数限制。例如,在64位操作系统中,通常会有一个默认的栈大小限制,例如1MB或者8MB。这个限制可以在操作系统内核中进行配置和调整。
此外,栈的大小还受到操作系统的限制,操作系统会为每个进程分配一定大小的栈空间。如果栈帧的大小超出了这个限制,就会发生栈溢出错误。
在编写程序时,需要注意栈帧的大小限制,避免过多的局部变量或者递归调用导致栈溢出。如果需要更大的栈空间,可以通过操作系统提供的机制来调整栈大小限制。
指令
将汇编转为二进制
gcc -c phase4.s
objdump -d phase4.o > phase4.d
phase4.s
phase4.d
以十六进制的方式打印寄存器
x/x $rsp
是 GDB 中的命令,用于查看内存中地址为 $rsp
的内容。这个命令的含义是以十六进制格式显示 $rsp
地址处的内容。$rsp
是 x86 架构中的寄存器,用于存储栈顶的地址。
link
https://zhuanlan.zhihu.com/p/60724948
https://zhuanlan.zhihu.com/p/107048472