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

作者:meteor_yh 时间:2023-07-24 08:04:47 

一.背景

一道ctf题,通过破解2048游戏获得flag

游戏的规则很简单,需要控制所有方块向同一个方向运动,两个相同数字方块撞在一起之后合并成为他们的和,每次操作之后会随机生成一个2或者4,最终得到一个“2048”的方块就算胜利了。

python反编译教程之2048小游戏实例python反编译教程之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打开终端

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

2.运行脚本

在终端中输入python后输入脚本名和待反编译exe文件名

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

编译成功后会在原路径生成如下文件夹

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

3.找到软件名文件和struct文件

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

4.托入winhex进行对比

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

5.将struct多出的那一行复制到puzzle前面

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

6.更改其后缀为.pyc

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

7.安装第三方库uncompyle

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

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

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

来源:https://blog.csdn.net/qq_50216270/article/details/114261616

标签:python,反编译,2048游戏
0
投稿

猜你喜欢

  • js 获取后台的字段 改变 checkbox的被选中的状态 代码

    2024-04-22 22:33:40
  • python梯度下降算法的实现

    2022-01-25 11:11:09
  • python 包实现 time 时间管理操作

    2023-03-13 21:37:52
  • python调用matlab的方法详解

    2023-10-18 06:39:02
  • Python openpyxl读取单元格字体颜色过程解析

    2023-02-09 06:57:02
  • PyQt QCombobox设置行高的方法

    2021-10-15 19:50:20
  • 零基础写python爬虫之抓取糗事百科代码分享

    2021-02-01 11:54:39
  • 一篇文章彻底弄懂Python中的if __name__ == __main__

    2023-04-27 08:42:14
  • 谈谈CSS样式表的命名规范

    2007-10-08 12:41:00
  • Python迭代器与生成器基本用法分析

    2022-07-06 20:19:50
  • 详解Python如何求不同分辨率图像的峰值信噪比

    2023-03-25 09:51:58
  • 解决python对齐错误的方法

    2023-08-11 05:40:07
  • 彻底搞懂Python字符编码

    2023-10-14 01:05:03
  • 发工资啦!教你用Python实现邮箱自动群发工资条

    2023-10-12 19:11:17
  • python opencv将图片转为灰度图的方法示例

    2021-03-24 22:22:54
  • python如何获取apk的packagename和activity

    2023-07-11 20:50:19
  • MySQL转义字符的实际应用

    2010-08-31 14:55:00
  • 90行Python代码开发个人云盘应用

    2021-12-17 13:44:12
  • Python爬虫获取基金净值信息详情

    2022-04-23 07:53:42
  • Vue实现父子组件页面刷新的几种常用方法

    2024-06-07 16:05:52
  • asp之家 网络编程 m.aspxhome.com