這篇文章主要簡單分析了linux下systemlinux,具有一定的參考價值,感興趣的小伙伴們可以參考一下
簡單分析了linux下system函數的相關內容,具體內容如下
int libc_system?(const?char?*line) { ?if?(line?==?NULL) ??/*?Check?that?we?have?a?command?processor?available.?It?might ????not?be?available?after?a?chroot(),?for?example.?*/ ??return?do_system?("exit?0")?==?0; ?return?do_system?(line); } weak_alias?(libc_system,?system)
代碼位于glibc/sysdeps/posix/system.c,這里system是libc_system的弱別名,而libc_system是do_system的前端函數,進行了參數的檢查,接下來看do_system函數。
static?int do_system?(const?char?*line) { ?int?status,?save; ?pid_t?pid; ?struct?sigaction?sa; #ifndef?_LIBC_REENTRANT ?struct?sigaction?intr,?quit; #endif ?sigset_t?omask; ?sa.sa_handler?=?SIG_IGN; ?sa.sa_flags?=?0; ?sigemptyset?(&sa.sa_mask); ?DO_LOCK?(); ?if?(ADD_REF?()?==?0) ??{ ???if?(sigaction?(SIGINT,?&sa,?&intr)?<p>首先函數設置了一些信號處理程序,來處理SIGINT和SIGQUIT信號,此處我們不過多關心,關鍵代碼段在這里</p><pre class="brush:bash;">#ifdef?FORK ?pid?=?FORK?(); #else ?pid?=?fork?(); #endif ?if?(pid?==?(pid_t)?0) ??{ ???/*?Child?side.?*/ ???const?char?*new_argv[4]; ???new_argv[0]?=?SHELL_NAME; ???new_argv[1]?=?"-c"; ???new_argv[2]?=?line; ???new_argv[3]?=?NULL; ???/*?Restore?the?signals.?*/ ???(void)?sigaction?(SIGINT,?&intr,?(struct?sigaction?*)?NULL); ???(void)?sigaction?(SIGQUIT,?&quit,?(struct?sigaction?*)?NULL); ???(void)?sigprocmask?(SIG_SETMASK,?&omask,?(sigset_t?*)?NULL); ???INIT_LOCK?(); ???/*?Exec?the?shell.?*/ ???(void)?execve?(SHELL_PATH,?(char?*const?*)?new_argv,?environ); ???_exit?(127); ??} ?else?if?(pid?<p>首先通過前端函數調用系統調用fork產生一個子進程,其中fork有兩個返回值,對父進程返回子進程的pid,對子進程返回0。所以子進程執行6-24行代碼,父進程執行30-35行代碼。</p><p>子進程的邏輯非常清晰,調用execve執行SHELL_PATH指定的程序,參數通過new_argv傳遞,環境<a href="http://www.php.cn/wiki/1497.html" target="_blank">linux</a>為全局變量environ。</p><p>其中SHELL_PATH和SHELL_NAME定義如下</p><pre class="brush:bash;">#define??SHELL_PATH??"/bin/sh"??/*?Path?of?the?shell.?*/ #define??SHELL_NAME??"sh"????/*?Name?to?give?it.?*/
其實就是生成一個子進程調用/bin/sh -c “命令”來執行向system傳入的命令。?
下面其實是我研究system函數的原因與重點:
在CTF的pwn題中,通過棧溢出調用system函數有時會失敗,聽師傅們說是環境變量被覆蓋,但是一直都是懵懂,今天深入學習了一下,總算搞明白了。
在這里system函數需要的環境變量儲存在全局變量environ中,那么這個變量的內容是什么呢。
environ是在glibc/csu/libc-start.c中定義的,我們來看幾個關鍵語句。
#?define?LIBC_START_MAIN?libc_start_main
libc_start_main是_start調用的函數,這涉及到程序開始時的一些初始化工作,對這些名詞不了解的話可以看一下這篇文章。接下來看LIBC_START_MAIN函數。
STATIC?int LIBC_START_MAIN?(int?(*main)?(int,?char?**,?char?**?MAIN_AUXVEC_DECL), ?????int?argc,?char?**argv, #ifdef?LIBC_START_MAIN_AUXVEC_ARG ?????ElfW(auxv_t)?*auxvec, #endif ?????typeof?(main)?init, ?????void?(*fini)?(void), ?????void?(*rtld_fini)?(void),?void?*stack_end) { ?/*?Result?of?the?'main'?function.?*/ ?int?result; ?libc_multiple_libcs?=?&_dl_starting_up?&&?!_dl_starting_up; #ifndef?SHARED ?char?**ev?=?&argv[argc?+?1]; ?environ?=?ev; ?/*?Store?the?lowest?stack?address.?This?is?done?in?ld.so?if?this?is ???the?code?for?the?DSO.?*/ ?libc_stack_end?=?stack_end; ...... ?/*?Nothing?fancy,?just?call?the?function.?*/ ?result?=?main?(argc,?argv,?environ?MAIN_AUXVEC_PARAM); #endif ?exit?(result); }
我們可以看到,在沒有define SHARED的情況下,在第19行定義了environ的值。啟動程序調用LIBC_START_MAIN之前,會先將環境變量和argv中的linux保存起來(其實是保存到棧上),然后依次將環境變量中各項字符串的地址,argv中各項字符串的地址和argc入棧,所以環境變量linux一定位于argv數組的正后方,以一個空地址間隔。所以第17行的&argv[argc + 1]語句就是取環境變量數組在棧上的首地址,保存到ev中,最終保存到environ中。第203行調用main函數,會將environ的值入棧,這個被棧溢出覆蓋掉沒什么問題,只要保證environ中的地址處不被覆蓋即可。
所以,當棧溢出的長度過大,溢出的內容覆蓋了environ中地址中的重要內容時,調用system函數就會失敗。具體環境變量距離溢出地址有多遠,可以通過在_start中下斷查看。