CVE-2015-7547 分析代码bug

本文详细解析了Glibc中处理DNS查询时存在的栈溢出漏洞CVE-2015-7547,阐述了漏洞触发的具体过程,并列出了受影响的操作系统版本。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原址:https://www.sdk.cn/news/2189

Glibc中处理DNS查询的代码中存在栈溢出漏洞,远端攻击者可以通过DNS服务回应特定构造的DNS响应数据包,导致glibc相关的应用程序crash或者利用栈溢出运行任意代码。应用程序调用使用getaddrinfo 函数将会收到该漏洞的影响。该漏洞编号CVE-2015-7547

◆◆◆

受影响的系统有哪些?

CentOS6 所有版本

CentOS7 所有版本

SUSE Linux Enterprise Server 11 SP3

SUSE Linux Enterprise Server 12

Ubuntu Server 14.04.1 LTS 32位

Ubuntu Server 14.04.1 LTS 64位

Ubuntu Server 12.04 LTS 64位

Debian 8.2 32位

Debian 8.2 64位

Debian 7.8 32位

Debian 7.8 64位

Debian 7.4 64位

CoreOS 717.3.0 64位

◆◆◆

如何修复该漏洞?

修复方法请点击文末的“阅读原文”获取。

PS: 目前腾讯云提供的基础操作系统镜像 CentOS系列、Ubuntu 系列、Debian 系列都已修复该问题,新创建的云服务器不存在该漏洞。腾讯云提供的更新源已经提供了修改该漏洞的glibc版本,对于已经运行的云服务器可以通过手动更新glibc修复。 

◆◆◆

深度剖析:这个漏洞具体是如何触发的?

要搞清楚漏洞的产生,我们需要从代码层面分析一下这个漏洞。

首先从getaddrinfo函数开始,getaddrinfo函数在resolv/nss_dns/dns-host.c中,调用了alloca 在栈上分配了2048个字节。

  host_buffer.buf = orig_host_buffer = (querybuf *) alloca (2048);   

  int n = __libc_res_nsearch (&_res, name, C_IN, T_UNSPEC,

      host_buffer.buf->buf, 2048, &host_buffer.ptr,

      &ans2p, &nans2p, &resplen2);

alloca 函数在栈分配2048个字节,用于存放DNS服务器的响应数据。函数还传递了ans2p,nans2p,resplen2三个参数,存放DNS服务器的响应数据。ans2p用于返回存放第二份响应数据包缓冲区地址,nans2p返回存放第二份响应数据包的扩充缓冲区的大小,resplen2返回第二份响应数据包数据包的大小。函数的返回值是第一份响应数据包的大小。

getaddrinfo函数如果传递的是AF_UNSPEC参数的话,会同时进行IPv4,IPv6的查询,分配组建IPv4和IPv6的数据包发送和接受。

__libc_res_nsearch继续调用__libc_res_nquery函数。__libc_res_nquery在调用__libc_res_nsend函数。__libc_res_nsend用来发送和接受DNS相关的数据包。

__libc_res_nsend(statp, query1, nquery1, query2, nquery2, answer,

     anslen, answerp, answerp2, nanswerp2, resplen2)

__libc_res_nsend中会调用send_vc和send_dg函数和DNS服务器交互,TCP场景下调用send_vc,UDP的话调用send_dg。本文以send_dg为例进行分析。

send_dg(statp, buf, buflen, buf2, buflen2,

    &ans, &anssiz, &terrno,

    ns, &v_circuit, &gotsomewhere, ansp,

    ansp2, nansp2, resplen2)

send_dg首先调用__sendmmsg发送buf, buflen, buf2, buflen2中的数据包到DNS服务器。buf, buflen, buf2, buflen2是对应的查询消息。再调用recvfrom接受从DNS的响应包,而问题就出现在这段代码。

首先分析send_dg函数中recvfrom的使用使用thisansp变量标识接受数据缓冲区的地址,使用thisanssizp变量标识接受数据缓冲区的大小。

*thisresplenp = recvfrom(pfd[0].fd, (char*)*thisansp,

 *thisanssizp, 0,

(struct sockaddr *)&from, &fromlen);

继续分析send_dg对thisansp,thisanssizp变量的处理逻辑。

第一次收到数据包,使用之前在栈上分配的2048个字节,代码处理如下:

if ((recvresp1 | recvresp2) == 0 || buf2 == NULL) {

thisanssizp = anssizp;

thisansp = anscp ?: ansp;

assert (anscp != NULL || ansp2 == NULL);

thisresplenp = &resplen;

}

当第二次收到数据包时,处理如下:

if (*anssizp != MAXPACKET) {

/* 判断第一个缓冲区长度anssizp 是否是65536,如果不是,表示2048个缓冲区在接受第一个数据包的时候足够,因此第二个缓冲区继续使用2048个缓冲区的剩余部分*/

*anssizp2 = orig_anssizp - resplen;

*ansp2 = *ansp + resplen;

} else {

/* anssizp 等于65536,说明之前的接受数据包大于2048个字节,栈中分配的空间不足以存放,但是第二次查询的数据有可能小于2048,因此尝试使用栈中的内存保存响应包*/

*anssizp2 = orig_anssizp;

*ansp2 = *ansp;

}

              /*修改thisanssizp thisansp thisresplenp,分别表示调用recvfrom函数接受缓冲区的大小,接受缓冲区地址,接受到的数据包长度*/

thisanssizp = anssizp2;

thisansp = ansp2;

thisresplenp = resplen2;

    if (*thisanssizp < MAXPACKET

    /* 判断接受的数据包,thisanssizp 是否足够存放,如果不够的话,调用malloc 从堆上分配65536个字节,用来存放接受到的数据包*/

    && anscp

    && (ioctl (pfd[0].fd, FIONREAD, thisresplenp) < 0

|| *thisanssizp < *thisresplenp)) {

u_char *newp = malloc (MAXPACKET);

if (newp != NULL) {

*anssizp = MAXPACKET;    /*修改anssizp 变量表示已经从堆上分配了内存*/

*thisansp = ans = newp;     /*修改thisansp 变量,本地recvfrom使用新分配的内存进行存放*/

}

}

而问题就出现在这段代码上,存在两个问题:

1.在使用新分配的内存是,修改了thisansp变量,但是没有修改thisanssizp 变量为新分配的malloc内存的大小。

2.更新了anssizp 标识第一个缓存区的大小,但是没有更新ansp变量,ansp还是指向之前在栈上分配的2048个字节。

考虑如下场景:

* 第一次的DNS响应数据包是2048,正好使用完了在栈上分配的2048个字节.。

* 接受第二次DNS响应数据包,由于之前第一个数据包已经使用完2048个字节,所以代码会走到malloc流程从堆上分配内存,但是由于前面提到的bug,thisanssizp 没有被更新,而thisanssizp 在这种场景下为0,会导致recvfrom返回失败,导致send_dg直接退出。这个时候,ansp指向栈上的2048个内存区,但是anssizp被修改为65536。

* send_dg再次被调用,这个时候接受第三个DNS响应数据包,ansp指向栈上的2048个内存区,anssizp被修改为65536. 这个时候如果接受超过2048个数据包,会导致栈溢出。因此攻击者可以构造一个65536的数据包,前面2048个字节是正规的DNS数据,后面63487 个字节利用栈溢出执行自己的代码。

◆◆◆ 

结论

触发该漏洞的触发条件:

* 程序调用getaddrinfo函数,传递AF_UNSPEC参数进行DNS查询。

* DNS服务器第一次响应2048字节的数据包。

* DNS服务器响应第二个数据包,触发send_dg出错退出.

* DNS服务器响应第三个数据包,该数据包可以被攻击者利用,前2048包含有效的DNS响应数据包,后63487个字节可以作为栈溢出漏洞利用,执行攻击者自己的代码。

<think>我们正在处理一个关于修改Android系统中DNS请求随机值生成方式的问题。根据用户的问题,我们需要修改DNS请求中的随机值生成方式。在Android系统中,DNS请求通常涉及DNS over TLS (DoT) 或 DNS over HTTPS (DoH) 等安全DNS协议,但用户关注的是随机值生成部分。 在Android系统中,DNS请求的随机值生成可能位于不同的层次。根据引用[1]的内容,我们知道在Android 4.3之后,直接修改系统属性或解析文件来修改DNS服务器已经不可能。因此,我们需要考虑其他方法,比如在NDK层修改DNS解析逻辑或者修改Android系统源代码。 用户的问题具体是“替换或修改DNS请求中的随机值生成方式”。随机值在DNS请求中通常用于防止缓存污染(cache poisoning)攻击,例如在DNS请求中使用随机的事务ID(Transaction ID)和源端口号(Source Port)。因此,我们需要修改这两个随机值的生成方式。 根据引用[1]提到的老外基于NDK的修改方案,我们可以推测在NDK层修改是可能的。但是,该引用并没有提供具体的修改随机值生成的方法。因此,我们需要进一步分析。 在Android系统中,DNS解析通常由libc库中的getaddrinfo等函数完成,而底层则使用Bionic C库。Bionic库中的DNS解析逻辑位于`bionic/libc/dns/`目录下。具体来说,事务ID和源端口号的生成可能位于`bionic/libc/dns/net/getaddrinfo.c`或相关的文件中。 然而,由于用户无法修改系统源代码(除非是自定义ROM),我们可能需要考虑在应用层或通过NDK来拦截和修改DNS请求。但是,DNS请求的随机值生成是在底层库中完成的,因此修改应用层代码可能无法直接达到目的。 因此,我们有两种可能的方案: 1. 修改Android系统源代码(需要编译整个系统,适用于自定义ROM): - 在Bionic库中修改生成随机数的逻辑。例如,在生成事务ID和源端口号的地方,替换成我们自己的随机数生成算法。 - 具体位置可能在`bionic/libc/dns/net/getaddrinfo.c`中的`res_nsend`函数相关代码,或者`res_randomid`函数(用于生成事务ID)和端口选择部分。 2. 在NDK层拦截和修改DNS请求(无需修改系统,但需要root权限): - 通过LD_PRELOAD机制,在应用启动时预加载一个共享库,该库覆盖libc中的相关函数(如`res_nsend`、`res_randomid`等),从而替换随机值的生成。 - 但是,在Android中,由于安全限制和Bionic库的实现,使用LD_PRELOAD可能比较复杂,且需要应用具有执行该机制的环境(通常需要root)。 考虑到用户可能没有系统源代码的修改权限,我们重点讨论第二种方法(NDK层修改)的可能性。 ### 基于NDK的修改方案(需要root权限) 步骤大致如下: 1. **编写一个共享库**,覆盖Bionic中的关键函数。例如,我们需要覆盖生成事务ID的函数和选择源端口的函数。 在Bionic库中,事务ID的生成通常由`res_randomid`函数完成(定义在`bionic/libc/dns/resolv/res_randomid.c`)。我们可以编写一个同名的函数,并在其中使用自定义的随机数生成算法。 同时,源端口的选择在`res_nsend`函数中会调用`res_nmkquery`函数,其中会设置端口号(端口号可能是随机选择的)。因此,我们可能需要覆盖`res_nmkquery`函数或者更底层的socket绑定函数。 2. **使用LD_PRELOAD**来预加载我们的共享库。这样,当应用程序调用DNS解析函数时,会优先使用我们覆盖的函数。 3. **在应用程序中加载这个共享库**,这可以通过在应用的启动代码(如JNI_OnLoad)中调用`dlopen`来实现,或者通过修改应用启动脚本(需要root权限)。 但是,由于Android系统的安全机制,普通应用无法使用LD_PRELOAD,除非设备已经root,并且应用在root环境下运行。 ### 具体实现步骤(NDK方案) 1. 创建一个Android NDK项目,编写一个共享库(例如libdnsrandom.so)。 2. 在共享库中,覆盖以下函数(以Bionic为例): - `res_randomid`:用于生成事务ID。 - 端口选择:端口选择可能没有单独的函数,而是直接调用`bind`系统调用,并指定端口为0(由系统随机分配)。因此,我们可以覆盖`bind`函数,在绑定DNS套接字时,如果端口是0,则替换为我们自己选择的端口(使用自定义随机算法)。 示例代码片段(覆盖`res_randomid`): ```c #include <stdint.h> // 原函数声明 uint16_t res_randomid(void); // 我们的实现 uint16_t res_randomid(void) { // 自定义的随机数生成算法,例如使用更安全的随机源 uint16_t id; // 这里使用系统提供的安全随机函数(如getentropy)来生成 // 注意:需要包含头文件<unistd.h>,但注意getentropy在Android API level 28以上才支持 getentropy(&id, sizeof(id)); return id; } ``` 覆盖`bind`函数以自定义端口选择: ```c #include <sys/socket.h> #include <stdint.h> #include <dlfcn.h> typedef int (*orig_bind_type)(int sockfd, const struct sockaddr *addr, socklen_t addrlen); int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { static orig_bind_type orig_bind = NULL; if (!orig_bind) { orig_bind = (orig_bind_type)dlsym(RTLD_NEXT, "bind"); } // 检查是否是DNS请求的套接字(通常DNS使用UDP,端口53,但这里是源端口,不是目的端口) // 注意:我们无法直接知道这个套接字是否用于DNS,因此可能需要通过目的地址来判断? // 实际上,我们只想修改源端口,所以我们可以检查addr中的端口是否为0(表示系统自动分配) if (addr->sa_family == AF_INET) { struct sockaddr_in *addr_in = (struct sockaddr_in *)addr; if (addr_in->sin_port == 0) { // 替换为我们自定义的随机端口(范围在1024-65535之间,注意避免使用特权端口) uint16_t port = ...; // 自定义随机端口生成 addr_in->sin_port = htons(port); } } else if (addr->sa_family == AF_INET6) { struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)addr; if (addr_in6->sin6_port == 0) { uint16_t port = ...; // 自定义随机端口生成 addr_in6->sin6_port = htons(port); } } return orig_bind(sockfd, addr, addrlen); } ``` 3. 编译共享库,并在应用启动时加载它(例如在JNI_OnLoad中): ```c #include <jni.h> #include <dlfcn.h> JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { // 预加载我们的共享库 dlopen("libdnsrandom.so", RTLD_NOW | RTLD_GLOBAL); return JNI_VERSION_1_6; } ``` 注意:这种方法需要应用是Native应用(使用JNI),并且需要在Android.mk或CMakeLists.txt中链接dl库。 4. 由于需要覆盖系统函数,应用必须运行在具有root权限的环境中,否则LD_PRELOAD可能无法生效。 ### 注意事项 1. **权限问题**:这种方法需要root权限,因为普通应用无法全局覆盖libc函数。 2. **兼容性**:不同Android版本中Bionic库的实现可能不同,因此覆盖的函数需要针对不同版本进行测试。 3. **安全性**:自定义随机数生成算法必须足够安全,否则可能降低DNS请求的安全性。 ### 替代方案 如果用户不需要修改所有应用的DNS请求,而只是修改自己应用的DNS请求,那么可以在应用层使用自定义的DNS解析库(如使用OkHttp的Dns接口),在应用层实现DNS解析,并自定义随机值生成。例如: ```java import okhttp3.Dns; import java.net.InetAddress; import java.util.List; import java.util.Random; public class CustomDns implements Dns { private Random random = new SecureRandom(); // 使用安全的随机数生成器 @Override public List<InetAddress> lookup(String hostname) throws UnknownHostException { // 自定义DNS解析,包括生成随机的端口和事务ID(如果使用原始套接字) // 但是,OkHttp的Dns接口只负责解析域名,并不直接控制底层DNS请求的随机值。 // 因此,如果我们需要控制事务ID和端口,需要自己实现基于UDP的DNS请求。 // 这相当于是自己实现一个DNS解析器。 // 或者,我们可以使用第三方DNS库,如dnsjava,并在其中修改随机值生成部分。 } } ``` 然后配置OkHttp使用这个自定义的Dns: ```java OkHttpClient client = new OkHttpClient.Builder() .dns(new CustomDns()) .build(); ``` 但是,这种方法需要自己实现完整的DNS解析逻辑(包括处理事务ID、端口等),或者修改第三方库(如dnsjava)的源代码。 ### 总结 根据用户的需求,修改Android系统中DNS请求的随机值生成方式,有两种主要途径: 1. **修改系统源代码**:适用于自定义ROM,直接修改Bionic库中的相关函数。 2. **NDK层拦截**:通过LD_PRELOAD覆盖libc中的相关函数,需要root权限,且实现复杂。 如果用户只需要在特定应用中修改,则可以考虑在应用层使用自定义的DNS解析库。 由于问题较为复杂,用户需要根据自身情况选择合适的方法。 ### 相关问题 1. 如何为Android设备获取root权限? 2. 如何编译自定义的Android ROM? 3. 如何在Android应用中使用自定义DNS解析? 4. 如何安全地生成随机数用于DNS请求? 5. DNS请求中的随机值(事务ID和源端口)的作用是什么? [^1]: 在Android4.3以后,通过系统属性或者解析文件来手动修改DNS服务器已经是不可能了。主要有两种方法,一个是在NDK下面修改DNS解析逻辑,第二个是通过Android系统源代码修改相关逻辑,让Android4.3的新修改无效,然后重构Android。下面是一个老外基于NDK的修改方案,该方案需要以下权限:</think>### Android 修改 DNS 请求随机值生成方法 在 Android 系统中修改 DNS 请求的随机值生成方式(如事务 ID、源端口号等随机参数),主要有以下两种实现方案: --- ### 方案一:NDK 层修改(无需编译整个系统) **核心思路**:通过 Hook Bionic C 库中的 DNS 解析函数,覆盖随机值生成逻辑 **实现步骤**: 1. **定位关键函数** Android 的 DNS 解析位于 Bionic 库的 `res_randomid()`(生成事务 ID)和端口选择逻辑(通常在内核 socket 层)。使用 `objdump` 分析 `libc.so` 定位符号地址: ```bash aarch64-linux-android-objdump -T libc.so | grep res_randomid ``` 2. **创建 Hook 库** 编写共享库(`libdns_hook.so`),覆盖关键函数: ```c #include <stdint.h> #include <dlfcn.h> // 自定义随机生成器(示例:使用硬件熵源) static uint16_t custom_rand() { uint16_t val; FILE* f = fopen("/dev/urandom", "rb"); fread(&val, sizeof(val), 1, f); fclose(f); return val; } // Hook res_randomid() uint16_t res_randomid(void) { return custom_rand() & 0xFFFF; // 返回16位随机数 } // Hook bind() 控制源端口 typedef int (*orig_bind_t)(int, const struct sockaddr*, socklen_t); int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { static orig_bind_t orig_bind = NULL; if (!orig_bind) orig_bind = (orig_bind_t)dlsym(RTLD_NEXT, "bind"); if (addr->sa_family == AF_INET) { struct sockaddr_in *a = (struct sockaddr_in*)addr; if (a->sin_port == 0) { // 系统自动分配端口 a->sin_port = htons(1024 + (custom_rand() % 64512)); } } return orig_bind(sockfd, addr, addrlen); } ``` 3. **注入 Hook 库** 通过 `LD_PRELOAD` 加载(需 root 权限): ```bash export LD_PRELOAD=/data/local/tmp/libdns_hook.so setprop net.dns1 8.8.8.8 ``` **权限需求**: ```xml <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <!-- Root 权限需设备解锁 --> ``` --- ### 方案二:修改系统源码(需完整编译) **核心路径**: 1. 事务 ID 生成: `bionic/libc/dns/resolv/res_randomid.c` ```c uint16_t res_randomid(void) { // 替换为自定义随机算法 return (arc4random() & 0xffff); } ``` 2. 源端口随机化: `system/netd/server/DnsProxyListener.cpp` 中的 `send_dns_packet()` 方法: ```cpp int port = 1024 + (custom_rand() % 64512); // 自定义端口范围 bind(sock, &ss, sizeof(ss)); ``` **编译部署**: 1. 修改后全系统编译: ```bash source build/envsetup.sh lunch target_device-eng make -j8 ``` 2. 刷入修改后的系统镜像 --- ### 技术难点与注意事项 1. **随机性安全** - 避免使用 `rand()` 等弱随机源,优先使用 `/dev/urandom` 或硬件熵源 - 事务 ID 应满足 RFC 5452 的随机性要求以防止 DNS 欺骗攻击[^1] 2. **Android 版本兼容** | Android 版本 | 影响点 | |-------------|--------| | 4.3+ | 禁止直接修改 `net.dns` 属性[^1] | | 9.0+ | 私有 API 限制(需签名权限) | | 11+ | 增强的 seccomp 沙盒(需调整策略) | 3. **DNS over TLS/HTTPS** 若启用 DoT/DoH(`Settings.Global.DNS_MODE`),随机值在加密层生成,需修改: ```java // packages/modules/NetworkStack/src/com/android/server/connectivity/DnsManager.java class DnsProxy implements DnsResolver.DnsCallback { private int generateRandomId() { ... } // 修改此处 } ``` --- ### 验证方法 1. 抓包确认随机值变化: ```bash adb shell tcpdump -i any -s0 -w /sdcard/dns.pcap port 53 ``` 2. 解析事务 ID 和端口: ```bash tshark -r dns.pcap -T fields -e dns.id -e udp.srcport ``` --- ### 相关问题 1. 如何在不 root 的情况下自定义 Android 应用的 DNS 解析? 2. DNS over HTTPS (DoH) 在 Android 中的实现原理是什么? 3. 如何防止 Android 设备上的 DNS 劫持攻击? 4. Bionic C 库中还有哪些网络相关函数可以 Hook? 5. 修改系统 DNS 行为会如何影响 Android 的网络安全机制? [^1]: 在Android4.3以后,通过系统属性或者解析文件来手动修改DNS服务器已经是不可能了。主要有两种方法,一个是在NDK下面修改DNS解析逻辑,第二个是通过Android系统源代码修改相关逻辑。 [^2]: 自定义ContentProvider,继承ContentProvider类并实现抽象方法,除了OnCreate由系统回调并运行在主线程里,其他五个方法均由外界回调并运行在Binder线程池中。 [^3]: LocalDns是不可以被信任的,经常会有运营商会搞一些奇奇怪怪的Dns拦截,导致大家收到的请求是运营商所缓存的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值