Paranoid Penguin - Limitations of shc, a Shell Encryption Utility
Listing 3. objdump browses the object file for interesting-looking strings.
/usr/bin/objdump --section=.data -s pub.sh.x pub.sh.x: file format elf32-i386 Contents of section .data: 804a4e0 00000000 00000000 3ca80408 00000000 ........<....... 804a4f0 00000000 00000000 00000000 00000000 ................ 804a500 00000000 506c6561 73652063 6f6e7461 ....Please conta 804a510 63742079 6f757220 70726f76 69646572 ct your provider 804a520 00000000 01000000 00000000 00000000 ................ 804a530 00000000 00000000 00000000 00000000 ................ 804a540 e554f49f 93dcd6dc bb0bdc9b ad60edd0 .T...........`.. 804a550 7a9beb67 60277cb2 dd9e0886 0797aeec z..g`'|......... 804a560 eba28b7e 7e615a3a 6d37d51a 97c2ea11 ...~~aZ:m7...... ...
The first column of the output in listing 3 specifies the starting addresses in hexadecimal, followed by the stored data in the next four columns. The last column represents the stored data in printable characters. So somewhere in the first four columns of the output is the array of characters that form the encryption key (password) and the encrypted shell script. Comparing the original C source code and Listing 3, you can see that the password most likely begins at address 0x804a540. After comparing other executables, I determined that the first address after the zeroes leading the “Please contact your provider” text usually is the starting address. To retrieve these arrays, such as the one depicted in Listing 2, we also need to look at the disassembled code. We use objdump again here, except this time with the -d option, for disassemble, as shown in Listing 4.
Listing 4. The output of objdump -d pub.sh.x shows information needed to find the encrypted script. Lines in parentheses were added.
8048e52: 68 28 01 00 00 push $0x128 (Length of encryption key) 8048e57: 68 40 a5 04 08 push $0x804a540 (Key address) 8048e5c: e8 17 fb ff ff call 0x8048978 8048e61: 83 c4 10 add $0x10,%esp 8048e64: 83 ec 08 sub $0x8,%esp 8048e67: 6a 08 push $0x8 (Length of shll) 8048e69: 68 72 a6 04 08 push $0x804a672 (shll address) 8048e6e: e8 a0 fb ff ff call 0x8048a13 8048e73: 83 c4 10 add $0x10,%esp 8048e76: 83 ec 08 sub $0x8,%esp 8048e79: 6a 03 push $0x3 (length of inlo) 8048e7b: 68 8a a6 04 08 push $0x 8048e80: e8 8e fb ff ff call 0x8048a13
The last two columns represent assembly instructions. The movl instruction is used to move data—movl Source, Dest. The Source and Dest are prefixed with $ when referencing a C constant. The push takes a single operand, the data source, and stores it at the top of stack.
Now that we have the basics of objdump, we can proceed to extract the encryption password and eventually the shell code.
In the intermediate C code produced by shc, about nine arrays are referenced by the variables pswd, shll, inlo, xecc, lsto, chk1, opts, txt and chk2. The pswd variable stores the encryption key, and the txt variable stores the encrypted shell text. shc hides the useful information as smaller arrays within these variables. Thus, obtaining the actual array involves two steps. First, identify the length of the array. Second, identify the starting address of the array.
The objdump output needs to be looked at in detail to obtain the actual array length and the starting address. My first hint here is to look for all addresses that are within the data section (Listing 2) of the disassembled object code. Next, seek out all the push and mov commands in Listing 4. Addresses will be different for different scripts, but when you encrypt a few scripts and read the resulting C code, the patterns become familiar.
The 804a540 address seems to correspond to the pswd variable, the encryption key. The length of the useful portion of the encryption key is represented by 0x128, or 296 in decimal form. Similarly, the next variables, shll and inlo, have useful lengths of 0x8 and 0x3 and starting addresses of 804a672 and 804a68a, respectively. This way, we are able to obtain the starting addresses and lengths of all nine variables. Next, we need to be able to decrypt the original shell script using only the binary as input.
In shc, before the shell script itself is encrypted, many other pieces of information are encrypted. Furthermore, the RC4 implementation maintains state between encrypting and decrypting each individual piece of information. This means that the order in which shc encrypts and decrypts information must be maintained. Failure to do so results in illegible text. To extract the original shell script, we need to perform several decryptions. For this step, I wrote a small program called deshc, using the existing code from one of the intermediate C files. The program reads two files as its input, the binary executable and an input file that specifies the array lengths and addresses. deshc executes the following four steps:
Reads binary executable.
Extracts data section from the disassembled output.
Retrieves individual arrays based on input file.
Decrypts individual arrays in order, so that the RC4 state is maintained.
Based on the objdump output, I have arrived at the following array lengths and addresses for the pub.sh.x executable:
pswd 0x128 0x804a540 shll 0x8 0x804a672 inlo 0x3 0x804a68a xecc 0xf 0x804a68e lsto 0x1 0x804a6a4 chk1 0xf 0x804a6a6 opts 0x1 0x804a6be txt 0x76 0x804a6e0
All of these parameters are used in an input file to deshc, which then decrypts and prints the original shell script.
- Let's Go to Mars with Martian Lander
- My Childhood in a Cigar Box
- Applied Expert Systems, Inc.'s CleverView for TCP/IP on Linux
- Papa's Got a Brand New NAS
- Rogue Wave Software's TotalView for HPC and CodeDynamics
- Panther MPC, Inc.'s Panther Alpha
- Debugging Democracy
- NethServer: Linux without All That Linux Stuff
- Simplenote, Simply Awesome!