2025-11-08
Tags: misc
[1mEmacsjail[22m was an Emacs Lisp jail challenge I wrote for
UIUCTF 2025.
There were 17 solves during the competition, some of which were
quite creative!
== [1m[4mDescription[22m[24m
Mom, can we have SBCL?
No, we have SBCL at home.
SBCL at home:
[33m;;; -*- lexical-binding:t -*-[0m[1m[37m[0m
[1m[37m[0m
[1m[37m([0m[37mdefun[0m[1m[37m [0m[1m[36mcheck[0m[1m[37m [0m[1m[37m([0m[1m[36minput[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37m([0m[1m[36mnot[0m[1m[37m [0m[1m[37m([0m[1m[36mstring-match-p[0m[1m[37m [0m[33m".*(.*).*"[0m[1m[37m [0m[1m[36minput[0m[1m[37m)))[0m[1m[37m[0m
[1m[37m[0m
[1m[37m([0m[37mdefun[0m[1m[37m [0m[1m[36mpanic[0m[1m[37m [0m[1m[37m([0m[1m[36mstr[0m[1m[37m [0m[33m&rest[0m[1m[37m [0m[1m[36mfmt[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37m([0m[37mmessage[0m[1m[37m [0m[1m[36mstr[0m[1m[37m [0m[1m[36mfmt[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37m([0m[37mkill-emacs[0m[1m[37m))[0m[1m[37m[0m
[1m[37m[0m
[1m[37m([0m[37mlet*[0m[1m[37m [0m[1m[37m(([0m[1m[36minput[0m[1m[37m [0m[1m[37m([0m[37mread-string[0m[1m[37m [0m[33m"Input: "[0m[1m[37m))[0m[1m[37m[0m
[1m[37m [0m[1m[37m([0m[1m[36mcode[0m[1m[37m [0m[1m[37m([0m[37mif[0m[1m[37m [0m[1m[37m([0m[1m[36mcheck[0m[1m[37m [0m[1m[36minput[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37m([0m[37mcar[0m[1m[37m [0m[1m[37m([0m[37mread-from-string[0m[1m[37m [0m[1m[36minput[0m[1m[37m))[0m[1m[37m[0m
[1m[37m [0m[1m[37m([0m[1m[36mpanic[0m[1m[37m [0m[33m"no eval for you"[0m[1m[37m))))[0m[1m[37m[0m
[1m[37m [0m[1m[37m([0m[1m[36mfind-file-noselect[0m[1m[37m [0m[33m"./flag.txt"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37m([0m[37mmapcar[0m[1m[37m[0m
[1m[37m [0m[1m[37m([0m[37mlambda[0m[1m[37m [0m[1m[37m([0m[1m[36msym[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37m([0m[1m[36madvice-add[0m[1m[37m [0m[1m[36msym[0m[1m[37m [0m[37m:before[0m[1m[37m [0m[1m[37m([0m[37mlambda[0m[1m[37m [0m[1m[37m([0m[33m&rest[0m[1m[37m [0m[1m[36m_[0m[1m[37m)[0m[1m[37m [0m[1m[37m([0m[1m[36mpanic[0m[1m[37m [0m[33m"no functional programming allowed: %s"[0m[1m[37m [0m[1m[37m([0m[37msymbol-name[0m[1m[37m [0m[1m[36msym[0m[1m[37m)))))[0m[1m[37m[0m
[1m[37m [0m[1m[36m'[0m[1m[37m([0m[37mbuffer-size[0m[1m[37m [0m[37mfollowing-char[0m[1m[37m [0m[37mpreceding-char[0m[1m[37m [0m[37mchar-after[0m[1m[37m [0m[37mchar-before[0m[1m[37m[0m
[1m[37m [0m[37mbuffer-string[0m[1m[37m [0m[37mbuffer-substring[0m[1m[37m [0m[37mbuffer-substring-no-properties[0m[1m[37m [0m[37mcompare-buffer-substrings[0m[1m[37m [0m[37msubst-char-in-region[0m[1m[37m [0m[37mdelete-and-extract-region[0m[1m[37m[0m
[1m[37m [0m[37mcall-process[0m[1m[37m [0m[37mcall-process-region[0m[1m[37m [0m[37mgetenv-internal[0m[1m[37m[0m
[1m[37m [0m[37mfind-file-name-handler[0m[1m[37m[0m
[1m[37m [0m[37mcopy-file[0m[1m[37m [0m[37mrename-file[0m[1m[37m [0m[37madd-name-to-file[0m[1m[37m [0m[37mmake-symbolic-link[0m[1m[37m [0m[37maccess-file[0m[1m[37m [0m[37minsert-file-contents[0m[1m[37m))[0m[1m[37m[0m
[1m[37m [0m[1m[37m([0m[37munless[0m[1m[37m [0m[1m[37m([0m[37mfunctionp[0m[1m[37m [0m[1m[36mcode[0m[1m[37m)[0m[1m[37m [0m[1m[37m([0m[1m[36mpanic[0m[1m[37m [0m[33m"only functional programming allowed"[0m[1m[37m))[0m[1m[37m[0m
[1m[37m [0m[1m[37m([0m[37mmessage[0m[1m[37m [0m[1m[37m([0m[37mfuncall[0m[1m[37m [0m[1m[36mcode[0m[1m[37m))[0m[1m[37m[0m
[1m[37m [0m[1m[37m([0m[37mkill-emacs[0m[1m[37m))[0m[1m[37m[0m
[1m[37mBonus round: revengeance[0m
[1m[37m--- a/challenge.el[0m
[1m[37m+++ b/challenge.el[0m
[1m[37m@@ -18,7 +18,9 @@[0m
[1m[37m '(buffer-size following-char preceding-char char-after char-before[0m
[1m[37m buffer-string buffer-substring buffer-substring-no-properties compare-buffer-substrings subst-char-in-region delete-and-extract-region[0m
[1m[37m call-process call-process-region getenv-internal[0m
[1m[37m+ set-process-plist make-process make-pipe-process make-serial-process make-network-process[0m
[1m[37m find-file-name-handler[0m
[1m[37m+ set-buffer get-buffer get-buffer-create get-file-buffer get-truename-buffer other-buffer find-buffer buffer-local-value buffer-local-variables buffer-swap-text make-overlay[0m
[1m[37m copy-file rename-file add-name-to-file make-symbolic-link access-file insert-file-contents))[0m
[1m[37m (unless (functionp code) (panic "only functional programming allowed"))[0m
[1m[37m (message (funcall code))[0m
== [1m[4mSolution[22m[24m
So we're writing Lisp code without parens... a little cursed, but
ok.
The first insight is that the read syntax for closures [1] Closure
Type\")") doesn't (necessarily) use any parentheses if we use
bytecode.
[1m[37m([0m[37mformat[0m[1m[37m [0m[33m"%s (%s check)\n%s (%s check)"[0m[1m[37m[0m
[1m[37m [0m[1m[37m([0m[37mlambda[0m[1m[37m [0m[1m[37mnil[0m[1m[37m)[0m[1m[37m [0m[1m[37m([0m[37mif[0m[1m[37m [0m[1m[37m([0m[1m[36mcheck[0m[1m[37m [0m[1m[37m([0m[37mprin1-to-string[0m[1m[37m [0m[1m[37m([0m[37mlambda[0m[1m[37m [0m[1m[37mnil[0m[1m[37m)))[0m[1m[37m [0m[33m"passes"[0m[1m[37m [0m[33m"fails"[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37m([0m[1m[36mbyte-compile[0m[1m[37m [0m[1m[37m([0m[37mlambda[0m[1m[37m [0m[1m[37mnil[0m[1m[37m))[0m[1m[37m [0m[1m[37m([0m[37mif[0m[1m[37m [0m[1m[37m([0m[1m[36mcheck[0m[1m[37m [0m[1m[37m([0m[37mprin1-to-string[0m[1m[37m [0m[1m[37m([0m[1m[36mbyte-compile[0m[1m[37m [0m[1m[37m([0m[37mlambda[0m[1m[37m [0m[1m[37mnil[0m[1m[37m))))[0m[1m[37m [0m[33m"passes"[0m[1m[37m [0m[33m"fails"[0m[1m[37m))[0m[1m[37m[0m
[1m[37m"#[nil (nil) (t)] (fails check)[0m
[1m[37m#[0 \\300\\207 [nil] 1] (passes check)"[0m
The second insight is that Emacs advice does not apply to functions
which have dedicated opcodes in the Emacs Lisp bytecode
interpreter.[^fn:1]
Naturally, this behaviour is documented in the Emacs manual.
It is possible to advise a primitive (see What Is a
Function? [2] What Is a Function\")")), but one should
typically not do so, for two reasons. Firstly, some
primitives are used by the advice mechanism, and advising
them could cause an infinite recursion. Secondly, many
primitives are called directly from C, and such calls
ignore advice; hence, one ends up in a confusing situation
where some calls (occurring from Lisp code) obey the advice
and other calls (from C code) do not.
There are two such functions, [40m[35m`set-buffer`[39m[49m and
[40m[35m`buffer-substring`[39m[49m, that allow us to extricate
the flag.
[1m[37m([0m[37mdefun[0m[1m[37m [0m[1m[36mdisass[0m[1m[37m [0m[1m[37m([0m[1m[36mform[0m[1m[37m)[0m[1m[37m[0m
[1m[37m [0m[1m[37m([0m[37mwith-temp-buffer[0m[1m[37m[0m
[1m[37m [0m[1m[37m([0m[1m[36mdisassemble[0m[1m[37m [0m[1m[37m([0m[1m[36mbyte-compile[0m[1m[37m [0m[1m[36mform[0m[1m[37m)[0m[1m[37m [0m[1m[37m([0m[37mcurrent-buffer[0m[1m[37m))[0m[1m[37m[0m
[1m[37m [0m[1m[37m([0m[37mbuffer-string[0m[1m[37m)))[0m[1m[37m[0m
[1m[37m[0m
[1m[37m([0m[1m[36mdisass[0m[1m[37m [0m[1m[37m([0m[37mlambda[0m[1m[37m [0m[1m[37m()[0m[1m[37m [0m[1m[37m([0m[37mset-buffer[0m[1m[37m [0m[33m"*flag.txt*"[0m[1m[37m)[0m[1m[37m [0m[1m[37m([0m[37mbuffer-substring[0m[1m[37m [0m[1m[37m([0m[37mpoint-min[0m[1m[37m)[0m[1m[37m [0m[1m[37m([0m[37mpoint-max[0m[1m[37m))))[0m[1m[37m[0m
[1m[37m"byte code:[0m
[1m[37m args: nil[0m
[1m[37m0 constant \"*flag.txt*\"[0m
[1m[37m1 set-buffer[0m
[1m[37m2 discard[0m
[1m[37m3 point-min[0m
[1m[37m4 point-max[0m
[1m[37m5 buffer-substring[0m
[1m[37m6 return[0m
[1m[37m"[0m
(Note that [40m[35m`byte-compile`[39m[49m can behave strangely
depending on your Emacs version and you might have to write the
bytecode by hand, in which case the Emacs Lisp Bytecode Reference
Manual [3] is very useful.)
At least, that was the intended solve: as it turns out, I forgot to
advise quite a few functions.
Take for example the following solution:
[1m[37m([0m[1m[36mdisass[0m[1m[37m [0m[1m[37m([0m[37mcar[0m[1m[37m [0m[1m[37m([0m[37mread-from-string[0m[1m[37m [0m[33m"#[0 \"\\300\\301!\\210\\305\\302\\303 \\304 \\\"!\\207\" [set-buffer \"flag.txt\" filter-buffer-substring point-min point-max message] 3]"[0m[1m[37m)))[0m[1m[37m[0m
[1m[37m"byte code:[0m
[1m[37m args: nil[0m
[1m[37m0 constant set-buffer[0m
[1m[37m1 constant \"flag.txt\"[0m
[1m[37m2 call 1[0m
[1m[37m3 discard[0m
[1m[37m4 constant message[0m
[1m[37m5 constant filter-buffer-substring[0m
[1m[37m6 constant point-min[0m
[1m[37m7 call 0[0m
[1m[37m8 constant point-max[0m
[1m[37m9 call 0[0m
[1m[37m10 call 2[0m
[1m[37m11 call 1[0m
[1m[37m12 return[0m
[1m[37m"[0m
[^fn:1]: https://nullprogram.com/blog/2013/01/22/ [4]
References:
(HTM) [1] closures
(HTM) [2] What Is a Function?
(HTM) [3] Emacs Lisp Bytecode Reference Manual
(HTM) [4] https://nullprogram.com/blog/2013/01/22/
>=================================================================<
(DIR) Blog
(DIR) Writeups
(DIR) jp
copyright 2026 George Huebner
(HTM) email