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


Posted in Python onMarch 03, 2021

一.背景

一道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小游戏实例

总结

到此这篇关于python反编译教程之2048小游戏实例的文章就介绍到这了,更多相关python反编译2048小游戏内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
python web框架学习笔记
May 03 Python
python win32 简单操作方法
May 25 Python
Python的装饰器使用详解
Jun 26 Python
python的socket编程入门
Jan 29 Python
selenium + python 获取table数据的示例讲解
Oct 13 Python
python实现顺时针打印矩阵
Mar 02 Python
Python正则表达式匹配数字和小数的方法
Jul 03 Python
Python基于BeautifulSoup和requests实现的爬虫功能示例
Aug 02 Python
无需压缩软件,用python帮你操作压缩包
Aug 17 Python
Python字符串查找基本操作代码案例
Oct 27 Python
通过Django Admin+HttpRunner1.5.6实现简易接口测试平台
Nov 11 Python
python爬虫beautifulsoup库使用操作教程全解(python爬虫基础入门)
Feb 19 Python
python 如何读、写、解析CSV文件
Mar 03 #Python
聊聊python在linux下与windows下导入模块的区别说明
Mar 03 #Python
python 递归相关知识总结
Mar 03 #Python
使用pandas读取表格数据并进行单行数据拼接的详细教程
Mar 03 #Python
用gpu训练好的神经网络,用tensorflow-cpu跑出错的原因及解决方案
Mar 03 #Python
神经网络训练采用gpu设置的方式
Mar 03 #Python
解决TensorFlow训练模型及保存数量限制的问题
Mar 03 #Python
You might like
PHP读取大文件的几种方法介绍
2016/10/27 PHP
扩展JavaScript功能的正确方法(译文)
2012/04/12 Javascript
jquery remove方法应用详解
2012/11/22 Javascript
JS字符串处理实例代码
2013/08/05 Javascript
JavaScript中使用ActiveXObject操作本地文件夹的方法
2014/03/28 Javascript
JavaScript语言对Unicode字符集的支持详解
2014/12/30 Javascript
JS简单计算器实例
2015/01/20 Javascript
JS获取下拉框显示值和判断单选按钮的方法
2015/07/09 Javascript
jQuery+CSS3文字跑马灯特效的简单实现
2016/06/25 Javascript
JS函数多个参数默认值指定方法分析
2016/11/28 Javascript
vue.js移动端app之上拉加载以及下拉刷新实战
2017/09/11 Javascript
JS调用安卓手机摄像头扫描二维码
2018/10/16 Javascript
VueCli3构建TS项目的方法步骤
2018/11/07 Javascript
Vue分页插件的前后端配置与使用
2019/10/09 Javascript
微信小程序swiper使用网络图片不显示问题解决
2019/12/13 Javascript
JS Thunk 函数的含义和用法实例总结
2020/04/08 Javascript
Python 的描述符 descriptor详解
2016/02/27 Python
答题辅助python代码实现
2018/01/16 Python
对numpy中shape的深入理解
2018/06/15 Python
Python SQLAlchemy库的使用方法
2020/10/13 Python
Python根据字符串调用函数过程解析
2020/11/05 Python
python excel多行合并的方法
2020/12/09 Python
HTML5中外部浏览器唤起微信分享
2020/01/02 HTML / CSS
英国奢侈皮具品牌:Aspinal of London
2018/09/02 全球购物
实习生单位鉴定意见
2013/12/04 职场文书
万年牢教学反思
2014/02/15 职场文书
共产党员公开承诺书
2014/03/25 职场文书
整顿机关作风心得体会
2014/09/10 职场文书
省委召开党的群众路线教育实践活动总结大会报告
2014/10/21 职场文书
三方股东合作协议书
2014/10/28 职场文书
婚庆司仪开场白
2015/05/29 职场文书
重温入党誓词主持词
2015/06/29 职场文书
机关干部正风肃纪心得体会
2016/01/15 职场文书
煤矿施工安全协议书
2016/03/22 职场文书
有关信念的名言语录集锦
2019/12/06 职场文书
无线电通信名词解释
2022/02/18 无线电