The most likely place you will be placing shellcode is into a buffer. Even more likely, this buffer will be a character array. If you go back and look at our shellcode
\xbb\x00\x00\x00\x00\xb8\x01\x00\x00\x00\xcd\x80
you will notice that there are some nulls ( \x00 ) present. These nulls will cause shellcode to fail when injected into a character array because the null character is used to terminate strings. We need to get a little creative and find ways to change our nulls into non-null opcodes. There are two popular methods of doing so. The first is to simply replace assembly instructions that create nulls with other instructions that do not. The second method is a little more complicated ”it involves adding nulls at runtime with instructions that do not create nulls. This method is also tricky because we will have to know the exact address in memory where our shellcode lies. Finding the exact location of our shellcode involves using yet another trick, so we will save this second method for the next , more advanced, example.
We'll use the first method of removing nulls. Go back and look at our three assembly instructions and the corresponding opcodes:
mov ebx,0 \xbb\x00\x00\x00\x00 mov eax,1 \xb8\x01\x00\x00\x00 int 0x80 \xcd\x80
The first two instructions are responsible for creating the nulls. If you remember assembly, the Exclusive OR ( xor ) instruction will return zero if both operands are equal. This means that if we use the Exclusive OR instruction on two operands that we know are equal, we can get the value of without having to use a value of in an instruction. Consequently, we won't have to have a null opcode. Instead of using the mov instruction to set the value of EBX to , let's use the Exclusive OR ( xor ) instruction. So, our first instruction
mov ebx,0
becomes
xor ebx,ebx
One of the instructions has hopefully been removed of nulls ”we'll test it shortly.
You may be wondering why we have nulls in our second instruction. We didn't put a zero value into the register, so why do we have nulls? Remember, we are using a 32-bit register in this instruction. We are moving only one byte into the register, but the EAX register has room for four. The rest of the register is going to be filled with nulls to compensate.
We can get around this problem if we remember that each 32-bit register is broken up into two 16-bit "areas"; the first-16 bit area can be accessed with the AX register. Additionally, the 16-bit AX register can be broken down further into the AL and AH registers. If you want only the first 8 bits, you can use the AL register. Our binary value of 1 will take up only 8 bits, so we can fit our value into this register and avoid EAX getting filled up with nulls. To do this, we change our original instruction
mov eax,1
to one that uses AL instead of EAX :
mov al,1
Now we should have taken care of all the nulls. Let's verify that we have by writing our new assembly instructions and seeing if we have any null opcodes.
Section .text global _start _start: xor ebx,ebx mov al,1 int 0x80
Put it together and disassemble using objdump .
[slap@0day root] nasm -f elf exit_shellcode.asm [slap@0day root] ld -o exit_shellcode exit_shellcode.o [slap@0day root] objdump -d exit_shellcode exit_shellcode: file format elf32-i386 Disassembly of section .text: 08048080 <.text>: 8048080: 31 db xor %ebx,%ebx 8048085: b0 01 mov[slap@0day root] nasm -f elf exit_shellcode.asm [slap@0day root] ld -o exit_shellcode exit_shellcode.o [slap@0day root] objdump -d exit_shellcode exit_shellcode: file format elf32-i386 Disassembly of section .text: 08048080 <.text>: 8048080: 31 db xor %ebx,%ebx 8048085: b0 01 mov $0x1,%al 804808a: cd 80 int $0x80x1,%al 804808a: cd 80 int[slap@0day root] nasm -f elf exit_shellcode.asm [slap@0day root] ld -o exit_shellcode exit_shellcode.o [slap@0day root] objdump -d exit_shellcode exit_shellcode: file format elf32-i386 Disassembly of section .text: 08048080 <.text>: 8048080: 31 db xor %ebx,%ebx 8048085: b0 01 mov $0x1,%al 804808a: cd 80 int $0x80x80
All our null opcodes have been removed, and we have significantly reduced the size of our shellcode. Now you have fully working, and more importantly, injectable shellcode.