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使用beautifulsoup从爱奇艺网抓取视频播放
Jan 23 Python
Python动态加载模块的3种方法
Nov 22 Python
Python用Bottle轻量级框架进行Web开发
Jun 08 Python
PyQt5每天必学之滑块控件QSlider
Apr 20 Python
Python实现查看系统启动项功能示例
May 10 Python
Python实现网站表单提交和模板
Jan 15 Python
解决Python selenium get页面很慢时的问题
Jan 30 Python
python 实现在shell窗口中编写print不向屏幕输出
Feb 19 Python
Django中Aggregation聚合的基本使用方法
Jul 09 Python
Scrapy-Redis之RedisSpider与RedisCrawlSpider详解
Nov 18 Python
python中实现栈的三种方法
Dec 19 Python
django注册用邮箱发送验证码的实现
Apr 18 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生成zip压缩文件的方法详解
2013/06/09 PHP
php调用mysql存储过程实例分析
2014/12/29 PHP
基于jquery的button默认enter事件(回车事件)。
2011/05/18 Javascript
javascript-表格排序(降序/反序)实现介绍(附图)
2013/05/30 Javascript
JS不能跨域借助jquery获取IP地址的方法
2014/08/20 Javascript
JavaScript中Function()函数的使用教程
2015/06/04 Javascript
基于AngularJS实现页面滚动到底自动加载数据的功能
2015/10/16 Javascript
全面解析Bootstrap表单使用方法(表单控件状态)
2015/11/24 Javascript
详解JavaScript 中的 replace 方法
2016/01/01 Javascript
jqueryMobile使用示例分享
2016/01/12 Javascript
Angularjs中使用Filters详解
2016/03/11 Javascript
javascript字体颜色控件的开发 JS实现字体控制
2017/11/27 Javascript
JavaScript中的 new 命令
2019/05/22 Javascript
node.js如何根据URL返回指定的图片详解
2020/10/21 Javascript
[01:44]Ti10举办地公布
2019/08/25 DOTA
详解Python中映射类型的内建函数和工厂函数
2015/08/19 Python
Python网络爬虫项目:内容提取器的定义
2016/10/25 Python
python实现列表中最大最小值输出的示例
2019/07/09 Python
Django结合ajax进行页面实时更新的例子
2019/08/12 Python
python连接PostgreSQL过程解析
2020/02/09 Python
matplotlib教程——强大的python作图工具库
2020/10/15 Python
python list的index()和find()的实现
2020/11/16 Python
Python3 + Appium + 安卓模拟器实现APP自动化测试并生成测试报告
2021/01/27 Python
纯CSS3实现绘制各种图形实现代码详细整理
2012/12/26 HTML / CSS
JOSEPH官网:英国奢侈时尚品牌
2018/01/31 全球购物
英国领先的在线高尔夫设备零售商:Golfgeardirect
2020/12/11 全球购物
网络工程与软件技术毕业生自荐信
2013/09/24 职场文书
应届毕业生就业自荐信
2013/10/26 职场文书
数据管理员的自我评价分享
2013/11/15 职场文书
电子商务专员岗位职责
2013/12/11 职场文书
《蚂蚁和蝈蝈》教学反思
2014/02/24 职场文书
《长江之歌》教学反思
2014/04/17 职场文书
传播学专业毕业生自荐书
2014/07/01 职场文书
场地使用证明模板
2014/10/25 职场文书
使用 MybatisPlus 连接 SqlServer 数据库解决 OFFSET 分页问题
2022/04/22 SQL Server
vue实现省市区联动 element-china-area-data插件
2022/04/22 Vue.js