在托管的 C# 世界裡,unsafe 關鍵字打開了一扇通往底層內存的大門。本文通過兩個極簡實驗,演示如何繞過 CLR 邊界檢查,直接讀寫棧內存,修改局部變量、甚至竄改方法返回值——純教學目的,展示托管語言的底層本質,切勿用於生產代碼。
方法調用時,棧上依次分配:
stackalloc 在棧上分配非托管內存塊,返回指針。CLR 不檢查指針越界——這就是突破口。
static unsafe void Main() {
int* p = stackalloc int[1]; // 僅分配 1 個 int (4 字節)
for (var i = 0; i < 30; i++) {
Thread.Sleep(200);
Console.WriteLine(i);
p[i] = 0; // 故意越界寫入
}
Console.ReadLine();
}
現象:循環變為無限循環,輸出 0,1,2,3,4,5,0,1,2,3,4,5...
原理:
p 指向棧頂附近的 4 字節區域i 緊鄰其上(高地址方向)p[6] 正好覆蓋 i 的內存位置(視編譯器佈局而定,實測 i=6 時被覆蓋)p[i] = 0 等價於 i = 0,循環重置
static unsafe void Main() {
Console.WriteLine(ChangeResult()); // 輸出 10000,而非 123
Console.ReadLine();
}
static unsafe int ChangeResult() {
int result = 123; // 預期返回值
int* p = stackalloc int[1];
int i = 0;
// 向高地址掃描,尋找值為 123 的內存單元
while (true) {
if (p[++i] == 123) {
p[i] = 10000; // 覆蓋為新值
break;
}
}
return result; // 實際讀取已被修改的內存
}
關鍵點:
result 分配在棧上result 的內存地址return result 讀取的是已被篡改的值棧幀中還保存著返回地址(指向調用者下一條指令)。若能精確定位並修改它,可實現:
現代防禦機制使此極難利用:
unsafe 區塊,嚴格把關stackalloc、指針運算C# 的 unsafe 不是漏洞,是受控的危險能力。理解棧緩衝區溢出原理,能讓你更深刻地體會托管語言的安全邊界、編譯器優化行為、以及為什麼現代 OS/運行時要層層設防。知攻擊,更懂防禦;懂底層,寫代碼才心中有數。