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);
}
我们可以看到,在没有defineSHARED的情况下,在第19行定义了__environ的值。启动程序调用LIBC_START_MAIN之前,会先将环境变量和argv中的字符串保存起来(其实是保存到栈上),然后依次将环境变量中各项字符串的地址,argv中各项字符串的地址和argc入栈,所以环境变量数组一定位于argv数组的正后方,以一个空地址间隔。所以第17行的&argv[argc+1]语句就是取环境变量数组在栈上的首地址,保存到ev中,最终保存到__environ中。第203行调用main函数,会将__environ的值入栈,这个被栈溢出覆盖掉没什么问题,只要保证__environ中的地址处不被覆盖即可。
所以,当栈溢出的长度过大,溢出的内容覆盖了__environ中地址中的重要内容时,调用system函数就会失败。具体环境变量距离溢出地址有多远,可以通过在_start中下断查看。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。