本文最后更新于 451 天前,其中的信息可能已经有所发展或是发生改变。
Guess by cyberutopian
我是只会复现的垃圾
- server.py
#!/usr/bin/env python3
import os
import base64
import random
import subprocess
random.seed(os.urandom(32))
# Enjoy the music :)
SALT_DICT=base64.b64decode(b'aHR0cHM6Ly95LnFxLmNvbS9uL3J5cXEvc29uZ0RldGFpbC8wMDAyOTJXNjJvODd3Rg==')
def generate_salts():
num_salts=random.randint(1,16)
return [bytes(random.choices(SALT_DICT,k=random.randint(16,32))) for _ in range(num_salts)]
def generate_tmpflag():
return os.urandom(32).hex().encode()
# create mem buffer which contains dl file
chal_fd=os.memfd_create("chal")
chal_path=f'/proc/{os.getpid()}/fd/{chal_fd}'
chal_template=open("a.dl","r").read()
# compile and write dl file using given salts and your guess rules
def generate_dl(salts,guesser):
G=f'GUESS(x) :- {guesser}.'
S='\n'.join([f'SALT("{i.decode()}").' for i in salts])
compiled_chal=chal_template.replace('//GUESS',G).replace("//SALTS",S)
os.truncate(chal_fd,0)
os.pwrite(chal_fd,compiled_chal.encode(),0)
# run souffle and check your answer
def run_chal(TMPFLAG):
cmdline=f"timeout -s KILL 1s ./souffle -D- -lhash --no-preprocessor -w {chal_path}"
proc=subprocess.Popen(args=cmdline,shell=True,stdin=subprocess.DEVNULL,stdout=subprocess.PIPE,stderr=subprocess.STDOUT,env={"TMPFLAG":TMPFLAG})
proc.wait()
out=proc.stdout.read()
prefix=b'---------------\nGUESS\nans\n===============\n'
if not out.startswith(prefix):
raise RuntimeError(f'Souffle error? {out}')
out=out.removeprefix(prefix)[:64]
if out!=TMPFLAG:
raise RuntimeError(f"Wrong guess")
# check user guess rules
def check_user_rules(r:str):
if len(r) > 300:
raise RuntimeError("rule too looong")
e=r.encode()
ban_chars=b'Ff.'
for i in e:
if i>0x7f or i<0x20 or i in ban_chars:
raise RuntimeError("illegal char in rule")
# how many rounds will you guess
DIFFICULTY=36
def game():
user_rules=input("your rules?\n")
check_user_rules(user_rules)
for i in range(DIFFICULTY):
generate_dl(generate_salts(),user_rules)
run_chal(generate_tmpflag())
print(f"guess {i} is correct")
print("Congratulations! Here is your flag:")
os.system("/readflag")
from pow import proof
import signal
signal.alarm(25)
try:
if os.getenv("NOPOW") is None:
proof()
game()
except Exception as e:
print(e)
- a.dl
.functor hash1(x:symbol):number
.functor hash2(x:symbol):number
.functor GETFLAG():symbol
.decl SALT(x:symbol)
//SALTS
.output SALT
.decl FLAG(x:symbol)
FLAG(@GETFLAG()).
.decl HINT(x:symbol)
HINT(substr(x,0,4)) :- FLAG(x).
.decl HASH(x:number)
HASH(@hash1(x)) :- FLAG(x).
.decl SALT_HASH1(h:number,s:symbol)
SALT_HASH1(h,s) :- h=@hash1(cat(flg,s)),FLAG(flg),SALT(s).
.decl SALT_HASH2(h:number,s:symbol)
SALT_HASH2(h,s) :- h=@hash2(cat(flg,s)),FLAG(flg),SALT(s).
.decl GUESS(x:symbol)
//GUESS
.output GUESS(attributeNames="ans")
.limitsize GUESS(n=1)
- a.dl为
datalog
,现学这门语言,勉强会写一点儿了。正常没有过滤的情况,只需要在GUESS
部分写入GUESS(x) :- FLAG(x)
便可以输出,但过滤Ff.
,从生成的hash salt
反推flag不太可能,比赛直接卡在这儿,然后放弃 - 以下思路来自NeSE
- 当datalog中进行symbol与number相互转换时存在安全隐患,在datalog中symbol的表现为number时,为0开始的编号,类似指针,那么在”内存”中,flag的编号就为最后一个
salt + 1
Note that type casts between numbers and symbols are particular dangerous because strings for certain ordinal numbers may not exist.
SALT(s), t=as(s, number) // symbol转换为number
max t: SALT(s), t=as(s, number) // 取最后一个
x=as(t + 1, symbol) // +1 取到flag
- 但此时先输出salt,不满足条件,改用HINT取flag
HINT(s), x = as(as(s, number)-1,symbol)
现场学习的能力还是有点儿差,而且阅读文档的时候受语言的影响很难快速定位自己需要的,还是得专心