Windows API

Windows Data Types

  1. DWORD là số nguyên không dấu 32-bit (), dùng trên cả hệ thống 32-bit và 64-bit.

    DWORD dwVariable = 42;

    DWORD thực chất là alias của unsigned long:

    typedef unsigned long DWORD;
  2. SIZE_T biểu diễn kích thước của một đối tượng. Trên hệ thống 32-bit, nó là số nguyên không dấu 32-bit (). Trên hệ thống 64-bit, nó là số nguyên không dấu 64-bit ().

    SIZE_T sVariable = sizeof(int);

    SIZE_T thực chất là alias của ULONG_PTR:

    typedef ULONG_PTR SIZE_T;
  3. ULONG_PTR là số nguyên không dấu có kích thước bằng kích thước của con trỏ trên kiến trúc hiện tại. ULONG_PTR được định nghĩa như sau:

    #if defined(_WIN64)
     typedef unsigned __int64 ULONG_PTR;
    #else
     typedef unsigned long ULONG_PTR;
    #endif

    Trong suốt khóa học về lập trình malware này, chúng ta sẽ ép kiểu con trỏ về ULONG_PTR trước khi thực hiện các thao tác toán học:

    PVOID Pointer = malloc(100);
    // Pointer = Pointer + 10; // not allowed
    Pointer = (ULONG_PTR)Pointer + 10; // allowed
  4. PVOID là con trỏ đến bất kỳ kiểu dữ liệu nào, có kích thước 4 byte trên hệ thống 32-bit và 8 byte trên hệ thống 64-bit.

    PVOID pVariable = &SomeData;

    PVOID thực chất là alias của void*:

    typedef void* PVOID;
  5. HANDLE là giá trị đại diện cho một đối tượng mà hệ điều hành quản lý chẳng hạn như tập tin, tiến trình hoặc luồng.

    HANDLE hFile = CreateFile(...);

    HANDLE thường là một PVOID:

    typedef PVOID HANDLE;
  6. HMODULE là handle của một module (có thể là file DLL hoặc file EXE), thường là base address của module.

    HMODULE hModule = GetModuleHandle(...);

    HMODULE thường là một HANDLE:

    typedef HANDLE HINSTANCE;
    typedef HINSTANCE HMODULE;
  7. LPCSTR/PCSTR là con trỏ đến một chuỗi hằng bao gồm các ký tự Windows 8-bit (hay còn gọi là ANSI) và kết thúc bằng ký tự null (0x00). Chữ L là viết tắt của “long” và không ảnh hưởng đến kiểu dữ liệu. Trong khi đó, chữ C cho biết đây là con trỏ đến chuỗi hằng (là chuỗi mà không thể bị thay đổi giá trị - chỉ đọc).

    LPCSTR  lpcString   = "Hello, world!";
    PCSTR   pcString    = "Hello, world!";

    Cả hai kiểu dữ liệu này đều tương đương với const char*.

  8. LPSTR/PSTR tương tự với LPCSTR/PCSTR nhưng có quyền thay đổi giá trị (đọc và ghi).

    LPSTR   lpString    = "Hello, world!";
    PSTR    pString     = "Hello, world!";

    Cả hai kiểu dữ liệu này đều tương đương với char*.

  9. LPCWSTR\PCWSTR là con trỏ đến một chuỗi hằng bao gồm các ký tự Windows 16-bit (hay còn gọi là Unicode) và kết thúc bằng ký tự null (0x00).

    LPCWSTR     lpwcString  = L"Hello, world!";
    PCWSTR      pcwString   = L"Hello, world!";

    Cả hai kiểu dữ liệu này đều tương đương với const wchar*.

  10. PWSTR\LPWSTR tương tự với LPCWSTR\PCWSTR nhưng có quyền thay đổi giá trị.

    LPWSTR  lpwString   = L"Hello, world!";
    PWSTR   pwString    = L"Hello, world!";

    Cả hai kiểu dữ liệu này đều tương đương với wchar*.

  11. wchar_t tương tự với wchar, được dùng để biểu diễn các ký tự Unicode (wide characters).

    wchar_t     wChar           = L'A';
    wchar_t*    wcString        = L"Hello, world!";

Data Types Pointers

Các kiểu dữ liệu có chữ P đằng trước đều là con trỏ. Ví dụ:

  • PHANDLE is the same as HANDLE*.
  • PSIZE_T is the same as SIZE_T*.
  • PDWORD is the same as DWORD*.

ANSI & Unicode Functions

Đa số các hàm của Windows API có chữ A và chữ W ở cuối. Chữ A là viết tắt của ANSI còn chữ W là viết tắt của Wide Characters (Unicode). Sự khác biệt giữa các hàm ANSI và các hàm Unicode là các hàm ANSI nhận đối số là các chuỗi ANSI còn các hàm Unicode nhận đối số là các chuỗi Unicode.

In and Out Parameters

Windows API có các tham số với từ khóa INOUT. Ví dụ về từ khóa OUT:

BOOL HackTheWorld(OUT int* num){
 
    // Setting the value of num to 123
    *num = 123;
    
    // Returning a boolean value
    return TRUE;
}
 
int main(){
    int a = 0;
 
    // 'HackTheWorld' will return true
    // 'a' will contain the value 123
    HackTheWorld(&a);
}

Tham số có kiểu OUT giúp lập trình viên biết được rằng giá trị của đối số sẽ bị thay đổi. Việc không dùng từ khóa OUT không làm thay đổi chức năng của hàm.

Windows API Example

Chúng ta sẽ lấy hàm CreateFileW làm ví dụ về cách sử dụng Windows API. Ta sẽ làm theo các bước sau:

Đọc tài liệu: nếu không rõ về các option của hàm cũng như là chức năng của nó thì nên đọc tài liệu. Tài liệu về hàm CreateFileW: CreateFileW function (fileapi.h) - Win32 apps | Microsoft Learn.

Phân tích kiểu trả về và các tham số: theo tài liệu, nếu hàm CreateFileW được thực hiện thành công thì nó sẽ trả về một HANDLE đến tập tin, thiết bị, named pipe hoặc mail slot. Ngoài ra, tất cả các tham số đều có kiểu là IN.

HANDLE CreateFileW(
  [in]           LPCWSTR               lpFileName,
  [in]           DWORD                 dwDesiredAccess,
  [in]           DWORD                 dwShareMode,
  [in, optional] LPSECURITY_ATTRIBUTES lpSecurityAttributes,
  [in]           DWORD                 dwCreationDisposition,
  [in]           DWORD                 dwFlagsAndAttributes,
  [in, optional] HANDLE                hTemplateFile
);

Chú ý rằng các từ khóa ở trong ngoặc vuông là không bắt buộc.

Sử dụng:

// This is needed to store the handle to the file object
// the 'INVALID_HANDLE_VALUE' is just to intialize the variable
Handle hFile = INVALID_HANDLE_VALUE; 
 
// The full path of the file to create.
// Double backslashes are required to escape the single backslash character in C
LPCWSTR filePath = L"C:\\Users\\maldevacademy\\Desktop\\maldev.txt";
 
// Call CreateFileW with the file path
// The additional parameters are directly from the documentation
hFile = CreateFileW(filePath, GENERIC_ALL, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
 
// On failure CreateFileW returns INVALID_HANDLE_VALUE
// GetLastError() is another Windows API that retrieves the error code of the previously executed WinAPI function
if (hFile == INVALID_HANDLE_VALUE){
    printf("[-] CreateFileW Api Function Failed With Error : %d\n", GetLastError());
    return -1;
}

Windows API Debugging Errors

Khi một hàm thực thi thất bại thì nó thường trả về những lỗi không rõ ràng. Ví dụ, nếu CreateFileW có lỗi thì nó sẽ trả về INVALID_HANDLE_VALUE nhằm cho biết rằng file không thể được tạo ra. Để biết được chi tiết lý do tại sao file không được tạo ra, ta có thể dùng hàm GetLastError.

Giá trị trả về của GetLastError có thể được tra cứu ở trong System Error Codes (0-499) (WinError.h) - Win32 apps | Microsoft Learn. Một vài mã lỗi phổ biến:

  • 5 - ERROR_ACCESS_DENIED
  • 2 - ERROR_FILE_NOT_FOUND
  • 87 - ERROR_INVALID_PARAMETER

Windows Native API Debugging Errors

Các hàm thuộc Native API trả về mã lỗi có kiểu là NTSTATUS. Kiểu này là thực chất là một số nguyên không dấu 32-bit và được dùng để biểu diễn trạng thái của một system call hoặc một hàm. Một lời gọi hàm thành công sẽ có giá trị là STATUS_SUCCESS, tương đương với 0. Danh sách các giá trị của NTSTATUS: [MS-ERREF]: NTSTATUS Values | Microsoft Learn.

Đoạn code sau minh họa cách kiểm tra mã lỗi của các system call:

NTSTATUS STATUS = NativeSyscallExample(...);
if (STATUS != STATUS_SUCCESS){
    // printing the error in unsigned integer hexadecimal format
    printf("[!] NativeSyscallExample Failed With Status : 0x%0.8X \n", STATUS); 
}
 
// NativeSyscallExample succeeded

NT_SUCCESS Macro

Một cách khác để kiểm tra giá trị trả về của các Native API là sử dụng macro NT_SUCCESS. Macro này sẽ trả về TRUE nếu hàm được gọi thành công và FALSE nếu ngược lại.

Ví dụ sử dụng macro:

NTSTATUS STATUS = NativeSyscallExample(...);
if (!NT_SUCCESS(STATUS)){
    // printing the error in unsigned integer hexadecimal format
    printf("[!] NativeSyscallExample Failed With Status : 0x%0.8X \n", STATUS); 
}
 
// NativeSyscallExample succeeded
list
from outgoing([[MalDev - Introduction to Windows API]])
sort file.ctime asc

Resources