標籤:

C++ 中對 main 函數的地址賦值會怎樣?

考慮如下代碼:

int main()

{

*(int*)main = 0; // or whatever

}

這樣寫會發生什麼,是未定義行為嗎?


以下引用均來自 n4700

首先,不能在你的程序中使用 main 函數

6.6.1 (3)

The function main shall not be used within a program.

其次,標準不保證一定可以將函數指針轉換到對象指針,void*也是

8.2.10 (8) Converting a function pointer to an object pointer type or vice versa is conditionally-supported.

再其次,函數是不可修改的

6.10 (7) An lvalue is modifiable unless its type is const-qualified or is a function type.

再再其次,強制通過不符合要求的類型來訪問其他類型也是ub

6.10 (8) If a program attempts to access the stored value of an object through a glvalue of other than one of the following types the behavior is undefined:

(8.1) the dynamic type of the object,

(8.2) a cv-qualified version of the dynamic type of the object,

(8.3) a type similar (as defined in 7.5) to the dynamic type of the object,

(8.4) a type that is the signed or unsigned type corresponding to the dynamic type of the object,

(8.5) a type that is the signed or unsigned type corresponding to a cv-qualified version of the dynamic type of the object,

(8.6) an aggregate or union type that includes one of the aforementioned types among its elements or non-static data members (including, recursively, an element or non-static data member of a sub aggregate or contained union),

(8.7) a type that is a (possibly cv-qualified) base class type of the dynamic type of the object,

(8.8) a char,unsigned char, or std::byte type.

題主可以數數違反了多少條【


是UB


好像有編譯器選項讓進入點變成別的函數

這樣說不定就可以達到沒有關係的狀況

不過這是一個奇怪的手段

嵌入式設備還沒有完善系統時會用到的樣子


限定工具和平台的話,行為就會變成確定的(逃


一般來說代碼段是只讀的,試圖往裡面寫東西程序就直接崩了


按題目中這麼寫。

行為挺確定的, 試圖寫程序段。

或者更準確地說試圖寫入沒有寫許可權地址,比如該頁不可寫。

在類 Unix 系統上發出個錯誤信號,比如 SIGSTOP。

然後程序就 dump 掉了。

--------------------------- 分割線

注意,為避免誤導,以下代碼不是標準,它只是小心翼翼地生成了特定目標代碼。編譯器還是 linux 64-bit / gcc

如果你只是想對 main 賦值,看會發生什麼。

那麼不用定義 main 函數了,直接賦值,比如

const int main[] = {
-443987883, 440, 113408, -1922629632,
4149, 899584, 84869120, 15544,
266023168, 1818576901, 1461743468, 1684828783,
-1017312735
};

編譯成功並輸出 hello world。Linux 系統。

也許這篇文章可以幫到你

Main is usually a function. So then when is it not?


題主自己跑一下不就知道了?

Ps:個人覺得,你寫代碼的地址已經被執行掉了(除非你一次寫大量,追上已經執行部分),所以什麼都不會發生。


code段不能直接賦值


你這個代碼是個UB。

不過,你這個代碼讓我想到了以前木馬、外掛常常使用的HOOK技術。實際上通過函數地址直接寫入數據的代碼是真的存在。但這種技術不屬於C和C++的範疇了。

舉個簡單的例子,在WINDOWS+x86平台下

比如有一個函數

void hello() {

printf("hello
");

}

那麼我們通過一段代碼把hello函數體第一條指令改成RET(機器碼是C3)

*(uint8_t*)hello = 0xc3;

然後去調用hello的時候就直接返回了……

更常見的是把原函數第一條指令改成JMP指令,然後跳轉到自己的函數里進行某些操作,最後再恢復原函數的頭部,重新調用原函數。

還有一點要注意的是,CODE段是不能直接寫入數據的,在WINDOWS下好像有某個函數可以解除這個限制,記不清了……


發生段錯誤

Start
Segmentation fault
Finish


禁止修鍊C++黑魔法。學語言的目的是解決問題,不是讓你變成語法律師。


程序已停止工作


可以inline hook實現代碼注入(逃

------------------看沒什麼人說這個,那我就扯一下吧。。。-----------------------

的確,這是UB,但如 @vczh 所說,在特定平台下就不是了,那來看看在windows32位的平台下,會(gai)怎(zen)么(me)樣(wan)。

#include &
#include &
#include &
#define JMP_OPCODE 0xe9
#define JMP_OPCODE_LEN 1
#define SIZE_OF_JMP 5
int main();
int hookmain();
class HookTheMain
{
public:
HookTheMain()
{//全局對象的構造函數會於main之前被調用
DWORD oldProtect;
VirtualProtect(main, SIZE_OF_JMP, PAGE_EXECUTE_READWRITE, oldProtect);
*(BYTE*)main = JMP_OPCODE;
*(DWORD*)((BYTE*)main + JMP_OPCODE_LEN) =
((BYTE*)hookmain - (BYTE*)main - SIZE_OF_JMP);
VirtualProtect(main, SIZE_OF_JMP, oldProtect, oldProtect);
//隨手寫的,比較丑。。。也沒做什麼錯誤判斷。。。
}
};
HookTheMain h;
int main()
{
printf("This code will not be executed
");
system("pause");
return 0;
}
int hookmain()
{
printf("This code will be executed instead
");
system("pause");
return 0;
}

這裡就要說下彙編語言了,jmp是一條跳轉指令,以0xE8打頭,後面跟一個小端的32位偏移,會跳轉到 jmp指令的地址+偏移數值+jmp指令的大小也就是5 的位置。

如果把main函數的前5個位元組寫成跳轉到另一個函數的jmp,代碼裡面我通過(BYTE*)hookmain - (BYTE*)main - SIZE_OF_JMP算出hookmain函數的偏移,這樣程序執行到main的時候就會跳轉,而不是執行之前的main。

調用構造函數前的main

調用構造函數後的main

會跳轉到這裡,即hookmain

因為代碼是不可寫的(像題主那樣直接寫會爆寫入異常),所以要先VirtualProtect(注意這不是C的標準,是windows的API),再寫。

還有就是C++全局類的構造函數執行會在main之前。。不然執行到main再改變那5個位元組就沒什麼用了。。。

在32位windows以上代碼運行結果如下:

可見,hookmain被調用了,而不是main

寫這個的目的是,給題主科普,這個除了是UB以外,還可以這麼玩,當然也歡迎題主進入二進位安全的大坑(霧


推薦閱讀:

關於2048局面的價值判斷及ai思路?
為什麼5%的CPU佔用會造成這麼大的性能損失?
為什麼工控還在用c?
C++中左值、右值與寄存器的關係是怎樣的?
LOL盒子這類的輔助工具一般都是用什麼開發的?

TAG:C編程語言 | C | CC |