- C和C++安全編碼(原書第2版)
- (美)Robert C.Seacord
- 721字
- 2020-10-30 17:56:50
3.8 atexit()和on_exit()函數
atexit()是C標準定義的一個通用工具函數。atexit()可以注冊無參函數,并在程序正常結束后調用該函數。C要求實現支持至少32個函數的注冊。SunOS上的on_exit()函數具有類似的功能。libc4、libc5和glibc也提供了這樣的函數[Bouchareine 2005]。
在例3.11展示的程序中,第8行(main()中)使用atexit()注冊test()函數。該程序在退出前將全局變量glob賦值為字符串“Exiting.\n”(第9行)。test()函數在程序退出后得以執行,并且打印出該字符串。
例3.11 使用atexit()的程序
01 char *glob; 02 03 void test(void) { 04 printf("%s", glob); 05 } 06 07 int main(void) { 08 atexit(test); 09 glob = "Exiting.\n"; 10 }
atexit()通過向一個退出時將被調用的已有函數的數組中添加指定的函數完成工作。當exit()被調用時,數組中的每一個函數都以“后進先出”(Last-in,First-out,LIFO)的順序被調用。由于atexit()和exit()都要訪問該數組,因此它被分配為一個全局性的符號(在BSD操作系統中是__atexit,在Linux操作系統中則是__exit_funcs)。
例3.12中使用gdb調試atexit程序的會話過程,展示了atexit數組的位置和結構。在該調試會話中,在main()中調用atexit()之前設了一個斷點,然后運行程序。接下來執行atexit(),注冊test()函數。在test()函數注冊后,顯示了在__exit_funcs位置處的內存。每一個函數都保存在由4個雙字(doubleword)構成的結構中。每一個結構的最后一個雙字保存著函數的實際地址。通過檢查這些地址的內存得知,已經注冊了3個函數:
_dl_fini()、__libc_csu_fini()以及我們編寫的test()。可以通過對__exit_funcs結構采用任意內存寫或緩沖區溢出手段將程序的控制權轉移到任意的代碼。請注意,即使受攻擊的程序不顯式調用atexit()注冊_dl_fini()和__libc_csu_fini()函數,它們也會存在。
例3.12 使用gdb的atexit程序的調試會話
(gdb) b main Breakpoint 1 at 0x80483f6: file atexit.c, line 6. (gdb) r Starting program: /home/rcs/book/dtors/atexit Breakpoint 1, main (argc=1, argv=0xbfffe744) at atexit.c:6 6 atexit(test); (gdb) next 7 glob = "Exiting.\n"; (gdb) x/12x __exit_funcs 0x42130ee0 <init>: 0x00000000 0x00000003 0x00000004 0x4000c660 0x42130ef0 <init+16>: 0x00000000 0x00000000 0x00000004 0x0804844c 0x42130f00 <init+32>: 0x00000000 0x00000000 0x00000004 0x080483c8 (gdb) x/4x 0x4000c660 0x4000c660 <_dl_fini>: 0x57e58955 0x5ce85356 0x81000054 0x0091c1c3 (gdb) x/3x 0x0804844c 0x804844c <__libc_csu_fini>: 0x53e58955 0x9510b850 x102d0804 (gdb) x/8x 0x080483c8 0x80483c8 <test>: 0x83e58955 0xec8308ec 0x2035ff08 0x68080496