The final challenge we are given a series of similar Win32 PE Binaries to analyze. We need to unlock each level to get to the next challenge. When we run any of the executables they just print out some asterisks.

When we open a challenge in IDA there is a long line moving bytes and then there is a call to the unpacked code. We can set a breakpoint at that location on the call to the register to take a look.

After a jmp we are at our shellcode that immediately calls LoadLibrary and GetProcAddress to resolve some APIs and then some more moving of bytes around and some hashing functions. If we keep debugging we get to the interesting part where GetEnvironmentVariableA is called looking for a long variable.

If the environment variable is found the result is then compared with a string and this determines the execution path. This process happens again and again taking different paths depending on if the correct environment variable was found or not.

We can instrument the program with a debugger to fuzz the correct path through the program to find the key. We break on GetEnvironmentVariableA to determine the variable that the program is checking for and then we look ahead to see what that value should be. We can then keep track of each path we have tried and decide whether or not to give the correct response.

We can see what this looks like when we run it on the final binary to print the key.

#!/usr/bin/env python

"""
solve for tos challenge
"""
from builtins import range
import sys
import random

from pykd import *
from flaredbg import flaredbg

from treelib import Node, Tree

import argparse
import logging
logging.getLogger(__name__).addHandler(logging.NullHandler())

__author__ = "fdivrp"
__version__ = "v0.1"

tree = Tree()
last = ""
path = []


class Track(object):
    def __init__(self, explored):
        self.explored = explored


def tos_dbg():
    global tree
    global last
    global path

    # Reset last
    last = ""

    # Setup Debugger
    dbg = flaredbg.DebugUtils()

    bp_getenvironmentvariablea = module('kernel32').offset(
        'GetEnvironmentVariableA')
    # Set GetEnvironmentVariableA Breakpoint
    logging.debug(
        "[+]  ADDING BREAKPOINT FOR kernel32!GetEnvironmentVariableA AT: {}".
        format(hex(bp_getenvironmentvariablea)))
    dbg.set_breakpoint(bp_getenvironmentvariablea)
    """
    DWORD WINAPI GetEnvironmentVariable(
      _In_opt_  LPCTSTR lpName,
      _Out_opt_ LPTSTR  lpBuffer,
      _In_      DWORD   nSize
    );
    If the function succeeds, the return value is the number of characters stored in the buffer pointed to by lpBuffer, not including the terminating null character.
    """

    # parent for tree set to empty to start first round
    tree_parent = ""
    local_path = []
    try:
        for i in range(1, 9000):
            try:
                # Break on environmentvariablea and get call parameters
                dbg.set_breakpoint(bp_getenvironmentvariablea)
                lpName = dbg.read_string(
                    dbg.read_pointer(dbg.get_stack_pointer() + 4))
                lpBuffer = dbg.read_pointer(dbg.get_stack_pointer() + 8)
                nSize = dbg.read_pointer(dbg.get_stack_pointer() + 12)

                # Set a BP on return from EnvironmentVariableA and set return eax to success nSize-1
                dbg.set_breakpoint(dbg.read_pointer(dbg.get_stack_pointer()))
                dbg.set_reg("eax", nSize - 1)

                # get location of compare
                va = dbg.get_pc()
                mnem = dbg.wdbg_get_mnem(va)
                while mnem != "cmp":
                    va += 1
                    mnem = dbg.wdbg_get_mnem(va)

                # get operand value from cmp
                disasm = dbg.disasm(va)
                offset = str(disasm).split(",")[-1].strip("byte ").lstrip(
                    "[").rstrip("]")
                if "+" in offset:
                    offset = offset.split("+ ")[-1]
                offset = int(offset)

                # write cmp constant to EnvironmentVariableA return buffer
                Buffer = dbg.read_string(dbg.get_stack_pointer() + offset)

                # Create tree node if not already created
                node_name = "{}_{}_{}".format(i, lpName, tree_parent[0:9])
                local_path.append(node_name)
                if not tree_parent:
                    if not tree.get_node(node_name):
                        tree.create_node(Buffer, node_name, data=Track(False))
                else:
                    if not tree.get_node(node_name):
                        tree.create_node(
                            Buffer,
                            node_name,
                            parent=tree_parent,
                            data=Track(False))

                # Set tree_parent to current node for next round
                tree_parent = node_name

                # Get immediate children of current node
                children = tree.children(node_name)

                # set all children explored to True unless proven wrong
                all_children_explored = True

                if not children:
                    # if children don't exist write buffer correctly
                    dbg.write_memory(lpBuffer, Buffer)
                else:
                    for index, child in enumerate(children):
                        if not child.data.explored and len(children) == 1:
                            # 1 Not Explored = Write Buffer
                            dbg.write_memory(lpBuffer, Buffer)
                            all_children_explored = False
                            break
                        elif child.data.explored and len(children) == 1:
                            # 1 Explored = Don't Write Buffer, more explore
                            all_children_explored = False
                            dbg.write_memory(lpBuffer, "NOTSET")
                            break
                        elif not child.data.explored:
                            # > 1 Not Explored = Don't Write Buffer, more explore
                            all_children_explored = False
                            dbg.write_memory(lpBuffer, "NOTSET")
                            break
                    if all_children_explored:
                        # > 1 All Explored mark self explored and exit
                        tree.get_node(node_name).data.explored = True
                        dbg.write_memory(lpBuffer, "NOTSET")
                        pykd.detachAllProcesses()
                        del dbg
                        break
                """# Are you feeling lucky? Randomly write the buffer correctly
                if bool(random.getrandbits(1)):
                    dbg.write_memory(lpBuffer, Buffer)
                else:
                    dbg.write_memory(lpBuffer, "NOTSET")
                """

                last = node_name
                logging.debug("[+] R{} lpName: {}=({},{})->{}".format(
                    i, lpName, hex(lpBuffer), nSize - 1, Buffer))

            except:
                break

    except:
        pass

    finally:
        # Check if this path was the same as last and set highest node explored
        try:
            if path == local_path:
                for path_index in range(len(path)):
                    if tree.get_node(
                            path[-(path_index + 1)]).data.explored == False:
                        logging.debug("[!] SAME PATH SET EXPLORED: {}".format(
                            tree.get_node(path[-(path_index + 1)]).identifier))
                        tree.get_node(
                            path[-(path_index + 1)]).data.explored = True
                        break
        except Exception as e:
            logging.debug("[!] SAME PATH ERROR {}".format(e))
        logging.debug("PATH: {}".format(path))
        logging.debug("LOCALPATH: {}".format(local_path))
        path = local_path

        tree.get_node(last).data.explored = True
        logging.debug("[+] Last: {}".format(last))
        parent = tree.parent(last).identifier
        logging.debug("[+] Parent: {}".format(parent))
        logging.debug("[+] Parent Children: {}".format(
            [x.identifier for x in tree.children(parent)]))
        pykd.detachAllProcesses()
        del dbg


def main():
    """Main method
    """
    global last
    global path
    parser = argparse.ArgumentParser(
        description="%s solves tos challenge" % __file__)
    parser.add_argument(
        "-v",
        "--verbosity",
        action="store_true",
        help="Print verbose debugging information")
    parser.add_argument("filename", type=str, help="Filename test")
    args = parser.parse_args()

    if args.verbosity:
        logging.basicConfig(
            level=logging.DEBUG,
            format=' %(asctime)s - %(levelname)s - %(message)s')

    logging.debug("[+] Args: %s" % args)

    # Loop 5ever trying to test all paths
    while 1:
        try:
            pykd.startProcess(args.filename)
            tos_dbg()
            # print tree.to_dict()
            print tree.show(line_type="ascii")

        except Exception as e:
            logging.debug(e)


if __name__ == '__main__':
    sys.exit(main())
env_vars1 = {
    'tos_a43b832e46b34d6990066f2bef5c43d6':
    'And my time was running wild',
    'tos_f482e72b184a421d86f02f742e25af30':
    'Yes were lovers, and that is that',
    'tos_0ec81c65eb904dfcaf125ea5f2f355b2':
    'I said that time may change me',
    'tos_8c0b75d408df48b9a0444b5c3b7be3ac':
    'Oh we can be heroes, just for one day',
    'tos_5354f9ec1ab341e0a07a2a296d917f05':
    'But I cant trace time',
    'tos_3b1d5947b6b743dc8c1b0a9f37d01427':
    'Oh we can beat them, forever and ever'
}
# http://dl.labyrenth.com/tos.inf/52b820b4471767e1be68c61d5fd0cd67cd380a2ba84280e6901d0166dd522fc8.7z


env_vars2 = {
    'tos_963b7e4e2f3a4da59e73bdf4849cda3d':
    'Dropped my cell phone down below',
    'tos_f1e8c8aff51f4a81bfe26f93f508e1d2':
    'Of warm impermanence and',
    'tos_c184a7ee23d6489791667168324114d2':
    'Oh we can beat them, forever and ever',
    'tos_723ad3b7e2d344ae82d2222fe67ba0ff':
    'Chchchchchanges',
    'tos_66d40d4cb17e4b28b4079eaf653f7730':
    'We can be heroes, just for one day',
    'tos_7f9411315d7642c2bd88bb4c9a5e6f58':
    'A million deadend streets',
    'tos_414667c264b5475b8610d9fcd94bed97':
    'Wheres your shame',
    'tos_860afef9bfd4478d8959d92c150a2e27':
    'And these children that you spit on',
    'tos_26d1b1b3ade243ec9e54c75f2d2104fb':
    'Then we could be heroes, just for one day'
}

# http://dl.labyrenth.com/tos.inf/aabde9674c602172beccbdc0616292ec533474d30807c7b099febdb45d5aa405.7z


env_vars3 = {
    'tos_4abde6b94d9d42bd88701d59de979f9a':
    'I never did anything out of the blue',
    'tos_4e801728d1054995bb3d99ddfefa8ab3':
    'Ashes to ashes, fun to funky',
    'tos_68f4f342be354e06bc4cc846b10573e0':
    'Your circuits dead, theres something wrong',
    'tos_1e46cef8455c4c8f81e7366d64f58984':
    'Do you remember a guy thats been in such an early song',
    'tos_f60ad38f40d442f696c1c4bf4dde4840':
    'And theres nothing I can do',
    'tos_68652ae0be884b4683e4fd8968f24877':
    'To get things done',
    'tos_56b4cd654be24143b50bb8dc9cb42581':
    'Im feeling very still',
    'tos_e47886d1a9f6422b8b1627da6b060792':
    'Now its time to leave the capsule if you dare'
}

# http://dl.labyrenth.com/tos.inf/d2476ee0823448f4e6628676b252d65a6a5d6169d73a31fa5df3e16bee4b95f2.7z


env_varsF = {
    'tos_f383daf0a74b41f8990393c17b650daa':
    'Ziggy played guitar',
    'tos_16f6199a29ed4c9b925a22a4495963e0':
    'Then we could be heroes just for one day',
    'tos_1044c6fa7bf84c2bbd3a408fe7b12aeb':
    'Heaven knows, shed have taken anything, but',
    'tos_9558027f72f24a3a82dc3754069d073a':
    'Showing nothing, he swoops like a song',
    'tos_4a3836298c4b443385632486897bb13e':
    'Oh, look out you rock n rollers',
    'tos_b82921bc59f5465d8e9a676d569c0baf':
    'He kissed her then and there'
}

# PAN{dd864aebeeba3e125dce2e111e6ea04fb759333409a87da8e7bd413b3e36105b}