一.背景
一道ctf题,通过破解2048游戏获得flag
游戏的规则很简单,需要控制所有方块向同一个方向运动,两个相同数字方块撞在一起之后合并成为他们的和,每次操作之后会随机生成一个2或者4,最终得到一个“2048”的方块就算胜利了。
二.工具准备
1.pyinstxtractor.py脚本用于反编译python
脚本内容如下
from __future__ import print_function import os import struct import marshal import zlib import sys import imp import types from uuid import uuid4 as uniquename class CTOCEntry: def __init__(self, position, cmprsdDataSize, uncmprsdDataSize, cmprsFlag, typeCmprsData, name): self.position = position self.cmprsdDataSize = cmprsdDataSize self.uncmprsdDataSize = uncmprsdDataSize self.cmprsFlag = cmprsFlag self.typeCmprsData = typeCmprsData self.name = name class PyInstArchive: PYINST20_COOKIE_SIZE = 24 # For pyinstaller 2.0 PYINST21_COOKIE_SIZE = 24 + 64 # For pyinstaller 2.1+ MAGIC = b'MEI\014\013\012\013\016' # Magic number which identifies pyinstaller def __init__(self, path): self.filePath = path def open(self): try: self.fPtr = open(self.filePath, 'rb') self.fileSize = os.stat(self.filePath).st_size except: print('[*] Error: Could not open {0}'.format(self.filePath)) return False return True def close(self): try: self.fPtr.close() except: pass def checkFile(self): print('[*] Processing {0}'.format(self.filePath)) # Check if it is a 2.0 archive self.fPtr.seek(self.fileSize - self.PYINST20_COOKIE_SIZE, os.SEEK_SET) magicFromFile = self.fPtr.read(len(self.MAGIC)) if magicFromFile == self.MAGIC: self.pyinstVer = 20 # pyinstaller 2.0 print('[*] Pyinstaller version: 2.0') return True # Check for pyinstaller 2.1+ before bailing out self.fPtr.seek(self.fileSize - self.PYINST21_COOKIE_SIZE, os.SEEK_SET) magicFromFile = self.fPtr.read(len(self.MAGIC)) if magicFromFile == self.MAGIC: print('[*] Pyinstaller version: 2.1+') self.pyinstVer = 21 # pyinstaller 2.1+ return True print('[*] Error : Unsupported pyinstaller version or not a pyinstaller archive') return False def getCArchiveInfo(self): try: if self.pyinstVer == 20: self.fPtr.seek(self.fileSize - self.PYINST20_COOKIE_SIZE, os.SEEK_SET) # Read CArchive cookie (magic, lengthofPackage, toc, tocLen, self.pyver) = struct.unpack('!8siiii', self.fPtr.read(self.PYINST20_COOKIE_SIZE)) elif self.pyinstVer == 21: self.fPtr.seek(self.fileSize - self.PYINST21_COOKIE_SIZE, os.SEEK_SET) # Read CArchive cookie (magic, lengthofPackage, toc, tocLen, self.pyver, pylibname) = struct.unpack('!8siiii64s', self.fPtr.read(self.PYINST21_COOKIE_SIZE)) except: print('[*] Error : The file is not a pyinstaller archive') return False print('[*] Python version: {0}'.format(self.pyver)) # Overlay is the data appended at the end of the PE self.overlaySize = lengthofPackage self.overlayPos = self.fileSize - self.overlaySize self.tableOfContentsPos = self.overlayPos + toc self.tableOfContentsSize = tocLen print('[*] Length of package: {0} bytes'.format(self.overlaySize)) return True def parseTOC(self): # Go to the table of contents self.fPtr.seek(self.tableOfContentsPos, os.SEEK_SET) self.tocList = [] parsedLen = 0 # Parse table of contents while parsedLen < self.tableOfContentsSize: (entrySize, ) = struct.unpack('!i', self.fPtr.read(4)) nameLen = struct.calcsize('!iiiiBc') (entryPos, cmprsdDataSize, uncmprsdDataSize, cmprsFlag, typeCmprsData, name) = struct.unpack( '!iiiBc{0}s'.format(entrySize - nameLen), self.fPtr.read(entrySize - 4)) name = name.decode('utf-8').rstrip('\0') if len(name) == 0: name = str(uniquename()) print('[!] Warning: Found an unamed file in CArchive. Using random name {0}'.format(name)) self.tocList.append( CTOCEntry( self.overlayPos + entryPos, cmprsdDataSize, uncmprsdDataSize, cmprsFlag, typeCmprsData, name )) parsedLen += entrySize print('[*] Found {0} files in CArchive'.format(len(self.tocList))) def extractFiles(self): print('[*] Beginning extraction...please standby') extractionDir = os.path.join(os.getcwd(), os.path.basename(self.filePath) + '_extracted') if not os.path.exists(extractionDir): os.mkdir(extractionDir) os.chdir(extractionDir) for entry in self.tocList: basePath = os.path.dirname(entry.name) if basePath != '': # Check if path exists, create if not if not os.path.exists(basePath): os.makedirs(basePath) self.fPtr.seek(entry.position, os.SEEK_SET) data = self.fPtr.read(entry.cmprsdDataSize) if entry.cmprsFlag == 1: data = zlib.decompress(data) # Malware may tamper with the uncompressed size # Comment out the assertion in such a case assert len(data) == entry.uncmprsdDataSize # Sanity Check with open(entry.name, 'wb') as f: f.write(data) if entry.typeCmprsData == b's': print('[+] Possible entry point: {0}'.format(entry.name)) elif entry.typeCmprsData == b'z' or entry.typeCmprsData == b'Z': self._extractPyz(entry.name) def _extractPyz(self, name): dirName = name + '_extracted' # Create a directory for the contents of the pyz if not os.path.exists(dirName): os.mkdir(dirName) with open(name, 'rb') as f: pyzMagic = f.read(4) assert pyzMagic == b'PYZ\0' # Sanity Check pycHeader = f.read(4) # Python magic value if imp.get_magic() != pycHeader: print('[!] Warning: The script is running in a different python version than the one used to build the executable') print(' Run this script in Python{0} to prevent extraction errors(if any) during unmarshalling'.format(self.pyver)) (tocPosition, ) = struct.unpack('!i', f.read(4)) f.seek(tocPosition, os.SEEK_SET) try: toc = marshal.load(f) except: print('[!] Unmarshalling FAILED. Cannot extract {0}. Extracting remaining files.'.format(name)) return print('[*] Found {0} files in PYZ archive'.format(len(toc))) # From pyinstaller 3.1+ toc is a list of tuples if type(toc) == list: toc = dict(toc) for key in toc.keys(): (ispkg, pos, length) = toc[key] f.seek(pos, os.SEEK_SET) fileName = key try: # for Python > 3.3 some keys are bytes object some are str object fileName = key.decode('utf-8') except: pass # Make sure destination directory exists, ensuring we keep inside dirName destName = os.path.join(dirName, fileName.replace("..", "__")) destDirName = os.path.dirname(destName) if not os.path.exists(destDirName): os.makedirs(destDirName) try: data = f.read(length) data = zlib.decompress(data) except: print('[!] Error: Failed to decompress {0}, probably encrypted. Extracting as is.'.format(fileName)) open(destName + '.pyc.encrypted', 'wb').write(data) continue with open(destName + '.pyc', 'wb') as pycFile: pycFile.write(pycHeader) # Write pyc magic pycFile.write(b'\0' * 4) # Write timestamp if self.pyver >= 33: pycFile.write(b'\0' * 4) # Size parameter added in Python 3.3 pycFile.write(data) def main(): if len(sys.argv) < 2: print('[*] Usage: pyinstxtractor.py <filename>') else: arch = PyInstArchive(sys.argv[1]) if arch.open(): if arch.checkFile(): if arch.getCArchiveInfo(): arch.parseTOC() arch.extractFiles() arch.close() print('[*] Successfully extracted pyinstaller archive: {0}'.format(sys.argv[1])) print('') print('You can now use a python decompiler on the pyc files within the extracted directory') return arch.close() if __name__ == '__main__': main()
2.winhex用于编辑16进制的软件
压缩包已上传至博主资源,下载地址:https://blog.csdn.net/qq_50216270"text-align: center">
2.运行脚本
在终端中输入python后输入脚本名和待反编译exe文件名
编译成功后会在原路径生成如下文件夹
3.找到软件名文件和struct文件
4.托入winhex进行对比
5.将struct多出的那一行复制到puzzle前面
6.更改其后缀为.pyc
7.安装第三方库uncompyle
8.python版本为3.8以下可以调用uncompyle
对应路径终端输入uncompyle6 puzzle.pyc > puzzle.py
9.python版本为3.8以上可以选择在线工具(.pyc>.py)
https://tool.lu/pyc/
10.最后可以得到puzzle.py文件
代码如下
#!/usr/bin/env python # visit http://tool.lu/pyc/ for more information import random from tkinter import Frame, Label, CENTER import logic import constants as c class GameGrid(Frame): def __init__(self): Frame.__init__(self) self.grid() self.master.title('C1CTF2019') self.master.bind('<Key>', self.key_down) self.commands = { c.KEY_J: logic.down, c.KEY_K: logic.up, c.KEY_L: logic.right, c.KEY_H: logic.left, c.KEY_RIGHT_ALT: logic.right, c.KEY_LEFT_ALT: logic.left, c.KEY_DOWN_ALT: logic.down, c.KEY_UP_ALT: logic.up, c.KEY_RIGHT: logic.right, c.KEY_LEFT: logic.left, c.KEY_DOWN: logic.down, c.KEY_UP: logic.up } self.grid_cells = [] self.init_grid() self.init_matrix() self.update_grid_cells() self.mainloop() def init_grid(self): background = Frame(self, c.BACKGROUND_COLOR_GAME, c.SIZE, c.SIZE, **('bg', 'width', 'height')) background.grid() for i in range(c.GRID_LEN): grid_row = [] for j in range(c.GRID_LEN): cell = Frame(background, c.BACKGROUND_COLOR_CELL_EMPTY, c.SIZE / c.GRID_LEN, c.SIZE / c.GRID_LEN, **('bg', 'width', 'height')) cell.grid(i, j, c.GRID_PADDING, c.GRID_PADDING, **('row', 'column', 'padx', 'pady')) t = Label(cell, '', c.BACKGROUND_COLOR_CELL_EMPTY, CENTER, c.FONT, 5, 2, **('master', 'text', 'bg', 'justify', 'font', 'width', 'height')) t.grid() grid_row.append(t) self.grid_cells.append(grid_row) def gen(self): return random.randint(0, c.GRID_LEN - 1) def init_matrix(self): self.matrix = logic.new_game(4) self.history_matrixs = list() self.matrix = logic.add_two(self.matrix) self.matrix = logic.add_two(self.matrix) def update_grid_cells(self): for i in range(c.GRID_LEN): for j in range(c.GRID_LEN): new_number = self.matrix[i][j] if new_number == 0: self.grid_cells[i][j].configure('', c.BACKGROUND_COLOR_CELL_EMPTY, **('text', 'bg')) continue self.grid_cells[i][j].configure(str(new_number), c.BACKGROUND_COLOR_DICT[new_number], c.CELL_COLOR_DICT[new_number], **('text', 'bg', 'fg')) self.update_idletasks() def key_down(self, event): key = repr(event.char) if key == c.KEY_BACK and len(self.history_matrixs) > 1: self.matrix = self.history_matrixs.pop() self.update_grid_cells() print('back on step total step:', len(self.history_matrixs)) elif key in self.commands: (self.matrix, done) = self.commands[repr(event.char)](self.matrix) if done: self.matrix = logic.add_two(self.matrix) self.history_matrixs.append(self.matrix) self.update_grid_cells() done = False if logic.game_state(self.matrix) == 'win': self.grid_cells[1][0].configure('C1CTF', c.BACKGROUND_COLOR_CELL_EMPTY, **('text', 'bg')) self.grid_cells[1][1].configure('{2048', c.BACKGROUND_COLOR_CELL_EMPTY, **('text', 'bg')) self.grid_cells[1][2].configure('_1s_', c.BACKGROUND_COLOR_CELL_EMPTY, **('text', 'bg')) self.grid_cells[1][3].configure('fun}', c.BACKGROUND_COLOR_CELL_EMPTY, **('text', 'bg')) if logic.game_state(self.matrix) == 'lose': self.grid_cells[1][1].configure('You', c.BACKGROUND_COLOR_CELL_EMPTY, **('text', 'bg')) self.grid_cells[1][2].configure('Lost!', c.BACKGROUND_COLOR_CELL_EMPTY, **('text', 'bg')) def generate_next(self): index = (self.gen(), self.gen()) while self.matrix[index[0]][index[1]] != 0: index = (self.gen(), self.gen()) self.matrix[index[0]][index[1]] = 2 gamegrid = GameGrid()
11.找到flag大公告成