实验代码
实验环境为Win10,编译工具为VS2017(Release)。
#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#include <stdio.h>
#include <string.h>
#define PASSWORD "1234567"
int verifyPassword(char* password)
{
int authenticated;
char buffer[44];
authenticated = strcmp(password, PASSWORD);
strcpy(buffer, password);
return authenticated;
}
int main()
{
int validFlag = 0;
char password[1024];
LoadLibraryA("user32.dll");
FILE* fp = fopen("D:\\password.txt", "r");
if (fp == NULL)
return 0;
fscanf(fp, "%s", password);
validFlag = verifyPassword(password);
if (validFlag)
printf("incorrect password\n");
else
printf("Congratulation!");
return 0;
}
需要注意一些编译设置,否则实验可能无法按照步骤完成。
优化项选择 已禁用(/Od);启用内部函数项选择 否。
安全检查项选择 禁用安全检查(/GS-)。
数据执行保护(DEP)项选择 否。
代码分析
通过IDA查看verifyPassword函数:
栈内容比较简单:
ebp-48 = buffer地址
ebp-4 = authenticated地址
ebp
RA
pPassword
1.修改邻接变量
如果读取的password比"1234567",authenticated=0x00000001,buffer字符串的NULL正好能覆盖0x01变成0x00,从而使authenticated=0x00000000。比如文本里输入44个’z’即可。
2.修改函数返回地址
继续往后覆盖的话就会覆盖到栈上保存的函数返回地址。我们使文本包含56个字节。在读取文件之前下好断点后启动VS进行调试,查看printf(“Congratulation!”);的地址。
本次将最后4个字节改为012211BA,继续执行后控制台打印出"Congratulation!"。
3.代码植入
将buffer起始地址处填入执行代码,再将返回地址改为buffer的起始地址。当程序返回时便会跳转到buffer起始地址进行执行注入的代码。
需要注入弹窗代码,通过以下代码获得user32.dll的基地址和MessageBoxA函数的地址:
HMODULE hDll = LoadLibrary("user32.dll");
FARPROC proc = GetProcAddress(hDll, "MessageBoxA");
获得user32.dll的基地址和MessageBoxA函数的地址分别为0x76430000和0x764a7e60,在读取文件之前下好断点后启动VS进行调试。
查看当前user32.dll的基地址也为0x76430000,那么MessageBoxA的地址也为0x764a7e60。
查看此时栈帧esp寄存器为0x00eff604,计算buffer的首地址等于D9F500-12-48=00D9F4C4。
地址都得到了,编写注入代码如下:
xor ebx,ebx
push ebx
push 74736574H
mov eax,esp
push ebx
push eax
push eax
push ebx
mov eax,764a7e60H
call eax
loop0:
jmp loop0
获得汇编代码的二进制表示,写入文件开头,并在最后四个字节填充此次buffer地址0x00D9F4C4。
继续运行VS,可以看到弹出窗口:
后记
如果没有 优化项选择 已禁用(/Od);启用内部函数项选择 否;的话,strcmp和strcpy都是内联在函数内的,循环复制的地址变量也存在栈上,所以不能简单的进行溢出覆盖,否则会将复制目的地址和源地址更改。
如果没有 安全检查项选择 禁用安全检查(/GS-) 的话,函数返回时进行检查则会发生异常。
如果没有 数据执行保护(DEP)项选择 否 的话,则栈上无法执行代码。