Last active
October 15, 2023 08:36
-
-
Save amkisko/e0bf0f845bac886bc82566787906d28b to your computer and use it in GitHub Desktop.
Simple DPI with notifications using PacketFu, Redis, Discord and Telegram
This file contains 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
# AUTHOR: Andrei Makarov (github.com/amkisko) | |
require "packetfu" | |
require "redis" | |
require "discordrb/webhooks" | |
require "telegram/bot" | |
require "time" | |
require "httpx" | |
require "dotenv" | |
Dotenv.load | |
$notifications = [] | |
$redis = Redis.new | |
$monitor_name = ENV["MONITOR_NAME"] | |
$guard_name = ENV["GUARD_NAME"] | |
$service_name = ENV["SERVICE_NAME"] | |
["#{$monitor_name}:*", "#{$guard_name}:*"].each do |pattern| | |
$redis.scan_each(match: pattern) do |key| | |
puts "Removing #{key}" | |
$redis.del(key) | |
end | |
end | |
def addr_info(addr) | |
url = "http://ip-api.com/json/#{addr}?fields=status,message,country,countryCode,region,regionName,city,zip,lat,lon,timezone,isp,org,as,asname,reverse,mobile,proxy,hosting,query" | |
JSON.parse(HTTPX.get(url).body, symbolize_names: true) | |
rescue StandardError => e | |
p e | |
end | |
def monitor | |
puts "Starting monitor" | |
iface = PacketFu::Utils.default_int | |
ip = PacketFu::Utils.default_ip | |
port = ENV["EXTERNAL_PORT"] | |
proto = ENV["EXTERNAL_PROTO"] | |
cap = PacketFu::Capture.new(iface: iface) | |
cap.bpf(filter: "ip host #{ip} and #{proto} port #{port}") | |
cap.start | |
cap.stream.each do |p| | |
pkt = PacketFu::Packet.parse p | |
next unless pkt.ip_daddr == ip && pkt.udp_dst == port.to_i | |
pk = "#{$monitor_name}:#{proto}://#{pkt.ip_saddr}:#{pkt.udp_src}@#{pkt.ip_daddr}:#{pkt.udp_dst}" | |
dt = begin | |
val = $redis.get(pk) | |
Time.parse(val) | |
rescue TypeError, ArgumentError | |
nil | |
end | |
if dt.nil? || (Time.now.to_i - dt.to_i > 5*60) | |
info = [addr_info(pkt.ip_saddr)].map do |data| | |
[data.dig(:reverse), data.dig(:isp), data.dig(:country), data.dig(:city), data.dig(:mobile) ? "mobile" : nil, data.dig(:proxy) ? "proxy" : nil, data.dig(:hosting) ? "hosting" : nil].compact.join(" ") | |
end.first | |
$notifications << ["Detected activity", pkt.ip_saddr.to_s, info].compact.join(" ") | |
end | |
$redis.set(pk, Time.now) | |
sleep(1) | |
end | |
end | |
def telegram_msg(bot, msg) | |
send_notification("Bot envoked by #{msg.from.first_name} with #{msg.text}") | |
if msg.text == "/start" | |
bot.api.send_message(chat_id: msg.chat.id, text: "Hello, #{msg.from.first_name}") | |
elsif msg.text == "/stop" | |
bot.api.send_message(chat_id: msg.chat.id, text: "Bye, #{msg.from.first_name}") | |
elsif msg.text == "/status" | |
seed_set = !!$redis.get("#{$guard_name}:seed") | |
service_chat_id_eq = "#{msg.chat.id}" == $redis.get("#{$service_name}:service_chat_id") | |
text = "Seed: #{seed_set}\nService: #{service_chat_id_eq}\n" | |
bot.api.send_message(chat_id: msg.chat.id, text: text) | |
elsif msg.text == "/#{$guard_name}" | |
seed = "#{((0..9).to_a+('A'..'Z').to_a).shuffle.take(6).join}" | |
$redis.set("#{$guard_name}:seed", seed) | |
bot.api.send_message(chat_id: msg.chat.id, text: "New #{$guard_name}") | |
puts "New seed: #{seed}" | |
elsif msg.text.start_with? "/#{$guard_name}" | |
seed = $redis.get("#{$guard_name}:seed") | |
if seed && msg.text.gsub(/^\/#{$guard_name}/, "").strip == "seed #{seed}" | |
$redis.del("#{$guard_name}:seed") | |
$redis.set("#{$service_name}:service_chat_id", msg.chat.id) | |
bot.api.send_message(chat_id: msg.chat.id, text: "Welcome, #{msg.from.first_name} (#{msg.chat.id})") | |
else | |
bot.api.send_message(chat_id: msg.chat.id, text: "Not authenticated") | |
end | |
end | |
end | |
def telegram_bot | |
puts "Starting telegram bot" | |
Telegram::Bot::Client.run(ENV["TELEGRAM_TOKEN"]) do |bot| | |
bot.listen do |message| | |
telegram_msg(bot, message) | |
sleep(1) | |
end | |
end | |
puts "Stopping telegram bot" | |
end | |
def send_notification(content) | |
client = Discordrb::Webhooks::Client.new(url: ENV["DISCORD_WEBHOOK_URL"]) | |
client.execute do |builder| | |
builder.content = content | |
end | |
Telegram::Bot::Client.run(ENV["TELEGRAM_TOKEN"]) do |bot| | |
chat_id = $redis.get("#{$service_name}:service_chat_id") | |
if chat_id | |
bot.api.send_message(chat_id: chat_id, text: content) | |
end | |
end | |
end | |
def notifier | |
puts "Starting notifier" | |
while 1 | |
sleep(1) | |
next unless $notifications.size > 0 | |
data = $notifications.shift | |
send_notification(data) | |
end | |
puts "Stopping notifier" | |
end | |
threads = [ | |
Thread.new{monitor}, | |
Thread.new{telegram_bot}, | |
Thread.new{notifier} | |
] | |
threads.each(&:join) |
This file contains 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
TELEGRAM_TOKEN="123456:ABCDEF" | |
DISCORD_WEBHOOK_URL="https://discordapp.com/api/webhooks/123456/abcdefg" | |
EXTERNAL_IP="123.123.123.123" | |
EXTERNAL_PORT=2345 | |
EXTERNAL_PROTO="udp" | |
MONITOR_NAME="packetfu" | |
GUARD_NAME="watchdog" | |
SERVICE_NAME="monitor" |
This file contains 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
source "https://rubygems.org" | |
gem "dotenv" | |
gem "packetfu" | |
gem "redis" | |
gem "discordrb-webhooks" | |
gem "telegram-bot-ruby" | |
gem "httpx" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment