NothingToSeeHere (reverse)

Are you ready to play a game? beware of snakes!.

ntsh.py

We are given a game written in python that looks quite short, but that’s because the main game logic is loaded from bytecode.

logic = "eJzU+7muxNye5Yl9997MrKrsLqm9<truncated>"
logic = base64.b64decode(logic)
logic = zlib.decompress(logic)
logic = marshal.loads(logic[16:])
mod = types.ModuleType("gamelogic")
exec(logic, mod.__dict__)
logic = mod.Logic(player_cpos)

The bytecode can be decompiled by importing the decompile function from uncompyle6.

import os, sys, time
import base64 ,zlib, marshal, importlib, types, dis
from uncompyle6.main import decompile

logic = "eJzU+7muxNye5Yl9997MrKrsLqm9<truncated>"
logic = base64.b64decode(logic)
logic = zlib.decompress(logic)
logic = marshal.loads(logic[16:])
mod = types.ModuleType("gamelogic")
exec(logic, mod.__dict__)
# print(dir(logic))
decompile(3.7, logic, sys.stdout)

This gives

# uncompyle6 version 3.6.0
# Python bytecode 3.7
# Decompiled from: Python 3.7.2 (v3.7.2:9a3ffc0492, Dec 24 2018, 02:44:43) 
# [Clang 6.0 (clang-600.0.57)]
# Embedded file name: gamelogic.py
import random, zlib, pickle, base64

class Logic:
    DEBUG = False

    def __init__(self, player_pos):
        if self.DEBUG:
            self.game_map = []
            gd = open('clean.txt', 'r').read().split('\n')
            for _ in range(4):
                self.game_map.append('                                                                                                                                                                     ')

            for gl in gd:
                self.game_map.append('     ' + gl + '     ')

            for _ in range(2):
                self.game_map.append('                                                                                                                                                                     ')

        else:
            self.game_map = 'eJxN2+eWolzYIGwRRDCgYkAEzDnnhKE655xD<truncated>'
            self.game_map = base64.b64decode(self.game_map)
            self.game_map = zlib.decompress(self.game_map)
            self.game_map = pickle.loads(self.game_map)
        self.viewport = 'What you see =>\n╔═══════════╗\n║           ║ \n║           ║ \n║     ☻     ║ \n║           ║ \n║           ║ \n╚═══════════╝'
        self._Logic__gen_decode_key()
        self.player_move(player_pos)

    def __update_viewport(self, data):
        new_viewport = []
        if self.DEBUG:
            new_viewport.append('[DEBUG] What you see =>')
        else:
            new_viewport.append('What you see =>')
        if self.DEBUG:
            new_viewport.append('╔════╣DEBUG╠═════╗')
        else:
            new_viewport.append('╔════════════════╗')
        for dl in data:
            new_viewport.append('║' + dl + '║')

        if self.DEBUG:
            new_viewport.append('╚════╣DEBUG╠═════╝')
        else:
            new_viewport.append('╚════════════════╝')
        # smile_edit = list(new_viewport[4])
        # smile_edit[8] = '☻'
        # new_viewport[4] = ''.join(smile_edit)
        self.viewport = '\n'.join(new_viewport)

    def __gen_decode_key(self):
        random.seed(949127234)
        self.d_keys = []
        for r in range(93):
            kr = []
            for k in range(155):
                kr.append(random.randint(33, 126))

            self.d_keys.append(kr)

    def __decode_view(self, data, key):
        if self.DEBUG:
            return data
        new_data = []
        for d, k in zip(data, key):
            l = []
            for i, sd in enumerate(d):
                l.append(chr(ord(sd) ^ k[i]))

            new_data.append(''.join(l))

        return new_data

    def player_move(self, player_pos):
        pos_x, pos_y = player_pos
        if pos_x > 15:
            pos_x = 15
        if pos_y > 10:
            pos_y = 10
        data = []
        key = []
        for i in range(5):
            vp = self.game_map[(pos_y + i)]
            vp = vp[pos_x:pos_x]
            vk = self.d_keys[(pos_y + i)]
            vk = vk[pos_x:pos_x]
            data.append(vp)
            key.append(vk)

        data = self._Logic__decode_view(data, key)
        self._Logic__update_viewport(data)

There is a game map in this class, but after decoding it, it turns out that it is encrypted.

import base64, zlib, pickle

game_map = 'eJxN2+eWolzYIGwRRDCgYkAEzDnnhKE655xD<truncated>'
game_map = base64.b64decode(game_map)
game_map = zlib.decompress(game_map)
game_map = pickle.loads(game_map)
print('\n'.join(game_map))
▶ python3 load_map.py

]Olv@Dn[SZmQCQT\zM
DSJ~i|ALI

...

player_move is responsible for decrypting it using the key generated in __gen_decode_key. The reason we cannot play the game to find the flag is because of the following checks.

if pos_x > 15:
    pos_x = 15
if pos_y > 10:
    pos_y = 10

Comment them out and we can navigate to even further parts of the map. But the map is still showing too little, because we only set to show a 5x5 grid.

for i in range(5):
    vp = self.game_map[(pos_y + i)]
    vp = vp[pos_x:pos_x + 5]
    vk = self.d_keys[(pos_y + i)]
    vk = vk[pos_x:pos_x + 5]
    data.append(vp)
    key.append(vk)

So change the numbers to show a larger part of the map, then modify ntsh.py to import from the modified gamelogic instead of the original bytecode.

for i in range(85):
    vp = self.game_map[(pos_y + i)]
    vp = vp[pos_x:pos_x + 145]
    vk = self.d_keys[(pos_y + i)]
    vk = vk[pos_x:pos_x + 145]
    data.append(vp)
    key.append(vk)

Now play the game and we can find the flag at the right end of the map.

flag