Monday, September 2, 2013

Building a multiplatform shellcode header

This post is a quick overview of one method used to quickly create machine code capable of branching to up to four different platforms:

  • 32-bit Linux
  • 64-bit Linux
  • 32-bit Windows
  • 64-bit Windows

The multi-platform process is a multi-step process that can be broken into small pieces of code - first to detect the processor architecture, then to determine operating system.

Determining Architecture

When I made my last post, I was reminded of an awesomely shorter technique I found while googling for architecture detection developed by the guys over at ragestorm.net. Because theirs is shorter (which in my opinion makes it cooler since we aren't focusing on strictly alphanumeric code), I'll use their getCPU in stead of my own for this little demonstration. Admittedly, I changed a 'jz' to a 'jnz' and re-ordered the code a little bit. Instead of bits_32 we're going to have a determine_32_os label:

arch_detect:
  xorl %eax, %eax
  rex
  nop
  jnz determine_32_os

This is a 6-byte header that works on both windows and linux for determining the CPU architecture, so lets get started with determining the operating system.

Determining operating system using segment registers

EDIT:

Apparently something was seriously wrong with my testing environment. Whether it was due to virtualization or this or that, we suspect that the controversy came from running windows 8.1 in a VM on an amd system with the VM providing an intel interface. In any case, here's the work-around that doesn't rely on the parity bit: segment registers.

All of the segment registers aren't always used by the operating system's runtime environment. In the case of windows, the %ds segment register is nearly always set, whereas in linux, it is nearly always zero. To that end, we can use the following snippet of code to determine operating system by testing for a zero value in the %ds segment register (zero means linux), but this is only valid for 64 bit. In a 32 bit world, %fs is 0 on linux while it has a value on windows:
determine_64_os:
  mov %ds, %eax
  test %eax, %eax
  jnz win64_code
  jmp lin64_code

determine_32_os:
  mov %fs, %eax
  test %eax, %eax
  jz lin32_code 

Final code:

The final version of this header comes out to a 20 bytes that could definitely be made shorter:
arch_detect:
  xorl %eax, %eax
  rex
  nop
  jnz determine_32_os

determine_64_os:
  mov %ds, %eax
  test %eax, %eax
  jnz win64_code
  jmp lin64_code

determine_32_os:
  mov %fs, %eax
  test %eax, %eax
  jz lin32_code 

win32_code:
  nop

lin64_code: 
  nop

win64_code:
  nop

lin32_code:
  nop

And it disassembles to:

0000000000000001 :
   1: 31 c0                 xor    %eax,%eax
   3: 40 90                 rex xchg %eax,%eax
   5: 75 08                 jne    f 

0000000000000007 :
   7: 8c d8                 mov    %ds,%eax
   9: 85 c0                 test   %eax,%eax
   b: 75 0a                 jne    17 
   d: eb 07                 jmp    16 

000000000000000f :
   f: 8c e0                 mov    %fs,%eax
  11: 85 c0                 test   %eax,%eax
  13: 74 03                 je     18 

0000000000000015 :
  15: 90                    nop

0000000000000016 :
  16: 90                    nop

0000000000000017 :
  17: 90                    nop

0000000000000018 :
  18: 90                    nop

Further reading:


No comments:

Post a Comment