libcurl在Windows服务中崩溃

时间:2018-06-28 15:35:01

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

我的服务从某些进程接收数据,对其进行解析,然后将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使用之所以不会发生崩溃的原因是,除了此之后,我不使用此函数,然后我创建了一个与使用此函数的线程并行工作的接收器线程,普通程序也没有崩溃,因为此功能仅用于服务(不在本地系统中运行的程序可以使用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和流程黑客):

 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;

2 个答案:

答案 0 :(得分:0)

导致堆损坏的确切行是:

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的缓冲区中,则会引起此类奇怪的错误

该函数的文档对此一无所知

有人遇到这样的问题吗?

答案 1 :(得分:0)

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

CommandLine.Buffer = new WCHAR[(CommandLine.MaximumLength / sizeof(WCHAR)) + 1];

但是,不能保证您读取的UNICODE_STRING数据将以null结尾,但是您在构造最后一个wstring时将其当作是空值。您应该考虑Length(也以字节表示),而不要依赖空终止符:

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

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

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