看雪论坛作者ID:ScriptKiddies
准备工作
backtrace
[#0] 0x55d829ab7ad0 → parse_args(argc=0x3, argv=0x55d82b875220, old_optind=0x7ffede10dd2c, nargc=0x7ffede10dd28, nargv=0x7ffede10dd30, settingsp=0x7ffede10dd58, env_addp=0x7ffede10dd38)
[#1] 0x55d829aa2bfb → main(argc=0x4, argv=0x7ffede10dfc8, envp=0x7ffede10dff0)
#0 set_cmnd () at ../../../plugins/sudoers/sudoers.c:857
#1 0x00007f2892e36cf4 in sudoers_policy_main (argc=argc@entry=0x3, argv=argv@entry=0x56435dfcb220, pwflag=pwflag@entry=0x0, env_add=env_add@entry=0x0, verbose=verbose@entry=0x0, closure=closure@entry=0x7ffddfd1dc40) at ../../../plugins/sudoers/sudoers.c:353
#2 0x00007f2892e2fbb2 in sudoers_policy_check (argc=0x3, argv=0x56435dfcb220, env_add=0x0, command_infop=0x7ffddfd1dd00, argv_out=0x7ffddfd1dd08, user_env_out=0x7ffddfd1dd10, errstr=0x7ffddfd1dd28) at ../../../plugins/sudoers/policy.c:984
#3 0x000056435c62de6a in policy_check (user_env_out=0x7ffddfd1dd10, argv_out=0x7ffddfd1dd08, command_info=0x7ffddfd1dd00, env_add=0x0, argv=0x56435dfcb220, argc=0x3) at ../../src/sudo.c:1161
#4 main (argc=<optimized out>, argv=<optimized out>, envp=<optimized out>) at ../../src/sudo.c:272
源码分析
sudoedit -s '\' `perl -e 'print "A" x 65536'`
gef➤ bt
#0 parse_args (argc=0x4, argv=0x7ffede10dfc8, old_optind=0x7ffede10dd2c, nargc=0x7ffede10dd28, nargv=0x7ffede10dd30, settingsp=0x7ffede10dd58, env_addp=0x7ffede10dd38) at ../../src/parse_args.c:257
#1 0x000055d829aa2bfb in main (argc=0x4, argv=0x7ffede10dfc8, envp=0x7ffede10dff0) at ../../src/sudo.c:218
/*
* Command line argument parsing.
* Sets nargc and nargv which corresponds to the argc/argv we'll use
* for the command to be run (if we are running one).
*/
// 参数 argc argv 都是直接从 main 函数的 argc argv 传过来的
int
parse_args(int argc, char **argv, int *old_optind, int *nargc, char ***nargv,
struct sudo_settings **settingsp, char ***env_addp)
{
int mode = 0; /* what mode is sudo to be run in? */ // sudo 的运行模式
int flags = 0; /* mode flags */ // sudo 的运行模式的标识
int valid_flags = DEFAULT_VALID_FLAGS; // 用来校验 flags 是否合法
int ch, i;
char *cp;
const char *progname; // 运行的程序名,判别运行的是 sudo 还是 sudoedit
int proglen;
/* Pass progname to plugin so it can call initprogname() */
// 获取运行的程序的名称,其实这个东西跟 busybox 是一样的,sudo 和 sudoedit 其实都是同一个二进制文件
// sudoedit 是 sudo 的一个软链接而已,但是运行的时候 进程名 是 sudoedit ,可以依据这个来判定需要执行 sudo 还是 sudoedit 的功能,可以看看 initprogname 的实现,其实 getprogname 返回的是全局变量 progname,存的就是当前运行程序的名称,程序在 parse_args 之前程序通过 initprogname 初始化了,好了有点扯远了。。。
progname = getprogname();
sudo_settings[ARG_PROGNAME].value = progname;
static struct sudo_settings sudo_settings[] = {
......
#define ARG_PROGNAME 12
{ "progname" },
......
};
/* First, check to see if we were invoked as "sudoedit". */
proglen = strlen(progname); // 获取进程名的长度
// 因为 sudo 有一个软链接 sudoedit,只要进程名长度大于 4 并且最后 4 个字母是 edit 时
// 确定运行的是 sudoedit 而不是 sudo
// 碎碎念:其实这里的 (progname + proglen - 4, "edit") 写的很妙,程序名无关,就算 sudoedit 改成 sedit 还是能完美运行
if (proglen > 4 && strcmp(progname + proglen - 4, "edit") == 0) {
progname = "sudoedit";
mode = MODE_EDIT; // 设置运行模式为 MODE_EDIT
sudo_settings[ARG_SUDOEDIT].value = "true";
}
/* XXX - should fill in settings at the end to avoid dupes */
for (;;) {
/*
* Some trickiness is required to allow environment variables
* to be interspersed with command line options.
*/
// 解析 -xxx 参数,我删掉了其他无关的 case,我们只用到了 -s
if ((ch = getopt_long(argc, argv, short_opts, long_opts, NULL)) != -1) {
switch (ch) {
.......
case 's':
sudo_settings[ARG_USER_SHELL].value = "true";
SET(flags, MODE_SHELL); // 加上 MODE_SHELL
break;
......
}
} else if (!got_end_of_args && is_envar) {
/* Insert key=value pair, crank optind and resume getopt. */
env_insert(&extra_env, argv[optind]);
optind++;
} else {
/* Not an option or an environment variable -- we're done. */
break;
}
}
argc -= optind;
argv += optind;
*old_optind = optind;
// 按照我们使用 POC 调试,现在 argv 指针指向的是 '/', argc 是 2
// mode 是从在解析 -xxx 参数的时候设置的,如果没有加参数,那么 mode 就是 0,相当于是运行 sudo xxxx,就比如我们平时 sudo cat 这样
// MODE_RUN 就是使用 sudo 运行一个命令
if (!mode)
mode = MODE_RUN; /* running a command */
}
......
#ifdef ENABLE_SUDO_PLUGIN_API
sudo_settings[ARG_PLUGIN_DIR].value = sudo_conf_plugin_dir_path();
#endif
.......
/*
* For sudoedit we need to rewrite argv
*/
// 因为我们运行的是 sudoedit ,在上面检测 进程名 的时候设置了 MODE_EDIT
if (mode == MODE_EDIT) {
#if defined(HAVE_SETRESUID) || defined(HAVE_SETREUID) || defined(HAVE_SETEUID)
char **av;
int ac;
// 这里 reallocarray 的用法,第一个参数是 NULL,其实相当于调用了 realloc(NULL, 4 * sizeof(char *)),(我这里只是简单形容,真正的 reallocarray 源码会使用编译器内建函数 __builtin_mul_overflow 去计算 4 * sizeof(char *),同时检查溢出......)
// 其实到 realloc 里面(记住,如果你要看源码的话对应的函数应该是 __libc_realloc),里面会有一句检查第一个参数是不是 NULL,如果是 NULL 直接就是通过 __libc_malloc (就是我们所说的 malloc)去分配所需的内存,扯远了,感兴趣可以自己去看看源码(好像是我啰嗦了,这些应该都是这篇文章读者的常识吧,呜呜呜)
av = reallocarray(NULL, argc + 2, sizeof(char *)); // 其实相当分配了一个 char * 数组,懂我的意思吧
if (av == NULL)
sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
if (!gc_add(GC_PTR, av))
exit(EXIT_FAILURE);
/* Must have the command in argv[0]. */
av[0] = "sudoedit";
// 把我们的 '/' 和 一堆 A 的地址放入 av
for (ac = 0; argv[ac] != NULL; ac++) {
av[ac + 1] = argv[ac];
}
av[++ac] = NULL;
argv = av;
argc = ac;
#else
sudo_fatalx(U_("sudoedit is not supported on this platform"));
#endif
}
// c 语言不能有多个返回值,直接修改传入的指针
*settingsp = sudo_settings;
*env_addp = extra_env.envp;
*nargc = argc;
*nargv = argv; // av 必须放在堆上
// 返回 mode | flags
debug_return_int(mode | flags);
}
sudo_mode = parse_args(argc, argv, &submit_optind, &nargc, &nargv,
&settings, &env_add);
/* Load plugins. */
if (!sudo_load_plugins(&policy_plugin, &io_plugins, &audit_plugins,
&approval_plugins))
sudo_fatalx(U_("fatal error, unable to load plugins"));
/* Allocate event base so plugin can use it. */
if ((sudo_event_base = sudo_ev_base_alloc()) == NULL)
sudo_fatalx("%s", U_("unable to allocate memory"));
/* Open policy and audit plugins. */
/* XXX - audit policy_open errors */
audit_open(settings, user_info, submit_optind, argv, envp);
policy_open(settings, user_info, envp);
switch (sudo_mode & MODE_MASK) {
case MODE_EDIT:
case MODE_RUN:
// 跟进去
policy_check(nargc, nargv, env_add, &command_info, &argv_out,
&user_env_out);
static void
policy_check(int argc, char * const argv[],
char *env_add[], char **command_info[], char **argv_out[],
char **user_env_out[])
{
const char *errstr = NULL;
int ok;
ok = policy_plugin.u.policy->check_policy(argc, argv, env_add,
command_info, argv_out, user_env_out, &errstr);
}
static int
sudoers_policy_check(int argc, char * const argv[], char *env_add[],
char **command_infop[], char **argv_out[], char **user_env_out[],
const char **errstr)
{
struct sudoers_exec_args exec_args;
int ret;
if (!ISSET(sudo_mode, MODE_EDIT))
SET(sudo_mode, MODE_RUN);
exec_args.argv = argv_out;
exec_args.envp = user_env_out;
exec_args.info = command_infop;
ret = sudoers_policy_main(argc, argv, 0, env_add, false, &exec_args);
}
int
sudoers_policy_main(int argc, char * const argv[], int pwflag, char *env_add[],
bool verbose, void *closure)
{
char *iolog_path = NULL;
mode_t cmnd_umask = ACCESSPERMS;
struct sudo_nss *nss;
int cmnd_status = -1, oldlocale, validated;
int ret = -1;
/* Environment variables specified on the command line. */
if (env_add != NULL && env_add[0] != NULL)
sudo_user.env_vars = env_add;
/*
* Make a local copy of argc/argv, with special handling
* for pseudo-commands and the '-i' option.
*/
if (argc == 0) {
......
} else {
/* Must leave an extra slot before NewArgv for bash's --login */
NewArgc = argc;
NewArgv = reallocarray(NULL, NewArgc + 2, sizeof(char *));
if (NewArgv == NULL) {
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
goto done;
}
sudoers_gc_add(GC_VECTOR, NewArgv);
NewArgv++; /* reserve an extra slot for --login */
// 将我们传入的参数的地址拷贝到 NewArgv
memcpy(NewArgv, argv, argc * sizeof(char *));
NewArgv[NewArgc] = NULL;
if (ISSET(sudo_mode, MODE_LOGIN_SHELL) && runas_pw != NULL) {
NewArgv[0] = strdup(runas_pw->pw_shell);
if (NewArgv[0] == NULL) {
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
goto done;
}
sudoers_gc_add(GC_PTR, NewArgv[0]);
}
}
/* If given the -P option, set the "preserve_groups" flag. */
// 没有 -P,不会进入这个判断
if (ISSET(sudo_mode, MODE_PRESERVE_GROUPS))
def_preserve_groups = true;
/* Find command in path and apply per-command Defaults. */
cmnd_status = set_cmnd();
if (cmnd_status == NOT_FOUND_ERROR)
goto done;
......
}
/*
* Fill in user_cmnd, user_args, user_base and user_stat variables
* and apply any command-specific defaults entries.
*/
static int
set_cmnd(void)
{
struct sudo_nss *nss;
char *path = user_path;
int ret = FOUND;
debug_decl(set_cmnd, SUDOERS_DEBUG_PLUGIN);
/* Allocate user_stat for find_path() and match functions. */
user_stat = calloc(1, sizeof(struct stat));
if (user_stat == NULL) {
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
debug_return_int(NOT_FOUND_ERROR);
}
/* Default value for cmnd, overridden below. */
// 这里 user_cmnd == NULL 成立
if (user_cmnd == NULL)
user_cmnd = NewArgv[0];
#define user_cmnd (sudo_user.cmnd)
if (sudo_mode & (MODE_RUN | MODE_EDIT | MODE_CHECK)) {
......
/* set user_args */
// 现在 NewArgc == 3,这三个参数分别是 sudoedit / AAAAAAAAAAAAAAA....
if (NewArgc > 1) {
char *to, *from, **av;
size_t size, n;
/* Alloc and build up user_args. */
// 计算参数的长度,其实是计算 / AAAAAAAAAAAAAAA.... 加起来的长度,因为后面拷贝的是参数,不拷贝进程名
// 因为 av = NewArgv + 1,是从 / 开始的
for (size = 0, av = NewArgv + 1; *av; av++)
size += strlen(*av) + 1;
// 这里,malloc 分配的 内存 的大小等于 / AAAAAAAAAAAAAAA.... 加起来的长度(这样说其实不严谨,还需要考虑对齐的,不只是分配那么大)
if (size == 0 || (user_args = malloc(size)) == NULL) {
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
debug_return_int(-1);
}
if (ISSET(sudo_mode, MODE_SHELL|MODE_LOGIN_SHELL)) {
/*
* When running a command via a shell, the sudo front-end
* escapes potential meta chars. We unescape non-spaces
* for sudoers matching and logging purposes.
*/
// 问题就出在这里,一开始 from 指向的是字符串 "\\x00"
for (to = user_args, av = NewArgv + 1; (from = *av); av++) {
while (*from) {
// 这个条件肯定为真,因为 from[0] == '\\' 并且 from[1] == '\x00',isspace 判断的是 from[1] 是不是为 '\x20',(这就是 POC 中 \ 的用处)
// 可能是写代码的人当时蒙圈了,因为我们的参数在内存中是这样的:0x414141414141005c,\ 和 AAAAAA... 是 '\x00' 分隔的
// 他可能把 '\x00' 当成了空格 '\x20',导致 !isspace((unsigned char)from[1] 然后 from++
// 看下面的第二张图,from++ 的汇编其实是 add r15,0x2
// 导致 r15 存的地址是 0x00007ffd3083ae66,这个地址存的是 AAAAAA....
// 导致,第一次 for 的时候其实就已经把 '\x00' 和 AAAAA..... 复制到 user_args 来了
// 而会进行两次 for 循环,导致复制了 '\x00' (因为 from 指向 \ 时,from++; 了才 *to++ = *from++;) 和 两次 AAAAA..... 直接破坏了 heap 上的其他 chunk,在再次使用 malloc 的时候可能就因为 heap 上的 chunk 被破坏而 crash
if (from[0] == '\\' && !isspace((unsigned char)from[1]))
from++;
*to++ = *from++;
}
*to++ = ' ';
}
*--to = '\0';
引用
看雪ID:ScriptKiddies
https://bbs.pediy.com/user-home-832510.htm
# 往期推荐
球分享
球点赞
球在看
点击“阅读原文”,了解更多!