目錄
- 一.背景
- 二.工具準(zhǔn)備
- 1.pyinstxtractor.py腳本用于反編譯python
- 2.winhex用于編輯16進(jìn)制的軟件
- 三.反編譯
- 1.放置腳本
- 2.運(yùn)行腳本
- 3.找到軟件名文件和struct文件
- 4.托入winhex進(jìn)行對(duì)比
- 5.將struct多出的那一行復(fù)制到puzzle前面
- 6.更改其后綴為.pyc
- 7.安裝第三方庫(kù)uncompyle
- 8.python版本為3.8以下可以調(diào)用uncompyle
- 9.python版本為3.8以上可以選擇在線工具(.pyc>.py)
- 10.最后可以得到puzzle.py文件
- 總結(jié)
一.背景
一道ctf題,通過(guò)破解2048游戲獲得flag
游戲的規(guī)則很簡(jiǎn)單,需要控制所有方塊向同一個(gè)方向運(yùn)動(dòng),兩個(gè)相同數(shù)字方塊撞在一起之后合并成為他們的和,每次操作之后會(huì)隨機(jī)生成一個(gè)2或者4,最終得到一個(gè)“2048”的方塊就算勝利了。
二.工具準(zhǔn)備
1.pyinstxtractor.py腳本用于反編譯python
腳本內(nèi)容如下
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進(jìn)制的軟件
壓縮包已上傳至博主資源,下載地址:https://blog.csdn.net/qq_50216270?type=download
三.反編譯
1.放置腳本
將腳本和待編譯的exe文件放在同一路徑下后,在路徑框中輸入cmd打開(kāi)終端
2.運(yùn)行腳本
在終端中輸入python后輸入腳本名和待反編譯exe文件名
編譯成功后會(huì)在原路徑生成如下文件夾
3.找到軟件名文件和struct文件
4.托入winhex進(jìn)行對(duì)比
5.將struct多出的那一行復(fù)制到puzzle前面
6.更改其后綴為.pyc
7.安裝第三方庫(kù)uncompyle
8.python版本為3.8以下可以調(diào)用uncompyle
對(duì)應(yīng)路徑終端輸入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大公告成
總結(jié)
到此這篇關(guān)于python反編譯教程之2048小游戲?qū)嵗奈恼戮徒榻B到這了,更多相關(guān)python反編譯2048小游戲內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
您可能感興趣的文章:- Python實(shí)現(xiàn)簡(jiǎn)單2048小游戲
- Python實(shí)現(xiàn)簡(jiǎn)單的2048小游戲
- 一步步教你用Python實(shí)現(xiàn)2048小游戲
- 用Python寫一個(gè)無(wú)界面的2048小游戲
- Python新手實(shí)現(xiàn)2048小游戲
- python實(shí)現(xiàn)2048小游戲
- 用Python手把手教你實(shí)現(xiàn)2048小游戲