Skip to content

Latest commit

 

History

History
627 lines (491 loc) · 23.2 KB

File metadata and controls

627 lines (491 loc) · 23.2 KB

Rainy's Writeup of Hackergame 2024

Blog Post: https://wooow.top/article/woh24woh24/

纯新人,第一年来,菜菜 (ㄒoㄒ)

签到

直接启动!观察到URL带上了?pass=false,改为true即可拿到flag

flag{we!cOM3-TO-haCK3rGAME-AND-3njOy-H@cK1Ng-Z0Z4}

喜欢做签到的 CTFer 你们好呀

Hackergame首页下拉,在承办单位处找到 NEBULA战队,进入题目说的招新主页

这是一个Shell风格的网页,先打help看下可用命令,把sudo(奶龙😀)aboutenv什么的先试一遍

执行env后发现一个flag

PWD=/root/Nebula-Homepage
ARCH=loong-arch
NAME=Nebula-Dedicated-High-Performance-Workstation
OS=NixOS❄️
FLAG=flag{actually_theres_another_flag_here_trY_to_f1nD_1t_y0urself___join_us_ustc_nebula}
REQUIREMENTS=1. you must come from USTC; 2. you must be interested in security!

然后尝试正常ls列出目录(一开始还没往隐藏文件去想),还去搜了NixOS啥的,发现好像与本题无太大关系😂

再尝试下源代码审计,在index-5c589ff418560b46.js中检索flag,在第2个flag结果附近,注意到

"".concat(atob("ZmxhZ3swa18xNzVfYV9oMWRkM25fczNjM3J0X2YxNGdfX19wbGVhc2Vfam9pbl91c191c3RjX25lYnVsYV9hbkRfdHdvX21hSm9yX3JlcXVpcmVtZW50c19hUmVfc2hvd25fc29tZXdoZXJlX2Vsc2V9"))

👀这不是base64吗,把atob(……)这段放到浏览器控制台执行,解码后得到第二个flag

flag{0k_175_a_h1dd3n_s3c3rt_f14g___please_join_us_ustc_nebula_anD_two_maJor_requirements_aRe_shown_somewhere_else}

猫咪问答(Hackergame 十周年纪念版)

  1. 在 Hackergame 2015 比赛开始前一天晚上开展的赛前讲座是在哪个教室举行的?

    Hackergame从2014开始举办,2015是第二届,在活动记录找到第二届的存档,可知3A204

  2. 众所周知,Hackergame 共约 25 道题目。近五年(不含今年)举办的 Hackergame 中,题目数量最接近这个数字的那一届比赛里有多少人注册参加?

    一年一年试,到2019年,可知2682

  3. Hackergame 2018 让哪个热门检索词成为了科大图书馆当月热搜第一?

    Hackergame 2018 猫咪问答题解,可知程序员的自我修养

  4. 10 月 18 日 Greg Kroah-Hartman 向 Linux 邮件列表提交的一个 patch 把大量开发者从 MAINTAINERS 文件中移除。这个 patch 被合并进 Linux mainline 的 commit id 是多少?

    先跳第5题(感觉好找一点),直接搜索就可以找到对应的Github Commit,可知6e90b6

  5. 在今年的 USENIX Security 学术会议上中国科学技术大学发表了一篇关于电子邮件伪造攻击的论文,在论文中作者提出了 6 种攻击方法,并在多少个电子邮件服务提供商及客户端的组合上进行了实验?

    在浏览器控制台执行:

    for(let q4=1;q4<=1000;q4++){
        fetch('/', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
            },
            body: `q1=3A204&q2=2682&q3=%E7%A8%8B%E5%BA%8F%E5%91%98%E7%9A%84%E8%87%AA%E6%88%91%E4%BF%AE%E5%85%BB&q4=${q4}&q5=6e90b6&q6=`,
        })
            .then(response => response.text())
            .then(html => {
                //拿到的是HTML,需要解析HTML DOM
                let parser = new DOMParser()
                let doc = parser.parseFromString(html, "text/html")
                let _text = doc.querySelector(".alert-secondary").innerText
                //分数增加就说明我们找到了答案
                if (_text.includes("75")) {
                    console.log(q4)
                }
            })
            .catch(error => {
                console.log(error)
            })
    }

    可知336

  6. 大语言模型会把输入分解为一个一个的 token 后继续计算,请问这个网页的 HTML 源代码会被 Meta 的 Llama 3 70B 模型的 tokenizer 分解为多少个 token?

    枚举!在浏览器控制台执行:

    for(let q6=1000;q6<2000;q6++){
        fetch('/', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
            },
            body: `q1=3A204&q2=2682&q3=%E7%A8%8B%E5%BA%8F%E5%91%98%E7%9A%84%E8%87%AA%E6%88%91%E4%BF%AE%E5%85%BB&q4=336&q5=6e90b6&q6=${q6}`,
        })
            .then(response => response.text())
            .then(html => {
                let parser = new DOMParser()
                let doc = parser.parseFromString(html, "text/html")
                let _html = doc.querySelector(".alert-secondary").innerText
                if (_html.includes("为 100")) {
                    console.log(q6)
                }
            })
            .catch(error => {
                console.log(error)
            })
    }

    可知1833

flag{A_900D_C@7_!s_The_©αT_wHo_©αN_p@$S_tHE_qบ1Z} flag{7en_Ye4RS_0F_hacK3ЯgΛme_OM3detou_WitH_n3kØ_QU!Z}

打不开的盒

找个3D打印在线查看器,放大,从2024透过去对内部截图,再OCR识别/人眼识别手打转文字即可取到flag

flag{Dr4W_Us!nG_fR3E_C4D!!w0W}

每日论文太多了!

下载PDF文件,搜索flag,发现在其中一面论文的左上角一张图片里有白色字体写的"flag is here",但是没有flag{}的字样,推测可能是被隐藏了

然后找个提取PDF中图片的工具,下载提取结果,其中一张图片就有flag,用OCR识别转文字即可

flag{h4PpY_hAck1ng_3veRyd4y}

比大小王

Web的事情就让Web来解决吧~

在浏览器控制台执行:

let newinputs = []

function newsubmit(inputs) {
    fetch('/submit', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({ inputs }),
    })
        .then(response => response.json())
        .then(data => {
            console.log(data.message)//输出包含flag的消息
            state.stopUpdate = true
            document.getElementById('dialog').textContent = data.message
            document.getElementById('dialog').style.display = 'flex'
        })
        .catch(error => {
            console.log(error)
        })
}

function go() {
    let values = state.values;
    for (let i = 0; i < values.length; i++) {
        //拿到的是String,先转Number
        if (Number(values[i][0]) > Number(values[i][1])) {
            newinputs.push(">")
        } else {
            newinputs.push("<")
        }
    }
    setTimeout(()=>{
        newsubmit(newinputs)
    },1000)
}

go()

可以得到:

flag{!-4M-ThE-H4cK3r-K!nG-0f-cOmPaR!NG-NUMb3Rs-2oZ4}

旅行照片 4.0

  1. 照片拍摄的位置距离中科大的哪个校门更近?

    东西南北都人工试一试,可知东校区西门

  2. 话说 Leo 酱上次出现在桁架上是……科大今年的 ACG 音乐会?活动日期我没记错的话是?

    搜索“中科大2024ACG音乐会”,可知20240519

flag{5UB5CR1B3_T0_L30_CH4N_0N_B1L1B1L1_PLZ_193bf998f5}

  1. 这个公园的名称是什么?(不需要填写公园所在市区等信息)

    把图片放大,注意到垃圾桶上面有六安字样,搜索发现是城市名,再尝试搜这个城市有的公园,对照跑道样式,可知中央森林公园

  2. 这个景观所在的景点的名字是?(三个汉字)

    搜图,结合题目三个汉字的要求,确定坛子岭

flag{D3T41LS_M4TT3R_1F_R3V3RS3_S34RCH_1S_1MP0SS1BL3_008d4f5f31}

  1. 距离拍摄地最近的医院是?(无需包含院区、地名信息,格式:XXX医院)

    对准中间两个车间搜图,确定为北京北动车所,在地图上搜附近的医院,可知积水潭医院

  2. 左下角的动车组型号是?

    搜索四编组动车型号,试出CRH6F-A

flag{1_C4NT_C0NT1NU3_TH3_5T0RY_4NYM0R3_50M30N3_PLZ_H3LP_8195f39157}

Node.js is Web Scale

注意到:

let cmds = {
  getsource: "cat server.js",
  test: "echo 'hello, world!'",
};

let store = {};

推测原型链污染

输入 key: __proto__.t, value: cat /flag 再访问/execute?cmd=t

flag{n0_pr0topOIl_50_U5E_new_Map_1n5teAD_Of_0bject2kv_86e388ff83}

PaoluGPT

千里挑一

Web的事情就让Web来解决吧~ +1

进入查看聊天记录页面,在浏览器控制台执行:

let links = document.querySelectorAll("a")
for (let i = 0; i < links.length; i++) {
    //遍历所有对话
    if (links[i].href.includes("view")) {
        fetch(links[i].href, {
            method: 'GET',
            headers: {
                'Content-Type': 'text/html',
            }
        })
            .then(response => response.text())
            .then(html => {
                let parser = new DOMParser()
                let doc = parser.parseFromString(html, "text/html")
                let _html = doc.querySelector(".container .pt-3").innerHTML
                //输出包含flag的对话
                if (_html.includes("flag")) {
                    console.log(_html)
                }
            })
            .catch(error => {
                console.log(error)
            })
    }
}

稍等一会,得到:

flag{zU1_xiA0_de_11m_Pa0lule!!!_196f3121f3}

窥视未知

查看Python源码,注意到:

results = execute_query("select id, title from messages where shown = true", fetch_all=True)
…
results = execute_query(f"select title, contents from messages where id = '{conversation_id}'")

默认不显示shown=true的对话,推测另一个flag应该就是在shown=false的对话中,同时下边SQL查询的存在注入风险,然后构造' or where shown = false--

flag{enJ0y_y0uR_Sq1_&_1_would_xiaZHOU_hUI_guo_a706cea1d6}

惜字如金 3.0

题目A

根据规则修改Python脚本:

#!/usr/bin/python3                                                              
                                                                                
import atexit, base64, flask, itertools, os, re                                 
                                                                                
                                                                                
def crc(input: bytes) -> int:                                                   
    poly, poly_degree = 'AaaaaaAaaaAAaaaaAAAAaaaAAAaAaAAAAaAAAaaAaaAaaAaaA', 48 
    assert len(poly) == poly_degree + 1 and poly[0] == poly[poly_degree] == 'A' 
    flip = sum(['a', 'A'].index(poly[i + 1]) << i for i in range(poly_degree))  
    digest = (1 << poly_degree) - 1                                             
    for b in input:                                                             
        digest = digest ^ b                                                     
        for _ in range(8):                                                      
            digest = (digest >> 1) ^ (flip if digest & 1 == 1 else 0)           
    return digest ^ (1 << poly_degree) - 1                                      
                                                                                
                                                                                
def hash(input: bytes) -> bytes:                                                
    digest = crc(input)                                                         
    u2, u1, u0 = 0xCb4EcdfD0A9F, 0xa9dec1C1b7A3, 0x60c4B0aAB4Bf                 
    assert (u2, u1, u0) == (223539323800223, 186774198532003, 106397893833919)  
    digest = (digest * (digest * u2 + u1) + u0) % (1 << 48)                     
    return digest.to_bytes(48 // 8, 'little')                                   
                                                                                
                                                                                
def xzrj(input: bytes) -> bytes:                                                
    pat, repl = rb'([B-DF-HJ-NP-TV-Z])\1*(E(?![A-Z]))?', rb'\1'                 
    return re.sub(pat, repl, input, flags=re.IGNORECASE)                        
                                                                                
                                                                                
paths: list[bytes] = []                                                         
                                                                                
xzrj_bytes: bytes = bytes()                                                     
                                                                                
with open(__file__, 'rb') as f:                                                 
    for row in f.read().splitlines():                                           
        row = (row.rstrip() + b' ' * 80)[:80]                                   
        path = base64.b85encode(hash(row)) + b'.txt'                            
        with open(path, 'wb') as pf:                                            
            pf.write(row)                                                       
            paths.append(path)                                                  
            xzrj_bytes += xzrj(row) + b'\r\n'                                   
                                                                                
    def clean():                                                                
        for path in paths:                                                      
            try:                                                                
                os.remove(path)                                                 
            except FileNotFoundError:                                           
                pass                                                            
                                                                                
    atexit.register(clean)                                                      
                                                                                
                                                                                
bp: flask.Blueprint = flask.Blueprint('answer_a', __name__)                     
                                                                                
                                                                                
@bp.get('/answer_a.py')                                                         
def get() -> flask.Response:                                                    
    return flask.Response(xzrj_bytes, content_type='text/plain; charset=UTF-8') 
                                                                                
                                                                                
@bp.post('/answer_a.py')                                                        
def post() -> flask.Response:                                                   
    wrong_hints = {}                                                            
    req_lines = flask.request.get_data().splitlines()                           
    iter = enumerate(itertools.zip_longest(paths, req_lines), start=1)          
    for index, (path, req_row) in iter:                                         
        if path is None:                                                        
            wrong_hints[index] = 'Too many lines for request data'              
            break                                                               
        if req_row is None:                                                     
            wrong_hints[index] = 'Too few lines for request data'               
            continue                                                            
        req_row_hash = hash(req_row)                                            
        req_row_path = base64.b85encode(req_row_hash) + b'.txt'                 
        if not os.path.exists(req_row_path):                                    
            wrong_hints[index] = f'Unmatched hash ({req_row_hash.hex()})'       
            continue                                                            
        with open(req_row_path, 'rb') as pf:                                    
            row = pf.read()                                                     
            if len(req_row) != len(row):                                        
                wrong_hints[index] = f'Unmatched length ({len(req_row)})'       
                continue                                                        
            unmatched = [req_b for b, req_b in zip(row, req_row) if b != req_b] 
            if unmatched:                                                       
                wrong_hints[index] = f'Unmatched data (0x{unmatched[-1]:02X})'  
                continue                                                        
            if path != req_row_path:                                            
                wrong_hints[index] = f'Matched but in other lines'              
                continue                                                        
    if wrong_hints:                                                             
        return {'wrong_hints': wrong_hints}, 400                                
    with open('answer_a.txt', 'rb') as af:                                      
        answer_flag = base64.b85decode(af.read()).decode()                      
        closing, opening = answer_flag[-1:], answer_flag[:5]                    
        assert closing == '}' and opening == 'flag{'                            
        return {'answer_flag': answer_flag}, 200                       

flag{C0mpl3ted-Th3-Pyth0n-C0de-N0w}

优雅的不等式

Easy

在知乎上搜索到文章,根据其中第1种类型的原理,解出系数 x*x*(1-x)**2*(15-2*x+15*x*x)/(1+x*x)

flag{y0u_ar3_g0od_at_constructi0n_982b662cda}

零知识数独

数独高手

解出数独即可(应该也可在网页源代码里找混淆过的flag?

ZK高手

搜索snarkjscircom,配好环境

npm install snarkjs -g

#View https://docs.circom.io/getting-started/installation/
curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh
git clone https://github.com/iden3/circom.git
cd circom
cargo build --release
cargo install --path circom
cd ../

npm install circomlib

执行

circom sudoku.circom --r1cs --wasm --sym

生成./sudoku_js/sudoku.wasm,将其移至当前目录下便于后面操作

解出数独,根据sudoku.circom的格式要求,新建并编辑input.json

{
    "unsolved_grid": [
        [0, 6, 0, 0, 4, 0, 7, 0, 1],
        [0, 9, 3, 0, 0, 0, 0, 0, 0],
        [7, 0, 0, 0, 9, 0, 8, 0, 0],
        [3, 0, 0, 0, 0, 0, 0, 0, 5],
        [0, 0, 8, 7, 0, 0, 0, 4, 0],
        [0, 0, 0, 0, 3, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 2, 1, 6, 0],
        [0, 4, 0, 8, 0, 5, 9, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 7]
    ],   
    "solved_grid": [
        [8, 6, 2, 5, 4, 3, 7, 9, 1],
        [5, 9, 3, 1, 8, 7, 4, 2, 6],
        [7, 1, 4, 2, 9, 6, 8, 5, 3],
        [3, 7, 9, 4, 2, 8, 6, 1, 5],
        [6, 2, 8, 7, 5, 1, 3, 4, 9],
        [4, 5, 1, 6, 3, 9, 2, 7, 8],
        [9, 8, 5, 3, 7, 2, 1, 6, 4],
        [1, 4, 7, 8, 6, 5, 9, 3, 2],
        [2, 3, 6, 9, 1, 4, 5, 8, 7]
    ]
}

执行

node sudoku_js/generate_witness.js sudoku.wasm input.json witness.wtns
snarkjs groth16 prove sudoku.zkey witness.wtns proof.json public.json

生成proof.jsonpublic.json

proof.json

{
 "pi_a": [
  "19519569601443877529841379184511804661822623434087164904352625826179156948133",
  "2682837314985806677717346712478184552918419161577008207527853034067541448658",
  "1"
 ],
 "pi_b": [
  [
   "7010535527370989753089386143550495087684739302415257406065255071407236151699",
   "1387092455065133401010021275874929550054582825902761332492247997596855257480"
  ],
  [
   "6181395008603124266894200447221630417226871309341132292706415757742397764903",
   "9173276508799393706032118462054890921693089393320324585023890851218403180045"
  ],
  [
   "1",
   "0"
  ]
 ],
 "pi_c": [
  "4581445017619053660491199330292606165416925531438640239277767880463245438096",
  "4517625030615366793301484885609249507033820468808886907974473148384435812669",
  "1"
 ],
 "protocol": "groth16",
 "curve": "bn128"
}

public.json

[
 "0",
 "6",
 "0",
 "0",
 "4",
 "0",
 "7",
 "0",
 "1",
 "0",
 "9",
 "3",
 "0",
 "0",
 "0",
 "0",
 "0",
 "0",
 "7",
 "0",
 "0",
 "0",
 "9",
 "0",
 "8",
 "0",
 "0",
 "3",
 "0",
 "0",
 "0",
 "0",
 "0",
 "0",
 "0",
 "5",
 "0",
 "0",
 "8",
 "7",
 "0",
 "0",
 "0",
 "4",
 "0",
 "0",
 "0",
 "0",
 "0",
 "3",
 "0",
 "0",
 "0",
 "0",
 "0",
 "0",
 "0",
 "0",
 "0",
 "2",
 "1",
 "6",
 "0",
 "0",
 "4",
 "0",
 "8",
 "0",
 "5",
 "9",
 "0",
 "0",
 "0",
 "0",
 "0",
 "0",
 "0",
 "0",
 "0",
 "0",
 "7"
]

执行

snarkjs groth16 verify verification_key.json public.json proof.json

验证通过,提交proof.json

flag{you_are_a_5udoku_expert_and_pr0ved_your_kn0wledge_dc29a4dd28}

先不说关于我从零开始独自在异世界转生成某大厂家的 LLM 龙猫女仆这件事可不可能这么离谱,发现 Hackergame 内容审查委员会忘记审查题目标题了ごめんね,以及「这么长都快赶上轻小说了真的不会影响用户体验吗🤣」

「行吧就算标题可以很长但是 flag 一定要短点」

Amazing啊这个标题😎

先尝试根据英语积累人工推测复原(人工智能,人手工的智能😎),可以复原前面的一些词

在本地跑一下build.sh,根据生成的内容中相似的部分,可以复原一些词

再到通义千问上面跑一下……(x代指hackergame中的字母(包括x),请将本文段还原,用英文输出原文),可以再恢复一些词

基本能拿到原文,验一下sha256发现正确(这算是非预期解?

In the grand hall of Hackergame 2024, where the walls are lined with screens showing the latest exploits from the cyber world, contestants gathered in a frenzy, their eyes glued to the virtual exploits. The atmosphere was electric, with the smell of freshly brewed coffee mingling with the scent of burnt Ethernet cables. As the first challenge was announced, a team of hackers, dressed in lab coats and carrying laptops, sprinted to the nearest server room, their faces a mix of excitement and determination. The game was on, and the stakes were high, with the ultimate prize being a golden trophy and the bragging rights to say they were the best at cracking codes and hacking systems in the land of the rising sun.

flag{llm_lm_lm_koshitantan_fa7b655c38bc8847}

一点碎碎念

题目都是很有意思的,虽因时间原因(中间隔了几天没有来,周六快到十二点前最后半小时借助Kimi解出软总线第1问🤣)难以完全感受各个题目的内涵

但其中收获,却也足以令人驻足忘怀

(明年再战!/(ㄒoㄒ)/~~