前置知识
32位-x86 cpu的寄存器
简单的汇编指令解释
栈长这样 小数字是标号,大数字是地址
实验一 反汇编c程序
先写一个小文件用来试验
// main.c
int g(int x)
{
return x + 2824;
}
int f(int x)
{
return g(x);
}
int main(void)
{
return f(66) + 2021;
}
然后把这个小程序汇编一下,命令如下
gcc –S –o main.s main.c -m32
然后加下来开始分析一下这个汇编后的文件
图是没有截全的,但是可以看见第五行有个g:
,这就是对应原来.c
中的g函数
且代码中有很多.
开头的字符串,如.text
,.cfi_def_cfa
啥的。这些.
打头的字符串是链接阶段会用到的辅助信息
.
打头的字符串有点多,可能会影响我们分析汇编代码,所以接下来可以把这些辅助信息都删除,精简一下。
删除代码如红框中所示g/\.s*/d
得到精简后的汇编文件
17行的main:
和8行的f:
代表.c文件中的俩函数(还有一个g函数,截图没截进来)
这个esp是栈顶指针寄存器,永远指向栈顶的
首先执行main
函数
pushl %ebp 1//将ESP寄存器指向堆栈中标号1的位置,然后讲EBP中的值放入标号1中(其实就是ebp入栈),与此同时EIP寄存器已经指向了下一行代码
movl %esp, %ebp 2//使EBP也指向标号1的位置,同时EIP寄存器指向下一行代码
sub $4, %esp 3//将ESP寄存器的值减4,其实就是把ESP向下移动一个标号,指向标号2,同时EIP指向下一行代码
movl $66, (%esp) 4//把立即数66放入ESP指向的标号2的位置,同时EIP指向下一行代码
call f 5//EIP自动+1指向下一行,eip入栈,调用f函数。eip的入栈是为了执行完f函数的时候能回到main函数
addl $2021,%eax 20//立即数2021放到eax里头
leave 21//栈回到初始状态
ret 22//pop EIP,所有东西都结束
f函数的分析
pushl %ebp 6//把ESP的值向下移一位到标号4,然后把EBP的值标号1放到标号4的位置,
movl %esp,%ebp 7//让EBP指向ESP的位置,即标号4
sub 4,%esp 8//ESP寄存器的值-4,即指向标号5
movl 8(%ebp),%eax 9//通过EBP变址寻址,EBP的值+8,指向标号2的位置,然后标号2存储的是立即数66.将66放入到EAX中
movl %eax,(%esp) 10//把EAX中的值放到ESP所指向的位置,即标号5
call g 11//eip入栈,调用g函数,eip的入栈是为了执行完g函数之后,能够回到f函数
leave 18//这东西就相当于move %ebp,%esp;pop %ebp
ret 19//pop eip,返回到main函数
g函数(其实都大同小异)
pushl %ebp 12//ebp入栈
movl %esp,%ebp 13//让EBP和ESP指向当前栈顶
movl 8(%ebp),%eax 14//EBP指向的位置向上移两个
addl $2824,%eax 15//把立即数2824加到EAX里头 2824+66=2890
popl %ebp 16//把第12步中入栈的ebp ,pop掉
ret 17//返回到f函数
所有函数的头两条指令都用于初始化自己的调用堆栈空间
总结和问题
0.刚开始的时候理解不了esp
指针的变动规则,后来发现这东西是栈顶指针,栈顶变它就变。
0.0 栈这个玩意儿是向下增长的,
1.每次执行call func
指令的时候,都会把eip+1
的值压到栈里,为了之后用
2.栈是向下长的
3.如果搞不清楚,就画一个栈自己整一下
4.这里有个动图,虽然里头的立即数和我的不一样,但是总体的执行过程是一样的
实验二 时间片轮转多道程序内核
patch
用于修补文件的命令