-
-
[原创]Windows平台用户层二进制漏洞模糊测试入门
-
发表于: 2022-2-10 18:44 10344
-
通过向应用程序提供非预期输入并监控输出中的异常来发现软件中的故障的方法。利用自动化或是半自动化的方法重复地向应用提供输入。
用于模糊测试地模糊测试器分为两类:
基于变异地模糊测试器:通过对已有的数据样本进行变异来创建测试用例
基于生成地模糊测试器:为被测系统使用的协议或是文件格式建模,基于模型生成输入并据此创建测试用例
采用何种模糊测试方法取决于众对因素。没有所谓的一定正确的模糊测试方法,决定采用何种模糊测试方法完全依赖于被测应用,测试者拥有的技能,以及被进行模糊测试的数据的格式。但是,不论对什么应用进行模糊测试,不论采用何种模糊测试方法,模糊测试执行过程都包含以下几个阶段:
确定测试目标: 只有有了明确的测试目标后,才能决定使用的模糊测试工具或方法。一个程序的供应商曾经出现过的安全漏洞是确定测试目标的一个重要参考
确定输入向量: 几乎所有可被利用的安全漏洞都是因为应用没有对用户的输入进行校验或进行必要的非法输入处理。从客户端向目标应用发送的任何东西,包括头,文件名,环境变量,注册表键,以及其他信息,都应该被看做输入向量。所有这些输入向量都可能是潜在的模糊测试变量
生成模糊测试数据: 一旦识别出输入向量,就可以根据输入向量产生模糊测试数据。究竟是使用预先确定的值,使用基于存在的数据通过变异生成的值,还是使用动态生成的值依赖于被测应用及其使用的数据格式。但是,无论选择哪种方式,都应该使用自动化过程来生成数据
执行模糊测试数据: 在该步骤中,一般会向被测目标发送数据包,打开文件,或是执行被测应用。与上一个步骤一个,该步骤也应该是自动化的
监视异常: 在进行模糊测试的过程中,如果输入数据触发了漏洞,往往会导致程序崩溃。因此,模糊测试器应当可以捕获到发生的错误,所以,模糊测试器应当具有对异常和错误进行监控的能力。模糊测试需要根据被测应用和所决定采用的模糊测试类型来设置各种形式的监视
判断发现的漏洞是否可能被利用: 如果在模糊测试中发现了一个错误,依据审计的目的,可能需要判定这个被发现的错误是否是一个可被利用的安全漏洞。这种判定过程是典型的手工过程,需要操者有特定的安全知识
模糊测试方法可以分为以下的五类:
预生成测试用例: 该方法要求首先研究特定的规约,理解该规约支持的数据结构和可接受的值的范围;然后依据这些理解生成用于测试边界条件或是违反规约的测试用例;接下来使用这些测试用例来测试该规约实现的完备性。创建这些测试用例需要花费很多精力,但这些用例一旦被创建,就很"容易"被复用,用于测试某种协议或文件格式的不同实现。该方法缺乏随机生成,一旦测试用例列表中的用例被执行完,测试只能结束
随机生成输入: 随机方法是最低效的方法,但是这种方法可以被用来快速地识别目标应用中是否有非常糟糕的代码。随机方法简单地向目标应用发送伪随机数据,希望得到最好或最坏地结果
手工协议变异测试: 在手工测试中,测试者就是模糊测试器。在加载了目标应用程序后,测试者仅仅通过输入不正确地数据,试图使服务器崩溃,或者诱发一些不正常地行为
变异或强制性测试: 模糊测试器从一个有效的协议样本或是数据格式样本开始,持续不断的打乱数据包或是文件中的每一个字节(byte),字(word),双字(dword)或是字符串(string)
自动协议生成测试: 自动协议生成测试是一种更高级的强制性方法。在这种方式中,首先要做的是对被测应用进行研究,理解和解释协议规约或文件定义。然而,这种方法并不基于协议规约或文件定义创建硬编码的测试用例,而是创建一个描述协议规约如何工作的文法。采用这种方式,测试者可以识别出数据包或是文件中的静态部分和动态部分,动态部分就是可以被模糊化变量替代的部分。随后,模糊测试器动态分析包含了静态和动态部分模板,生成模糊测试数据,将结果数据包或是文件发送给被测应用
根据实现方法的不同,模糊测试器大致可以分为以下两类:
常规模糊测试器: 该方法根据不同的应用程序的输入数据格式不同来构建符合要求的数据后启动程序进行测试,这里的不同可以是不同的应用,不同的协议,不同的文件格式。该方法的缺点是每次都要重新启动程序才能完成测试,性能损耗比较大
内存模糊测试器: 内存模糊测试器的一种实现方法是对进程执行一次快照,在生成快照后迅速向该进程的输入子例程中注入故障数据。当执行完一个测试用例后,恢复上次的快照并注入新的数据。重复以上过程知道所有测试用例都执行完成
对不同的应用程序进行探测的时候,会选择不同的数据类别进行探测,主要分为以下几种数据类型:
这类型数据主要用来探测导致输入的整型数据是否存在溢出的可能,因此,0和0xFFFFFFFF应当包含其中。此外,输入数据有可能被程序进行加减乘除的操作,因此也需要将这些可能性加入其中。所以,一个整数测试用例中,应当包含以下用例:
MAX32 - 16 <= MAX32 <= MAX32 + 16
MAX32 / 2 - 16 <= MAX32 <= MAX32 / 2 + 16
MAX32 / 3 - 16 <= MAX32 / 3 <= MAX32 / 3 + 16
MAX32 / 4 - 16 <= MAX32 / 4 <= MAX32 / 4 + 16
MAX16 - 16 <= MAX16 <= MAX16 + 16
MAX16 / 2 - 16 <= MAX16 <= MAX16 / 2 + 16
MAX16 / 3 - 16 <= MAX16 / 3 <= MAX16 / 3 + 16
MAX16 / 4 - 16 <= MAX32 / 4 <= MAX16 / 4 + 16
MAX8 - 16 <= MAX8 <= MAX8 + 16
MAX8 / 2 - 16 <= MAX8 <= MAX8 / 2 + 16
MAX8 / 3 - 16 <= MAX8 / 3 <= MAX8 / 3 + 16
MAX8 / 4 - 16 <= MAX8 / 4 <= MAX8 / 4 + 16
其中MAX32,MAX16,MAX8分别代表最大32位,16位,8位整型。
溢出漏洞的产生往往是由于没有对输入字符串长度进行控制,因此在生成的模糊测试用例中应当要包含不同长度的字符串,且长字符串更为重要。因为,漏洞通常是被长字符串触发
模糊测试数据集还需要包含非字母数字字符,例如空格和制表符。这些字符经常被用作字符分隔符和终止符。将这些字符随机地放到生成地模糊字符串中能够更好地模拟正在模糊测试地协议,由此可以覆盖更多的代码。
"%s"和"%n"是发现格式字符串漏洞最有效的两个字符串标记。因此,模糊测试器的数据集中应当包含足够多的不同长度的包含"%s"和"%n"字符串标记的字符串。
在字符转换和翻译过程中也会包含安全漏洞,特别是字符扩展。例如,16进制的0xFE和0xFF在UTF-16下被扩展成4个字符,代码中对字符扩展的不适当处理通常会导致安全漏洞。字符转换也可能会被不正确的实现,尤其是在处理少见或是很少被使用的边界值时。
接下来将创建一个简单的包含栈溢出漏洞的程序,针对这个包含漏洞的程序打造一个简易模糊测试器来复习上述内容
根据这个漏洞程序的代码将构建一个常规模糊测试器和内存模糊测试器,模糊测试器的源代码已经上传到github上,代码地址:https://github.com/LegendSaber/Fuzzer
以下是被测程序的源代码,可以看到,该程序的vuln函数包含了栈溢出漏洞:
常规的模糊测试器的实现是通过产生不同的输入数据来启动程序完成测试,实现起来相对简单,只需要不断构建满足条件的输入数据然后启动程序就好。为了捕获到程序发生的异常,还需要在模糊测试器和被测程序中建立调试关系,测试代码如下:
运行结果如下:
可以看到模糊测试器虽然能发现异常,但是性能开销是很大的。因此每次测试都要开启一个新的进程,被测程序每次都要经过Init->Decrypt->test->vuln才可以完成验证。如果这是一个需要大量运算的被测程序,或者是需要经过网络传输的程序,这个开销就会进一步增大。
有以下两种构建内存模糊测试器的方法:
变异循环插入(Mutation Loop Insertion: MLI)
快照恢复变异(Snapshot restoration mutation: SRM)
变异循环插入方法需要首先通过逆向工程,人工定位解析例程的起始和结束地址。一旦定位完成,变异循环插入工具能够向应用程序中插入一个mutation例程(变异例程),变异例程负责修改解析例程拿到的数据。接下来,需要在内存中插入两条无条件跳转指令,这两条指令分别是"从解析例程的结尾跳到变异例程的开始处"和"从变异例程的结尾跳到解析例程的开始处"。
根据上面的被测程序的代码,此时的程序执行就会如下图所示:
此时,当程序运行到test函数末尾的时候,就会跳转到mutate变异例程执行。变异例程会将输入数据经过变异以后在次跳转到test函数起始地址开始执行。这样,围绕解析代码和目标应用创建了一个自给自足的数据变异循环。不需要为每个测试用例都启动一次进程,自然就节省了性能开销。变异例程每次迭代都会向mutate例程传入不同的,可能引发错误的数据。
与变异循环插入方法一样,快照恢复变异方法也需要定位到解析代码的开始和结束位置。在标识出这些位置后,快照变异工具会在到达解析代码的开始位置时为目标进程建立快照。在解析函数执行完成后,快照恢复变异工具恢复进程快照,对原先数据产生变异,并使用变异得到的数据重新执行解析代码。
此时程序的执行流程就会如下图所示:
由于变异循环插入的方法需要把变异例程写入到被测程序,实现起来比较麻烦。而且要不少数据需要硬编码,所以在实现的模糊测试器中就没有实现该方法。只实现了快照恢复变异的插入方法
快照恢复变异的方法需要在程序的解析例程中找到插入点,这样才可以进行相应的保存快照和恢复快照的操作。在vuln.exe程序中,通过调试器可以定位,test函数的起始地址是0x004010B0,结束位置是0x004010D8。
由于模糊测试器和被测程序处于调试关系中,所以可以通过向test函数的起始和结束位置插入软件断点的方式来实现对被测程序的HOOK操作。在插入软件断点后,当被测程序运行到相应位置,模糊测试器就可以获得控制权,然后在对程序进行下一步操作。
当被测程序在函数起始处中断的时候,首先要判断是不是第一次运行到这个地址,如果是的话就需要保存进程的快照。
而进程快照由两部分构成:
被测进程的线程CONTEXT
被测进程的内存页状态和内容
当被测程序运行到test函数末尾的时候,就需要恢复快照,也就是恢复保存的线程CONTEXT和内存页的状态和内容。这两部分的代码分别在Thread.cpp和Pages.cpp中实现:
而保存的页面应当具有可写的属性,所以具有以下属性的页将会被忽略:
PAGE_READONLY
PAGE_EXECUTE_READ
PAGE_GUARD
PAGE_NOACCESS
现在模糊测试器可以保存和恢复进程快照,剩下的一个问题就是要能在被测程序中申请和是否随机数据。这样才可以对输入数据进行变异,这部分代码在Data.cpp中实现:
有了上面的功能,就可以实现一个实现恢复变异的模糊测试器了,代码如下:
运行结果如下:
模糊测试器成功捕获到错误,而且此时只需要启动一次被测程序就可以完成任务。不需要在反复经历Init->Decrypt->test->Vuln这些流程,降低了性能的开销。
/
/
vuln.cpp : 此文件包含
"main"
函数。程序执行将在此处开始并结束。
/
/
#include <cstdio>
#include <Windows.h>
void vuln(char
*
szInput)
{
char szStr[
100
]
=
{
0
};
strcpy(szStr, szInput);
printf(
"Vuln func...\n"
);
}
void test(char
*
szInput)
{
vuln(szInput);
printf(
"test func...\n"
);
}
void Decrypt(char
*
szInput)
{
DWORD dwLen
=
strlen(szInput);
for
(DWORD i
=
0
; i < dwLen; i
+
+
)
{
szInput[i] ^
=
190
;
}
printf(
"Decrypt func...\n"
);
}
void Init(char
*
szInput, char
*
szInit)
{
if
(strlen(szInit) < MAXBYTE)
{
strcpy(szInput, szInit);
printf(
"Init func...\n"
);
}
else
{
printf(
"输入的字符串过长\n"
);
}
}
int
main(
int
argc, char
*
argv[])
{
char szInput[MAXBYTE]
=
{
0
};
Init(szInput, argv[
0
]);
Decrypt(szInput);
test(szInput);
return
0
;
}
/
/
vuln.cpp : 此文件包含
"main"
函数。程序执行将在此处开始并结束。
/
/
#include <cstdio>
#include <Windows.h>
void vuln(char
*
szInput)
{
char szStr[
100
]
=
{
0
};
strcpy(szStr, szInput);
printf(
"Vuln func...\n"
);
}
void test(char
*
szInput)
{
vuln(szInput);
printf(
"test func...\n"
);
}
void Decrypt(char
*
szInput)
{
DWORD dwLen
=
strlen(szInput);
for
(DWORD i
=
0
; i < dwLen; i
+
+
)
{
szInput[i] ^
=
190
;
}
printf(
"Decrypt func...\n"
);
}
void Init(char
*
szInput, char
*
szInit)
{
if
(strlen(szInit) < MAXBYTE)
{
strcpy(szInput, szInit);
printf(
"Init func...\n"
);
}
else
{
printf(
"输入的字符串过长\n"
);
}
}
int
main(
int
argc, char
*
argv[])
{
char szInput[MAXBYTE]
=
{
0
};
Init(szInput, argv[
0
]);
Decrypt(szInput);
test(szInput);
return
0
;
}
#include "Fuzzy.h"
#include "auxiliary.h"
Fuzzy::Fuzzy(char
*
szFilePath)
{
if
(strlen(szFilePath) < MAX_PATH)
{
strcpy(this
-
>szFilePath, szFilePath);
}
else
{
printf(
"文件名过长\n"
);
}
}
char
*
Fuzzy::GetFilePath()
{
return
this
-
>szFilePath;
}
void Fuzzy::BeginFuzzy()
{
STARTUPINFO si
=
{
0
};
PROCESS_INFORMATION pi
=
{
0
};
DEBUG_EVENT DbgEvt
=
{
0
};
/
/
保存调试事件的数据结构
bool
bContinue
=
true;
/
/
是否继续
DWORD dwContinueStatus
=
DBG_CONTINUE;
/
/
恢复继续执行用的状态代码
DWORD dwSize
=
4
;
/
/
输入数据的大小
char szInput[MAXBYTE]
=
{
0
};
/
/
输入数据缓冲区
bool
bExit
=
false;
/
/
被调试进程结束则设为true
printf(
"Fzzy Begin....\n"
);
while
(dwSize < MAXBYTE)
{
/
/
随机输入数据
memset(szInput,
0
, MAXBYTE);
for
(DWORD i
=
0
; i < dwSize; i
+
+
)
{
szInput[i]
=
rand()
%
255
+
1
;
}
memset(&si,
0
, sizeof(si));
memset(&pi,
0
, sizeof(pi));
si.cb
=
sizeof(si);
if
(!CreateProcess(this
-
>szFilePath,
szInput,
NULL,
NULL,
FALSE,
CREATE_NEW_CONSOLE |
DEBUG_PROCESS |
DEBUG_ONLY_THIS_PROCESS,
NULL,
NULL,
&si,
&pi))
{
ShowError(
"CreateProcess"
);
goto exit;
}
bExit
=
false;
while
(bContinue && !bExit)
{
memset(&DbgEvt,
0
, sizeof(DbgEvt));
/
/
等待调试事件发生
bContinue
=
WaitForDebugEvent(&DbgEvt, INFINITE);
if
(!bContinue)
{
ShowError(
"WaitForDebugEvent"
);
break
;
}
switch (DbgEvt.dwDebugEventCode)
{
case EXCEPTION_DEBUG_EVENT:
{
switch (DbgEvt.u.Exception.ExceptionRecord.ExceptionCode)
{
/
/
捕获到程序异常
case EXCEPTION_ACCESS_VIOLATION:
{
printf(
".....Catch Access Violation.....\n"
);
printf(
"dwSize is: %d\n"
, dwSize);
printf(
"Address is 0x%X\n"
, DbgEvt.u.Exception.ExceptionRecord.ExceptionAddress);
bExit
=
true;
break
;
}
default:
{
break
;
}
}
break
;
}
case EXIT_PROCESS_DEBUG_EVENT:
{
bExit
=
true;
/
/
该进程已退出
break
;
}
default:
{
break
;
}
}
/
/
恢复被调试进程继续运行
bContinue
=
ContinueDebugEvent(DbgEvt.dwProcessId,
DbgEvt.dwThreadId,
dwContinueStatus);
if
(!bContinue)
{
ShowError(
"ContinueDebugEvent"
);
break
;
}
}
/
/
增加数据大小,供下一次进行测试
dwSize
+
=
4
;
if
(pi.hProcess) CloseHandle(pi.hProcess);
if
(pi.hThread) CloseHandle(pi.hThread);
}
exit:
printf(
"Fuzzy End...\n"
);
}
#include "Fuzzy.h"
#include "auxiliary.h"
Fuzzy::Fuzzy(char
*
szFilePath)
{
if
(strlen(szFilePath) < MAX_PATH)
{
strcpy(this
-
>szFilePath, szFilePath);
}
else
{
printf(
"文件名过长\n"
);
}
}
char
*
Fuzzy::GetFilePath()
{
return
this
-
>szFilePath;
}
void Fuzzy::BeginFuzzy()
{
STARTUPINFO si
=
{
0
};
PROCESS_INFORMATION pi
=
{
0
};
DEBUG_EVENT DbgEvt
=
{
0
};
/
/
保存调试事件的数据结构
bool
bContinue
=
true;
/
/
是否继续
DWORD dwContinueStatus
=
DBG_CONTINUE;
/
/
恢复继续执行用的状态代码
DWORD dwSize
=
4
;
/
/
输入数据的大小
char szInput[MAXBYTE]
=
{
0
};
/
/
输入数据缓冲区
bool
bExit
=
false;
/
/
被调试进程结束则设为true
printf(
"Fzzy Begin....\n"
);
while
(dwSize < MAXBYTE)
{
/
/
随机输入数据
memset(szInput,
0
, MAXBYTE);
for
(DWORD i
=
0
; i < dwSize; i
+
+
)
{
szInput[i]
=
rand()
%
255
+
1
;
}
memset(&si,
0
, sizeof(si));
memset(&pi,
0
, sizeof(pi));
si.cb
=
sizeof(si);
if
(!CreateProcess(this
-
>szFilePath,
szInput,
NULL,
NULL,
FALSE,
CREATE_NEW_CONSOLE |
DEBUG_PROCESS |
DEBUG_ONLY_THIS_PROCESS,
NULL,
NULL,
&si,
&pi))
{
ShowError(
"CreateProcess"
);
goto exit;
}
bExit
=
false;
while
(bContinue && !bExit)
{
memset(&DbgEvt,
0
, sizeof(DbgEvt));
/
/
等待调试事件发生
bContinue
=
WaitForDebugEvent(&DbgEvt, INFINITE);
if
(!bContinue)
{
ShowError(
"WaitForDebugEvent"
);
break
;
}
switch (DbgEvt.dwDebugEventCode)
{
case EXCEPTION_DEBUG_EVENT:
{
switch (DbgEvt.u.Exception.ExceptionRecord.ExceptionCode)
{
/
/
捕获到程序异常
case EXCEPTION_ACCESS_VIOLATION:
{
printf(
".....Catch Access Violation.....\n"
);
printf(
"dwSize is: %d\n"
, dwSize);
printf(
"Address is 0x%X\n"
, DbgEvt.u.Exception.ExceptionRecord.ExceptionAddress);
bExit
=
true;
break
;
}
default:
{
break
;
}
}
break
;
}
case EXIT_PROCESS_DEBUG_EVENT:
{
bExit
=
true;
/
/
该进程已退出
break
;
}
default:
{
break
;
}
}
/
/
恢复被调试进程继续运行
bContinue
=
ContinueDebugEvent(DbgEvt.dwProcessId,
DbgEvt.dwThreadId,
dwContinueStatus);
if
(!bContinue)
{
ShowError(
"ContinueDebugEvent"
);
break
;
}
}
/
/
增加数据大小,供下一次进行测试
dwSize
+
=
4
;
if
(pi.hProcess) CloseHandle(pi.hProcess);
if
(pi.hThread) CloseHandle(pi.hThread);
}
exit:
printf(
"Fuzzy End...\n"
);
}
#include "Thread.h"
#include "auxiliary.h"
PCONTEXTLIST g_pCtxListHead
=
NULL;
/
/
头节点,用来连接所有的线程
/
/
保存线程CONTEXT
BOOL
SaveThreadContext(DWORD dwPid)
{
BOOL
bRet
=
TRUE, bContninue
=
TRUE;
HANDLE hSnap
=
NULL, hThread
=
NULL;
THREADENTRY32 te
=
{
0
};
PCONTEXTLIST pContextList
=
NULL;
CONTEXT cxt;
hSnap
=
CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, NULL);
if
(!hSnap)
{
ShowError(
"CreateToolhelp32Snapshot"
);
bRet
=
FALSE;
goto exit;
}
te.dwSize
=
sizeof(te);
bContninue
=
Thread32First(hSnap, &te);
while
(bContninue)
{
/
/
根据进程PID选择要保存的线程
if
(te.th32OwnerProcessID
=
=
dwPid)
{
/
/
申请空间用来保存线程信息
pContextList
=
(PCONTEXTLIST)VirtualAlloc(NULL,
sizeof(CONTEXTLIST),
MEM_RESERVE | MEM_COMMIT,
PAGE_READWRITE);
if
(!pContextList)
{
bRet
=
FALSE;
ShowError(
"VirtualAlloc"
);
goto exit;
}
memset(pContextList,
0
, sizeof(CONTEXTLIST));
hThread
=
OpenThread(THREAD_ALL_ACCESS, FALSE, te.th32ThreadID);
if
(!hThread)
{
bRet
=
FALSE;
ShowError(
"OpenThread"
);
goto exit;
}
if
(SuspendThread(hThread)
=
=
0xFFFFFFFF
)
{
bRet
=
FALSE;
ShowError(
"SuspendThread"
);
goto exit;
}
/
/
保存线程
ID
pContextList
-
>dwThreadId
=
te.th32ThreadID;
/
/
获取线程的Context
pContextList
-
>ctx.ContextFlags
=
CONTEXT_FULL;
if
(!GetThreadContext(hThread, &(pContextList
-
>ctx)))
{
bRet
=
FALSE;
ShowError(
"GetThreadContext"
);
goto exit;
}
/
/
将Context挂入pCtxListHead中
pContextList
-
>
next
=
g_pCtxListHead;
g_pCtxListHead
=
pContextList;
if
(ResumeThread(hThread)
=
=
0xFFFFFFFF
)
{
bRet
=
FALSE;
ShowError(
"SuspendThread"
);
goto exit;
}
CloseHandle(hThread);
}
bContninue
=
Thread32Next(hSnap, &te);
}
exit:
return
bRet;
}
/
/
恢复线程CONTEXT
BOOL
RestoreThreadContext()
{
BOOL
bRet
=
TRUE;
HANDLE hThread
=
NULL;
PCONTEXTLIST pContextList
=
NULL;
pContextList
=
g_pCtxListHead;
while
(pContextList)
{
hThread
=
OpenThread(THREAD_ALL_ACCESS,
FALSE,
pContextList
-
>dwThreadId);
if
(!hThread)
{
bRet
=
FALSE;
ShowError(
"OpenThread"
);
goto exit;
}
if
(SuspendThread(hThread)
=
=
0xFFFFFFFF
)
{
bRet
=
FALSE;
ShowError(
"SuspendThread"
);
goto exit;
}
if
(!SetThreadContext(hThread, &(pContextList
-
>ctx)))
{
bRet
=
FALSE;
ShowError(
"SetThreadContext"
);
goto exit;
}
if
(ResumeThread(hThread)
=
=
0xFFFFFFFF
)
{
bRet
=
FALSE;
ShowError(
"SuspendThread"
);
goto exit;
}
CloseHandle(hThread);
pContextList
=
pContextList
-
>
next
;
}
exit:
return
bRet;
}
#include "Thread.h"
#include "auxiliary.h"
PCONTEXTLIST g_pCtxListHead
=
NULL;
/
/
头节点,用来连接所有的线程
/
/
保存线程CONTEXT
BOOL
SaveThreadContext(DWORD dwPid)
{
BOOL
bRet
=
TRUE, bContninue
=
TRUE;
HANDLE hSnap
=
NULL, hThread
=
NULL;
THREADENTRY32 te
=
{
0
};
PCONTEXTLIST pContextList
=
NULL;
CONTEXT cxt;
hSnap
=
CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, NULL);
if
(!hSnap)
{
ShowError(
"CreateToolhelp32Snapshot"
);
bRet
=
FALSE;
goto exit;
}
te.dwSize
=
sizeof(te);
bContninue
=
Thread32First(hSnap, &te);
while
(bContninue)
{
/
/
根据进程PID选择要保存的线程
if
(te.th32OwnerProcessID
=
=
dwPid)
{
/
/
申请空间用来保存线程信息
pContextList
=
(PCONTEXTLIST)VirtualAlloc(NULL,
sizeof(CONTEXTLIST),
MEM_RESERVE | MEM_COMMIT,
PAGE_READWRITE);
if
(!pContextList)
{
bRet
=
FALSE;
ShowError(
"VirtualAlloc"
);
goto exit;
}
memset(pContextList,
0
, sizeof(CONTEXTLIST));
hThread
=
OpenThread(THREAD_ALL_ACCESS, FALSE, te.th32ThreadID);
if
(!hThread)
{
bRet
=
FALSE;
ShowError(
"OpenThread"
);
goto exit;
}
if
(SuspendThread(hThread)
=
=
0xFFFFFFFF
)
{
bRet
=
FALSE;
ShowError(
"SuspendThread"
);
goto exit;
}
/
/
保存线程
ID
pContextList
-
>dwThreadId
=
te.th32ThreadID;
/
/
获取线程的Context
pContextList
-
>ctx.ContextFlags
=
CONTEXT_FULL;
if
(!GetThreadContext(hThread, &(pContextList
-
>ctx)))
{
bRet
=
FALSE;
ShowError(
"GetThreadContext"
);
goto exit;
}
/
/
将Context挂入pCtxListHead中
pContextList
-
>
next
=
g_pCtxListHead;
g_pCtxListHead
=
pContextList;
if
(ResumeThread(hThread)
=
=
0xFFFFFFFF
)
{
bRet
=
FALSE;
ShowError(
"SuspendThread"
);
goto exit;
}
CloseHandle(hThread);
}
bContninue
=
Thread32Next(hSnap, &te);
}
exit:
return
bRet;
}
/
/
恢复线程CONTEXT
BOOL
RestoreThreadContext()
{
BOOL
bRet
=
TRUE;
HANDLE hThread
=
NULL;
PCONTEXTLIST pContextList
=
NULL;
pContextList
=
g_pCtxListHead;
while
(pContextList)
{
hThread
=
OpenThread(THREAD_ALL_ACCESS,
FALSE,
pContextList
-
>dwThreadId);
if
(!hThread)
{
bRet
=
FALSE;
ShowError(
"OpenThread"
);
goto exit;
}
if
(SuspendThread(hThread)
=
=
0xFFFFFFFF
)
{
bRet
=
FALSE;
ShowError(
"SuspendThread"
);
goto exit;
}
if
(!SetThreadContext(hThread, &(pContextList
-
>ctx)))
{
bRet
=
FALSE;
ShowError(
"SetThreadContext"
);
goto exit;
}
if
(ResumeThread(hThread)
=
=
0xFFFFFFFF
)
{
bRet
=
FALSE;
ShowError(
"SuspendThread"
);
goto exit;
}
CloseHandle(hThread);
pContextList
=
pContextList
-
>
next
;
}
exit:
return
bRet;
}
#include "Pages.h"
PMEMORYBLOCKSLIST g_pMemoryBlocksListHead
=
NULL;
BOOL
SavePages(HANDLE hProcess)
{
BOOL
bRet
=
TRUE, bSaveBlock
=
TRUE;
MEMORY_BASIC_INFORMATION mbi
=
{
0
};
DWORD dwCursor
=
0
, dwQuerySize
=
0
;
PMEMORYBLOCKSLIST pMemoryBlocksList
=
NULL;
while
(dwCursor <
0xFFFFFFFF
)
{
/
/
查询页属性
memset(&mbi,
0
, sizeof(mbi));
dwQuerySize
=
VirtualQueryEx(hProcess,
(PVOID)dwCursor,
&mbi,
sizeof(mbi));
if
(dwQuerySize < sizeof(mbi))
{
break
;
}
bSaveBlock
=
TRUE;
/
/
判断是否是要保存的页
if
(mbi.State !
=
MEM_COMMIT ||
mbi.
Type
=
=
MEM_IMAGE ||
mbi.Protect & PAGE_READONLY ||
mbi.Protect & PAGE_EXECUTE_READ ||
mbi.Protect & PAGE_GUARD ||
mbi.Protect & PAGE_NOACCESS)
{
bSaveBlock
=
FALSE;
}
if
(bSaveBlock)
{
pMemoryBlocksList
=
(PMEMORYBLOCKSLIST)VirtualAlloc(NULL,
sizeof(MEMORYBLOCKSLIST),
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE);
if
(!pMemoryBlocksList)
{
bRet
=
FALSE;
ShowError(
"VirtualAlloc"
);
goto exit;
}
memset(pMemoryBlocksList,
0
, sizeof(MEMORYBLOCKSLIST));
/
/
申请用来保存页中的数据
pMemoryBlocksList
-
>read_buf
=
VirtualAlloc(NULL,
mbi.RegionSize,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE);
if
(!pMemoryBlocksList
-
>read_buf)
{
bRet
=
FALSE;
ShowError(
"VirtualAlloc"
);
goto exit;
}
/
/
保存页中的数据
if
(!ReadProcessMemory(hProcess,
mbi.BaseAddress,
pMemoryBlocksList
-
>read_buf,
mbi.RegionSize,
NULL))
{
bRet
=
FALSE;
ShowError(
"ReadProcessMemory"
);
goto exit;
}
memcpy(&pMemoryBlocksList
-
>mbi, &mbi, sizeof(mbi));
/
/
连入链表
pMemoryBlocksList
-
>
next
=
g_pMemoryBlocksListHead;
g_pMemoryBlocksListHead
=
pMemoryBlocksList;
}
dwCursor
+
=
mbi.RegionSize;
}
exit:
return
bRet;
}
BOOL
RestorePages(HANDLE hProcess)
{
BOOL
bRet
=
TRUE;
PMEMORYBLOCKSLIST pMemoryBlocksListHead
=
g_pMemoryBlocksListHead;
while
(pMemoryBlocksListHead)
{
if
(!WriteProcessMemory(hProcess,
pMemoryBlocksListHead
-
>mbi.BaseAddress,
pMemoryBlocksListHead
-
>read_buf,
pMemoryBlocksListHead
-
>mbi.RegionSize,
NULL))
{
bRet
=
FALSE;
ShowError(
"WriteProcessMemory"
);
goto exit;
}
/
/
恢复页属性
VirtualProtectEx(hProcess,
pMemoryBlocksListHead
-
>mbi.BaseAddress,
pMemoryBlocksListHead
-
>mbi.RegionSize,
pMemoryBlocksListHead
-
>mbi.Protect,
NULL);
/
/
获取下一个页信息
pMemoryBlocksListHead
=
pMemoryBlocksListHead
-
>
next
;
}
exit:
return
bRet;
}
#include "Pages.h"
PMEMORYBLOCKSLIST g_pMemoryBlocksListHead
=
NULL;
BOOL
SavePages(HANDLE hProcess)
{
BOOL
bRet
=
TRUE, bSaveBlock
=
TRUE;
MEMORY_BASIC_INFORMATION mbi
=
{
0
};
DWORD dwCursor
=
0
, dwQuerySize
=
0
;
PMEMORYBLOCKSLIST pMemoryBlocksList
=
NULL;
while
(dwCursor <
0xFFFFFFFF
)
{
/
/
查询页属性
memset(&mbi,
0
, sizeof(mbi));
dwQuerySize
=
VirtualQueryEx(hProcess,
(PVOID)dwCursor,
&mbi,
sizeof(mbi));
if
(dwQuerySize < sizeof(mbi))
{
break
;
}
bSaveBlock
=
TRUE;
/
/
判断是否是要保存的页
if
(mbi.State !
=
MEM_COMMIT ||
mbi.
Type
=
=
MEM_IMAGE ||
mbi.Protect & PAGE_READONLY ||
mbi.Protect & PAGE_EXECUTE_READ ||
mbi.Protect & PAGE_GUARD ||
mbi.Protect & PAGE_NOACCESS)
{
bSaveBlock
=
FALSE;
}
if
(bSaveBlock)
{
pMemoryBlocksList
=
(PMEMORYBLOCKSLIST)VirtualAlloc(NULL,
sizeof(MEMORYBLOCKSLIST),
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE);
if
(!pMemoryBlocksList)
{
bRet
=
FALSE;
ShowError(
"VirtualAlloc"
);
goto exit;
}
memset(pMemoryBlocksList,
0
, sizeof(MEMORYBLOCKSLIST));
/
/
申请用来保存页中的数据
pMemoryBlocksList
-
>read_buf
=
VirtualAlloc(NULL,
mbi.RegionSize,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE);
if
(!pMemoryBlocksList
-
>read_buf)
{
bRet
=
FALSE;
ShowError(
"VirtualAlloc"
);
goto exit;
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
赞赏
- [原创]CVE-2022-21882提权漏洞学习笔记 16402
- [原创]CVE-2021-1732提权漏洞学习笔记 19506
- [原创]CVE-2014-1767提权漏洞学习笔记 15200
- [原创]CVE-2018-8453提权漏洞学习笔记 18538
- [原创]CVE-2020-1054提权漏洞学习笔记 13550