如何在linux中使用system函数
今天就跟大家聊聊有关如何在linux中使用system函数,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。
具体内容如下
int __libc_system(constchar*line) { if(line==NULL) /*Checkthatwehaveacommandprocessoravailable.Itmight notbeavailableafterachroot(),forexample.*/ returndo_system("exit0")==0; returndo_system(line); } weak_alias(__libc_system,system)
代码位于glibc/sysdeps/posix/system.c,这里system是__libc_system的弱别名,而__libc_system是do_system的前端函数,进行了参数的检查,接下来看do_system函数。
staticint do_system(constchar*line) { intstatus,save; pid_tpid; structsigactionsa; #ifndef_LIBC_REENTRANT structsigactionintr,quit; #endif sigset_tomask; sa.sa_handler=SIG_IGN; sa.sa_flags=0; __sigemptyset(&sa.sa_mask); DO_LOCK(); if(ADD_REF()==0) { if(__sigaction(SIGINT,&sa,&intr)<0) { (void)SUB_REF(); gotoout; } if(__sigaction(SIGQUIT,&sa,&quit)<0) { save=errno; (void)SUB_REF(); gotoout_restore_sigint; } } DO_UNLOCK(); /*Wereusethebitmapinthe'sa'structure.*/ __sigaddset(&sa.sa_mask,SIGCHLD); save=errno; if(__sigprocmask(SIG_BLOCK,&sa.sa_mask,&omask)<0) { #ifndef_LIBC if(errno==ENOSYS) __set_errno(save); else #endif { DO_LOCK(); if(SUB_REF()==0) { save=errno; (void)__sigaction(SIGQUIT,&quit,(structsigaction*)NULL); out_restore_sigint: (void)__sigaction(SIGINT,&intr,(structsigaction*)NULL); __set_errno(save); } out: DO_UNLOCK(); return-1; } } #ifdefCLEANUP_HANDLER CLEANUP_HANDLER; #endif #ifdefFORK pid=FORK(); #else pid=__fork(); #endif if(pid==(pid_t)0) { /*Childside.*/ constchar*new_argv[4]; new_argv[0]=SHELL_NAME; new_argv[1]="-c"; new_argv[2]=line; new_argv[3]=NULL; /*Restorethesignals.*/ (void)__sigaction(SIGINT,&intr,(structsigaction*)NULL); (void)__sigaction(SIGQUIT,&quit,(structsigaction*)NULL); (void)__sigprocmask(SIG_SETMASK,&omask,(sigset_t*)NULL); INIT_LOCK(); /*Exectheshell.*/ (void)__execve(SHELL_PATH,(char*const*)new_argv,__environ); _exit(127); } elseif(pid<(pid_t)0) /*Theforkfailed.*/ status=-1; else /*Parentside.*/ { /*Notethesystem()isacancellationpoint.Butsincewecall waitpid()whichitselfisacancellationpointwedonot havetodoanythinghere.*/ if(TEMP_FAILURE_RETRY(__waitpid(pid,&status,0))!=pid) status=-1; } #ifdefCLEANUP_HANDLER CLEANUP_RESET; #endif save=errno; DO_LOCK(); if((SUB_REF()==0 &&(__sigaction(SIGINT,&intr,(structsigaction*)NULL) |__sigaction(SIGQUIT,&quit,(structsigaction*)NULL))!=0) ||__sigprocmask(SIG_SETMASK,&omask,(sigset_t*)NULL)!=0) { #ifndef_LIBC /*glibccannotbeusedonsystemswithoutwaitpid.*/ if(errno==ENOSYS) __set_errno(save); else #endif status=-1; } DO_UNLOCK(); returnstatus; } do_system
首先函数设置了一些信号处理程序,来处理SIGINT和SIGQUIT信号,此处我们不过多关心,关键代码段在这里
#ifdefFORK pid=FORK(); #else pid=__fork(); #endif if(pid==(pid_t)0) { /*Childside.*/ constchar*new_argv[4]; new_argv[0]=SHELL_NAME; new_argv[1]="-c"; new_argv[2]=line; new_argv[3]=NULL; /*Restorethesignals.*/ (void)__sigaction(SIGINT,&intr,(structsigaction*)NULL); (void)__sigaction(SIGQUIT,&quit,(structsigaction*)NULL); (void)__sigprocmask(SIG_SETMASK,&omask,(sigset_t*)NULL); INIT_LOCK(); /*Exectheshell.*/ (void)__execve(SHELL_PATH,(char*const*)new_argv,__environ); _exit(127); } elseif(pid<(pid_t)0) /*Theforkfailed.*/ status=-1; else /*Parentside.*/ { /*Notethesystem()isacancellationpoint.Butsincewecall waitpid()whichitselfisacancellationpointwedonot havetodoanythinghere.*/ if(TEMP_FAILURE_RETRY(__waitpid(pid,&status,0))!=pid) status=-1; }
首先通过前端函数调用系统调用fork产生一个子进程,其中fork有两个返回值,对父进程返回子进程的pid,对子进程返回0。所以子进程执行6-24行代码,父进程执行30-35行代码。
子进程的逻辑非常清晰,调用execve执行SHELL_PATH指定的程序,参数通过new_argv传递,环境变量为全局变量__environ。
其中SHELL_PATH和SHELL_NAME定义如下
#defineSHELL_PATH"/bin/sh"/*Pathoftheshell.*/ #defineSHELL_NAME"sh"/*Nametogiveit.*/
其实就是生成一个子进程调用/bin/sh -c "命令"来执行向system传入的命令。
下面其实是我研究system函数的原因与重点:
在CTF的pwn题中,通过栈溢出调用system函数有时会失败,听师傅们说是环境变量被覆盖,但是一直都是懵懂,今天深入学习了一下,总算搞明白了。
在这里system函数需要的环境变量储存在全局变量__environ中,那么这个变量的内容是什么呢。
__environ是在glibc/csu/libc-start.c中定义的,我们来看几个关键语句。
#defineLIBC_START_MAIN__libc_start_main
__libc_start_main是_start调用的函数,这涉及到程序开始时的一些初始化工作,对这些名词不了解的话可以看一下这篇文章。接下来看LIBC_START_MAIN函数。
STATICint LIBC_START_MAIN(int(*main)(int,char**,char**MAIN_AUXVEC_DECL), intargc,char**argv, #ifdefLIBC_START_MAIN_AUXVEC_ARG ElfW(auxv_t)*auxvec, #endif __typeof(main)init, void(*fini)(void), void(*rtld_fini)(void),void*stack_end) { /*Resultofthe'main'function.*/ intresult; __libc_multiple_libcs=&_dl_starting_up&&!_dl_starting_up; #ifndefSHARED char**ev=&argv[argc+1]; __environ=ev; /*Storetheloweststackaddress.Thisisdoneinld.soifthisis thecodefortheDSO.*/ __libc_stack_end=stack_end; ...... /*Nothingfancy,justcallthefunction.*/ result=main(argc,argv,__environMAIN_AUXVEC_PARAM); #endif exit(result); }
我们可以看到,在没有define SHARED的情况下,在第19行定义了__environ的值。启动程序调用LIBC_START_MAIN之前,会先将环境变量和argv中的字符串保存起来(其实是保存到栈上),然后依次将环境变量中各项字符串的地址,argv中各项字符串的地址和argc入栈,所以环境变量数组一定位于argv数组的正后方,以一个空地址间隔。所以第17行的&argv[argc + 1]语句就是取环境变量数组在栈上的首地址,保存到ev中,最终保存到__environ中。第203行调用main函数,会将__environ的值入栈,这个被栈溢出覆盖掉没什么问题,只要保证__environ中的地址处不被覆盖即可。
看完上述内容,你们对如何在linux中使用system函数有进一步的了解吗?如果还想了解更多知识或者相关内容,请关注恰卡编程网行业资讯频道,感谢大家的支持。
推荐阅读
-
linux怎么搭建ftp服务器(linux ftp命令)
linuxftp命令?很欢喜问本问题,此观点祝你好运吧!再连接ftp服务器。格式:ftp[hostname|ip-address...
-
linux怎么调出屏幕键盘(linux | 怎么打出来,管道符号怎么打)
linux|怎么打出来,管道符号怎么打?楼主,你好!“|”这个符号在linux环境称做“管道符”框输入方法:Shift键盘的“...
-
linux系统生成core文件(linux udp缓存配置)
linuxudp缓存配置?临时再添加:sysctl-w_max26214400无限制再添加:将以下行添加到中:_max26214...
-
linux Centos如何安装PHP7
linuxCentos如何安装PHP7今天小编给大家分享一下li...
-
干了10多年的php,还不会安装,是不是丢人
-
Linux编程的十大代码编辑器新鲜出炉,你用的入榜单了吗?
-
从7到8,CentOS又更新了什么
-
PHP程序员的技术成长规划-基础阶段篇
-
Linux curl命令
Linuxcurl命令是一个利用URL规则在命令行下工作的文件传输工具。它支持文件的上传和下载,所以是综合传输工具,但...
-
CentOS和RedHat下8个最常用的YUM库