Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Brainfuck interpreter #470

Merged
merged 6 commits into from
Oct 3, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions ciphey/basemods/Decoders/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@
multi_tap,
url,
tap_code,
brainfuck,
)
172 changes: 172 additions & 0 deletions ciphey/basemods/Decoders/brainfuck.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
from typing import Optional, Dict, List, Tuple
SkeletalDemise marked this conversation as resolved.
Show resolved Hide resolved

from ciphey.iface import Config, ParamSpec, T, U, Decoder, registry, WordList
from ciphey.common import fix_case
import time


@registry.register
class Brainfuck(Decoder[str, str]):
def decode(self, ctext: T) -> Optional[U]:
"""
Takes a ciphertext and treats it as a Brainfuck program,
interpreting it and saving the output as a string to return.

Brainfuck is a very simple, Turing-complete esoteric language.
Below is a simplified interpreter that attempts to check whether a
given ciphertext is a brainfuck program that would output a string.

A program that can be "decoded" like this is one that:
* Does not require user input ("," instruction)
* Includes at least one putchar instruction (".")
* Does not contain anything but the main 7 instructions,
(excluding ",") and whitespace (tabs, spaces, and newlines)

Details:
* This implementation wraps the memory pointer for ">" and "<"
* It is time-limited to 60 seconds, to prevent hangups
* The program starts with 100 memory cells, chosen arbitrarily
"""

result = ""
memory = [0] * 100
codeptr, memptr = 0, 0 # instruction pointer, stack pointer
timelimit = 60 # timeout, in seconds
SkeletalDemise marked this conversation as resolved.
Show resolved Hide resolved

bracemap, isbf = self.bracemap_and_check(ctext)

# this doesn't appear to be a brainfuck program we can decode
if not isbf:
return None

# get start time
start = time.time()

while codeptr < len(ctext):

current = time.time()

# arbitrarily quit if we've been running for over a minute
if current - start > timelimit:
return None
SkeletalDemise marked this conversation as resolved.
Show resolved Hide resolved

cmd = ctext[codeptr]

if cmd == "+":
if memory[memptr] < 255:
memory[memptr] = memory[memptr] + 1
else:
memory[memptr] = 0

elif cmd == "-":
if memory[memptr] > 0:
memory[memptr] = memory[memptr] - 1
else:
memory[memptr] = 255

elif cmd == ">":
if memptr == len(memory)-1:
memory.append(0)
memptr += 1

elif cmd == "<":
if memptr == 0:
memptr = len(memory) - 1
else:
memptr -= 1

# if we're at the beginning of the loop and the memory is 0,
# exit the loop
elif cmd == "[" and memory[memptr] == 0:
codeptr = bracemap[codeptr]

# if we're at the end of the loop and the memory is >0, jmp to
# the beginning of the loop
elif cmd == "]" and memory[memptr]:
codeptr = bracemap[codeptr]

# store the output as a string instead of printing it out
elif cmd == ".":
result += chr(memory[memptr])

codeptr += 1

return result

def bracemap_and_check(self, program: str) -> Tuple[Optional[Dict], bool]:
"""
Create a bracemap of brackets in the program, to compute jmps.
Maps open -> close brackets as well as close -> open brackets.

Also returns True if the program is legal Brainfuck code. If False, we
won't try to even run it.
"""

open_stack = []
bracemap = dict()
legal_instructions = {
"+", "-", ">", "<", "[", "]", ".", ",", "\t", "\n", " "
SkeletalDemise marked this conversation as resolved.
Show resolved Hide resolved
}
legal_count = 0

# if the program actually outputs anything (contains ".")
prints = False

for idx, instruction in enumerate(program):
if instruction in legal_instructions:
legal_count += 1

if not prints and instruction == ".":
# If there are no "." instructions,
# then this program will not output anything
prints = True

if instruction == ",":
# we don't handle input
return (None, False)

elif instruction == "[":
open_stack.append(idx)

elif instruction == "]":
try:
opbracket = open_stack.pop()
bracemap[opbracket] = idx
bracemap[idx] = opbracket
except IndexError:
# mismatched braces, not a valid program
# closing braces > opening braces
return (None, False)

# 1. all characters are instructions or whitespace,
# 2. no extra open braces, and
# 3. there is at least one character to be "printed"
# (result is >=1 in length)
is_brainfuck = legal_count == len(program) \
and len(open_stack) == 0 \
and prints

return bracemap, is_brainfuck

@staticmethod
def priority() -> float:
# not uncommon, but not especially likely either. slow.
return 0.08

def __init__(self, config: Config):
super().__init__(config)
self.ALPHABET = config.get_resource(self._params()["dict"], WordList)

@staticmethod
def getParams() -> Optional[Dict[str, ParamSpec]]:
return {
"dict": ParamSpec(
desc="Brainfuck alphabet (default English)",
req=False,
default="cipheydists::list::englishAlphabet",
)
}

@staticmethod
def getTarget() -> str:
return "brainfuck"
8 changes: 8 additions & 0 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,3 +226,11 @@ def test_brandon():
"R hvv blf tzgsvi yvuliv nv...sfmtib...gviirurvw... Xofgxsrmt blfi yzyvh gl blfi yivzhg. Vnkvili Vnsbi srh nzixsvw srh ovtrlmh rmgl lfi ozmwh... Ozrw hrvtv gl vevib uligivhh uiln sviv gl gsv Yofv Nlfmgzrmh. Izyrw zmw izevmlfh, sv yrgvh zmw yrgvh zdzb. Nvm lu gsv Mligs, blf hgzmw zg gsv kivxrkrxv. Blfi prmth szev uzrovw blf, hl mld blf gfim gl gsv tlwh! Zmw bvg blf wl mlg kovzw? Blf wl mlg pmvvo gl wfhg blfi svzwh drgs zhs? Rmhgvzw blf dzro, Dsb szev gsv tlwh ulihzpvm fh? Dv nfhg ollp rmgl gsv girzoh dv uzrovw olmt ztl! Rm z grnv kzhhvw, lfi dliow rmgvigdrmvw drgs zmlgsvi gsilfts zm fksvzezo hxslozih xzoo gsv Xlmqfmxgrlm lu gsv Hksvivh... Gsv tlwh zooldvw fmslob ulixvh gl hork rmgl lfi wlnzrm. Gsv luuhkirmt lu gszg xzgzxobhn dzh gsv mvuvirlfh ulixv xzoovw nztrx... Bvg dv wrw mlg yzmrhs rg, rmhgvzw hgfwbrmt gsv erov zixzmv uli lfi kldvi zmw dvzogs! Zmw gsv nlmhgvih zg lfi wlli...gsv fmslob ivorxgh lu gsrh Xlmqfmxgrlm? ...gsv gilooh...gsv xlikhv vzgvih...gsv dvivdloevh? Wrw dv izrhv lfi hdliwh ztzrmhg gsvn? Li szev dv ozrw gsrh yfiwvm lm lgsvih? Lm hl-xzoovw drgxsvih? Hgizb xsrowivm gzftsg gsv dzbh lu ulfo hlixvib, gsvri ylwrvh nfgzgvw gsilfts yozhksvnlfh irgfzo. Hvmg gl urtsg nlmhgvih gslfts gsvb xlfow mlg wrhgrmtfrhs tllw uiln vero. Gsv uorxpvi lu sfnzmrgb olmt vcgrmtfrhsvw drgsrm gsvn. Bvh, gsvri mfnyvih szev wdrmwovw gsilfts gsv bvzih. Yfg z uvd hgroo ilzn lfi ozmwh, luuvirmt gsvri yollwb dlip uli xlrm. Gl gsrh wzb gsvb hsznv fh drgs gsvri evib vcrhgvmxv! Gsv Mligs yovvwh, uolttvw yb dzi. Gsv yzggovh ziv gsv tlwh' dsrk, xszhgrhvnvmg uli lfi hrmh! Zmw ovg fh mlg ulitvg gsv gviilih, gsv hxlfitvh uiln yvblmw lfi dliow! Gsv Drow Sfmg irwvh gsv hpb drgs vevib ufoo nllm! Gsv wzip izrwvih zywfxg lfi xsrowivm rmgl ozmwh fmpmldm! Hlnv hzb gsvb svizow z hvxlmw Xlmqfmxgrlm! Xzm dv xszig z xlfihv yzxp rmgl gsv ortsg? Droo dv urmw gsv hgivmtgs gl yzmrhs gsv nztvh uiln lfi prmtwlnh? Fmrgv zilfmw gsv dzings lu gsv Vgvimzo Uriv? Mrts rh gsv Grnv lu gsv Hdliw zmw gsv Zcv! Mlmv droo urtsg gsrh dzi rm lfi hgvzw! Mrts rh gsv Grnv lu Nzwmvhh zmw Wrhwzrm!"
)
assert True

def test_brainfuck():
res = decrypt(
Config().library_default().complete_config(),
"+[+++++++>+<]>-.-[+>-----<]>++.+++++++..+++.+[+>++<]>.[++>+<]>---.--[+++>-<]>.-[+>++++<]>.[++>+<]>--.-[+++>++<]>-.+[-->---<]>.--------.[+++++>+<]>+.-[+++>--<]>-.++++++++++.---[+>++<]>.[+++>-<]>++.+++..[+++++>+<]>+.[+++>-<]>+.+[-->---<]>+.----------.-[+++>-<]>-.-[+++>+<]>--.-[+>----<]>.++[+++>--<]>.---.++.------.[+++++>+<]>+.+[+>---<]>+.+++++++++++.--------.-[+++>-<]>--.[+++>-<]>+.+[-->---<]>+.----------.-[+++>-<]>-.[+++>-<]>+.-[-->---<]>..----.-------.[+++++>+<]>+.[+++>-<]>+.+[-->---<]>+.----------.-[+++>-<]>-.[++>+<]>++++.--.-------------.."
)
assert res == answer_str