如何在linux中使用system函数

今天就跟大家聊聊有关如何在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函数有进一步的了解吗?如果还想了解更多知识或者相关内容,请关注恰卡编程网行业资讯频道,感谢大家的支持。

发布于 2021-03-16 13:46:17
收藏
分享
海报
0 条评论
166
上一篇:win7系统重装电脑没声音了怎么恢复 下一篇:怎么在Linux中调用fsync函数
目录

    0 条评论

    本站已关闭游客评论,请登录或者注册后再评论吧~

    忘记密码?

    图形验证码