当前位置 博文首页 > python反编译教程之2048小游戏实例

    python反编译教程之2048小游戏实例

    作者:meteor_yh 时间:2021-07-19 18:39

    目录
    • 一.背景
    • 二.工具准备
      • 1.pyinstxtractor.py脚本用于反编译python
      • 2.winhex用于编辑16进制的软件
    • 三.反编译
      • 1.放置脚本
      • 2.运行脚本
      • 3.找到软件名文件和struct文件
      • 4.托入winhex进行对比
      • 5.将struct多出的那一行复制到puzzle前面
      • 6.更改其后缀为.pyc
      • 7.安装第三方库uncompyle
      • 8.python版本为3.8以下可以调用uncompyle
      • 9.python版本为3.8以上可以选择在线工具(.pyc>.py)
      • 10.最后可以得到puzzle.py文件
    • 总结

      一.背景

      一道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?type=download

      三.反编译

      1.放置脚本

      将脚本和待编译的exe文件放在同一路径下后,在路径框中输入cmd打开终端

      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大公告成

      总结

      jsjbwy
      下一篇:没有了