Processes

Một chương trình có thể có một hoặc nhiều tiến trình. Một tiến trình duy trì và đại diện cho sự thực thi của một chương trình.

Attacker thường nhắm đến các tiến trình để tránh né sự phát hiện và giấu mã độc ở trong các process hợp lệ. Một vài attack vector:

Một tiến trình sẽ bao gồm các thành phần sau:

Process ComponentPurpose
Private Virtual Address SpaceVirtual memory addresses that the process is allocated.
Executable ProgramDefines code and data stored in the virtual address space.
Open HandlesDefines handles to system resources accessible to the process.
Security ContextThe access token defines the user, security groups, privileges, and other security information.
Process IDUnique numerical identifier of the process.
ThreadsSection of a process scheduled for execution.

Các thành phần của tiến trình được lưu trong bộ nhớ:

ComponentPurpose
CodeCode to be executed by the process.
Global VariablesStored variables.
Process HeapDefines the heap where data is stored.
Process ResourcesDefines further resources of the process.
Environment BlockData structure to define process information.

Minh họa:

Thông tin của từng tiến trình trong Task Manager bao gồm:

Value/ComponentPurposeExample
NameDefine the name of the process, typically inherited from the applicationconhost.exe
PIDUnique numerical value to identify the process7408
StatusDetermines how the process is running (running, suspended, etc.)Running
User nameUser that initiated the process. Can denote privilege of the processSYSTEM

Info

Một vài công cụ giúp quan sát các tiến trình dễ dàng hơn: Process Hacker 2Process Explorer, và Procmon.

Threads

Một luồng là một đơn vị thực thi bên trong một tiến trình và được lập lịch dựa trên một vài yếu tố của thiết bị. Đơn giản hơn, nó là thành phần kiểm soát luồng thực thi của một tiến trình. Do đó, nó thường là mục tiêu tấn công.

Các luồng chia sẻ các tài nguyên mà tiến trình cấp với các luồng khác. Một thread đồng thời cũng có các giá trị và dữ liệu độc nhất như trong bảng bên dưới:

ComponentPurpose
StackAll data relevant and specific to the thread (exceptions, procedure calls, etc.)
Thread Local StoragePointers for allocating storage to a unique data environment
Stack ArgumentUnique value assigned to each thread
Context StructureHolds machine register values maintained by the kernel

Virtual Memory

Cho phép các thành phần khác tương tác với bộ nhớ chính mà không cần lo về rủi ro bị chồng chéo giữa các ứng dụng.

Bộ nhớ ảo cung cấp cho mỗi tiến trình một không gian địa chỉ ảo riêng tư (private virtual address space). Sẽ có một trình quản lý bộ nhớ dịch các địa chỉ ảo sang địa chỉ vật lý. Nhờ vào việc có một không gian địa chỉ ảo riêng tư và không ghi dữ liệu trực tiếp lên bộ nhớ vật lý nên các thiệt hại gây ra bởi tiến trình được giảm thiểu.

Minh họa:

Theo lý thuyết, không gian địa chỉ ảo riêng tư tối đa là 4GB trên một hệ thống 32-bit x86. Không gian địa chỉ này được chia thành hai phần. Phần bên dưới (0x00000000 - 0x7FFFFFFF) được cấp phát cho các tiến trình còn phần bên trên (0x80000000 - 0xFFFFFFFF) được cấp phát cho hệ điều hành. Administrator có thể chỉnh sửa bố cục cấp phát này cho các ứng dụng cần không gian địa chỉ lớn hơn thông qua các cài đặt (increaseUserVA) hoặc  AWE (Address Windowing Extensions).

Bố cục cấp phát này cũng được sử dụng trong các hệ thống 64-bit.

Minh họa:

Tài liệu của Microsoft mô tả một DLL là một thư viện có chứa code và dữ liệu mà có thể được sử dụng bởi nhiều hơn một chương trình cùng một lúc. Các DLL giúp module hóa code, tái sử dụng code, sử dụng hiệu quả bộ nhớ và giảm thiểu không gian ổ đĩa.

Về bản chất, DLL được tạo ra không khác gì các ứng dụng thông thường nhưng có thêm một vài thay đổi nhỏ. Bên dưới là một ví dụ của một DLL từ dự án Visual C++ Win32 Dynamic-Link Library:

#include "stdafx.h"
#define EXPORTING_DLL
#include "sampleDLL.h"
BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved
)
{
    return TRUE;
}
 
void HelloWorld()
{
    MessageBox( NULL, TEXT("Hello World"), TEXT("In a DLL"), MB_OK);
}

Bên dưới là header của DLL, nó sẽ định nghĩa các hàm được import và export:

#ifndef INDLL_H
    #define INDLL_H
    #ifdef EXPORTING_DLL
        extern __declspec(dllexport) void HelloWorld();
    #else
        extern __declspec(dllimport) void HelloWorld();
    #endif
 
#endif

DLL có thể được nạp vào một chương trình bằng cách sử dụng load-time dynamic linking hoặc run-time dynamic linking.

Với load-time dynamic linking, ứng dụng sẽ dùng các câu lệnh gọi sử dụng DLL tường minh. Để sử dụng kiểu nạp DLL này thì ta cần cung cấp cho ứng dụng một file header và một file thư viện (.lib). Bên dưới là ví dụ cho việc gọi đến một exported DLL function trong một ứng dụng:

#include "stdafx.h"
#include "sampleDLL.h"
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    HelloWorld();
    return 0;
}

Với run-time dynamic linking, ứng dụng sẽ dùng một hàm riêng (LoadLibrary hoặc LoadLibraryEx) để nạp DLL lúc run time. Khi load lên thì ứng dụng cần phải dùng hàm GetProcAddress để xác định exported DLL function cần gọi. Bên dưới là ví dụ của việc nạp và import một DLL function trong một ứng dụng:

...
typedef VOID (*DLLPROC) (LPTSTR);
...
HINSTANCE hinstDLL;
DLLPROC HelloWorld;
BOOL fFreeDLL;
 
hinstDLL = LoadLibrary("sampleDLL.dll");
if (hinstDLL != NULL)
{
    HelloWorld = (DLLPROC) GetProcAddress(hinstDLL, "HelloWorld");
    if (HelloWorld != NULL)
        (HelloWorld);
    fFreeDLL = FreeLibrary(hinstDLL);
}
...

Khi DLL được nạp lên, nó sẽ được xem như là một dependency. Bởi vì một chương trình phụ thuộc vào một DLL nên các attacker có thể nhắm vào các DLL thay vì các ứng dụng để kiểm soát một phần nào đó quá trình thực thi. Một số loại tấn công:

Trong các đoạn mã độc, attacker sử dụng run-time dynamic linking nhiều hơn load-time dynamic linking. Lý do là vì một mã độc có thể cần phải trao đổi tập tin giữa nhiều vùng của bộ nhớ và việc trao đổi một file DLL duy nhất thì dễ kiểm soát hơn là import nhiều file.

Portable Executable Format

Định dạng PE (Portable Executable) là một định dạng tập tin cho các tập tin executable, object và DLL. Một tập tin có định dạng PE sẽ bao gồm một các header và section giúp dynamic linker biết cách map file vào bộ nhớ chính.

Định dạng PE gồm các thành phần sau:

DOS Header

Giúp định nghĩa loại tập tin. Giá trị DOS header nếu là MZ (0x5A4D) thì file đó có định dạng .exe.

DOS header trong hex dump:

Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000000  4D 5A 90 00 03 00 00 00 04 00 00 00 FF FF 00 00  MZ..........ÿÿ..
00000010  B8 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00  ¸.......@.......
00000020  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000030  00 00 00 00 00 00 00 00 00 00 00 00 E8 00 00 00  ............è...
00000040  0E 1F BA 0E 00 B4 09 CD 21 B8 01 4C CD 21 54 68  ..º..´.Í!¸.LÍ!Th

DOS Stub

Là một chương trình được chạy mặc định giúp in ra thông báo về tính tương thích (This program cannot be run in DOS mode). Chương trình này không làm ảnh hưởng đến các chức năng của file thực thi.

DOS stub trong hex dump:

00000040  0E 1F BA 0E 00 B4 09 CD 21 B8 01 4C CD 21 54 68  ..º..´.Í!¸.LÍ!Th
00000050  69 73 20 70 72 6F 67 72 61 6D 20 63 61 6E 6E 6F  is program canno
00000060  74 20 62 65 20 72 75 6E 20 69 6E 20 44 4F 53 20  t be run in DOS 
00000070  6D 6F 64 65 2E 0D 0D 0A 24 00 00 00 00 00 00 00  mode....$.......

NT Headers

File Header

Chứa các header cung cấp thông tin về file thực thi.

Chúng ta có thể tìm vị trí của file header ở chỗ có signature PE (0x50450000) trong hex dump như sau:

000000E0  00 00 00 00 00 00 00 00 50 45 00 00 64 86 06 00  ........PE..d†..
000000F0  10 C4 40 03 00 00 00 00 00 00 00 00 F0 00 22 00  .Ä@.........ð.".
00000100  0B 02 0E 14 00 0C 00 00 00 62 00 00 00 00 00 00  .........b......
00000110  70 18 00 00 00 10 00 00 00 00 00 40 01 00 00 00  p..........@....
00000120  00 10 00 00 00 02 00 00 0A 00 00 00 0A 00 00 00  ................
00000130  0A 00 00 00 00 00 00 00 00 B0 00 00 00 04 00 00  .........°......
00000140  63 41 01 00 02 00 60 C1 00 00 08 00 00 00 00 00  cA....`Á........
00000150  00 20 00 00 00 00 00 00 00 00 10 00 00 00 00 00  . ..............
00000160  00 10 00 00 00 00 00 00 00 00 00 00 10 00 00 00  ................
00000170  00 00 00 00 00 00 00 00 94 27 00 00 A0 00 00 00  ........”'.. ...
00000180  00 50 00 00 10 47 00 00 00 40 00 00 F0 00 00 00  .P...G...@..ð...
00000190  00 00 00 00 00 00 00 00 00 A0 00 00 2C 00 00 00  ......... ..,...
000001A0  20 23 00 00 54 00 00 00 00 00 00 00 00 00 00 00   #..T...........
000001B0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
000001C0  10 20 00 00 18 01 00 00 00 00 00 00 00 00 00 00  . ..............
000001D0  28 21 00 00 40 01 00 00 00 00 00 00 00 00 00 00  (!..@...........

Optional Header

Là một phần quan trọng của NT headers.

Data Dictionaries

Là một phần của optional header và chúng trỏ đến image data directory structure.

Section Table

Định nghĩa các section và thông tin khả dụng trong image. Các section chứa nội dung của file, bao gồm: code, các import và dữ liệu.

Có thể thấy từng section trong hex dump bên dưới:

000001F0  2E 74 65 78 74 00 00 00 D0 0B 00 00 00 10 00 00  .text...Ð.......
00000200  00 0C 00 00 00 04 00 00 00 00 00 00 00 00 00 00  ................
00000210  00 00 00 00 20 00 00 60 2E 72 64 61 74 61 00 00  .... ..`.rdata..
00000220  76 0C 00 00 00 20 00 00 00 0E 00 00 00 10 00 00  v.... ..........
00000230  00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 40  ............@..@
00000240  2E 64 61 74 61 00 00 00 B8 06 00 00 00 30 00 00  .data...¸....0..
00000250  00 02 00 00 00 1E 00 00 00 00 00 00 00 00 00 00  ................
00000260  00 00 00 00 40 00 00 C0 2E 70 64 61 74 61 00 00  ....@..À.pdata..
00000270  F0 00 00 00 00 40 00 00 00 02 00 00 00 20 00 00  ð....@....... ..
00000280  00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 40  ............@..@
00000290  2E 72 73 72 63 00 00 00 10 47 00 00 00 50 00 00  .rsrc....G...P..
000002A0  00 48 00 00 00 22 00 00 00 00 00 00 00 00 00 00  .H..."..........
000002B0  00 00 00 00 40 00 00 40 2E 72 65 6C 6F 63 00 00  ....@..@.reloc..
000002C0  2C 00 00 00 00 A0 00 00 00 02 00 00 00 6A 00 00  ,.... .......j..
000002D0  00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 42  ............@..B

Thông tin về một vài section:

SectionPurpose
.textContains executable code and entry point
.dataContains initialized data (strings, variables, etc.)
.rdata or .idataContains imports (Windows API) and DLLs.
.relocContains relocation information
.rsrcContains application resources (images, etc.)
.debugContains debug information

Minh họa cho định dạng PE:

Seealso

Chi tiết về định dạng PE: TryHackMe - Dissecting PE Headers

Interacting with Windows Internals

Cách dễ nhất để tương tác với hệ điều hành Windows là thông qua Windows API, bao gồm Win32 API và Win64 API.

Đa số các thành phần nội bộ bên trong Windows đều cần tương tác với phần cứng thông qua kernel. Một ứng dụng thông thường không thể tương tác với kernel mà cần sử dụng các processor mode (gồm user mode và kernel mode) và các access level. Việc chuyển đổi giữa hai chế độ thường được hỗ trợ bởi Windows API.

Bảng so sánh hai chế độ:

User modeKernel Mode
No direct hardware accessDirect hardware access
Creates a process in a private virtual address spaceRan in a single shared virtual address space
Access to “owned memory locations”Access to entire physical memory

Một ứng dụng chạy với user mode sẽ chuyển sang kernel mode khi có system call (syscall) được gọi từ ứng dụng đó.

Sau đây là các bước dùng để inject một message box (popup window) vào một process:

  • Cấp phát vùng nhớ cục bộ trong tiến trình cho message box.
  • Ghi/sao chép message box đến vùng nhớ đã được cấp phát.
  • Thực thi message box từ bộ nhớ cục bộ của tiến trình.

Ở bước một, chúng ta có thể sử dụng hàm OpenProcess để mở một handle với tiến trình cần inject message box:

HANDLE hProcess = OpenProcess(
	PROCESS_ALL_ACCESS, // Defines access rights
	FALSE, // Target handle will not be inhereted
	DWORD(atoi(argv[1])) // Local process supplied by command-line arguments 
);

Ở bước hai, chúng ta dùng VirtualAllocEx để cấp phát một vùng nhớ với payload buffer:

remoteBuffer = VirtualAllocEx(
	hProcess, // Opened target process
	NULL, 
	sizeof payload, // Region size of memory allocation
	(MEM_RESERVE | MEM_COMMIT), // Reserves and commits pages
	PAGE_EXECUTE_READWRITE // Enables execution and read/write access to the commited pages
);

Ở bước ba, dùng hàm WriteProcessMemory để ghi payload vào vùng nhớ đã được cấp phát:

WriteProcessMemory(
	hProcess, // Opened target process
	remoteBuffer, // Allocated memory region
	payload, // Data to write
	sizeof payload, // byte size of data
	NULL
);

Cuối cùng, ta dùng hàm CreateRemoteThread để thực thi payload:

remoteThread = CreateRemoteThread(
	hProcess, // Opened target process
	NULL, 
	0, // Default size of the stack
	(LPTHREAD_START_ROUTINE)remoteBuffer, // Pointer to the starting address of the thread
	NULL, 
	0, // Ran immediately after creation
	NULL
); 
list
from outgoing([[TryHackMe - Windows Internals]])
sort file.ctime asc

Resources