calculus
This commit is contained in:
Regular → Executable
+53
@@ -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)
|
||||
```
|
||||
Executable
+13
@@ -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')
|
||||
Executable
+6
@@ -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)
|
||||
+7
File diff suppressed because one or more lines are too long
BIN
Binary file not shown.
|
After Width: | Height: | Size: 4.5 KiB |
+4
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>
|
||||
Reference in New Issue
Block a user