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中使用全局日志时需要注意的问题
May 06 Python
Python 模拟登陆的两种实现方法
Aug 10 Python
Python封装原理与实现方法详解
Aug 28 Python
Django网络框架之HelloDjango项目创建教程
Jun 06 Python
Python实现FLV视频拼接功能
Jan 21 Python
pytorch 实现在一个优化器中设置多个网络参数的例子
Feb 20 Python
深入理解Tensorflow中的masking和padding
Feb 24 Python
pandas中ix的使用详细讲解
Mar 09 Python
Python 给下载文件显示进度条和下载时间的实现
Apr 02 Python
python实点云分割k-means(sklearn)详解
May 28 Python
python开发的自动化运维工具ansible详解
Aug 07 Python
在python中读取和写入CSV文件详情
Jun 28 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操作数组相关函数
2011/02/03 PHP
php中的filesystem文件系统函数介绍及使用示例
2014/02/13 PHP
php计算几分钟前、几小时前、几天前的几个函数、类分享
2014/04/09 PHP
PHP date函数常用时间处理方法
2015/05/11 PHP
php开发微信支付获取用户地址
2015/10/04 PHP
php array_walk 对数组中的每个元素应用用户自定义函数详解
2016/11/18 PHP
THINKPHP3.2使用soap连接webservice的解决方法
2017/12/13 PHP
防止网站内容被拷贝的一些方法与优缺点好处与坏处分析
2007/11/30 Javascript
24款非常有用的 jQuery 插件分享
2011/04/06 Javascript
浅析JQuery UI Dialog的样式设置问题
2013/12/18 Javascript
js取模(求余数)隔行变色
2014/05/15 Javascript
jQuery切换网页皮肤并保存到Cookie示例代码
2014/06/16 Javascript
利用原生JavaScript获取元素样式只是获取而已
2014/10/08 Javascript
在AngularJS中使用AJAX的方法
2015/06/17 Javascript
学习JavaScript设计模式之代理模式
2016/01/12 Javascript
Bootstrap 模态框自定义点击和关闭事件详解
2018/08/10 Javascript
Vue实现滑动拼图验证码功能
2019/09/15 Javascript
使用JS location实现搜索框历史记录功能
2019/12/23 Javascript
javascript实现前端分页效果
2020/06/24 Javascript
[06:33]DOTA2亚洲邀请赛小组赛第二日 TOP10精彩集锦
2015/01/31 DOTA
virtualenv实现多个版本Python共存
2017/08/21 Python
Python 获取命令行参数内容及参数个数的实例
2019/12/20 Python
解决Python logging模块无法正常输出日志的问题
2020/02/21 Python
基于python实现FTP文件上传与下载操作(ftp&amp;sftp协议)
2020/04/01 Python
基于matplotlib xticks用法详解
2020/04/16 Python
浅谈Keras参数 input_shape、input_dim和input_length用法
2020/06/29 Python
关于python tushare Tkinter构建的简单股票可视化查询系统(Beta v0.13)
2020/10/19 Python
大学生应聘自荐信
2013/10/11 职场文书
应届生求职信写作技巧
2013/10/24 职场文书
《老山界》教学反思
2014/04/08 职场文书
《生命的药方》教学反思
2014/04/08 职场文书
团干部培训方案
2014/06/03 职场文书
教育项目合作协议书格式
2014/10/17 职场文书
2014矛盾纠纷排查调处工作总结
2014/12/09 职场文书
话题作文之呼唤
2019/12/18 职场文书
MySQL的InnoDB存储引擎的数据页结构详解
2022/03/03 MySQL