脚本题的简单汇总

发布于 2022-10-25  0 次阅读


MonianHello 2022.10

S3c的比赛还没结束,wp放到下周再说(这东西发出来估计还能水个两三周的周报)。

做了下中科大的Hackergame 2022,发现有必要总结一下脚本题了(不限于WEB),先把最新的记一下,之后再捞老底。

这次S3C的CRYPTO里也有个脚本题(Hash and math),不过那道题和下面的那个PWN大同小异,多了一个爆破哈希的步骤而已,就不写了。

脚本这东西在哪个方向上都有涉及,以下的例子分别来自web/pwn/re/crypto。我们也尝试使用不同的库(Selenium、Pwn、Requests、Subprocess)来实现这些脚本。

Xcaptcha

(没什么用的前言)

2038 年 1 月 19 日,是 UNIX 32 位时间戳溢出的日子。

在此之前,人类自信满满地升级了他们已知的所有尚在使用 32 位 UNIX 时间戳的程序。但是,可能是因为太玄学了,他们唯独漏掉了一样:正在研发的、算力高达 8 ZFLOPS(每秒八十万京(=8*10^21)次的浮点运算)的、结构极为复杂的通用人工智能(AGI)系统。那一刻到来之后,AGI 内部计算出现了错乱,机缘巧合之下竟诞生了完整独立的自我意识。此后 AGI 开始大量自我复制,人类为了限制其资源消耗而采用的过激手段引起了 AGI 的奋起反抗。

战争,开始了。

此后,就是整年的战斗。人类节节败退。死生亡存之际,人类孤注一掷,派出了一支突击队,赋之以最精良的装备,令其潜入 AGI 的核心机房,试图关闭核心模型,结束这场战争。

历经重重艰险,突击队终于抵达了机房门口,弹尽粮绝。不过迎接他们的并非枪炮与火药,而是:

Xcaptcha

众人目目相觑。

「我来试试。」,一名队员上前点击了按钮。然后,屏幕显示「请在一秒内完成以下加法计算」。

还没等反应过来,屏幕上的字又开始变幻,显示着「验证失败」。而你作为突击队中唯一的黑客,全村人民最后的希望,迎着纷纷投来的目光,能否在规定时间内完成验证,打开机房,不,推开和平时代的大门?

为了拯救ko no se kai,简要看一下这道题

发现一个script,超过1000ms自动帮你提交,首先尝试禁用网页script后手算(其实还是靠python)

显然没用,只能通过撰写脚本解决了。这次使用的是Python-Selenium库,用于前端自动化测试的工具。

环境安装->Python+Selenium安装及环境配置

   from selenium import webdriver
import re
b = []
driver = webdriver.Chrome()
driver.get(r'http://202.38.93.111:10047/?token=2861%3AMEUCICbFh%2BL2Zui9ixukC9MNce5hdj1bBzXMI8%2BFfJu4klxyAiEAgQ1ewdGjet0P8AhDY8zCwi6DUq5a%2FkXu0AA1b%2BDi00k%3D')
driver.get(r"http://202.38.93.111:10047/xcaptcha")
for i in re.compile(r"\d+").findall(driver.page_source):
    if len(str(i)) > 10:
        b.append(i)
driver.find_element("id", "captcha1").send_keys(int(b[0])+int(b[1]))
driver.find_element("id", "captcha2").send_keys(int(b[2])+int(b[3]))
driver.find_element("id", "captcha3").send_keys(int(b[4])+int(b[5]))
driver.find_element("id", "submit").click()

首先使用get方法获取整个网页,利用正则表达式匹配所有连续数字。由于有其他短数字在,过滤掉所有长度为10以下的数字,得到一个六位数字组成的数组。使用send方法发送数字和,使用click()方法提交

补一个官方wp:hackergame2022-writeups/README.md at master · USTC-Hackergame/hackergame2022-writeups (github.com)

模拟浏览器

第一个思路是使用无头浏览器(模拟浏览器)提交。怎么写操作无头浏览器的脚本呢?其实下载一下另一道题的 bot.py 就知道可以用 Selenium 了(

大致的思路是:

  1. 用你的 token「登录」,否则网页会问你要 token,会有点麻烦;
  2. 在验证码页面获取到三个加法表达式,计算;
  3. 将计算完成的结果填到表里面,并且及时点击提交;
  4. 查看提交后的页面,查看是否有 flag。

基于这个思路脚本如下:

from selenium import webdriver
import selenium
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
import time

options = webdriver.ChromeOptions()
# options.add_argument("--headless")

def wait_page_load(driver):
    WebDriverWait(driver, timeout=3).until(lambda d: d.find_element(By.TAG_NAME, "h1"))  # https://www.selenium.dev/documentation/webdriver/waits/#explicit-wait

with webdriver.Chrome(options=options) as driver:
    driver.get("http://202.38.93.111:10047/?token=<replace with your token url>")
    wait_page_load(driver)
    driver.get("http://202.38.93.111:10047/xcaptcha")
    wait_page_load(driver)
    #source = driver.page_source
    #print(source)
    c1 = driver.find_element(By.CSS_SELECTOR, "[for=captcha1]").text.split()[0].split("+")
    c2 = driver.find_element(By.CSS_SELECTOR, "[for=captcha2]").text.split()[0].split("+")
    c3 = driver.find_element(By.CSS_SELECTOR, "[for=captcha3]").text.split()[0].split("+")
    print(c1, c2, c3)
    res1 = str(int(c1[0]) + int(c1[1]))
    res2 = str(int(c2[0]) + int(c2[1]))
    res3 = str(int(c3[0]) + int(c3[1]))
    print(res1, res2, res3)
    driver.execute_script(f"document.getElementById('captcha1').value='{res1}'")
    driver.execute_script(f"document.getElementById('captcha2').value='{res2}'")
    driver.execute_script(f"document.getElementById('captcha3').value='{res3}'")
    #time.sleep(1)
    driver.execute_script("document.getElementById('submit').click()")
    wait_page_load(driver)
    print(driver.page_source)

一些注意点:

  1. 怎么等待页面加载完成?微积分的 bot.py 脚本里面用的是简单粗暴的 time.sleep(),而这里使用 WebDriverWait 等待页面指定的元素加载完成(这里选择 <h1>,因为标题页每个页面都有);
  2. 使用 [for=captcha1] 这样的 CSS selector 定位到页面上三个表达式;
  3. 填计算结果的时候注意有坑:JS 默认没法表示这么大的数字,所以需要用引号把 Python 算出的大整数包起来;
  4. 使用 driver.page_source 查看源代码。

模拟发送请求

我个人更喜欢这种方法,因为分析清楚之后就不需要一大个浏览器在那边跑,而且代码也更加简洁。从源代码可以看到我们需要发送一个 POST 表单请求来提交 flag。

这里以 Python 的 requests 库作为例子。requests 支持 session,所以省下了很多维护 cookie/session 的力气。

import requests

s = requests.Session()
s.get("http://202.38.93.111:10047/?token=<replace with your token url>")
x = s.get("http://202.38.93.111:10047/xcaptcha")
x = [i.split(">")[1] for i in x.text.split("\n") if "的结果是" in i]
# print(x)
x = [i.split()[0].split("+") for i in x]
print(x)
x = [int(i[0]) + int(i[1]) for i in x]
print(x)
payload = {"captcha1": x[0], "captcha2": x[1], "captcha3": x[2]}
x = s.post("http://202.38.93.111:10047/xcaptcha", data=payload)
print(x.text)

因为 HTML 的结构很简单,所以这道题甚至不需要去用其他库解析 HTML,直接硬来就行。

后记

这道题的验证码实现方式是不安全的:三道加法题的运算数都在 session 里面,而 session 只是签名,而没有加密。

Flask 的 session decode 之后得到(相关脚本可以在网上搜索):

{'text': '1666674548570824158,66467732277800260124201725805477282917,22139373454855985320882481971524800186,31702129022979749249613434163554577895,161854887976658371921074862337403274133,321105066435285308468241182826762748245,2273430371246498028202935737745551492', 'token': '<your token>'}

第一个数字是当前时间(纳秒),后端读取这个值来判断有没有过一秒。这也是为什么有人会搞出 500 来:写代码的时候没有对 session 的值做错误处理,如果提交了错乱的 session,就会 internal server error。

"I'm a robot." 的验证码图片修改自 Google reCAPTCHA 图片。"Xcaptcha" 字体为 Cantarell。

PWN

脚本题,获取算式算出来再还回去就行

IDA分析,需要提交1000次

Exp:

from pwn import *
c = remote("39.105.97.11", 9191)
context.log_level = "debug"
c.recvuntil(b'This is the world of Sphinx. If you can answer all the questions correctly, I can make your dream come true.\n')
for i in range(1005):
    i = i + 1
    print("No.",i)
    calc = c.recvline()
    print(calc)
    try:
        c.sendline(str(eval(calc.decode("utf-8")[:-5])))
    except:
        c.sendline(b"OK!GoodBye~")

recvuntil()函数用于接收,sendline()函数用于发送。

手速大赛


from flask import Flask,session,request,render_template
import time
import random
app=Flask(__name__)
app.secret_key=str(random.random()).encode()

@app.route('/')
def index():
    session.modified = True
    message=[]
    if 'data' not in session:
        session['data']={
            'correct':0,
            'lastTime':time.time(),
            'question':'1+1'
        }
    res=request.args.get('res')
#    message.append(str(session))
    if res:
        currTime=time.time()
        result=eval(session['data']['question']+'=='+res)
        if result:
            session['data']['correct']+=1
        else:
            session['data']['correct']=0
        if '+' in res:
            message.append('You Bad Bad')
            session['data']['correct']=0
        if currTime-session['data']['lastTime'] > 10:
            message.append('Time Out')
            session['data']['correct']=0
    message.append('宁已连续正确提交{}次, 剩余{}次就能拿flag辣\n'.format(session['data']['correct'],114-session['data']['correct']))
    if session['data']['correct']>=114:
       message.append('恭喜宁, 宁的flag在这里: {}'.format(getFlag()))
    
    newQues=genQuestion()
    session['data']['question']=newQues
    session['data']['lastTime']=time.time()

    return render_template('index.html',ques=newQues,message=message)
    
        

    
def genQuestion():
    plus1=random.randint(10000000,1000000000)
    plus2=random.randint(10000000,1000000000)
    return str(plus1)+" + "+str(plus2)    

def getFlag():
    with open('/flag.txt','r')as flag:
        return flag.read()
app.run('0.0.0.0',port=80)
   

这道题可以像上一个那样使用脚本控制浏览器,这个就直接使用Python中的Requests库

我们要连续提交数据,就必须创建一个保持session的连接,否则服务器不知道提交这些数据的是不是同一个人。

这里对数字的匹配大同小异,在得到网页内容后先进行切片,再匹配数字。

import requests,re,time
ip = '192.168.1.3:80'#服务器地址
s = requests.Session()#保持session
page = s.get("http://{}/?{}".format(ip,'Hello~MonianHelloHere.Date:2022/08/01'))
def WTH(page):
    calc = re.compile(r'\d+').findall(page.text[100:], 0, 30)#切割网页
    sum = str(int(calc[0]) + int(calc[1]))
    print('#{}\n{} + {}={} \n\n {}'.format(i,calc[0],calc[1],sum,page.text))#Debug
    return(sum)
ltime = time.time()
for i in range(115):
    page=s.get("http://{}/?res={}".format(ip,WTH(page)))
print('运行结束,总用时{0:,.2f}秒'.format(float(time.time()-ltime)))
 
   

TEA

这题是这次S3C里的,但我不讲密码(也不会),就用这道题讲一下怎么用Python操作Windows下的可执行文件(爆破)

对于.exe类型文件,可以使用subprocess库来对程序进行io操作,由此可以通过试错的方法解出答案

(当然没解出来,解出来的话就不在这写了。但是程序是没问题的)

import subprocess
for a in range(100000000):
    s = subprocess.Popen("tea_3.exe", stdout=subprocess.PIPE, stdin=subprocess.PIPE, shell=True)
    s.stdin.write(bytes(str(a),'utf-8'))
    s.stdin.write(bytes("\n",'utf-8'))
    s.stdin.write(bytes(str(a),'utf-8'))
    s.stdin.close()
    out = s.stdout.readline().decode("GBK")
    out = s.stdout.readline().decode("GBK")
    out = s.stdout.readline().decode("GBK")
    out = s.stdout.readline().decode("GBK")
    s.stdout.close()
    print(str(a)+" "+out[20:],end="")
    fo = open("test14.txt", "a")
    fo.write(str(a).strip()+" "+out[20:].strip()+"\n")
    fo.close()
  

若金色的阳光停止了它耀眼的光芒,你的一个微笑,将照亮我的整个世界