早安午安晚安,我是 CX330。我們昨天看了 PE 文件的格式,把整個 PE 文件的架構都介紹了一遍。今明兩天呢,我們要來看一下前幾天介紹過的 Shellcode 會以什麼樣的形式存在在程式碼裡面,會經過什麼樣的加密或是混淆等。
這個主題會分成上下兩篇,礙於篇幅,會把加密跟混淆的部分散佈在兩篇。由於不是密碼學相關的主題,不會深入加密的算法,主要是會介紹很多種加密和混淆的手法,因此範例程式碼會偏多。那就讓我們開始吧!
中華民國刑法第 362 條:「製作專供犯本章之罪之電腦程式,而供自己或他人犯本章之罪,致生損害於公眾或他人者,處五年以下有期徒刑、拘役或科或併科六十萬元以下罰金。」
本系列文章涉及多種惡意程式的技術,旨在提升個人技術能力與資安意識。本人在此強烈呼籲讀者,切勿使用所學到的知識與技術從事任何違法行為!
本系列文章中使用的 Zig 版本號為 0.14.1。
XOR 是最簡單的加密方式,Payload 的每一個字節都會與一個 Key 進行 XOR 運算。解密也是使用相同的那把 Key。雖然如果知道了 Key 就可以很輕鬆的還原,但是 XOR 加密仍然可以把明文 Payload 隱藏於基本的檔案掃描中。
我們先來看一下程式碼。
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)
一樣先看程式碼。
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 更難被破解。
在 Windows 中,有兩個函數可以幫助我們簡單的做 RC4 的加密,不過由於這兩個函數都是未被文檔所記錄的,所以需要去翻一下 ReactOS 所逆向出來的的函數定義。這兩個函數分別是 SystemFunction032
和 SystemFunction033
。
這兩個函數是在 advapi32.dll
被匯出的,我們來把它丟進去 Binary Ninja 逆向看看。會發現它們兩個函數指向 cryptsp.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: []u8
和 payloadData: []u8
並會回傳一個布林值。由於 LoadLibraryW
是接受 Wide string,所以我們要先用 utf8ToUtf16LeStringLiteral
把原本的 "Advapi32"
轉為寬字串。接著再用 LoadLibraryW
去把 advapi.dll
給載入當前進程。接著再去用 GetProcAddress
去把 SystemFunction032
給抓出來,最後再用 @ptrCast
把那個地址的函數給轉成指針並賦值給 SystemFunction032
這個函數指針去做加密。
最後想要再次強調一下,這個程式碼中的 SystemFunction032
轉換成 SystemFunction033
也可以。
IP 地址混淆是會把 Payload 在程式碼中寫成 IP 地址的形式,如此一來,可以規避掉一些比較基礎的 AV/EDR 的靜態掃描。
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 語法,執行完的會像這樣。
而主要的 generateIpv4
函數也只是把它輸入的十六進制轉為十進制而已。
要解混淆也超級簡單,可以直接使用 ntdll.dll
的 RtlIpv4StringToAddressA
函數,這個函數就會把 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", .{});
}
和 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
>
2
之後的 generateIpv6Output
也只是用來格式化輸出而已,大家有興趣可以自己研究一下程式碼。
解混淆也是差不多,只不過這次要調用的是 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", .{});
}
好啦,那這就是今天的內容。明天我們將會繼續看更多的加密和混淆的技術,然後後天會帶大家一起實作一個小小的專案,用來幫助日後的惡意程式開發。是說寫著寫著也已經快過完三分之一了,加油加油繼續努力!
如果對惡意程式開發或是惡意程式分析有興趣的話,這個系列會很適合你!最後也感謝大家的閱讀,歡迎順手按讚留言訂閱轉發(轉發可以讓朋友們知道你都在讀這種很技術的文章,他們會覺得你好帥好強好電,然後開始裝弱互相吹捧)~明天見!