This commit is contained in:
emc2314
2020-11-07 02:22:37 +08:00
parent 1061a252b5
commit e531d8f8ae
13 changed files with 506 additions and 0 deletions
+53
View File
@@ -1 +1,54 @@
# 超基础的数理模拟器
这是一道简单的检查数理基础扎实与否的题目,网站会随机生成定积分的式子供选手计算,如果能答对 400 题即可拿到 flag。
对于数理基础扎实的同学,你们通过口算定积分可以很轻松拿到 flag。
但是,如果你们的数理基础不够扎实,有毅力的同学也可以将每一道题都输入 CASIO 计算器,重复 400 遍即可达成条件。
对于没有 CASIO 计算器以及 Mathematica 等计算软件的同学,你们可以使用中国科技大学大雾实验专用坐标纸,在上面精准描绘之后数格点个数,不过由于本题精度要求较高,所以可能比较废纸。
对于那些数理基础太不扎实的计院同学,你们可以考虑写出数理基础足够扎实的 python 代码,代替你们进行测试:
```python
# pip3 install sympy antlr4-python3-runtime
import requests
from sympy import Integral, Symbol, E, latex
from sympy.parsing.latex import parse_latex
from urllib.parse import quote_plus
# from IPython.core.display import display, HTML
e = Symbol('e')
x = Symbol('x')
token = 'REDACTED' # Use your own token here
s = requests.session()
s.get(f'http://202.38.93.111:10190/login?token={quote_plus(token)}')
t = s.get('http://202.38.93.111:10190/').text
num = 400
while num > 1:
num = t[t.find('<h1 class="cover-heading">')+26:t.find('</h1>')]
num = int(num.split(' ')[1])
print(num)
m = t[t.find('<p> $')+5:t.find('{d x}$</p>')]
# display(HTML(f"${m}$"))
lu = m.split(' ')[0]
lower = lu[6:lu.find('^')-1]
lower = parse_latex(lower).n(20)
upper = lu[lu.find('^')+2:-1]
upper = parse_latex(upper).n(20)
m = ' '.join(m.split(' ')[1:])
m = m.replace(r'\right)', ')').replace(r'\left(', '(').replace(r'\,', '')
m = parse_latex(m).subs(e,E)
# display(HTML(f"${latex(m)}$"))
ans = Integral(m, (x, lower, upper)).n(10)
t = s.post('http://202.38.93.111:10190/submit', data={'ans':str(ans)}).text
print(t)
```
@@ -0,0 +1,13 @@
version: "2.4"
services:
simulator:
restart: always
build: simulator
ports:
- "10190:80"
generator:
restart: always
build: generator
scale: 8
expose:
- "5000"
@@ -0,0 +1,11 @@
FROM sagemath/sagemath
RUN sudo sed -i 's/archive.ubuntu.com/mirrors.bfsu.edu.cn/g' /etc/apt/sources.list
RUN sage -pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
RUN sage -pip install flask
ADD gen.sage /home/sage
EXPOSE 5000
ENTRYPOINT sage gen.sage
@@ -0,0 +1,52 @@
from flask import Flask
from flask import request
app = Flask(__name__)
x = var('x')
y = var('y')
half1 = [log(x), sqrt(x)] # x >= 1
half2 = [x/y, y/x] # y > 0 and y is int and x >= 1
full1 = [e^x, sin(x), cos(x), sinh(x), cosh(x), arctan(x)]
full2 = [x+y, x+y, x*y, x-y]
def gen_f(depth=0):
if (randint(0,2) != 0 and depth < 4) or depth < 2:
return choice(full2)(x=gen_f(depth+1),y=gen_f(depth+1))
elif (randint(0,2) != 0 and depth < 4):
return choice(full1)(x=gen_f(depth+1))
elif randint(0,2) != 0:
return choice(half1)
else:
return choice(half2)(y=randint(1,4))
def gen_q():
while True:
f = gen_f()
if f(x=1) == f(x=2) and f(x=2) == f(x=3):
continue
l = [QQ(randint(1,10))/QQ(randint(1,5)), QQ(randint(1,10))/QQ(randint(1,5))]
if l[0] == l[1]:
continue
l.sort()
fint = f.integral(x,l[0],l[1],hold=True)
result, eps = numerical_integral(f,l[0],l[1])
if abs(result) < 100000 and eps < 1e-7:
break
return (result, latex(fint).replace('log', 'ln'))
@app.route('/')
def gen():
try:
times = int(request.args.get('times'))
except:
times = 1
ret = ''
for i in range(times):
r, s = gen_q()
ret += str(r) + '\n' + s + '\n'
return ret
if __name__ == '__main__':
app.run(host='0.0.0.0')
@@ -0,0 +1,6 @@
FROM tiangolo/uwsgi-nginx-flask:python3.8
COPY ./app /app
RUN pip3 config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
RUN pip3 install requests pycryptodome pyopenssl
@@ -0,0 +1,17 @@
-----BEGIN CERTIFICATE-----
MIICwDCCAmagAwIBAgIUHo/UysuMf4+WAcChW3hM1NHDQDkwCgYIKoZIzj0EAwIw
gbYxCzAJBgNVBAYTAkNOMQ4wDAYDVQQIDAVBbmh1aTEOMAwGA1UEBwwFSGVmZWkx
HjAcBgNVBAoMFVVTVEMgTGludXggVXNlciBHcm91cDEhMB8GA1UECwwYVGhlIEhh
Y2tlcmdhbWUgQ29tbWl0dGVlMR0wGwYDVQQDDBRoYWNrLmx1Zy51c3RjLmVkdS5j
bjElMCMGCSqGSIb3DQEJARYWaGFja2VyZ2FtZUB1c3RjbHVnLm9yZzAeFw0yMDEw
MDgxNTIwMzZaFw0yMTEwMDgxNTIwMzZaMIG2MQswCQYDVQQGEwJDTjEOMAwGA1UE
CAwFQW5odWkxDjAMBgNVBAcMBUhlZmVpMR4wHAYDVQQKDBVVU1RDIExpbnV4IFVz
ZXIgR3JvdXAxITAfBgNVBAsMGFRoZSBIYWNrZXJnYW1lIENvbW1pdHRlZTEdMBsG
A1UEAwwUaGFjay5sdWcudXN0Yy5lZHUuY24xJTAjBgkqhkiG9w0BCQEWFmhhY2tl
cmdhbWVAdXN0Y2x1Zy5vcmcwVjAQBgcqhkjOPQIBBgUrgQQACgNCAAQcAwLQKJf1
W8podrvryfX7GI3xEneVlEriKNh7ltLe35BfU405TSKVXOKZfxdukiUwKZUPOI4o
zqsAfklnl/Jco1MwUTAdBgNVHQ4EFgQUnHTMWSWk9BgfVDphGi/VhTH5zOYwHwYD
VR0jBBgwFoAUnHTMWSWk9BgfVDphGi/VhTH5zOYwDwYDVR0TAQH/BAUwAwEB/zAK
BggqhkjOPQQDAgNIADBFAiAtkdkJ6vO1abjJt5jCnRDHCnGHTPetOtAQ12Fr1fo1
eQIhAO4T9HjFgSAP5Bg/bzeTS5jGvZAnN14e2sjllR6392J8
-----END CERTIFICATE-----
@@ -0,0 +1,155 @@
from werkzeug.datastructures import CallbackDict
from flask.sessions import SessionInterface, SessionMixin
from Crypto.Cipher import AES
import json
from json import JSONEncoder, JSONDecoder
import base64
import hashlib
import zlib
class EncryptedSession(CallbackDict, SessionMixin):
def __init__(self, initial=None):
def on_update(self):
self.modified = True
CallbackDict.__init__(self, initial, on_update)
self.modified = False
class EncryptedSessionInterface(SessionInterface):
session_class = EncryptedSession
compress_threshold = 1024
def open_session(self, app, request):
'''
@param py: Flask py
@param request: Flask HTTP Request
@summary: Sets the current session from the request's session cooke. This overrides the default
Flask implementation, adding AES decryption of the client-side session cookie.
'''
# Get the session cookie
session_cookie = request.cookies.get(app.session_cookie_name)
if not session_cookie:
return self.session_class()
# Get the crypto key
crypto_key = app.config['SESSION_CRYPTO_KEY'] if 'SESSION_CRYPTO_KEY' in app.config else app.crypto_key
crypto_key = hashlib.sha256(crypto_key).digest()[:16]
# Split the session cookie : <z|u>.<base64 cipher text>.<base64 mac>.<base64 nonce>
itup = session_cookie.split(".")
if (len (itup) is not 4):
return self.session_class() # Session cookie not in the right format
try:
# Compressed data?
if (itup[0] == 'z'): # session cookie for compressed data starts with "z."
is_compressed = True
else:
is_compressed = False
# Decode the cookie parts from base64
ciphertext = base64.b64decode (bytes(itup[1], 'utf-8'))
mac = base64.b64decode (bytes(itup[2], 'utf-8'))
nonce = base64.b64decode (bytes(itup[3], 'utf-8'))
# Decrypt
cipher = AES.new (crypto_key, AES.MODE_EAX, nonce)
data = cipher.decrypt_and_verify (ciphertext, mac)
# Convert back to a dict and pass that onto the session
if is_compressed:
data = zlib.decompress (data)
session_dict = json.loads (str (data, 'utf-8'), cls=BinaryAwareJSONDecoder)
return self.session_class (session_dict)
except ValueError:
return self.session_class()
def save_session(self, app, session, response):
'''
@param py: Flask py
@param session: Flask / Werkzeug Session
@param response: Flask HTTP Response
@summary: Saves the current session. This overrides the default Flask implementation, adding
AES encryption of the client-side session cookie.
'''
domain = self.get_cookie_domain(app)
if not session:
if session.modified:
response.delete_cookie (app.session_cookie_name, domain=domain)
return
expires = self.get_expiration_time(app, session)
# Decide whether to compress
bdict = bytes (json.dumps (dict (session), cls=BinaryAwareJSONEncoder), 'utf-8')
if (len (bdict) > self.compress_threshold):
prefix = "z" # session cookie for compressed data starts with "z."
bdict = zlib.compress (bdict)
else:
prefix = "u" # session cookie for uncompressed data starts with "u."
# Get the crypto key
crypto_key = app.config['SESSION_CRYPTO_KEY'] if 'SESSION_CRYPTO_KEY' in app.config else app.crypto_key
crypto_key = hashlib.sha256(crypto_key).digest()[:16]
# Encrypt using AES in EAX mode
cipher = AES.new (crypto_key, AES.MODE_EAX)
ciphertext, mac = cipher.encrypt_and_digest (bdict)
nonce = cipher.nonce
# Convert the ciphertext, mac, and nonce to base64
b64_ciphertext = base64.b64encode (ciphertext)
b64_mac = base64.b64encode (mac)
b64_nonce = base64.b64encode (nonce)
# Create the session cookie as <u|z>.<base64 cipher text>.<base64 mac>.<base64 nonce>
tup = [prefix, b64_ciphertext.decode(), b64_mac.decode(), b64_nonce.decode()]
session_cookie = ".".join(tup)
# Set the session cookie
response.set_cookie(app.session_cookie_name, session_cookie,
expires=expires, httponly=True,
domain=domain)
class BinaryAwareJSONEncoder(JSONEncoder):
"""
Converts a python object, where binary data is converted into an object
that can be decoded using the BinaryAwareJSONDecoder.
"""
def default(self, obj):
if isinstance(obj, bytes):
return {
'__type__' : 'bytes',
'b' : base64.b64encode (obj).decode ()
}
else:
return JSONEncoder.default(self, obj)
class BinaryAwareJSONDecoder(JSONDecoder):
"""
Converts a json string, where binary data was converted into objects form
using the BinaryAwareJSONEncoder, back into a python object.
"""
def __init__(self):
JSONDecoder.__init__(self, object_hook=self.dict_to_object)
def dict_to_object(self, d):
if '__type__' not in d:
return d
typ = d.pop('__type__')
if typ == 'bytes':
return base64.b64decode (bytes (d['b'], 'utf-8'))
else:
# Oops... better put this back together.
d['__type__'] = typ
return d
@@ -0,0 +1,87 @@
import random
import os
import requests
import time
import OpenSSL
import base64
import hashlib
from functools import wraps
from flask import Flask, request, session, redirect, url_for, render_template, make_response
from encrypted_session import EncryptedSessionInterface
app = Flask(__name__, static_folder='static', static_url_path='', template_folder='templates')
app.secret_key = b"REDACTED"
app.config['SESSION_CRYPTO_KEY'] = b"REDACTED"
app.session_interface = EncryptedSessionInterface()
with open("cert.pem") as f:
cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, f.read())
def login_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if 'token' not in session:
return redirect(url_for('login'))
return f(*args, **kwargs)
return decorated_function
@app.route('/login')
def login():
try:
token = request.args.get('token')
id, sig = token.split(":", 1)
sig = base64.b64decode(sig, validate=True)
OpenSSL.crypto.verify(cert, sig, id.encode(), "sha256")
session['token'] = token
session['score'] = 0
session.pop('ans', None)
return redirect(url_for('index'))
except Exception:
return make_response('''
您尚未登录或登录 token 错误, 请使用 hackergame 网站上的题目链接访问
''', 403)
@app.route('/logout')
def logout():
session.pop('token', None)
session.pop('score', None)
session.pop('ans', None)
return redirect(url_for('login'))
@app.route('/')
@login_required
def index():
total = 400
width = (session['score'] / float(total)) * 100
if width >= 100:
token = session['token']
return f"flag{{S0lid_M4th_Phy_Foundation_{hashlib.sha256(('S01idF'+token).encode()).hexdigest()[:10]}}}"
formula = requests.get("http://generator:5000").text.split('\n')
session['ans'] = float(formula[0])
#print(formula[0])
result = session.pop('result', None)
return make_response(render_template("index.html", remaining=total-session['score'], width=width, latex=formula[1], result=result))
@app.route('/submit', methods=['POST'])
@login_required
def submit():
try:
ans = request.form['ans']
ans = float(ans)
if abs(session['ans'] - ans) < 1e-6:
session['score'] += 1
session['result'] = "true"
else:
session['result'] = "false"
except:
pass
session.pop('ans', None)
return redirect(url_for('index'))
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5001)
File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,100 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="shortcut icon" href="static/favicon.ico" />
<title>超基础的数理模拟器</title>
<link rel="stylesheet" href="static/bootstrap.min.css">
<link rel="stylesheet" href="static/font-awesome.min.css">
<script>window.console = window.console || function (t) { };</script>
<script>if (document.location.search.match(/type=embed/gi)) { window.parent.postMessage("resize", "*"); }</script>
<script>
MathJax = {
tex: {
inlineMath: [['$', '$'], ['\\(', '\\)']]
},
svg: {
fontCache: 'global',
scale: 1.2
}
};
</script>
<script type="text/javascript" id="MathJax-script" src="static/tex-svg.js">
</script>
</head>
<body translate="no">
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<nav class="navbar navbar-expand-lg navbar-light bg-light navbar-dark bg-dark">
<button class="navbar-toggler" type="button" data-toggle="collapse"
data-target="#bs-example-navbar-collapse-1">
<span class="navbar-toggler-icon"></span>
</button> <a class="navbar-brand" href="#">超基础的数理模拟器</a>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="navbar-nav ml-md-auto">
<a href="logout" class="btn btn-primary my-2 my-sm-0">
溜了溜了
</a>
</ul>
</div>
</nav>
<div class="progress">
<div class="progress-bar progress-bar-striped progress-bar-animated bg-success" role="progressbar"
style="width: {{ width }}%" aria-valuemin="0" aria-valuemax="100"></div>
</div>
{% if result == 'true' %}
<div class="alert alert-success" role="alert">
答案正确! 小小年纪, 果然满脑子都是<b>数理基础</b>
</div>
{% endif %}
{% if result == 'false' %}
<div class="alert alert-danger" role="alert">
答案错误 /(ㄒoㄒ)/~~ 又是数理基础不扎实的一天
</div>
{% endif %}
<div class="row" style="margin-top:5px">
<div class="col-md-4">
</div>
<div class="col-md-4 alert alert-info text-center">
<p class="lead" style="margin-bottom:5px">继续答对</p>
<h1 class="cover-heading"> {{ remaining }} 题</h1>
<p class="lead" style="margin-bottom:5px">即可视为通过(数理基础扎实的)图灵测试</p>
</div>
<div class="col-md-4">
</div>
</div>
<center>
<p> ${{ latex }}$</p>
</center>
<div class="row">
<div class="col-md-4">
</div>
<div class="col-md-4">
<form action="/submit" method="POST">
<div class="input-group">
<input type="text" placeholder="请计算以上定积分的结果 (精确到小数点后六位)" class="form-control"
name="ans">
<div class="pull-right" style="margin-left:5px">
<span class="input-group-btn">
<button class="btn btn-primary" type="submit">检查基础</button>
</span>
</div>
</div>
</form>
</div>
<div class="col-md-4">
</div>
</div>
</div>
</div>
</div>
</body>
</html>