libcurl 在 Windows 服务中崩溃

libcurl crashes in a Windows service

我的服务从某些进程接收数据,对其进行解析,然后将 HTTP post 发送到我的 PHP 服务器。

我开始写代码的时候,是一个普通的64位程序。完成后,我将其转换为服务,但是当服务尝试发送数据时发生了一些崩溃。

原因不清楚,因为我在服务的其他地方使用libcurl没有问题。

我的接收器是这样的:

while (true)
{
    memset(pipe_buffer, 0, 10000);
    cres = ReadFile(pipe, pipe_buffer, 10000, &read, 0);
    ofile << "[*] got a packet with length : " << read << endl;
    if (read > 0 && cres) {
        ofile << "[*] " << pipe_buffer << endl;

        // send the request
        string payload;
        payload += "data=";
        payload += pipe_buffer;
        ofile << "[*] sending post : " << url << "?" << payload<< endl;
        CURL *curl;
        curl = curl_easy_init();
        if (!curl) {
            ofile << "[!] curl failed to init" << endl;
            return 0;
        }
        curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); // crashes start here
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload.c_str());
        curl_easy_perform(curl);
        curl_easy_cleanup(curl);
    }
    else {
         // the client may dissconnect , wait for it to connect again
         DisconnectNamedPipe(pipe); ConnectNamedPipe(pipe, 0);
    }
}

我每次都会遇到非常不同和奇怪的错误。

其中大部分来自 libcurl 调用的 RtlFreeHeap(),以及 curl_easy_perform() 使用的一些 WSA 函数的整数除以零。

崩溃可能发生在任何 libcurl 函数中,从 curl_easy_setopt() 开始。

相同的代码在普通程序中运行没有问题。

编辑:挖掘后这是导致损坏的功能 以前的 curl 使用没有发生崩溃的原因是我不使用这个函数,除非在此之后,然后我创建一个与使用这个函数的线程并行工作的接收器线程,普通程序也没有' t 崩溃,因为此功能仅用于服务(本地系统中非 运行 的程序可以使用 EnumWindows) 我认为 Poco net 没有崩溃,因为它基于 c++ 而不是 c,并使用 new/delete 来分配和释放内存,但是 curl 和其他函数的崩溃从 _malloc_base 和类似的 c 分配函数开始

功能代码:

wstring GetCommandLineRemote(DWORD id) {
  PROCESS_BASIC_INFORMATION pbInfo = { 0 };
  HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, 0, id);
  if (!hProc || hProc == INVALID_HANDLE_VALUE) return wstring(L"");
  auto status = fNtQueryInformationProcess(hProc, ProcessBasicInformation, &pbInfo, sizeof(pbInfo), NULL);
  if (!NT_SUCCESS(status)) { CloseHandle(hProc); return wstring(L""); }
  BPEB bbeb = { 0 };
  BOOL result;
  result = ReadProcessMemory(hProc, (void*)pbInfo.PebBaseAddress, &bbeb, sizeof(BPEB), 0);
  if (!result) { CloseHandle(hProc); return wstring(L""); }
  BRTL_USER_PROCESS_PARAMETERS parameters = { 0 };
  result = ReadProcessMemory(hProc, (void*)((uintptr_t)bbeb.ProcessParameters), &parameters, sizeof(BRTL_USER_PROCESS_PARAMETERS), 0);
  if (!result) { CloseHandle(hProc); return wstring(L""); }
  UNICODE_STRING CommandLine = { 0 };
  CommandLine.Length = parameters.CommandLine.Length;
  CommandLine.MaximumLength = parameters.CommandLine.MaximumLength;
  CommandLine.Buffer = new WCHAR[CommandLine.MaximumLength];
  result = ReadProcessMemory(hProc, (void*)parameters.CommandLine.Buffer, CommandLine.Buffer, parameters.MaximumLength, 0);
  if (!result) { CloseHandle(hProc); return wstring(L""); }
  CloseHandle(hProc);
  wstring wCommandLine = CommandLine.Buffer;
  delete CommandLine.Buffer;
  return wCommandLine;
}

我正在使用此功能通过进程启动的命令行来区分我的辅助进程的实例

因此查找实例的机制如下所示:

vector<DWORD> enum_ids(wstring proc_name) {
  HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  vector<DWORD> ids;
  PROCESSENTRY32W entry = { 0 };
  entry.dwSize = sizeof(entry);
  if (!Process32FirstW(snap, &entry)) return ids;
  do {
      wstring p_name = entry.szExeFile;
      auto check_pos = p_name.find(proc_name);
      if (check_pos != wstring::npos) {
        ofile << "[*] found process instance with id : " << entry.th32ProcessID << endl;
        ids.push_back(entry.th32ProcessID);
      }
  } while (Process32NextW(snap, &entry));
  return ids;
}


DWORD find_process(wstring proc_name,wstring unique) {
  DWORD process_id = 0;
  auto ids = enum_ids(proc_name);
  for (auto id : ids) {
      wstring wCommandLine = GetCommandLineRemote(id);
      auto check_pos = wCommandLine.find(unique);
      if (check_pos != wstring::npos) {
          process_id = id;
          break;
      }
      //if (id == 83004) { process_id = id; break; } 83004 is example , if I used this instead of the above comparison code no errors occur so I assumed the errors come from GetCommandLineRemote 
  }
  return process_id;
}

然后在服务主线程中:

CreateThread(0, 0, recieve, 0, 0, 0);
CreateThread(0, 0, FindParticularInstance, (char*)"ch", 0, 0);

现在找到错误源后,这个函数是如何处理所有这些错误的,以及如何防止它发生的?

结构的定义(来自 NiroSoft 和 process hacker):

 typedef struct _CURDIR
{
  UNICODE_STRING DosPath;
  PVOID Handle;
} CURDIR, *PCURDIR;

typedef struct _RTL_DRIVE_LETTER_CURDIR
{
  WORD Flags;
  WORD Length;
  ULONG TimeStamp;
  STRING DosPath;
} RTL_DRIVE_LETTER_CURDIR, *PRTL_DRIVE_LETTER_CURDIR;

typedef struct _BRTL_USER_PROCESS_PARAMETERS
{
  ULONG MaximumLength;
  ULONG Length;
  ULONG Flags;
  ULONG DebugFlags;
  PVOID ConsoleHandle;
  ULONG ConsoleFlags;
  PVOID StandardInput;
  PVOID StandardOutput;
  PVOID StandardError;
  CURDIR CurrentDirectory;
  UNICODE_STRING DllPath;
  UNICODE_STRING ImagePathName;
  UNICODE_STRING CommandLine;
  PVOID Environment;
  ULONG StartingX;
  ULONG StartingY;
  ULONG CountX;
  ULONG CountY;
  ULONG CountCharsX;
  ULONG CountCharsY;
  ULONG FillAttribute;
  ULONG WindowFlags;
  ULONG ShowWindowFlags;
  UNICODE_STRING WindowTitle;
  UNICODE_STRING DesktopInfo;
  UNICODE_STRING ShellInfo;
  UNICODE_STRING RuntimeData;
  RTL_DRIVE_LETTER_CURDIR CurrentDirectores[32];
  ULONG EnvironmentSize;
} BRTL_USER_PROCESS_PARAMETERS, *PBRTL_USER_PROCESS_PARAMETERS;

typedef struct _BPEB
{
  UCHAR InheritedAddressSpace;
  UCHAR ReadImageFileExecOptions;
  UCHAR BeingDebugged;
  UCHAR BitField;
  ULONG ImageUsesLargePages : 1;
  ULONG IsProtectedProcess : 1;
  ULONG IsLegacyProcess : 1;
  ULONG IsImageDynamicallyRelocated : 1;
  ULONG SpareBits : 4;
  PVOID Mutant;
  PVOID ImageBaseAddress;
  PPEB_LDR_DATA Ldr;
  PBRTL_USER_PROCESS_PARAMETERS ProcessParameters;
  PVOID SubSystemData;
  PVOID ProcessHeap;
  PRTL_CRITICAL_SECTION FastPebLock;
  PVOID AtlThunkSListPtr;
  PVOID IFEOKey;
  ULONG CrossProcessFlags;
  ULONG ProcessInJob : 1;
  ULONG ProcessInitializing : 1;
  ULONG ReservedBits0 : 30;
  union
  {
      PVOID KernelCallbackTable;
      PVOID UserSharedInfoPtr;
  };
  ULONG SystemReserved[1];
  ULONG SpareUlong;
} BPEB, *PBPEB;

导致堆损坏的确切行是这些:

CommandLine.Buffer = new WCHAR[CommandLine.MaximumLength];
result = ReadProcessMemory(hProc, (void*)parameters.CommandLine.Buffer, CommandLine.Buffer, parameters.MaximumLength, 0);

ReadProcessMemory 成功但会导致使用 curl 函数时出现的堆损坏

更改此行:

CommandLine.Buffer = new WCHAR[CommandLine.MaximumLength];

对此:

CommandLine.Buffer = (wchar_t*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, CommandLine.MaximumLength);

然后在使用 HeapFree 而不是删除返回之前释放缓冲区解决了问题

我没有遇到过这样的问题,如果 ReadProcessMemory 读入分配有 new 的缓冲区会导致这种奇怪的错误

函数的文档对此没有任何说明

有人遇到过这样的问题吗?

GetCommandLineRemote()中,当你分配CommandLine.Buffer时,你分配过度了。 MaximumLength以字节表示,WCHAR是2个字节大小。您对 new[] 的使用分配的内存比您实际需要的内存多 2 倍,但这没关系,因为您读取的字节数没有超过您分配的字节数。您应该将 MaximumLength 除以 sizeof(WCHAR) 以避免在使用 new WCHAR[]:

时过度分配
CommandLine.Buffer = new WCHAR[(CommandLine.MaximumLength / sizeof(WCHAR)) + 1];

但是,您读取的 UNICODE_STRING 数据不保证以空值终止,但您在构造最终 wstring 时将其视为空值终止。您应该考虑 Length (也以字节表示)而不是依赖空终止符:

wstring wCommandLine(CommandLine.Buffer, CommandLine.Length / sizeof(WCHAR));

更重要的是,您正在使用 delete 而不是 delete[] 释放 CommandLine.Buffer,因此您的代码从那时起具有 未定义的行为 .用 new 分配的内存用 delete 释放。用 new[] 分配的内存用 delete[] 释放。不能互换。

此外,在旁注中,在 enum_ids() 中,您正在泄漏 CreateToolhelp32Snapshot() 返回的 HANDLE。使用完毕后,您需要使用 CloseHandle() 关闭它。