# PE injection technique

Dưới đây là một vài sơ đồ để mô tả tổng quan về quy trình giải nén và quy trình chèn PE:

{% embed url="<https://malwareunicorn.org/peinjection/img/e27d1b393a3e3634.gif>" %}

{% embed url="<https://malwareunicorn.org/peinjection/img/12517b65ec5bf16f.gif>" %}

{% embed url="<https://www.elastic.co/blog/ten-process-injection-techniques-technical-survey-common-and-trending-process>" %}

{% embed url="<https://www.malwaretech.com/2013/11/portable-executable-injection-for.html>" %}

## 1. Giải nén thủ công: Extracting the First Routine

Khi mở file thực thi, ta sẽ nhận thấy rằng chỉ có 2 hàm khả dụng, Rõ ràng là có nhiều hơn thế, nhưng phỏng đoán ban đầu của ta là phần mềm này đã được encrupted, packed, or the PE header bị thao tác.

<figure><img src="/files/CXD5O4xgGI2qVkCcGD3r" alt=""><figcaption></figcaption></figure>

**Xác định các decrypting routines (routine giải mã)**

Nếu bạn nhìn vào chế độ xem đồ thị trong IDA, có một vòng lặp xảy ra ở cuối đồ thị. Bên trong vòng lặp này, có một lệnh gọi đến mã hàm không tồn tại trong data section.

<figure><img src="/files/HnqkH0a0Ihs3OJuwLB3A" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/tmE4mqgYalUMdHRtEzfu" alt=""><figcaption></figcaption></figure>

Khi bạn thấy một mẫu như thế này, nó thực sự là a data manipulation loop (một vòng lặp thao tác dữ liệu):

* Một lệnh so sánh, theo sau là một lệnh nhánh.
* Một sự di chuyển dữ liệu đến một con trỏ của các byte trống hoặc blob dữ liệu hiện có.
* Một lệnh nhảy để hoàn thành vòng lặp.
* Sau đó, cuối cùng là một lối thoát khỏi vòng lặp kết thúc bằng cách nhảy đến các byte mới được ghi.

<figure><img src="/files/icwremu5Y9aa0JJ0dAn6" alt=""><figcaption></figcaption></figure>

Để đi đến mã thực tế, bạn sẽ cần sử dụng trình gỡ lỗi để vượt qua vòng lặp giải nén này. Mặc dù có thể thực hiện bằng tay, nhưng việc sử dụng trình gỡ lỗi sẽ dễ dàng hơn!

**Let's start debugging!**

Bây giờ, hãy mở trình gỡ lỗi và đặt một số điểm dừng. Đảm bảo đặt điểm dừng (F2) sau lệnh gọi `JNZ`. Tiếp theo, tạo một điểm dừng trên lệnh gọi đến mã đã giải nén.

<figure><img src="/files/l0MAIWLYaJH5U15Hc8jf" alt=""><figcaption></figcaption></figure>

Bây giờ, hãy chạy chương trình (F9) để con trỏ lệnh dừng lại ở lệnh gọi đến mã đã giải nén.

Trong trình gỡ lỗi, nhấp chuột phải vào địa chỉ của lệnh gọi đến mã đã giải nén. Select the option to dump the value of that address.

<figure><img src="/files/kV8fXL9gDUasPgrIXk8x" alt=""><figcaption></figcaption></figure>

Dưới đây là the dump of that address. Như bạn có thể thấy, giá trị đầu tiên là 0xEB, đó là một lệnh JMP.

<figure><img src="/files/RXGDq6dchQNDNWI4fZZK" alt=""><figcaption></figcaption></figure>

Tiếp theo, hãy bước vào (F7) lệnh gọi để bạn đến section mã mà bạn đã kết xuất trước đó. Trong suốt binary này, bạn sẽ sử dụng cùng một phương pháp để đến mã đã giải nén.

> **Mẹo:** Tốt nhất là luôn đặt điểm dừng ở đầu mã mà bạn đang nhảy đến. Đôi khi trình gỡ lỗi sẽ không cho phép bạn đặt a software breakpoint (điểm dừng phần mềm), thay vào đó hãy đặt điểm dừng phần cứng hoặc thực thi bộ nhớ trên byte tại địa chỉ đó. Ngoài ra, nếu bạn đặt điểm dừng vào một địa chỉ chưa tồn tại, bạn sẽ cần bật lại điểm dừng trong Breakpoints Tab sau khi không gian địa chỉ đó tồn tại trở lại.

## 2. Unpacking: Control Flow Obfuscation

<figure><img src="/files/QO6AtslfHmmjUM1VPvVW" alt=""><figcaption></figcaption></figure>

> **Note:** This is typically an assembly instruction that appears in a [function prologue](https://en.wikipedia.org/wiki/Function_prologue). Function prologues typically begin with a `push ebp, mov esp, ebp` in Windows.

<figure><img src="/files/zmnmSIO97qnuEd5Taqos" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/cZEL4zag7hicFjpFzup6" alt=""><figcaption></figcaption></figure>

Vì Ida Pro không thể hiển thị điều này một cách đẹp mắt trong chế độ xem đồ thị ngay lập tức, bạn sẽ cần kết hợp các phương pháp sau:

1. Duyệt qua các lệnh nhảy trong trình gỡ lỗi để tìm hiểu điều gì đang xảy ra trong section này.
2. and/or kết xuất mã đã được giải mã. Bạn có thể làm điều này bằng cách kiểm tra mã được so sánh để **lấy kích thước**, sau đó **chọn offset** cùng với kích thước và **kết xuất vào một file binary**. Tiếp theo, **mở trong Ida** và **điều chỉnh các segment** sao cho image base phản ánh địa chỉ mà bạn đã trích xuất.
3. and/or sử dụng trình gỡ lỗi để hiển thị đồ thị luồng điều khiển.

Tiếp tục cho đến khi bạn tìm thấy một opcode **XOR**. Bất cứ khi nào bạn thấy opcode **XOR** với một giá trị con trỏ dữ liệu và một giá trị thanh ghi byte đơn, điều này có nghĩa là nó đang giải mã một section mã.

<figure><img src="/files/WwijKnSeB9X22xgW6WPz" alt=""><figcaption></figcaption></figure>

Điều tiếp theo bạn cần tìm là nơi kết thúc vòng lặp. Một vòng lặp luôn bao gồm một lệnh tăng và một lệnh so sánh, sau đó là một nhánh sau lệnh so sánh. Bạn sẽ cần tìm nhánh này. Dưới đây là các đoạn trích được trích xuất từ luồng điều khiển bị làm rối.

```nasm
0041122E | 39F1                     | cmp ecx,esi              
004112E0 | 72 78                    | jb 546817.41135A
004111C4 | 58                       | pop eax                                
00411253 | FFE0                     | jmp eax                               
Size is 0xC80 
```

Trong trình gỡ lỗi của bạn, hãy đặt điểm dừng trên <mark style="color:red;">`JMP EAX`</mark> để bạn có thể bước vào mã mới được giải mã. Chạy chương trình để nó dừng lại ở điểm dừng của bạn. Tiếp theo, bạn sẽ cần kết xuất địa chỉ bộ nhớ đó để có thể trích xuất dữ liệu binary. Bạn có thể vá file thực thi gốc bằng trình chỉnh sửa hex hoặc đưa dữ liệu binary vào Ida để bạn có thể phân tích nó.

> **Mẹo:** Trong x32dbg, bạn có thể tìm kiếm các biểu thức lệnh bằng cách sử dụng phím tắt ctrl-f trong khi ở chế độ xem CPU. Nó giúp tìm kiếm (CTRL-F trong khi ở chế độ xem CPU) cho `JMP EAX` và đặt các điểm dừng trên đó để giảm bớt việc gỡ lỗi. Hãy chắc chắn luôn xác nhận với Ida rằng điểm dừng mà bạn đặt là một lệnh hợp lệ trong lộ trình bạn muốn đi.

## 3. Unpacking: Setting up Imports and Final Unpacking

> **Mẹo:** Tốt nhất là luôn sử dụng Ida làm lộ trình để bước các lệnh trong trình gỡ lỗi. Nếu bạn biết địa chỉ bắt đầu và kích thước của mã này, bạn có thể kết xuất nó bằng trình gỡ lỗi, sau đó mở kết xuất binary trong Ida. Hãy nhớ rằng phần mềm độc hại này đang chạy dưới dạng binary 32 bit, vì vậy hãy đảm bảo mở nó trong Ida ở chế độ đó. Chỉ cần sử dụng bộ xử lý mặc định (Meta-PC).

Section mã tiếp theo là một trình giải nén. Thật dễ dàng để xác định các trình đóng gói bằng cách tìm opcode <mark style="color:red;">`LOOP`</mark> cũng như sự kết hợp opcode <mark style="color:red;">`PUSHAD/POPAD`</mark>.

<figure><img src="/files/iNNPPfwkX4G6xeYfJeom" alt=""><figcaption></figcaption></figure>

Đặt điểm dừng trên lệnh sau lệnh <mark style="color:red;">`JNE`</mark> tại <mark style="color:red;">`0x0041CBDF`</mark> và tiếp tục chạy đến điểm dừng đó để bạn có thể bỏ qua vòng lặp.

Routine tiếp theo này sử dụng một thủ thuật để thêm các chuỗi vào stack bằng cách sử dụng lệnh CALL. Khi một lệnh gọi được thực hiện, những gì đến sau lệnh gọi được đặt trên stack vì đây được coi là địa chỉ trả về.

> *Lưu ý: Sự khác biệt giữa lệnh <mark style="color:red;">`JMP`</mark> và lệnh <mark style="color:red;">CALL</mark> là gì? Chúng có thể có opcode tương tự nhau, nhưng lệnh CALL sẽ đẩy EIP hiện tại, còn được gọi là địa chỉ lệnh trả về, lên stack.*

Đây là một cách lén lút để đặt các chuỗi lên stack thường được sử dụng trong shellcode. Trong trường hợp này, nó đang thực hiện <mark style="color:red;">`CALL, POP EAX, ADD EAX,3`</mark> để chuyển địa chỉ đến GetProcAddress.

> **Mẹo:** Các API như GetProcAddress đang được sử dụng ở đâu? Trong routine này, các lệnh gọi đến API sẽ được đặt trên stack. Trong khi ở trình gỡ lỗi, bất cứ khi nào bạn thấy một lệnh như <mark style="color:red;">`call dword ptr [ebp-24h]`</mark>, bạn có thể nhấp chuột phải vào địa chỉ <mark style="color:red;">`ebp-24h`</mark> và theo dõi trong chế độ xem disassembler. Điều này sẽ đưa bạn đến mã API và nó sẽ hiển thị tên xuất của API. Để quay lại vị trí bạn đang ở, bạn có thể nhấp chuột phải vào địa chỉ EIP và theo dõi trong chế độ xem disassembler. Tôi khuyên bạn nên điền vào các lệnh gọi API này dưới dạng comment nơi các lệnh gọi trong Ida.

<figure><img src="/files/DB5JRkyqTD9eVsxpmP9i" alt=""><figcaption></figcaption></figure>

Phần còn lại của mã này thiết lập routine giải nén trong một section bộ nhớ mới được cấp phát tại 0x30000. Bạn sẽ muốn tiếp tục bước qua để tìm lệnh tiếp theo cho <mark style="color:red;">`JMP EAX`</mark> và đặt một điểm dừng. Sau khi <mark style="color:red;">`EIP`</mark> của bạn ở 0x41CF7B nơi <mark style="color:red;">`JMP EAX`</mark> được đặt, hãy bước vào địa chỉ đó.

> *Lưu ý: Hãy nhớ lưu địa chỉ trong JMP EAX (EAX=0x303E4). Điều này sẽ đóng vai trò là entrypoint cho phần mã tiếp theo tại section bộ nhớ 0x30000 và bạn sẽ cần điều này cho Ida.*

<figure><img src="/files/sPkl9OawNCOeRhZ5EI6Q" alt=""><figcaption></figcaption></figure>

> **Mẹo:** Tốt nhất là luôn sử dụng Ida làm lộ trình để bước các lệnh trong trình gỡ lỗi. Trong x32dbg, có một Tab gọi là Memory Map chứa tất cả các section bộ nhớ được ánh xạ liên quan đến tiến trình. Thông thường, mã được lên kế hoạch thực thi sẽ có bảo vệ của section bộ nhớ được ánh xạ là Read/Write/Execute or ERW---. Bạn có thể nhấp chuột phải vào bộ nhớ 0x30000 và kết xuất nó vào một file binary. Tiếp theo, bạn có thể mở file binary này trong Ida để theo dõi trong trình gỡ lỗi.

> **Mẹo:** Vậy bạn đã mở kết xuất binary của section bộ nhớ 0x30000 trong Ida, vậy tiếp theo là gì? Bất cứ khi nào bạn mở dữ liệu binary vào Ida, Ida không có ý tưởng rằng mã này bắt đầu từ 0x3000 vì không có thông tin header PE nào để cho nó biết cách thiết lập nó. Bạn sẽ cần "rebase" địa chỉ image của dữ liệu binary của mình. Để rebase image của bạn, hãy đi tới **Edit->Segments->Rebase Program->Select Image Base** và đặt nó thành 0x30000 (địa chỉ bắt đầu của section bộ nhớ). Bây giờ bạn sẽ có thể theo dõi trong trình gỡ lỗi.

> *Lưu ý: Bây giờ bạn đã rebase image trong Ida, vậy tại sao nó không được tháo rời như trong trình gỡ lỗi? Ida Pro* disassembly *theo hướng* a flow-oriented disassembly *so với* a linear disassembly *như trình gỡ lỗi. Điều này có nghĩa là Ida sẽ theo dõi các lệnh gọi, jmp và return và* a flow-oriented disassembly *khi nó theo dõi luồng đó.*
>
> Bạn có thể đã thấy một cửa sổ bật lên cho biết "***IDA cannot identify the entry point automatically as there is no standard for binaries. Please move to what you think is an entry point and press "C" to start the autoanalysis** (*&#x49;DA không thể tự động xác định entry point vì không có tiêu chuẩn cho binary. Vui lòng chuyển đến vị trí bạn cho là entry point và nhấn "C" để bắt đầu phân tích tự động.)" Bạn nên lưu entrypoint từ lệnh <mark style="color:red;">`JMP EAX`</mark> là `0x303E4`. Chuyển đến địa chỉ đó bằng cách nhấn phím tắt "g".

<figure><img src="/files/cL5URMqIwt51Lw9WvGWe" alt=""><figcaption></figcaption></figure>

> Bạn có thể hỏi, cái quái gì thế này? Ida đang cố gắng phân tích section này dưới dạng double dwords (dd), nhưng rõ ràng bạn không thể xem các byte tại địa chỉ entrypoint của mình. Bạn sẽ cần "**undefine**" phân tích tự động này. **Chọn dd bạn muốn undefine và nhấn phím tắt "u".** Bây giờ hãy chọn byte tại địa chỉ entrypoint 0x303E4 và nhấn phím tắt "c" để chuyển đổi các byte này thành "code"/disassembly.
>
> Bây giờ công việc của bạn với tư cách là một reverse engineer là chuyển đổi thủ công các byte được phân tích cú pháp sai thành disassembly.

**Self modification**

Routine mã tiếp theo này chuẩn bị phần chính của mã Cryptowall bằng cách đặt mã đã giải nén vào đầu section text và sửa đổi header của image bộ nhớ tiến trình riêng của nó. Hãy nhớ lưu bản sao của header gốc vì trong blog gốc, họ đã đề cập rằng bảng section bị hỏng sau khi sửa đổi. Tiếp theo, hãy tiếp tục bước qua để tìm kiếm lệnh `JMP EAX` và bước vào.

> **Mẹo:** Điểm dừng không thành công hoặc địa chỉ không tồn tại? Đôi khi bạn phải đợi một section bộ nhớ tồn tại trước khi đặt điểm dừng hoặc bạn có thể phải bật lại điểm dừng. Phương pháp dễ nhất là chỉ cần đặt a hardware execute breakpoint (một điểm dừng thực thi phần cứng) trên byte tại địa chỉ đó.

<figure><img src="/files/T4WxHxFfmjlMjQRs1cif" alt=""><figcaption></figcaption></figure>

## 4. Unpacking: Cryptowall Unpacked Code

Sau khi bạn đã đến mã đã giải nén, tốt nhất là hãy kết xuất section text bắt đầu từ 0x401000 của file thực thi này từ bộ nhớ tiến trình. Bằng cách này, bạn có thể đặt mã đã giải nén này bằng cách ghi đè lên file thực thi gốc bằng trình chỉnh sửa hex để bạn có thể theo dõi trong Ida Pro. Tại sao không kết xuất toàn bộ, header và tất cả? Bởi vì Cryptowall làm hỏng header section. Tốt nhất là chỉ nên giữ lại header gốc và sửa đổi entrypoint bằng **PEBear**.

<figure><img src="/files/VID9WIZIKIzWK0c8e4sW" alt=""><figcaption></figcaption></figure>

## 5. Unpacking: Import Table Restoration

Với mỗi routine giải nén, sẽ luôn có một phương pháp để khôi phục bảng import. Hàm đầu tiên trong mã đã giải nén đang thiết lập bảng import tại 0x4016F0. Để xác định loại phương pháp này, bạn sẽ thấy một vòng lặp hoặc một lệnh gọi liên tục đến cùng một hàm để lưu trữ các địa chỉ của các hàm vào một mảng. Thông thường, phần mềm độc hại lưu trữ các hàm này được biểu thị bằng hash hoặc offset và lưu trữ chúng trong section .data hoặc trong chính các lệnh. Sau khi bạn có quyền truy cập vào bảng import, sẽ dễ dàng điền vào các lệnh gọi động đến các hàm này trong trình disassembler của bạn.

<figure><img src="/files/ysqS4G1HxLy4ORydmES1" alt=""><figcaption></figcaption></figure>

Tôi khuyên bạn nên bắt đầu điền các lệnh gọi API trong Ida để bạn có thể theo dõi cùng với trình gỡ lỗi.

<figure><img src="/files/6zgvnKotngCkbknW3w7k" alt=""><figcaption></figcaption></figure>

Dưới đây là việc cấp phát bộ nhớ mới tại 0x1D0000 cho bảng import. Bạn có thể xem điều này bằng cách nhấp chuột phải vào địa chỉ và dumping to the Dump panel in x32dbg.

<figure><img src="/files/mLxVXJWonti0jzXJN9Mb" alt=""><figcaption></figcaption></figure>

Các lệnh gọi API đáng chú ý từ Import Table (Tôi không bao gồm tất cả chúng ở đây):

<table><thead><tr><th width="171">Offset (hex)</th><th>API Call</th></tr></thead><tbody><tr><td>0</td><td>ZwClose</td></tr><tr><td>4</td><td>LdrLoadDll</td></tr><tr><td>8</td><td>LdrGetProcedureAddress</td></tr><tr><td>C</td><td>NtAllocateVirtualMemory</td></tr><tr><td>10</td><td>ZwFreeVirtualMemory</td></tr><tr><td>14</td><td>NtProtectVirtualMemory</td></tr><tr><td>18</td><td>ZwQueryVirtualMemory</td></tr><tr><td>1C</td><td>ZwWriteVirtualMemory</td></tr><tr><td>20</td><td>ZwReadVitrualMemory</td></tr><tr><td>24</td><td>ZwWow64ReadVirtualMemory64</td></tr><tr><td>28</td><td>RtlFreeHeap</td></tr><tr><td>2C</td><td>memset</td></tr><tr><td>30</td><td>memcopy</td></tr><tr><td>38</td><td>memchr</td></tr><tr><td>3C</td><td>ZwCreateEvent</td></tr><tr><td>40</td><td>ZwOpenEvent</td></tr><tr><td>44</td><td>ZwSetEvent</td></tr><tr><td>48</td><td>NtWaitForSingleObject</td></tr><tr><td>4C</td><td>ZwWaitForMultipleObjects</td></tr><tr><td>50</td><td>NtQuerySystemInformation</td></tr><tr><td>54</td><td>NtShutdownSystem</td></tr><tr><td>58</td><td>RtlGetNtProductType</td></tr><tr><td>5C</td><td>ZwOpenProcess</td></tr><tr><td>60</td><td>NtTerminateProcess</td></tr><tr><td>64</td><td>ZwQueryInformationProcess</td></tr><tr><td>68</td><td>NtDelayExecution</td></tr><tr><td>6C</td><td>RtlAdjustPrivilege</td></tr><tr><td>70</td><td>RtlSetProcessIsCritical</td></tr><tr><td>74</td><td>ZwOpenThread</td></tr><tr><td>78</td><td>​​ZwTerminateThread</td></tr><tr><td>7C</td><td>NtResumeThread</td></tr><tr><td>80</td><td>NtSuspendThread</td></tr><tr><td>84</td><td>ZwQueryInformationThread</td></tr><tr><td>88</td><td>ZwImpersonateThread</td></tr><tr><td>8C</td><td>RtlCreateUserThread</td></tr><tr><td>90</td><td>ZwCreateThreadEx</td></tr><tr><td>94</td><td>CsClientCallServer</td></tr><tr><td>98</td><td>ZwWow64CsrClientCallServer</td></tr><tr><td>9C</td><td>NtGetContextThread</td></tr><tr><td>A0</td><td>ZwSetContextThread</td></tr><tr><td>A4</td><td>RtlExitUserThread</td></tr><tr><td>A8</td><td>NtQueueApcThread</td></tr><tr><td>AC</td><td>NtSetInformationThread</td></tr><tr><td>B0</td><td>ZwOpenProcessToken</td></tr><tr><td>B4</td><td>NtQueryInformationToken</td></tr><tr><td>B8</td><td>ZwCreateFile</td></tr><tr><td>C0</td><td>ZwWriteFile</td></tr><tr><td>C4</td><td>NtReadFile</td></tr><tr><td>C8</td><td>ZwDeleteFile</td></tr><tr><td>CC</td><td>ZwQueryInformationFile</td></tr><tr><td>D0</td><td>NtSetInformationFile</td></tr><tr><td>D4</td><td>ZwQueryVolumeInformationFile</td></tr><tr><td>D8</td><td>NtCreateSection</td></tr><tr><td>DC</td><td>ZwMapViewOfSection</td></tr><tr><td>E0</td><td>ZwUnmapViewOfSection</td></tr><tr><td>E4</td><td>RtlCreateSecurityDescriptor</td></tr><tr><td>E8</td><td>RtlSetDaclSecurityDescriptor</td></tr><tr><td>EC</td><td>NtSetSecurityObject</td></tr><tr><td>F0</td><td>ZwCreateKey</td></tr><tr><td>F4</td><td>ZwOpenKey</td></tr><tr><td>F8</td><td>ZwQueryKey</td></tr><tr><td>FC</td><td>ZwDeleteKey</td></tr><tr><td>100</td><td>ZwQueryValueKey</td></tr><tr><td>104</td><td>ZwSetValueKey</td></tr><tr><td>108</td><td>NtDeleteValueKey</td></tr><tr><td>10C</td><td>ZwRenameKey</td></tr><tr><td>134</td><td>wcscat</td></tr><tr><td>170</td><td>RtlDosPathNameToNtPathName_U</td></tr><tr><td>12C</td><td>wcsncpy</td></tr><tr><td>15C</td><td>RtlInitUnicodeString</td></tr><tr><td>1A0</td><td>NtQuerySystemTime</td></tr><tr><td>1B4</td><td>CreateProcessInternal</td></tr><tr><td>224</td><td>CreateRemoteThread</td></tr><tr><td>228</td><td>GetCommandLineW</td></tr><tr><td>22C</td><td>AllocateAndInitializedSid</td></tr><tr><td>230</td><td>CheckTokenMembership</td></tr><tr><td>234</td><td>FreeSid</td></tr><tr><td>238</td><td>LookupAccountSidW</td></tr><tr><td>23C</td><td>GetUserNameW</td></tr><tr><td>294</td><td>GetKeyboardLayoutList</td></tr><tr><td>298</td><td>GetSystemMetrics</td></tr></tbody></table>

**Token Check**

Trong cùng hàm này (0x4016F00), có một lệnh gọi để cố gắng kiểm tra token để biết các đặc quyền nâng cao (0x409260).

**Victim Fingerprinting**

Như đã đề cập trong sơ đồ, tôi sẽ nói ngắn gọn ở đây. Tiếp theo, bạn sẽ thấy một hàm (0x4041C0) liên quan đến việc tạo một event mới cho "BaseNamedObjects" và sau đó là một hàm thực hiện việc lấy dấu vân tay nạn nhân (0x404160). Event này được tạo ra như một phương tiện để phần mềm độc hại xác định xem nó có phải là một tiến trình đang chạy trùng lặp hay không. Về cơ bản, nó thu thập thông tin nạn nhân và băm nó để tạo tên đối tượng (ví dụ: \BaseNamedObjects\6224336787).

## 6. Injection Into Explorer: New Section Creation

**Injecting Into Child Process explorer.exe (Function 0x40A680)**

<figure><img src="/files/dYdoi4EosXqPPUfbYPy0" alt=""><figcaption></figcaption></figure>

**Querying the process**

Đầu hàm này, có một truy vấn thông tin tiến trình để xác định xem nó có đang thực thi trong ngữ cảnh của kiến trúc 32 bit hay 64 bit hay không. Điều này sẽ xác định việc sử dụng explorer từ các thư mục System32 hoặc SysWOW64 tương ứng. API Windows được sử dụng ở đây là ZwQueryInformationProcess.

> *Lưu ý: Đối với phần còn lại của workshop này, sẽ chia sẻ các nguyên mẫu hàm gọi API Windows để bạn có thể theo dõi các đối số của hàm. Tôi cũng sẽ cung cấp mã golang tương đương.*

**Disassembly**

<figure><img src="/files/iQMMTmCr5mtUZbcUA0va" alt=""><figcaption></figcaption></figure>

**Function Prototype**

```c
NTSTATUS WINAPI ZwQueryInformationProcess(
  _In_      HANDLE           ProcessHandle,
  _In_      PROCESSINFOCLASS ProcessInformationClass,
  _Out_     PVOID            ProcessInformation,
  _In_      ULONG            ProcessInformationLength,
  _Out_opt_ PULONG           ReturnLength
);
```

**Golang**

```go
func IsSysWow64(ntdll syscall.Handle) (bool, error) {
        var pInfo uintptr
        pInfoLen := uint32(unsafe.Sizeof(pInfo))
        ZwQueryInformationProcess, err := syscall.GetProcAddress(
                syscall.Handle(ntdll), "ZwQueryInformationProcess")
        if err != nil {
                return false, err
        }
        r, _, err := syscall.Syscall6(uintptr(ZwQueryInformationProcess),
                5,
                uintptr(windows.CurrentProcess()),        // ProcessHandle
                uintptr(windows.ProcessWow64Information), // ProcessInformationClass
                uintptr(unsafe.Pointer(&pInfo)),         // ProcessInformation
                uintptr(pInfoLen),                       // ProcessInformationLength
                uintptr(unsafe.Pointer(&pInfoLen)),      // ReturnLength
                0)
        if r != 0 {
                log.Printf("ZwQueryInformationProcess ERROR CODE: %x", r)
                return false, err
        }
        if pInfo != 0 {
                return true, nil
        }
        return false, nil
}

```

**Creating New Process**

Tiếp theo, nó gọi CreateProcessInternalW, đây là một lệnh gọi API không được ghi lại. Điều này sẽ tạo một explorer.exe mới dưới dạng một tiến trình con bị tạm dừng.

**Disassembly**

<figure><img src="/files/23aWbj6Vr3GEk03mdzOE" alt=""><figcaption></figcaption></figure>

**Function Prototype**

```c
BOOL
 WINAPI
 CreateProcessInternalW(IN HANDLE hUserToken,
                        IN LPCWSTR lpApplicationName,
                        IN LPWSTR lpCommandLine,
                        IN LPSECURITY_ATTRIBUTES lpProcessAttributes,
                        IN LPSECURITY_ATTRIBUTES lpThreadAttributes,
                        IN BOOL bInheritHandles,
                        IN DWORD dwCreationFlags,
                        IN LPVOID lpEnvironment,
                        IN LPCWSTR lpCurrentDirectory,
                        IN LPSTARTUPINFOW lpStartupInfo,
                        IN LPPROCESS_INFORMATION lpProcessInformation,
                        OUT PHANDLE hNewToken)

```

**Golang**

```go
func CreateProcessInt(kernel32 syscall.Handle, procPath string) (uintptr, uintptr, error) {
        CreateProcessInternalW, err := syscall.GetProcAddress(
                syscall.Handle(kernel32), "CreateProcessInternalW")
        if err != nil {
                log.Fatalln(err)
                return 0, 0, err
        }
        var si windows.StartupInfo
        var pi windows.ProcessInformation
        log.Println(procPath)
        r, a, err := syscall.Syscall12(uintptr(CreateProcessInternalW),
                12,
                0, // IN HANDLE hUserToken,
                uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(procPath))), // IN LPCWSTR lpApplicationName,
                0,                                 // IN LPWSTR lpCommandLine,
                0,                                 // IN LPSECURITY_ATTRIBUTES lpProcessAttributes,
                0,                                 // IN LPSECURITY_ATTRIBUTES lpThreadAttributes,
                0,                                 // IN BOOL bInheritHandles,
                uintptr(windows.CREATE_SUSPENDED), // IN DWORD dwCreationFlags,
                0,                                 // IN LPVOID lpEnvironment,
                0,                                 // IN LPCWSTR lpCurrentDirectory,
                uintptr(unsafe.Pointer(&si)),      // IN LPSTARTUPINFOW lpStartupInfo,
                uintptr(unsafe.Pointer(&pi)),      // IN LPPROCESS_INFORMATION lpProcessInformation,
                0)                                 // OUT PHANDLE hNewToken)
        if r > 1 { // hack for error code invalid function
                log.Printf("CreateProcessInternalW ERROR CODE: %x", r)
                return 0, 0, err
        }
        log.Printf("%x %x %s %x", r, a, err, pi.Process)
        return uintptr(pi.Process), uintptr(pi.Thread), nil
}
```

**Creating and Writing to New Section**

Thay vì giải phóng image tiến trình hoặc khoét rỗng section text của tiến trình, Cryptowall thay vào đó tạo một section mới trong explorer.exe, sau đó ánh xạ section đó trong cả tiến trình cục bộ và từ xa.

**Disassembly**

![](https://malwareunicorn.org/peinjection/img/91eefdebbe300628.png)

**Function Prototype**

```c
NTSTATUS NtCreateSection(
  PHANDLE            SectionHandle,
  ACCESS_MASK        DesiredAccess,
  POBJECT_ATTRIBUTES ObjectAttributes,
  PLARGE_INTEGER     MaximumSize,
  ULONG              SectionPageProtection,
  ULONG              AllocationAttributes,
  HANDLE             FileHandle
);
```

**Golang**

```go
func CreateNewSection(ntdll syscall.Handle, size int64) (uintptr, error) {
        var err error
        NtCreateSection, err := syscall.GetProcAddress(
                syscall.Handle(ntdll), "NtCreateSection")
        if err != nil {
                return 0, err
        }
        var section uintptr
        r, a, err := syscall.Syscall9(uintptr(NtCreateSection),
                7,
                uintptr(unsafe.Pointer(&section)), // PHANDLE            SectionHandle,
                FILE_MAP_ALL_ACCESS,               // ACCESS_MASK        DesiredAccess,
                0,                                 // POBJECT_ATTRIBUTES ObjectAttributes,
                uintptr(unsafe.Pointer(&size)),    // PLARGE_INTEGER     MaximumSize,
                windows.PAGE_EXECUTE_READWRITE,    // ULONG              SectionPageProtection,
                SEC_COMMIT,                        // ULONG              AllocationAttributes,
                0,                                 // HANDLE             FileHandle
                0,
                0)
        if r != 0 {
                log.Printf("NtCreateSection ERROR CODE: %x", r)
                return 0, err
        }
        log.Printf("%x %x %s", r, a, err)
        if section == 0 {
                return 0, fmt.Errorf("NtCreateSection failed for unknown reason")
        }
        log.Printf("Section: %0x\n", section)
        return section, nil
}
```

Bằng cách ánh xạ section vào cả hai tiến trình bằng ZwMapViewOfSection, bạn có thể dễ dàng ghi vào bằng cách sử dụng memcpy đơn giản mà không cần gọi ZwWriteVirtualMemory và cập nhật bảo vệ để cho phép thực thi. NtCreateSection đã có các cờ bảo vệ thực thi (PAGE\_EXECUTE\_READWRITE) để đặt khi tạo, trong khi gọi ZwMapViewOfSection sử dụng PAGE\_READWRITE. Lưu ý rằng phần mềm độc hại sử dụng -1 (0xFFFFFFFF) làm handle tiến trình, điều này cho biết tiến trình hiện tại. Trong phiên bản golang, việc lấy handle tiến trình hiện tại sạch hơn một chút.

**Disassembly**

![](https://malwareunicorn.org/peinjection/img/4c31046cc1f17410.png)

**Function Prototype**

```c
NTSYSAPI NTSTATUS ZwMapViewOfSection(
  HANDLE          SectionHandle,
  HANDLE          ProcessHandle,
  PVOID           *BaseAddress,
  ULONG_PTR       ZeroBits,
  SIZE_T          CommitSize,
  PLARGE_INTEGER  SectionOffset,
  PSIZE_T         ViewSize,
  SECTION_INHERIT InheritDisposition,
  ULONG           AllocationType,
  ULONG           Win32Protect
);
```

**Golang**

```go
func MapViewOfSection(
        ntdll syscall.Handle, section uintptr,
        phandle uintptr, commitSize uint32,
        viewSize uint32) (uintptr, uint32, error) {
        if phandle == 0 {
                return 0, 0, nil
        }
        var err error
        ZwMapViewOfSection, err := syscall.GetProcAddress(
                syscall.Handle(ntdll), "ZwMapViewOfSection")
        if err != nil {
                return 0, 0, err
        }
        var sectionBaseAddr uintptr
        r, a, err := syscall.Syscall12(uintptr(ZwMapViewOfSection),
                10,
                section, // HANDLE          SectionHandle,
                phandle, // HANDLE          ProcessHandle,
                uintptr(unsafe.Pointer(&sectionBaseAddr)), // PVOID           *BaseAddress,
                0,                                  // ULONG_PTR       ZeroBits,
                uintptr(commitSize),                // SIZE_T          CommitSize,
                0,                                  // PLARGE_INTEGER  SectionOffset,
                uintptr(unsafe.Pointer(&viewSize)), // PSIZE_T         ViewSize,
                1,                                  // SECTION_INHERIT InheritDisposition,
                0,                                  // ULONG           AllocationType,
                windows.PAGE_READWRITE,             // ULONG           Win32Protect
                0,
                0)
        if r != 0 {
                log.Printf("ZwMapViewOfSection ERROR CODE: %x", r)
                return 0, 0, err
        }
        log.Printf("%x %x %s", r, a, err)

        return sectionBaseAddr, viewSize, nil
}
```

Nếu routine này không thành công, Cryptowall sẽ mặc định sử dụng routine NtAllocateVirtualMemory, ZwWriteVirtualMemory, NtProtectVirtualMemory thông thường để ghi vào bộ nhớ của tiến trình mục tiêu.

**How to view the new memory section**

Sau khi section đã được tạo và các byte đã được ghi vào địa chỉ cơ sở section đó, hãy mở process explorer từ sysinternals suite và một phiên bản trình gỡ lỗi mới của bạn.

> **Mẹo:** Đặt điểm dừng sau lệnh gọi memcpy (giữa 2 lệnh gọi ZwMapViewOfSection) hoặc sau lệnh gọi cuối cùng đến ZwMapViewOfSection.

Trong process explorer, hãy xác định tiến trình con của tiến trình Cryptowall, đó sẽ là explorer.exe. Trong phiên bản trình gỡ lỗi mới, hãy đính kèm vào ID tiến trình explorer.exe từ những gì bạn thấy trong process explorer.

<figure><img src="/files/NEV3ZUBGA8PurHU6EhhC" alt=""><figcaption></figcaption></figure>

Hãy tiếp tục và attach to explorer.exe. Lưu ý rằng binary là 32 bit.

<figure><img src="/files/l5YzNvDBTvGsFKlWIxSI" alt=""><figcaption></figcaption></figure>

Trong tab Memory Map của trình gỡ lỗi, hãy tìm section mới được tạo (đây sẽ là một địa chỉ cơ sở được điền từ ZwMapViewOfSection) trong danh sách bộ nhớ. Điều này thường ở cuối danh sách bộ nhớ cho explorer.exe. Một cách khác để xác định section bộ nhớ là bảo vệ của nó là thực thi, đọc, ghi. Mặc dù bảo vệ RWX là dấu hiệu cảnh báo chính, nhưng section này được ánh xạ dưới dạng Type MAP và mặc dù nó có thể thực thi, nhưng nó không được cấp phát ban đầu là copy-on-write (ERWC), điều đó có nghĩa là nó không được hỗ trợ bởi một image trên đĩa.

<figure><img src="/files/92eGZVjGs2H291G29Ey8" alt=""><figcaption></figcaption></figure>

Như bạn có thể thấy bên dưới, Cryptowall quyết định đưa toàn bộ file thực thi đã giải nén vào bộ nhớ.

<figure><img src="/files/OoY6uR86sqKt3EWK2Hkf" alt=""><figcaption></figcaption></figure>

Vì mẫu Cryptowall này cũng đang chèn mã độc lập vị trí, tôi muốn duy trì tính tương đồng bằng cách hiển thị một ví dụ đơn giản. Bây giờ đây là mã golang của tôi chỉ chèn "HELLO WORLD!" vào explorer.exe. Rõ ràng, bạn có thể thay byte buffer đó bằng một số shellcode 32 bit (tôi đã thực hiện điều này trong mã ví dụ được liên kết).

<figure><img src="/files/Bl6ul9MFI0GfeVmx3eCY" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/dohCpoL6UHNWYXvtDZ68" alt=""><figcaption></figcaption></figure>

## 7. Injection Into Explorer: Spawning a New Thread

Khi hàm 0x40A680 được gọi, nó đã truyền một địa chỉ đến ApcRoutine (0x413B40) mà NtQueueApcThread dự định thực thi. Nhìn vào the std call panel, bạn có thể thấy rằng ApcRoutine là một offset địa chỉ tồn tại trong section bộ nhớ mới.

<figure><img src="/files/NYPFlBKbqGUk0TfteThk" alt=""><figcaption></figcaption></figure>

**Disassembly**

![](https://malwareunicorn.org/peinjection/img/264380f568aa6d95.png)

**Function Prototype (Undocumented)**

```c
NTSYSAPI 
NTSTATUS
NTAPI
NtQueueApcThread(
  IN HANDLE               ThreadHandle,
  IN PIO_APC_ROUTINE      ApcRoutine,
  IN PVOID                ApcRoutineContext OPTIONAL,
  IN PIO_STATUS_BLOCK     ApcStatusBlock OPTIONAL,
  IN ULONG                ApcReserved OPTIONAL );
```

**Golang**

```go
func QueueApcThread(ntdll syscall.Handle, thandle uintptr, funcaddr uintptr) error {
        var err error
        NtQueueApcThread, err := syscall.GetProcAddress(
                syscall.Handle(ntdll), "NtQueueApcThread")
        if err != nil {
                return err
        }
        r, _, err := syscall.Syscall6(uintptr(NtQueueApcThread),
                5,
                thandle,  // IN HANDLE               ThreadHandle,
                funcaddr, // IN PIO_APC_ROUTINE      ApcRoutine, (RemoteSectionBaseAddr)
                0,        // IN PVOID                ApcRoutineContext OPTIONAL,
                0,        // IN PIO_STATUS_BLOCK     ApcStatusBlock OPTIONAL,
                0,        // IN ULONG                ApcReserved OPTIONAL
                0)
        if r != 0 {
                log.Printf("NtQueueApcThread ERROR CODE: %x", r)
                return err
        }
        return nil
}
```

Sau đó, cuối cùng là đặt ThreadInformationClass và tiếp tục luồng chính của tiến trình mục tiêu. Bây giờ tôi không chắc ý định của việc sử dụng ThreadTimes (0x1) ở đây là gì. Tôi thực sự nghĩ rằng đây có thể là một lỗi chính tả từ phía tác giả phần mềm độc hại. Chỉ cần thêm một số 1 nữa sẽ thay đổi ThreadInformationClass thành ThreadHideFromDebugger (0x11), có lẽ đó là những gì họ muốn, nếu không nó sẽ tiếp tục đưa ra lỗi STATUS\_INVALID\_INFO\_CLASS (0xC0000003).

**Disassembly**

![](https://malwareunicorn.org/peinjection/img/3c1dcafeac432313.png)

**NtSetInformationThread**

```c
__kernel_entry NTSYSCALLAPI NTSTATUS NtSetInformationThread(
  HANDLE          ThreadHandle,
  THREADINFOCLASS ThreadInformationClass,
  PVOID           ThreadInformation,
  ULONG           ThreadInformationLength
);
```

**NtResumeThread**

```c
NTSYSAPI 
NTSTATUS
NTAPI
NtResumeThread(
  IN HANDLE               ThreadHandle,
  OUT PULONG              SuspendCount OPTIONAL );
```

**Golang**

```go
func SetInformationThread(ntdll syscall.Handle, thandle uintptr) error {
        var err error
        NtSetInformationThread, err := syscall.GetProcAddress(
                syscall.Handle(ntdll), "NtSetInformationThread")
        if err != nil {
                return err
        }
        ti := int32(0x11) //ThreadHideFromDebugger
        r, _, err := syscall.Syscall6(uintptr(NtSetInformationThread),
                4,
                thandle,     //         HANDLE          ThreadHandle,
                uintptr(ti), //   THREADINFOCLASS ThreadInformationClass,
                0,           //   PVOID           ThreadInformation,
                0,           //   ULONG           ThreadInformationLength
                0,
                0)
        if r != 0 {
                log.Printf("NtSetInformationThread ERROR CODE: %x", r)
                return err
        }

        return nil
}

func ResumeThread(ntdll syscall.Handle, thandle uintptr) error {
        NtResumeThread, err := syscall.GetProcAddress(
                syscall.Handle(ntdll), "NtResumeThread")
        if err != nil {
                return err
        }
        r, _, err := syscall.Syscall(uintptr(NtResumeThread),
                2,
                thandle, //         IN HANDLE               ThreadHandle,
                0,       //   OUT PULONG              SuspendCount OPTIONAL
                0)
        if r != 0 {
                log.Printf("NtResumeThread ERROR CODE: %x", r)
                return err
        }
        return nil
}
```

Nếu routine NtQueueApcThread không thành công, thì Cryptowall sẽ mặc định gọi CreateRemoteThread. Tôi không có kế hoạch xem xét phần này nhưng bạn có thể thoải mái xem nó theo tốc độ của riêng bạn.

**Disassembly**

![](https://malwareunicorn.org/peinjection/img/dbfaa717607fece.png)

![](https://malwareunicorn.org/peinjection/img/6f24f51b8ae7dc24.png)

**Function Prototype**

```c
HANDLE CreateRemoteThread(
  HANDLE                 hProcess,
  LPSECURITY_ATTRIBUTES  lpThreadAttributes,
  SIZE_T                 dwStackSize,
  LPTHREAD_START_ROUTINE lpStartAddress,
  LPVOID                 lpParameter,
  DWORD                  dwCreationFlags,
  LPDWORD                lpThreadId
);
```

Mục đích của workshop này là đảo ngược kỹ thuật đủ để đưa bạn đến routine chèn vào explorer. Chúc bạn đảo ngược kỹ thuật vui vẻ!

***

**Appendix:**&#x20;

1. [https://malwareunicorn.org/workshops/peinjection.html](https://malwareunicorn.org/workshops/peinjection.html#11)
2. <https://github.com/malware-unicorn/GoPEInjection>
3. <https://www.elastic.co/blog/ten-process-injection-techniques-technical-survey-common-and-trending-process>
4. <https://www.malwaretech.com/2013/11/portable-executable-injection-for.html>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://viettaliii.gitbook.io/home/education/reverse-engineering/pe-injection-technique.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
