Hello World!
Hello World!
This is, perhaps obviously, a starting post. So I’ve opted to start with something reasonably fun:
a breakdown of how to write & test a piece of ‘hello world’ shellcode assembly targeting Linux.
We’ll start with a simple assembly program which prints output using interrupts to trigger system calls,
move to compiling just part of that function, dumping it as hex, and calling it from C, and then use
a couple of tricks to remove the nulls and parameters from it–which will allow it to be used as shellcode.
These tricks are useful for ‘classic’ buffer overflow attacks (which I’ll likely dig into at some later date).
I opted to go with x86-32 bit assembly (since it’s what I know well off the top of my head). Perhaps someday I’ll revisit this post but use 64 bit assembly.
The code samples here are going to be available on my github page here. If you want to follow along, you’ll need to install the GCC and GAS to compile the samples, and if you’re on a x86_64 system, you may need to configure your system for multilib.
# For multilib, since I wrote this on an x86_64 machine:
$dpkg --add-architecture i386
$apt-get install build-essentials gcc-multilib libstdc++6:i386
Getting Started
First, we’ll start with a basic “Hello World”-esque assembly program. We’ll be using the GNU assembler syntax. This illustrates how to make system calls using int 0x80 on linux–something we’ll use more later.
# Declare the 'main' function for the linker.
.global main
# Declare the local variables--a 'Hello Reader' string,
# and the length of that string.
.data
HelloMsg:
.ascii "Hello Reader!\n"
.set len, .-HelloMsg
.text
main:
#Print Hello Msg
movl $0x4, %eax # Syscall Id for "Write"
movl $0x1, %ebx # File Descriptor for stdout
movl $HelloMsg, %ecx # Address of "HelloMsg"
movl $len, %edx # Length of "HelloMsg"
int $0x80
#Exit the program.
mov $0x1, %eax # Syscall id for Exit
mov $0x0, %ebx # Exit code = 0
int $0x80
We’re using the instruction int 0x80 to make syscalls here–this basically tells the processor to go to the kernel and call a function with a set of parameters–in particular, the EAX register controls which syscall number you want to use (the two we’re using are Write, which is 0x4, and Exit, which is 0x1), and the rest of the registers control what parameters are passed to the function (in order: EBX, ECX, EDX, ESI, EDI, EBP). The full list of syscalls is available in the linux kernel. The parameters are documented in man pages, but they’re basically the standard POSIX calls–in general, you can access them via a man call like:
$ man 2 write
You should be able to compile and run the above with:
$ gcc basic-hello-world.s -m32 -o Hello-Reader
$ ./Hello-Reader
Hello Reader!
Calling this from C
The above program is compiled to a full executable–if you run “objdump -t Hello-Reader” you’ll get a whole bunch of symbols, and on my system it compiled down to a 4832 byte file. If we’re going to do a classic buffer overflow attack, we’re likely pushing our shellcode into input somewhere, and don’t need the full executable file to do that–just the instructions we’ll want to execute.
So in order to just get down to the core of our assembly program and test it without having to actually inject it anywhere, we’ll create two files–first, an assembly file containing the code we wish to execute, and second a C wrapper that contains the hexadecimal representation of just the assembly we’re calling into.
First, the assembly function: we’ll start with just a simple assembly function which wraps our syscall to “write” in a function that takes two arguments and passes them along.
.text
write_call:
# Start the function--store the stack frame and save ebx,
# as ebx is a callee saved register.
pushl %ebp
movl %esp, %ebp
pushl %ebx
movl $0x4, %eax # Syscall Id for "Write"
movl $0x1, %ebx # File Descriptor for stdout
movl 8(%ebp), %ecx # Address of "HelloMsg"
movl 12(%ebp), %edx # Length of "HelloMsg"
int $0x80
#End the function--restore the stack frame and ebx
# before returning to the caller.
popl %ebx # restore ebx
popl %ebp
ret
We can compile this to just object code (which is essentially just the compiled code for this function) using ‘as’ (with the –32 flag so that it’s 32 bit), and then we can dump the machine code of this function using objdump. It should be similar to:
$ as asm-function.s -o asm-function.o --32
$ objdump -d asm-function.o
asm-function.o: file format elf32-i386
Disassembly of section .text:
00000000 <write_call>:
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: 53 push %ebx
4: b8 04 00 00 00 mov $0x4,%eax
9: bb 01 00 00 00 mov $0x1,%ebx
e: 8b 4d 08 mov 0x8(%ebp),%ecx
11: 8b 55 0c mov 0xc(%ebp),%edx
14: cd 80 int $0x80
16: 5b pop %ebx
17: 5d pop %ebp
18: c3 ret
Then we can call that machine code from a simple C-wrapper, by dumping it into a C-style string, populating an executable buffer with the hex, and calling the function with the appropriate arguments.
#include <string.h>
#include <sys/mman.h>
char code[] = "\x55\x89\xe5\x53\xb8\x04\x00\x00\x00\xbb\x01\x00\x00\x00\x8b\x4d\x08\x8b\x55\x0c\xcd\x80\x5b\x5d\xc3";
char msg[] = "Hello Reader!\n";
int main(){
void * buf;
// mmap a executable memory block and copy our code into it.
buf = mmap(0, sizeof(code), PROT_EXEC | PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
memcpy(buf, (void*) code, sizeof(code));
// Call our code as a function that takes a pointer and an integer,
((void (*) (char*, int))buf)(msg, sizeof(msg));
return 0;
}
This can be compiled with
$ gcc asm-function-c-wrapper.c -m32 -o c-wrapper
$ ./c-wrapper
Hello Reader!
If you want to dig more into what this is doing, you can compile it with the -g flag and step through it with gdb. Once you get into the call into our code, some useful commands are “si”, which steps a single assembly instruction, and “x/10i $eip”, which disassembles the next 10 instructions after the instruction pointer.
Removing Nulls
Now that we can limit our code to a simple C function, we should look more closely at the hexadecimal we’re using. In many buffer overflow attacks, we’re providing input to an unsafe function like strcpy or gets, which terminates on nulls–so it’s going to be handy to avoid null characters. Unfortunately, we’ve got several in that function we’ve constructed above. However, we can get rid of all of them with a fairly simple trick: instead of “movl $0x1, %ebx”, which winds up expanding out to “movl $0x00000001, %ebx” (since we’re moving 4 bytes into a 4 byte register), we’ll zero the register and then just access the lowest order byte like so:
# this is equivalent to movl $0x1, %ebx
xor %ebx, %ebx # zero out ebx
movb $0x1, %bl # move a byte into the lowest byte of %ebx.
Note that we can’t make assumptions about the upper bytes of EBX, so we have to ensure it’s zeroed first.
We’re also going to add another nuance to make this a little more realistic–we’d like to print our message without passing any parameters into the assembly, since in general you can’t assume you’ll be able to do that during exploitation. However, if we’re including our message in the assembly we’re writing, and then copying that assembly to memory that we allocated with mmap, we won’t necessarily know what address the string is at, which is a parameter to write(). (This is generally the case when writing shellcode intended for exploitation–you don’t always necessarily know what address your code is going to end up in.)
To get around that problem, we’ll use a nuance of how “call” works–for short distances, call uses relative addressing (which allows us to get around not knowing the address we’re in), and call pushes the address of the ‘next instruction’ to the stack. We can then pull that address off the stack and use it.
That gives us this assembly:
.text
write_call:
jmp tail
head:
# This is a null-free "movl $0x4, %eax"
xor %eax,%eax
movb $0x4, %al
# This is "movl $0x1, %ebx"
xor %ebx, %ebx
movb $0x1, %bl
# Get the address of the .ascii section below
# it was pushed to the stack by the "call" below.
popl %ecx
# "movl $0x12, %edx -- the length of "Hello Shellcoder!\n"
xor %edx, %edx
movb $0x12, %dl
# Syscall
int $0x80
# And we'll exit, because we've smashed EBX, which we'd because
# required to save if we wanted to use the standard calling convention.
xor %eax,%eax
movb $0x1, %al
xor %ebx,%ebx
int $0x80
tail:
call head
.ascii "Hello Shellcoder!\n"
As before, we can compile this and dump the hexadecimal:
$ as null-free-write.s --32 -o null-free-write.o
$ objdump -D null-free-write.o
null-free-write.o: file format elf32-i386
Disassembly of section .text:
00000000 <write_call>:
0: eb 17 jmp 19 <tail>
00000002 <head>:
2: 31 c0 xor %eax,%eax
4: b0 04 mov $0x4,%al
6: 31 db xor %ebx,%ebx
8: b3 01 mov $0x1,%bl
a: 59 pop %ecx
b: 31 d2 xor %edx,%edx
d: b2 12 mov $0x12,%dl
f: cd 80 int $0x80
11: 31 c0 xor %eax,%eax
13: b0 01 mov $0x1,%al
15: 31 db xor %ebx,%ebx
17: cd 80 int $0x80
00000019 <tail>:
19: e8 e4 ff ff ff call 2 <head>
1e: 48 dec %eax
1f: 65 6c gs insb (%dx),%es:(%edi)
21: 6c insb (%dx),%es:(%edi)
22: 6f outsl %ds:(%esi),(%dx)
23: 20 53 68 and %dl,0x68(%ebx)
26: 65 6c gs insb (%dx),%es:(%edi)
28: 6c insb (%dx),%es:(%edi)
29: 63 6f 64 arpl %bp,0x64(%edi)
2c: 65 72 21 gs jb 50 <tail+0x37>
2f: 0a .byte 0xa
And we can drop that into a slightly modified C wrapper (we remove the arguments we passed to the previous iteration of this):
#include <string.h>
#include <sys/mman.h>
char code[] = "\xeb\x17\x31\xc0\xb0\x04\x31\xdb\xb3\x01\x59\x31\xd2\xb2\x12\xcd\x80\x31\xc0\xb0\x01\x31\xdb\xcd\x80\xe8\xe4\xff\xff\xff\x48\x65\x6c\x6c\x6f\x20\x53\x68\x65\x6c\x6c\x63\x6f\x64\x65\x72\x21\x0a";
int main(){
void * buf;
// mmap a executable memory block and copy code into it.
buf = mmap(0, sizeof(code), PROT_EXEC | PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
memcpy(buf, (void*) code, sizeof(code));
((void (*) (void))buf)();
return -1;
}
Which we can compile and run in a similar way, which should output the new greeting as expected:
$ gcc shellcode-c-wrapper.c -m32
$ ./a.out
Hello Shellcoder!
Hope this was educational/helpful!