The vulnerability:
The patch provided by vendors like SuSE is not sufficient. It only closed one of at least 3 different holes.
Hole #1 : (closed in the recent patch) (gdb) r -P -q 1 -n $(perl -e 'print"0"x13000')127.0.0.1
Starting program: /usr/sbin/traceroute -P -q 1 -n $(perl -e
'print"0"x13000')127.0.0.1
(no debugging symbols found)...(no debugging symbols found)...(no
debugging symbols found)...(no debugging symbols found)...
Program received signal SIGSEGV, Segmentation fault.
0x400d9634 in strcpy () from /lib/libc.so.6
This is caused by insufficient length checking in the hostname buffer copied from the command line. The buffer is global static so it is located in .bss. The vulnerable code is:
Hole #2: (gdb) r -P -q 1 -n -S -999999 -m 0 localhost
Starting program: /usr/sbin/traceroute -P -q 1 -n -S -999999 -m 0 localhost
traceroute to localhost (127.0.0.1), 0 hops max, 40 byte packets
(no debugging symbols found)...(no debugging symbols found)...(no
debugging symbols found)...(no debugging symbols found)...(no debugging
symbols found)...
Program received signal SIGSEGV, Segmentation fault.
0x08049f3c in strcpy ()
This comes from an array index overflow in the following code: /*
* Enter Spray mode
*/
spray_target = spray_max = spray_total = 0;
spray_min = SPRAYMAX+1;
/* For all TTL do */
for (ttl = min_ttl; ttl <= max_ttl; ++ttl) {
spray_rtn[ttl]=0;
for (probe = 0; probe < nprobes; ++probe) {
send_probe(++seq, ttl);
}
}
Obviously we can write an (int)0 at any 4 bytes aligned memory location! Furthermore, due to a malloc() just right before this code (it is not clear where it comes from in the binary - maybe gcc optimization?) we can write the address returned by malloc at any 4bytes aligned memory location as can be seen from the disassembly:
For example, by carefully manipulating the -m and -S arguments, we can jump into the memory allocated by malloc(): (gdb) bt
#0 0x08049f53 in strcpy ()
#1 0x40182bd8 in __DTOR_END__ () from /lib/libc.so.6
#2 0x4007d9ed in __libc_start_main () from /lib/libc.so.6
(gdb) r -P -q 1 -n -S -302075256 -m -302075256 localhost
Starting program: /usr/sbin/traceroute -P -q 1 -n -S -302075256 -m -302075256 localhost
traceroute to localhost (127.0.0.1), -302075256 hops max, 40 byte packets
(no debugging symbols found)...(no debugging symbols found)...(no
debugging symbols found)...(no debugging symbols found)...(no debugging
symbols found)...
Program received signal SIGSEGV, Segmentation fault.
0x08053228 in ?? ()
(gdb) bt
#0 0x08053228 in ?? ()
(gdb) disass $eip $eip+16
Dump of assembler code from 0x8053228 to 0x8053238:
0x8053228: add %al,(%eax)
0x805322a: add %al,(%eax)
0x805322c: enter $0x1803,$0x40
0x8053230: add %al,(%eax)
0x8053232: add %al,(%eax)
0x8053234: cmp %eax,(%eax)
0x8053236: add %al,(%eax)
End of assembler dump.
Hole #3:
Just run with the following arguments:
(gdb) r -P -q 999 -n localhost
Starting program: /usr/sbin/traceroute -P -q 999 -n localhost
traceroute to localhost (127.0.0.1), 30 hops max, 40 byte packets
(no debugging symbols found)...(no debugging symbols found)...(no
debugging symbols found)...(no debugging symbols found)...(no debugging
symbols found)...
Program received signal SIGSEGV, Segmentation fault.
0x0804a765 in strcpy ()
(gdb)
This time the vulnerable code is: for (probe = 0; probe < nprobes; ++probe) {
send_probe(++seq, ttl);
}
Together with: send_probe(seq, ttl)
int ttl;
int seq;
{
....
#ifdef SPRAY
if (spray_mode) {
spray[seq].dport = up->uh_dport;
spray[seq].ttl = ttl;
bcopy(&op->tv, &spray[seq].out, sizeof(struct timeval));
}
#endif
So one can overwrite consecutive memory blocks of type struct {
u_long dport; /* check for matching dport */
u_char ttl; /* ttl we sent it to */
u_char type; /* icmp response type */
struct timeval out; /* time packet left */
struct timeval rtn; /* time packet arrived */
struct sockaddr_in from; /* whom from */
} spray
Starting at the address of 'spray' (which is again located in the heap) with the values stored in out, dport, ttl. So far Paul looked at this, nothing really sense-full can be overwritten this way. Two candidates are:
[a] the socket descriptor s, which is later used by FD_SET (instant memory writer... (un)fortunately the system time is stored in s by overflowing the spray array.
[b] malloc structures on the heap?
Impact:
A potential exploit must either:
[a] Change the flow of execution just writing N (int) zeros to adjacent memory cells (self modifying code? find movl something, %eax, write zeros after that .... until something helpful is reached? - not possible on systems with non-writable code pages)
[b] Or be able to control the memory returned by an malloc() call
[c] Change the flow of execution by writing the spray type data (with the partially controllable content) into adjacent cells
None of them seems very practicable on an x86. Other architectures may present another behavior. More research must be done on this flaw.