Hey, kudos!
You don't run arbitrary scripts either!

My apologies for the JS on this page…
it's prettify.js for syntax highlighting
in code blocks. I've added one line of
CSS for you; the rest of this site
should work fine.

      ♥Ⓐ isis

code.

Learning Assembly Through Writing Shellcode

Months ago, I wrote hello world in X86 Assembly, and later that same day I wrote hello world in Python. Python is fast, elegant, and powerful. But unfortunately, it doesn’t really give you an understanding of what’s going on inside your computer. And any good little hacker should know precisely what’s going on inside their computer.

Every time I start teaching myself some complicated thing, I try to make the learning process enjoyable because I know that I’ll retain more information if I can apply it to something fun or useful. Being a terribly precocious kid, I taught myself quantum mechanics when I was fourteen. It was really difficult, and I probably wouldn’t have been able to pull it off if I hadn’t made it fun. And, oh, did I make it fun: FOIA’ed thermonuclear weapons manuals, ten years expired, from some obscure and slightly sketchy web page. I didn’t mean any harm, and I neither was nor am a proponent of nuclear weapons production, maintenance, or warfare. I wasn’t planning on starting up an Uranium-238 enrichment program, or searching the black markets for hollow plutonium cores. I wanted to learn physics, and what’s more fun than learning how to destroy things?

Assembly languages are cumbersome and arcane. The learning curve is steep, and progress is always slow compared to higher level programming languages. Fortunately, however, Assembly can be used to destroy things! Enter shellcode.

The best introduction I found to writing shellcode was in Gray Hat hacking, so I’m going to quote the first few pages of the Linux shellcoding chapter, and then leave you to somehow obtain your own copy.

Basic Linux Shellcode

The term “shellcode” refers to self-contained binary code that completes a task. The task may range from issuing a system command to providing a shell back to the attacker, as was the original purpose of shellcode.

There are basically three ways to write shellcode:

  • Directly write the hex opcodes.
  • Write a program in a high level language like C, compile it, and then disassemble it to obtain the assembly imstructions and hex opcodes.
  • Write as assembly program, assemble the program, and then extract the hex opcodes from the binary.

Writing the hex opcodes directly is a little extreme. We will start with learning the C approach, but quickly move to writing assembly, then to extraction of the opcodes. In any event, you will need to understand low level (kernel) functions such as read, write, and execute. Since these system functions are performed at the kernel level, we will need to learn a little about how user processes communicate with the kernel.

System Calls

The purpose of the operating system is to serve as a bridge between the user (process) and the hardware. There are basically three ways to communicate with the operating system kernel:

  • Hardware interrupts  For example, an asynchronous signal from the keyboard
  • Hardware traps  For example, the result of an illegal “divide by zero” error
  • Software traps   For example, the request for a process to be scheduled for execution

Software traps are the most useful to […] hackers because they provide a method for the user process to communicate to the kernel. The kernel abstracts some basic system level functions from the user and provides an interface through a system call.

Definitions for system calls can be found on a Linux system in the following file:

$ cat /usr/include/asm/unistd.h

#ifndef _ASM_I386_UNISTD_H_

#define _ASM_I386_UNISTD_H_

#define __NR_exit 1

…snip…

#define __NR_execve 11

…snip…

#define __NR_setreuid 70

…snip…

#define __NR_dup2 99

…snip…

#define __NR_socketcall 102

…snip…

#define __NR_exit_group 252

…snip…

In the next section, we will begin the process, starting with C.

System Calls by C

At a C level, the programmer simply uses the system call interface by referring to the function signature and supplying the proper number of parameters. The simplest way to find out the function signature is to look up the function’s man page.

For example, to learn more about the execve system call, you would type

$ man 2 execve

This would display the following man page:

[]

As the next section shows, the previous system call can be implemented directly with assembly.

System Calls by Assembly

At an assembly level, the following registries are loaded to make a system call:

  • eax   Used to load the hex value of the system call (see unistd.h earlier)
  • ebx  Used for first parameter—ecxis used for second parameter, edx for third, esi for fourth, and edifor fifth

If more than five parameters are required, an array of the parameters must be stored in memory and the address of that array stored in ebx.

Once the registers are loaded, an int 0x80 assembly instruction is called to issue a software interrupt, forcing the kernel to stop what it is doing and handle the interrupt. The kernel first checks the parameters for correctness, then copies the register values to kernel memory space and handles the interrupt by referring to the Interrupt Descriptor Table (IDT).

The easiest way to understand this is to see an example, as in the next section.

Exit System Call

The first system call we will focus on executes exit(0). The signature of the exit system call is as follows:

  • eax   0x01 (from the unistd.h file earlier)
  • ebx   User-provided parameter (in this case 0)

Since this is our first attempt at writing system calls, we will start with C.

Starting with C

The following code will execute the function exit(0):

$ cat exit.c

#include

main(){

exit(0);

}

Go ahead and compile the program. Use the -static flag to compile in the library call to exit as well.

$ gcc -static -0 exit exit.c

Now launch gdb in quiet mode (skip banner) with the -q flag. Start by setting a breakpoint at the main function; then run the program with r.Finally, disassemble the _exit function call with dissass _exit.

[]

[NOTE FROM isis agora lovecruft: My computer is doing weird things here, because I use a modern Linux kernel which supports stack randomization, like a good modern OS should. While there are ways to turn stack randomization off for practice purposes, I feel like it’s better to just get used to stack randomization because you’re going to have to deal with that randomization breaking your shellcode when you execute it on a system other than the one on which it was written. I’m going to explain what happened here, and leave out the silly, non-stack-randomized explanation in the book.]

You can see that the function starts by loading the last user input (%edi) into the 64-bit registry space %rdx at line \<+0>. For a good paper on assembly for 64-bit processors, see the attached documents at the end of this post. Also, to understand the hex in the above dump, see the attached chart at the end of this post titled “Machine Hex Opcodes to Assembly”. For now, you’re just going to have to take my word for it that this information is what I’m telling you it is. So, next, the program stores a bunch of index registers (0xffffffffffffff…) and a subtraction from those registers (…b0) into the delay slot %r9. Then, it moves an accumulator (0xe7) to the delay slot %r8d, moves a return from interrupt (0x3c) to %esi, and then jumps to line \<_exit+48>. Then it moves the last user input (0), which was temporarily stored in %rdx to %rdi. Then, everything that was accumulated into the delay slot %r8d gets moved into the %eax registry.

At this point, we’re exactly where we would be if stack randomization were turned off. Except that if it were turned off, all of this would have only taken two lines of assembly code without any jumps. Stack randomization: Is. Fucking. Hell. And it also is supposed to keep your computer safe from hackers, but as I said, there’s ways around it. But, as you can see, it does make things complicated.

So, next, that program makes the syscall at \<+54>. Finally. Then it jumps around and copies a bunch more things into other literally random places, and I’m not going to go through with explaining the rest of it, because it’s complicated and I’m tired of typing percent signs all over the place.

Move to Assembly

Now for rewriting the exit() syscall in assembly:

[]

Alright, so this is the code the book gives. This code will not produce working shellcode for a system with stack randomization. As you can see, the assembly that my machine dumped was extremely complicated. We would need to rewrite that dump in assembly language to get working shellcode for my system, but another trouble/benefit with stack randomization is that, next time I call that program, it’s going to operate at some other place in memory. Also, if I reboot my computer, all the system calls are going to be at different locations in memory as well. But, let’s just pretend that this would work for now, so that I can show you without typing too much and confusing you. Back to the book.

Assemble, Link, and Test

Once we have the assembly file, we can assemble it with nasm, link it withld, then execute the file as shown:

$ nasm -f elf64 exit.asm

\$ ld exit.o -o exit

\$ ./exit

Not much happened, because we simply called exit(0), which exited the process politely. Luckily for us, there is another way to verify.

Verify with strace

As in our previous example, you may need to verify the execution of a binary to ensure the proper system calls were executed:

$ strace ./exit

0

_exit(0) = ?

As we can see, the _exit(0) syscall was executed!

I’m not going to go through the rest of the book. Obviously, the exit syscall is the first and most basic one. Any syscalls could be used, such as setreuid (which would grant user permissions), execve (to execute code), or bind and listen (to bind ports).

The last step to producing shellcode is to turn the assembly program into a single hex string, because often only one string can be injected into a vulnerable program. To obtain the hex opcodes, we simply use the objdump tool with the -d flag for disassembly:

[]

The most important thing about this printout is to verify that no NULL characters (\x00) are present in the hex opcodes. If there are any NULL characters, the shellcode will fail when we place it into a string for injection during an exploit.

So the shellcode for the exit(0) program would be “\x31\xc0\x31\xdb\xb0\x01\xcd\x80“.

Voila, shellcode. Assembly is still cool.

Further Reading:

Here’s a few free books on Assembly programming. And here’s a Hacker News thread with useful comments on learning Assembly. You’ll need a list of system calls. And you’ll probably want to keep a conversion table from hex to decimal to binary handy, unless you’re one of those geeks who has a binary clock on their desktop.

You’ll also need this Intel PDF explaining Assembly for 64-bit processors: Understanding and Analyzing Assembly Language.pdf

And you’re going to find this PDF chart which translates machine hex opcodes into assembly very useful: Machine Hex Opcodes to Assembly.pdf

And here’s a basic tutorial on shellcoding for Linux and Windows.

And then you can move up to analyzing other’s shellcode, there’s some here.

[]: http://www.patternsinthevoid.net/blog/wp-content/uploads/2011/09/manexecve.png

[]: http://www.patternsinthevoid.net/blog/wp-content/uploads/2011/09/disemblyexit.png

[]: http://www.patternsinthevoid.net/blog/wp-content/uploads/2011/09/assemblyexit.png

[]: http://www.patternsinthevoid.net/blog/wp-content/uploads/2011/09/objdump.png


<<< Robots Windsurfing Asteroids DIY Compostable Electronics >>>

blogroll

social