【问题标题】:libcurl crashes in a Windows servicelibcurl 在 Windows 服务中崩溃
【发布时间】:2018-06-28 15:35:00
【问题描述】:

我的服务从某些进程接收数据,对其进行解析,然后将 HTTP 帖子发送到我的 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 函数除以零的整数。

curl_easy_setopt()开始的任何libcurl函数都可能发生崩溃。

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

编辑:挖掘后这是导致损坏的函数 以前的 curl 使用没有发生崩溃的原因是我不使用这个函数,除非在这个之后,然后我创建一个接收器线程,它与使用这个函数的线程并行工作,普通程序也没有t 崩溃,因为此功能仅适用于服务(不在本地系统中运行的程序可以使用 EnumWindows) 我认为 Poco 网络没有崩溃,因为它基于 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 和进程黑客):

 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;

【问题讨论】:

  • 是时候启动调试器了。
  • 我调试了很多,所以我知道崩溃发生在哪里,但不知道为什么?正如我所说,相同的代码在服务中不使用时也可以工作
  • 你在url上做什么操作?也许您以某种方式破坏了变量?也许它是无效的?
  • @dev65 你能发布更多关于崩溃的信息吗?
  • @dev65 崩溃发生在你所说的那一行。有一件事是url。到目前为止,我们还没有看到这个变量是在哪里初始化的,它是如何获得它的值的,等等。而且仅仅因为你可以打印并不意味着没有内存损坏。

标签: c++ curl winapi windows-services libcurl


【解决方案1】:

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() 关闭它。

【讨论】:

  • 我会实现这个,我认为 UNCODE_STRING::Length 包括空终止字符,但来自微软:“长度:指定 Buffer 成员指向的字符串的长度,以字节为单位,而不是包括终止 NULL 字符,如果有的话"
【解决方案2】:

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

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 而不是 delete 释放缓冲区可以解决问题

如果 ReadProcessMemory 读入一个用 new 分配的缓冲区,我没有遇到类似这样的问题

函数的文档对此一无所知

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

【讨论】:

  • 使用new[] 的原始代码没有破坏内存,但它过度分配了超出您需要的内存。我更新了我的答案来解释这一点。
猜你喜欢
  • 2018-01-18
  • 2012-04-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-03-26
  • 1970-01-01
  • 2012-01-18
  • 1970-01-01
相关资源
最近更新 更多