snack
拿到题目,贪吃蛇
使用DIE工具检查可以得知该小游戏是用Py写的,且使用了PyInstaller打包
于是按照常规流程,使用现成的pyinstxtractor脚本拆包
得到exe反编译的文件后,找到该程序的主函数部分,可以发现snack是没有被反编译为pyc文件的,这是由于pyinstaller在打包过程中会自动将PyProject文件头抹除,所以用010edi添上后即可反编译(该头与Python版本有关)
这里又是经典环节之本地工具不如在线工具,同样的库本地就是跑不出来
使用在线反编译工具可出主函数源代码
#!/usr/bin/env python
# visit https://tool.lu/pyc/ for more information
# Version: Python 3.8
import pygame
import random
import key
def initialize(key):
key_length = len(key)
S = list(range(256))
j = 0
for i in range(256):
j = (j + S[i] + key[i % key_length]) % 256
S[i] = S[j]
S[j] = S[i]
return S
def generate_key_stream(S, length):
i = 0
j = 0
key_stream = []
for _ in range(length):
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i] = S[j]
S[j] = S[i]
key_stream.append(S[(S[i] + S[j]) % 256])
return key_stream
def decrypt(data, key):
S = initialize(key)
key_stream = generate_key_stream(S, len(data))
decrypted_data = None((lambda .0 = None: [ i ^ data[i] ^ key_stream[i] for i in .0 ])(range(len(data))))
return decrypted_data
pygame.init()
WINDOW_WIDTH = 800
WINDOW_HEIGHT = 600
SNAKE_SIZE = 20
SNAKE_SPEED = 20
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
window = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
pygame.display.set_caption('贪吃蛇')
font = pygame.font.Font(None, 36)
snake = [
(200, 200),
(210, 200),
(220, 200)]
snake_direction = (SNAKE_SPEED, 0)
food = ((random.randint(0, WINDOW_WIDTH - SNAKE_SIZE) // SNAKE_SIZE) * SNAKE_SIZE, (random.randint(0, WINDOW_HEIGHT - SNAKE_SIZE) // SNAKE_SIZE) * SNAKE_SIZE)
key_bytes = bytes((lambda .0: [ ord(char) for char in .0 ])(key.xor_key))
data = [
101,
97,
39,
125,
218,
172,
205,
3,
235,
195,
72,
125,
89,
130,
103,
213,
120,
227,
193,
67,
174,
71,
162,
248,
244,
12,
238,
92,
160,
203,
185,
155]
decrypted_data = decrypt(bytes(data), key_bytes)
running = True
if running:
window.fill(BLACK)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN or event.key == pygame.K_UP:
snake_direction = (0, -SNAKE_SPEED)
elif event.key == pygame.K_DOWN:
snake_direction = (0, SNAKE_SPEED)
elif event.key == pygame.K_LEFT:
snake_direction = (-SNAKE_SPEED, 0)
elif event.key == pygame.K_RIGHT:
snake_direction = (SNAKE_SPEED, 0)
continue
snake_head = (snake[0][0] + snake_direction[0], snake[0][1] + snake_direction[1])
snake.insert(0, snake_head)
snake.pop()
if snake[0] == food:
food = ((random.randint(0, WINDOW_WIDTH - SNAKE_SIZE) // SNAKE_SIZE) * SNAKE_SIZE, (random.randint(0, WINDOW_HEIGHT - SNAKE_SIZE) // SNAKE_SIZE) * SNAKE_SIZE)
snake.append(snake[-1])
if snake[0][0] < 0 and snake[0][0] >= WINDOW_WIDTH and snake[0][1] < 0 and snake[0][1] >= WINDOW_HEIGHT or snake[0] in snake[1:]:
running = False
for segment in snake:
pygame.draw.rect(window, WHITE, (segment[0], segment[1], SNAKE_SIZE, SNAKE_SIZE))
pygame.draw.rect(window, RED, (food[0], food[1], SNAKE_SIZE, SNAKE_SIZE))
score_text = font.render(f'''Score: {len(snake)}''', True, WHITE)
speed_text = font.render(f'''Speed: {SNAKE_SPEED}''', True, WHITE)
window.blit(score_text, (10, 10))
window.blit(speed_text, (10, 40))
score = len(snake)
if score >= 9999:
flag_text = font.render('Flag: ' + decrypted_data.decode(), True, WHITE)
window.blit(flag_text, (10, 70))
pygame.display.update()
pygame.time.Clock().tick(10)
continue
pygame.quit()
可以看出主函数里已经给出了加密后的密文以及详细的加密过程/函数,但是还缺异或的Key
从key_bytes可以看出异或的key是从key.xor_key引用来的
在pyc工程文件中找到key.pyc
尝试反编译失败,使用010发现其文件头少了四个字节
添加后即可反编译得到xor_key
exp:
xor_key = 'V3rY_v3Ry_Ez'
data = [
101, 97, 39, 125, 218, 172, 205, 3, 235, 195, 72, 125, 89, 130, 103, 213,
120, 227, 193, 67, 174, 71, 162, 248, 244, 12, 238, 92, 160, 203, 185, 155
]
key_bytes = bytes(ord(char) for char in xor_key)
def initialize(key):
key_length = len(key)
S = list(range(256))
j = 0
for i in range(256):
j = (j + S[i] + key[i % key_length]) % 256
S[i], S[j] = S[j], S[i]
return S
def generate_key_stream(S, length):
i = 0
j = 0
key_stream = []
for _ in range(length):
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
key_stream.append(S[(S[i] + S[j]) % 256])
return key_stream
def decrypt(data, key):
S = initialize(key)
key_stream = generate_key_stream(S, len(data))
decrypted_data = bytes(i ^ data[i] ^ key_stream[i] for i in range(len(data)))
return decrypted_data
decrypted_data = decrypt(bytes(data), key_bytes)
flag = decrypted_data.decode()
print(flag)
主函数中还用到了诸如lambda之类意义不明的函数,只能暂且理解为生成游戏中会用到的功能了
不同的 Python 版本会有不同的 PyObject_HEAD,以下是各版本的文件头: