libcurl crashes in a Windows service

廉价感情. 提交于 2019-12-25 01:43:39

问题


My service receives data from some processes, parses it, and sends an HTTP post to my PHP server.

When I started writing the code, it was an ordinary 64-bit program. After I finished, I converted it to a service, but some crashes happen when the service tries to send the data.

The reason isn't clear, as I use libcurl in other places in the service without problems.

My receiver is something like this:

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);
    }
}

I'm getting very different and strange errors every time.

Most of them come from RtlFreeHeap() that libcurl calls, and integer divided by zero from some WSA functions that curl_easy_perform() uses.

The crash may occur in any of the libcurl functions, starting from curl_easy_setopt().

The same code works without problems in an ordinary program.

EDIT : after digging this is the function causes corruption the reason why the crash didn't happen for previous curl usage is that I don't use this function except after this , then I create a receiver thread that works in parallel with a thread that uses this function , also the ordinary program didn't crash as this function was only for the service (a program not running in local system can use EnumWindows) I think Poco net didn't crash as it's based on c++ not c and uses new/delete to allocate and free the memory but the crash coming from curl and other functions start from _malloc_base and similar c allocation functions

the function code :

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;
}

I'm using this function to distinguish the instances of my helper process by the command line that the process started with

so the mechanism to find the instance looks like this :

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;
}

then in the service main thread :

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

now after finding the errors source , how this function do all these errors and how to prevent it from this ?

the definitions of the structures (from NiroSoft and 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;

回答1:


In GetCommandLineRemote(), when you allocate CommandLine.Buffer, you are over-allocating. MaximumLength is expressed in bytes, and WCHAR is 2 bytes in size. Your use of new[] is allocating 2x more memory than you really need, but that is OK since you are not reading more bytes than you allocate. You should divide MaximumLength by sizeof(WCHAR) to avoid over-allocating when using new WCHAR[]:

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

However, the UNICODE_STRING data you read is not guaranteed to be null terminated, but you are treating it as if it were when constructing the final wstring. You should take the Length into account (which is also expressed in bytes) instead of relying on a null terminator:

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

More importantly, you are freeing CommandLine.Buffer with delete instead of delete[], so your code has undefined behavior from that point onward. Memory allocated with new is freed with delete. Memory allocated with new[] is freed with delete[]. You cannot interchange them.

Also, on a side note, in enum_ids(), you are leaking the HANDLE returned by CreateToolhelp32Snapshot(). You need to close it with CloseHandle() when you are done using it.




回答2:


the exactly lines that caused the heap corruption are these :

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

the ReadProcessMemory succeeds but causes a heap corruption that appears on using curl functions

changing this line :

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

to this :

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

then freeing the buffer before returning with HeapFree instead of delete solves the problem

I didn't face any problem like this where ReadProcessMemory caused such strange errors if it read into a buffer allocated with new

the documentation of the function hasn't anything about this

did anyone face such problem ?



来源:https://stackoverflow.com/questions/51086754/libcurl-crashes-in-a-windows-service

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!