├── utils.py ├── examples ├── simple.py ├── challenge.py └── challenge_dist.py ├── transformers ├── namescrabler.py ├── curry.py ├── unbind.py ├── bind.py └── normalize.py ├── LICENSE ├── lambdifier.py ├── README.md └── .gitignore /utils.py: -------------------------------------------------------------------------------- 1 | import ast 2 | 3 | def pp(node): 4 | print(ast.dump(node, indent=4)) -------------------------------------------------------------------------------- /examples/simple.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.setrecursionlimit(10000000) 3 | 4 | # obfuscate 5 | 6 | 7 | p = 37 8 | 9 | eq = lambda x, y: x == y 10 | add = lambda x, y: x + y 11 | asd = print("Hello World!") -------------------------------------------------------------------------------- /transformers/namescrabler.py: -------------------------------------------------------------------------------- 1 | import ast 2 | from utils import pp 3 | 4 | class NameScramblerTransformer(ast.NodeTransformer): 5 | def __init__(self): 6 | self.mapping = {} 7 | self.counter = 0 8 | super().__init__() 9 | 10 | def visit_Lambda(self, node): 11 | var_name = f'_{self.counter}' 12 | self.counter += 1 13 | 14 | # assume curried lambda 15 | original_name = node.args.args[0].arg 16 | self.mapping[original_name] = var_name 17 | self.visit(node.body) 18 | node.args.args[0].arg = var_name 19 | del self.mapping[original_name] 20 | 21 | return node 22 | 23 | def visit_Name(self, node): 24 | if node.id in self.mapping: 25 | node.id = self.mapping[node.id] 26 | return node 27 | -------------------------------------------------------------------------------- /transformers/curry.py: -------------------------------------------------------------------------------- 1 | import ast 2 | 3 | class CurryTransformer(ast.NodeTransformer): 4 | def visit_Lambda(self, node): 5 | # recurr on the body 6 | last_body = self.visit(node.body) 7 | 8 | # transform the lambda into a chain of lambdas 9 | for arg in node.args.args[::-1]: 10 | new_node = ast.Lambda( 11 | args=ast.arguments( 12 | args=[arg], 13 | defaults=[], 14 | vararg=[], 15 | kwonlyargs=[], 16 | kw_defaults=[], 17 | kwarg=None, 18 | posonlyargs=[], 19 | ), 20 | body=last_body, 21 | ) 22 | last_body = new_node 23 | return new_node 24 | 25 | def visit_Call(self, node): 26 | last_fun = self.visit(node.func) 27 | for arg in node.args: 28 | last_fun = ast.Call( 29 | func=last_fun, 30 | args=[self.visit(arg)], 31 | keywords=[], 32 | ) 33 | return last_fun -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Giorgio Dell'Immagine 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/challenge.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.setrecursionlimit(10000000) 3 | 4 | # obfuscate 5 | 6 | 7 | p = 37 8 | 9 | eq = lambda x, y: x == y 10 | add = lambda x, y: x + y 11 | mod = lambda x, y: x % y 12 | mul = lambda x, y: x * y 13 | xor = lambda x: lambda y: x ^ y 14 | mul_p = lambda x, y: mod(mul(x, y), p) 15 | add_p = lambda x, y: mod(add(x, y), p) 16 | 17 | # fix point combinator to make recursion possible 18 | F = lambda f: (lambda x: f(lambda y: x(x)(y)))(lambda x: f(lambda y: x(x)(y))) 19 | 20 | fact = lambda f, x: 1 if eq(0, x) else mul(x, f(add(-1, x))) 21 | gen_perm = lambda f, x: [] if eq(0, x) else add(f(x - 1), [add_p(x, mul_p(F(fact, 4), x))]) 22 | permute = lambda f, x, y: [] if eq(0, len(y)) else add(f(x, y[1:]), [x[y[0]]]) 23 | map_ = lambda f, ff, y: [] if eq(0, len(y)) else add([ff(y[0])], f(ff, y[1:])) 24 | 25 | group = lambda f, x, y: [] if eq(0, len(y)) else add([y[:x]], f(x, y[x:])) 26 | pad = lambda s, x: s + mul('X',(mod(add(x, -len(s)), x))) 27 | string_to_int = lambda f, s: 0 if eq(0, len(s)) else add(ord(s[0]), mul(256, f(s[1:]))) 28 | 29 | _a = print("Give me the flag...") 30 | inp = input('λ >') 31 | 32 | permuted_ = F(permute, inp, F(gen_perm, p)) 33 | permuted = ''.join(permuted_) 34 | padded = pad(permuted, 5) 35 | grouped = F(group, 5, padded) 36 | xorred = xor(mul(0xcafebabe, 42)) 37 | mapped1 = F(map_, F(string_to_int), grouped) 38 | mapped2 = F(map_, xorred, mapped1) 39 | 40 | res = [] 41 | x_1 = res.append(541982718533) 42 | x_2 = res.append(541752425566) 43 | x_3 = res.append(541920185944) 44 | x_4 = res.append(507556842335) 45 | x_5 = res.append(288512657218) 46 | x_6 = res.append(542133179466) 47 | x_7 = res.append(305508892749) 48 | x_8 = res.append(520052997187) 49 | 50 | _res = print("That's it!" if eq(res, mapped2) else "Nope!") 51 | 52 | # the flag is ifctf{What?_You_want_more_lambdas?__} -------------------------------------------------------------------------------- /transformers/unbind.py: -------------------------------------------------------------------------------- 1 | import ast 2 | 3 | """ 4 | Transform this: 5 | 6 | (lambda f1: f1(expr1))(lambda x1: 7 | (lambda f2: f2(expr2))(lambda x2: 8 | [...] 9 | ) 10 | ) 11 | 12 | Into this: 13 | 14 | x1 = expr1 15 | x2 = expr2 16 | [...] 17 | 18 | """ 19 | 20 | 21 | class UnbindTransformer(ast.NodeTransformer): 22 | def __init__(self): 23 | super().__init__() 24 | self.statements = [] 25 | 26 | def visit_Module(self, node): 27 | self.generic_visit(node) 28 | # return the statements 29 | node.body = self.statements 30 | return node 31 | 32 | def visit_Call(self, node): 33 | """ 34 | We are interested in this pattern 35 | (lambda f1: f1(expr1))(lambda x1: something) 36 | 37 | We want to transform it into 38 | x1 = expr1 39 | and recurse on something 40 | """ 41 | f = node.func 42 | 43 | if isinstance(f, ast.Lambda): 44 | # pp(node) 45 | identifier = f.args.args[0].arg 46 | # print(identifier) 47 | # pp(f.body) 48 | if isinstance(f.body, ast.Call): 49 | func_name = f.body.func.id 50 | if func_name == identifier: 51 | # print('bind lambda') 52 | expr = f.body.args[0] 53 | bind_lambda = node.args[0] 54 | bind_name = bind_lambda.args.args[0].arg 55 | # print('bind', bind_name, 'to', ast.dump(expr)) 56 | self.statements.append(ast.Assign( 57 | targets=[ast.Name(id=bind_name, ctx=ast.Store())], 58 | value=expr 59 | )) 60 | # pp(bind_lambda) 61 | self.visit(bind_lambda.body) 62 | return node 63 | self.visit(node) 64 | return node -------------------------------------------------------------------------------- /lambdifier.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import argparse 3 | 4 | from transformers.curry import CurryTransformer 5 | from transformers.bind import BindTransformer 6 | from transformers.normalize import NormalizeTransformer 7 | from transformers.namescrabler import NameScramblerTransformer 8 | from transformers.unbind import UnbindTransformer 9 | 10 | def obfuscate(node, level=3): 11 | if level >= 0: 12 | node = CurryTransformer().visit(node) 13 | if level >= 1: 14 | node = NormalizeTransformer().visit(node) 15 | if level >= 2: 16 | node = BindTransformer().visit(node) 17 | if level >= 3: 18 | node = NameScramblerTransformer().visit(node) 19 | 20 | node = ast.fix_missing_locations(node) 21 | return node 22 | 23 | 24 | def main(): 25 | # parse command line arguments 26 | parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter) 27 | parser.add_argument("input_file", help="input file to obfuscate") 28 | description_text_level = """obfuscation level: 29 | - 0: curry transform 30 | - 1: normalize 31 | - 2: bind functions 32 | - 3: obfuscate names 33 | """ 34 | parser.add_argument("-l", "--level", help=description_text_level, type=int, default=3, choices=[0, 1, 2, 3]) 35 | parser.add_argument("-d", "--deobfuscate", help="deobfuscate instead of obfuscate", action="store_true") 36 | args = parser.parse_args() 37 | 38 | 39 | # read sample file 40 | with open(args.input_file, "r") as f: 41 | sample = f.read() 42 | 43 | if args.deobfuscate: 44 | # assume that the lambda terms are on the last line 45 | preamble, sample = sample.strip().rsplit("\n", 1) 46 | else: 47 | preamble, sample = sample.split("# obfuscate", 1) 48 | 49 | 50 | parsed = ast.parse(sample) 51 | 52 | 53 | if args.deobfuscate: 54 | # deobfuscate 55 | parsed = UnbindTransformer().visit(parsed) 56 | parsed = ast.fix_missing_locations(parsed) 57 | else: 58 | # obfuscate 59 | parsed = obfuscate(parsed, level=args.level) 60 | 61 | script = f"{preamble.strip()}\n\n{ast.unparse(parsed)}" 62 | 63 | print(script) 64 | 65 | if __name__ == "__main__": 66 | main() -------------------------------------------------------------------------------- /transformers/bind.py: -------------------------------------------------------------------------------- 1 | import ast 2 | from utils import pp 3 | 4 | """ 5 | transform 6 | 7 | x = body1 8 | y = body2 9 | 10 | into 11 | 12 | (lambda g: g(body1))(lambda x: (lambda g: g(body2))(lambda y: ...) 13 | 14 | """ 15 | 16 | 17 | class BindTransformer(ast.NodeTransformer): 18 | # visit assignments 19 | def visit_Module(self, node): 20 | statements = node.body 21 | # last body is identity 22 | body = ast.Lambda( 23 | args=ast.arguments( 24 | args=[ast.arg(arg='x')], 25 | defaults=[], 26 | vararg=None, 27 | kwonlyargs=[], 28 | kw_defaults=[], 29 | kwarg=None, 30 | posonlyargs=[], 31 | ), 32 | body=ast.Name(id='x', ctx=ast.Load()), 33 | ) 34 | 35 | for stmt in statements[::-1]: 36 | if isinstance(stmt, ast.Assign): 37 | name = stmt.targets[0].id 38 | value = stmt.value 39 | continuation = ast.Lambda( 40 | args=ast.arguments( 41 | args=[ast.arg(arg='g')], 42 | defaults=[], 43 | vararg=None, 44 | kwonlyargs=[], 45 | kw_defaults=[], 46 | kwarg=None, 47 | posonlyargs=[], 48 | ), 49 | body=ast.Call( 50 | func=ast.Name(id='g', ctx=ast.Load()), 51 | args=[value], 52 | keywords=[], 53 | ), 54 | ) 55 | body = ast.Call( 56 | func=continuation, 57 | args=[ast.Lambda( 58 | args=ast.arguments( 59 | args=[ast.arg(arg=name)], 60 | defaults=[], 61 | vararg=None, 62 | kwonlyargs=[], 63 | kw_defaults=[], 64 | kwarg=None, 65 | posonlyargs=[], 66 | ), 67 | body=body 68 | )], 69 | keywords=[], 70 | ) 71 | return body -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lambdifier 2 | Lambdifier is a Python obfuscation tool that transforms normal* python scripts into a one liner that uses only lambdas and function applications. 3 | Here is what the `examples/simple.py` script comes out when obfuscated 4 | ```py 5 | (lambda _0: _0(37))(lambda _1: (lambda _2: _2(lambda _3: lambda _4: _3 == _4))(lambda _5: (lambda _6: _6(lambda _7: lambda _8: _7 + _8))(lambda _9: (lambda _10: _10(print))(lambda _11: (lambda _12: _12('Hello World!'))(lambda _13: (lambda _14: _14(_11(_13)))(lambda _15: lambda _16: _16)))))) 6 | ``` 7 | 8 | (\*) Not really, see below. 9 | 10 | ## Usage 11 | ``` 12 | usage: lambdifier.py [-h] [-l {0,1,2,3}] [-d] input_file 13 | 14 | positional arguments: 15 | input_file input file to obfuscate 16 | 17 | options: 18 | -h, --help show this help message and exit 19 | -l {0,1,2,3}, --level {0,1,2,3} 20 | obfuscation level: 21 | - 0: curry transform 22 | - 1: normalize 23 | - 2: bind functions 24 | - 3: obfuscate names 25 | 26 | -d, --deobfuscate deobfuscate instead of obfuscate 27 | ``` 28 | 29 | Input scripts are split by a 30 | ```py 31 | # obfuscate 32 | ``` 33 | comment. All the things that come before it will not be obfuscated, and will be copied as they are in the output script. 34 | 35 | ## How it works 36 | The obfucator applies a pipeline of four abstract syntax tree transformations. 37 | 1. All the lambda functions definitions and function applications are transformed into their curried version, to enable doing partial application. This is crucial for example for achieving recursion using the fixpoint combinator. 38 | 2. All the constant symbols and function applications are recursively extracted from the original expressions and put into their own definitions. Since the function calls are curried, function calls with multiple arguments are split between different assignments. 39 | 3. All assignments are converted into their lambda forms, exploiting lambda variable binding. 40 | 4. All names are obfuscated and transformed into meaningless numbers. 41 | 42 | Using the `-l` flag it is possible to inspect the intermediate forms of the script. 43 | 44 | The flag `-d` basically applies an inverse transformation of the third step, making the obfuscated script much more readable (or at least a bit comprehensible). 45 | 46 | ## Limitations 47 | 48 | Input scripts have to respect certain conditions, namely 49 | - there must be only assignments at top level, 50 | - only lambda functions are supported (who would have guessed), 51 | - recursion is not supported, but you can get recursion by using the fixpoint point combinator, 52 | - assignments must have unique names (basically only let semantics is supported without shadowing), 53 | - other random python features break this tool pretty bad, so use with caution. 54 | 55 | -------------------------------------------------------------------------------- /transformers/normalize.py: -------------------------------------------------------------------------------- 1 | import ast 2 | from utils import pp 3 | 4 | 5 | class VariableReferenceChecker(ast.NodeVisitor): 6 | def __init__(self, variable_name): 7 | self.variable_name = variable_name 8 | self.referenced = False 9 | 10 | def visit_Name(self, node): 11 | if node.id == self.variable_name: 12 | self.referenced = True 13 | self.generic_visit(node) 14 | 15 | def check_referenced(self, tree): 16 | self.visit(tree) 17 | return self.referenced 18 | 19 | class NormalizeTransformer(ast.NodeTransformer): 20 | def visit_Module(self, node): 21 | self.assignments = [] 22 | self.counter = 0 23 | 24 | statements = node.body 25 | new_body = [] 26 | for stmt in statements: 27 | if isinstance(stmt, ast.Assign): 28 | body = self.__rec_transform(stmt.value, toplevel=True) 29 | stmt.value = body 30 | for x in self.assignments: 31 | new_body.append(x) 32 | self.assignments = [] 33 | new_body.append(stmt) 34 | node.body = new_body 35 | return node 36 | 37 | def __rec_transform(self, node, toplevel = False, bound_names = []): 38 | 39 | if isinstance(node, ast.Call): 40 | func = self.__rec_transform(node.func, bound_names=bound_names) 41 | args = [self.__rec_transform(arg, bound_names=bound_names) for arg in node.args] 42 | keywords = [] 43 | call = ast.Call(func=func, args=args, keywords=keywords) 44 | if toplevel: 45 | return call 46 | for name in bound_names: 47 | if VariableReferenceChecker(name).check_referenced(node): 48 | return call 49 | new_name = f'_x{self.counter}' 50 | self.counter += 1 51 | self.assignments.append(ast.Assign(targets=[ast.Name(new_name, ast.Store())], value=call)) 52 | return ast.Name(new_name, ast.Load()) 53 | elif isinstance(node, ast.Lambda): 54 | # recur on the body, but add the arguments to the bound names 55 | new_body = self.__rec_transform(node.body, bound_names=bound_names + [arg.arg for arg in node.args.args]) 56 | node.body = new_body 57 | return node 58 | elif isinstance(node, ast.IfExp): 59 | test = self.__rec_transform(node.test, bound_names=bound_names) 60 | body = self.__rec_transform(node.body, bound_names=bound_names) 61 | orelse = self.__rec_transform(node.orelse, bound_names=bound_names) 62 | return ast.IfExp(test=test, body=body, orelse=orelse) 63 | elif isinstance(node, ast.List) and node.elts != []: 64 | elts = [self.__rec_transform(elt, bound_names=bound_names) for elt in node.elts] 65 | return ast.List(elts=elts, ctx=ast.Load()) 66 | else: 67 | for name in bound_names: 68 | if VariableReferenceChecker(name).check_referenced(node): 69 | return node 70 | if isinstance(node, ast.Call): 71 | new_value = self.__rec_transform(node, bound_names=bound_names) 72 | else: 73 | new_value = node 74 | if toplevel: 75 | return new_value 76 | new_name = f'_x{self.counter}' 77 | self.counter += 1 78 | self.assignments.append(ast.Assign(targets=[ast.Name(new_name, ast.Store())], value=new_value)) 79 | return ast.Name(new_name, ast.Load()) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | -------------------------------------------------------------------------------- /examples/challenge_dist.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.setrecursionlimit(10000000) 3 | 4 | (lambda _0: _0(37))(lambda _1: (lambda _2: _2(lambda _3: lambda _4: _3 == _4))(lambda _5: (lambda _6: _6(lambda _7: lambda _8: _7 + _8))(lambda _9: (lambda _10: _10(lambda _11: lambda _12: _11 % _12))(lambda _13: (lambda _14: _14(lambda _15: lambda _16: _15 * _16))(lambda _17: (lambda _18: _18(lambda _19: lambda _20: _19 ^ _20))(lambda _21: (lambda _22: _22(_13))(lambda _23: (lambda _24: _24(_17))(lambda _25: (lambda _26: _26(_1))(lambda _27: (lambda _28: _28(lambda _29: lambda _30: _23(_25(_29)(_30))(_27)))(lambda _31: (lambda _32: _32(_13))(lambda _33: (lambda _34: _34(_9))(lambda _35: (lambda _36: _36(_1))(lambda _37: (lambda _38: _38(lambda _39: lambda _40: _33(_35(_39)(_40))(_37)))(lambda _41: (lambda _42: _42(lambda _43: (lambda _44: _43(lambda _45: _44(_44)(_45)))(lambda _46: _43(lambda _47: _46(_46)(_47)))))(lambda _48: (lambda _49: _49(_5))(lambda _50: (lambda _51: _51(0))(lambda _52: (lambda _53: _53(_50(_52)))(lambda _54: (lambda _55: _55(1))(lambda _56: (lambda _57: _57(_17))(lambda _58: (lambda _59: _59(_9))(lambda _60: (lambda _61: _61(-1))(lambda _62: (lambda _63: _63(_60(_62)))(lambda _64: (lambda _65: _65(lambda _66: lambda _67: _56 if _54(_67) else _58(_67)(_66(_64(_67)))))(lambda _68: (lambda _69: _69(_5))(lambda _70: (lambda _71: _71(0))(lambda _72: (lambda _73: _73(_70(_72)))(lambda _74: (lambda _75: _75([]))(lambda _76: (lambda _77: _77(_9))(lambda _78: (lambda _79: _79(_41))(lambda _80: (lambda _81: _81(_31))(lambda _82: (lambda _83: _83(_48))(lambda _84: (lambda _85: _85(_68))(lambda _86: (lambda _87: _87(_84(_86)))(lambda _88: (lambda _89: _89(4))(lambda _90: (lambda _91: _91(_88(_90)))(lambda _92: (lambda _93: _93(_82(_92)))(lambda _94: (lambda _95: _95(lambda _96: lambda _97: _76 if _74(_97) else _78(_96(_97 - 1))([_80(_97)(_94(_97))])))(lambda _98: (lambda _99: _99(_5))(lambda _100: (lambda _101: _101(0))(lambda _102: (lambda _103: _103(_100(_102)))(lambda _104: (lambda _105: _105(len))(lambda _106: (lambda _107: _107([]))(lambda _108: (lambda _109: _109(_9))(lambda _110: (lambda _111: _111(lambda _112: lambda _113: lambda _114: _108 if _104(_106(_114)) else _110(_112(_113)(_114[1:]))([_113[_114[0]]])))(lambda _115: (lambda _116: _116(_5))(lambda _117: (lambda _118: _118(0))(lambda _119: (lambda _120: _120(_117(_119)))(lambda _121: (lambda _122: _122(len))(lambda _123: (lambda _124: _124([]))(lambda _125: (lambda _126: _126(_9))(lambda _127: (lambda _128: _128(lambda _129: lambda _130: lambda _131: _125 if _121(_123(_131)) else _127([_130(_131[0])])(_129(_130)(_131[1:]))))(lambda _132: (lambda _133: _133(_5))(lambda _134: (lambda _135: _135(0))(lambda _136: (lambda _137: _137(_134(_136)))(lambda _138: (lambda _139: _139(len))(lambda _140: (lambda _141: _141([]))(lambda _142: (lambda _143: _143(_9))(lambda _144: (lambda _145: _145(lambda _146: lambda _147: lambda _148: _142 if _138(_140(_148)) else _144([_148[:_147]])(_146(_147)(_148[_147:]))))(lambda _149: (lambda _150: _150(lambda _151: lambda _152: _151 + _17('X')(_13(_9(_152)(-len(_151)))(_152))))(lambda _153: (lambda _154: _154(_5))(lambda _155: (lambda _156: _156(0))(lambda _157: (lambda _158: _158(_155(_157)))(lambda _159: (lambda _160: _160(len))(lambda _161: (lambda _162: _162(0))(lambda _163: (lambda _164: _164(_9))(lambda _165: (lambda _166: _166(ord))(lambda _167: (lambda _168: _168(_17))(lambda _169: (lambda _170: _170(256))(lambda _171: (lambda _172: _172(_169(_171)))(lambda _173: (lambda _174: _174(lambda _175: lambda _176: _163 if _159(_161(_176)) else _165(_167(_176[0]))(_173(_175(_176[1:])))))(lambda _177: (lambda _178: _178(print))(lambda _179: (lambda _180: _180('Give me the flag...'))(lambda _181: (lambda _182: _182(_179(_181)))(lambda _183: (lambda _184: _184(input))(lambda _185: (lambda _186: _186('λ >'))(lambda _187: (lambda _188: _188(_185(_187)))(lambda _189: (lambda _190: _190(_48))(lambda _191: (lambda _192: _192(_115))(lambda _193: (lambda _194: _194(_191(_193)))(lambda _195: (lambda _196: _196(_189))(lambda _197: (lambda _198: _198(_195(_197)))(lambda _199: (lambda _200: _200(_48))(lambda _201: (lambda _202: _202(_98))(lambda _203: (lambda _204: _204(_201(_203)))(lambda _205: (lambda _206: _206(_1))(lambda _207: (lambda _208: _208(_205(_207)))(lambda _209: (lambda _210: _210(_199(_209)))(lambda _211: (lambda _212: _212(''.join))(lambda _213: (lambda _214: _214(_211))(lambda _215: (lambda _216: _216(_213(_215)))(lambda _217: (lambda _218: _218(_153))(lambda _219: (lambda _220: _220(_217))(lambda _221: (lambda _222: _222(_219(_221)))(lambda _223: (lambda _224: _224(5))(lambda _225: (lambda _226: _226(_223(_225)))(lambda _227: (lambda _228: _228(_48))(lambda _229: (lambda _230: _230(_149))(lambda _231: (lambda _232: _232(_229(_231)))(lambda _233: (lambda _234: _234(5))(lambda _235: (lambda _236: _236(_233(_235)))(lambda _237: (lambda _238: _238(_227))(lambda _239: (lambda _240: _240(_237(_239)))(lambda _241: (lambda _242: _242(_21))(lambda _243: (lambda _244: _244(_17))(lambda _245: (lambda _246: _246(3405691582))(lambda _247: (lambda _248: _248(_245(_247)))(lambda _249: (lambda _250: _250(42))(lambda _251: (lambda _252: _252(_249(_251)))(lambda _253: (lambda _254: _254(_243(_253)))(lambda _255: (lambda _256: _256(_48))(lambda _257: (lambda _258: _258(_132))(lambda _259: (lambda _260: _260(_257(_259)))(lambda _261: (lambda _262: _262(_48))(lambda _263: (lambda _264: _264(_177))(lambda _265: (lambda _266: _266(_263(_265)))(lambda _267: (lambda _268: _268(_261(_267)))(lambda _269: (lambda _270: _270(_241))(lambda _271: (lambda _272: _272(_269(_271)))(lambda _273: (lambda _274: _274(_48))(lambda _275: (lambda _276: _276(_132))(lambda _277: (lambda _278: _278(_275(_277)))(lambda _279: (lambda _280: _280(_255))(lambda _281: (lambda _282: _282(_279(_281)))(lambda _283: (lambda _284: _284(_273))(lambda _285: (lambda _286: _286(_283(_285)))(lambda _287: (lambda _288: _288([]))(lambda _289: (lambda _290: _290(_289.append))(lambda _291: (lambda _292: _292(541982718533))(lambda _293: (lambda _294: _294(_291(_293)))(lambda _295: (lambda _296: _296(_289.append))(lambda _297: (lambda _298: _298(541752425566))(lambda _299: (lambda _300: _300(_297(_299)))(lambda _301: (lambda _302: _302(_289.append))(lambda _303: (lambda _304: _304(541920185944))(lambda _305: (lambda _306: _306(_303(_305)))(lambda _307: (lambda _308: _308(_289.append))(lambda _309: (lambda _310: _310(507556842335))(lambda _311: (lambda _312: _312(_309(_311)))(lambda _313: (lambda _314: _314(_289.append))(lambda _315: (lambda _316: _316(288512657218))(lambda _317: (lambda _318: _318(_315(_317)))(lambda _319: (lambda _320: _320(_289.append))(lambda _321: (lambda _322: _322(542133179466))(lambda _323: (lambda _324: _324(_321(_323)))(lambda _325: (lambda _326: _326(_289.append))(lambda _327: (lambda _328: _328(305508892749))(lambda _329: (lambda _330: _330(_327(_329)))(lambda _331: (lambda _332: _332(_289.append))(lambda _333: (lambda _334: _334(520052997187))(lambda _335: (lambda _336: _336(_333(_335)))(lambda _337: (lambda _338: _338(print))(lambda _339: (lambda _340: _340(_5))(lambda _341: (lambda _342: _342(_289))(lambda _343: (lambda _344: _344(_341(_343)))(lambda _345: (lambda _346: _346(_287))(lambda _347: (lambda _348: _348(_345(_347)))(lambda _349: (lambda _350: _350("That's it!"))(lambda _351: (lambda _352: _352('Nope!'))(lambda _353: (lambda _354: _354(_339(_351 if _349 else _353)))(lambda _355: lambda _356: _356)))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) 5 | --------------------------------------------------------------------------------