比赛地址:b01lers CTF 2025

比赛时间:19 Apr 2025 07:00 CST - 21 Apr 2025 07:00 CST

复现的题目用🔁标注

Web

when

Challenge

web/whenbeginner

author: tillvit

the sunk cost fallacy

https://when.atreides.b01lersc.tf/

app.ts

ts
import express from 'express'import { rateLimit } from 'express-rate-limit'import path from "path" const limiter = rateLimit({	windowMs: 60 * 1000,	limit: 60, // 60 per minute	standardHeaders: 'draft-7',	legacyHeaders: false,    skip: (req, res) => {        return req.path != "/gamble"    }}) const app = express() app.use(limiter)app.use(express.static(path.join(__dirname, 'static'))) async function gamble(number: number) {    return crypto.subtle.digest("SHA-256", Buffer.from(number.toString()))} app.post('/gamble', (req, res) => {    const time = req.headers.date ? new Date(req.headers.date) : new Date()    const number = Math.floor(time.getTime() / 1000)    if (isNaN(number)) {        res.send({            success: false,            error: "Bad Date"        }).status(400)        return    }    gamble(number).then(data => {        const bytes = new Uint8Array(data)        if (bytes[0] == 255 && bytes[1] == 255) {            res.send({                success: true,                result: "1111111111111111",                flag: "bctf{fake_flag}"            })        } else {            res.send({                success: true,                result: bytes[0].toString(2).padStart(8, "0") + bytes[1].toString(2).padStart(8, "0")            })        }    })}); app.listen(6060, () => {    console.log(`Started express server`);});

Solution

检查 /gamble 路由的逻辑发现服务端会检查请求头中的 Date 字段。如果存在,则将其解析为时间戳;否则使用当前时间。时间戳会被转换为秒级时间(Math.floor(time.getTime() / 1000)),然后将时间戳传递给 gamble 函数,该函数计算时间戳的 SHA-256 哈希值。如果前两个字节的值分别为 255255(即二进制表示为 1111111111111111),服务器会就返回 flag ,否则服务器会返回前两个字节的二进制表示。

因此我们要做的就是伪造一个 Date 请求头,使得时间戳经过 SHA-256 哈希计算后前两个字节恰好是 255255

python
import requestsimport hashlibfrom datetime import datetime # Step 1: 寻找符合条件的时间戳def find_valid_timestamp():    # 把上限设置大一点确保有解    for timestamp in range(0, 9999999999999999):        # 计算时间戳的 SHA-256 哈希值        sha256_hash = hashlib.sha256(str(timestamp).encode()).digest()        # 检查是否符合条件        if sha256_hash[0] == 0xFF and sha256_hash[1] == 0xFF:            return timestamp    return None # Step 2: 发送请求def send_forged_request(timestamp):    url = "https://when.atreides.b01lersc.tf/gamble"    # 把时间戳转换日期格式    date_header = datetime.utcfromtimestamp(timestamp).strftime('%a, %d %b %Y %H:%M:%S GMT')    headers = {"Date": date_header}    return requests.post(url, headers=headers).json() if __name__ == "__main__":    valid_timestamp = find_valid_timestamp()     if valid_timestamp:        print(send_forged_request(valid_timestamp))    else:        print("没找到符合条件的时间戳")

得到以下响应

text
{'success': True, 'result': '1111111111111111', 'flag': 'bctf{ninety_nine_percent_of_gamblers_gamble_81dc9bdb}'}
flag
bctf{ninety_nine_percent_of_gamblers_gamble_81dc9bdb}

trouble at the spa

Challenge

web/trouble at the spa

author: ky28059

I had this million-dollar app idea the other day, but I can’t get my routing to work! I’m only using state-of-the-art tools and frameworks, so that can’t be the problem… right? Can you navigate me to the endpoint of my dreams?

https://ky28060.github.io/

trouble_at_the_spa.zip

Solution

关键代码:

tsx
import { StrictMode } from 'react';import { createRoot } from 'react-dom/client';import { BrowserRouter, Routes, Route } from 'react-router'; // Pagesimport App from './App.tsx';import Flag from './Flag.tsx'; import './index.css';  createRoot(document.getElementById('root')!).render(    <StrictMode>        <BrowserRouter>            <Routes>                <Route index element={<App />} />                <Route path="/flag" element={<Flag />} />            </Routes>        </BrowserRouter>    </StrictMode>);

通过浏览器扩展(Tampermonkey)注入脚本,强制触发路由跳转

js
// ==UserScript==// @name         Force React Router Navigation// @author       Aristore// @match        https://ky28060.github.io/*// ==/UserScript== (function() {    'use strict';     // 模拟访问 /flag    window.history.pushState({}, '', '/flag');     // 触发 React Router 的路由更新    const event = new Event('popstate');    window.dispatchEvent(event);})();

然后直接访问 https://ky28060.github.io/ 就会跳转到 https://ky28060.github.io/flag

b01lersCTF2025-1

flag
bctf{r3wr1t1ng_h1st0ry_1b07a3768fc}

Reverse

class-struggle

Challenge

rev/class-strugglebeginner

author: CygnusX & ky28059

I miss the good old days before OOP, when we lived in a classless, stateless society…

marx.c

c
#include <stdio.h>#include <string.h> #define The#define history#define of#define all#define hitherto       struct tbnlw{int srlpn;}tbnlw_o;#define existing       unsigned char tfkysf(unsigned char j,int#define society#define is             vfluhzftxror){vfluhzftxror&=7;#define the#define class#define struggles      (void)tbnlw_o#define Freeman        srlpn;#define and#define slave          (void)((void)0#define patrician      0);#define plebeian       (void)((void)0#define lord           0);#define serf           (void)((void)0#define guild#define master         0);#define journeyman     (void)((void)0#define in#define a#define word           0);(void)((void)0#define oppressor      0);#define oppressed      (void)((void)0#define stood          0);#define constant       return (j<<vfluhzftxror)|#define opposition     (j>>(8-vfluhzftxror));}#define to             unsigned char b(unsigned#define one            char j,int vfluhzftxror){#define another        (void)((void)0#define carried        0);#define on             vfluhzftxror&=7;return(j#define an             >>vfluhzftxror)|(j<<(8-vfluhzftxror));#define uninterrupted  (void)((void)0#define now#define hidden         0);(void)((void)0#define open           0);(void)((void)0#define fight#define that#define each           0);}#define time           unsigned char jistcuazjdma(unsigned char jistcuazjdma,int mpnvtqeqmsgc){#define ended          (void)((void)0#define either         0);#define revolutionary  jistcuazjdma^=(#define reconstitution mpnvtqeqmsgc *#define at             37);#define large          (void)((void)0#define or             0);#define common         jistcuazjdma=#define ruin           tfkysf(jistcuazjdma,(mpnvtqeqmsgc+3)%7);#define contending     (void)tbnlw_o#define classes#define In#define earlier        srlpn;#define epochs         (void)((void)0#define we#define find           0);#define almost#define everywhere     jistcuazjdma+=42;#define complicated    return jistcuazjdma;}int#define arrangement    evhmllcbyoqu(const#define into           char#define various        *g){#define orders         (void)((void)0#define manifold       0);#define gradation      const unsigned char gnmupmhiaosg[]={0x32,0xc0,0xbf,0x6c,0x61,0x85,0x5c,0xe4,0x40,0xd0,0x8f,0xa2,0xef,0x7c,0x4a,0x2,0x4,0x9f,0x37,0x18,0x68,0x97,0x39,0x33,0xbe,0xf1,0x20,0xf1,0x40,0x83,0x6,0x7e,0xf1,0x46,0xa6,0x47,0xfe,0xc3,0xc8,0x67,0x4,0x4d,0xba,0x10,0x9b,0x33};int#define social         guxytjuvaljn=strlen(g);#define rank           (void)tbnlw_o#define ancient        srlpn;#define Rome           if(guxytjuvaljn!=sizeof#define have           (gnmupmhiaosg)){#define patricians     (void)((void)0#define knights        0);(void)((void)0#define plebeians      0);(void)((void)0#define slaves         0);(void)(0?0#define Middle         0);#define Ages           (void)((void)0#define feudal#define lords          0);(void)((void)0#define vassals        0);(void)((void)0#define masters        0);(void)((void)0#define journeymen     0);(void)((void)0#define apprentices    0);(void)((void)0#define serfs          0);(void)(0?0#define these          0);(void)((void)0#define again          0);(void)((void)0#define subordinate    0);#define gradations     (void)tbnlw_o#define modern         srlpn;#define bourgeois      return  0;}for(int mpnvtqeqmsgc=0;mpnvtqeqmsgc<guxytjuvaljn;mpnvtqeqmsgc++){#define has#define sprouted       unsigned char z=jistcuazjdma(g[mpnvtqeqmsgc],#define from           mpnvtqeqmsgc);unsigned#define ruins          char e=b((z&0xF0)|((~z)&0x0F),#define not            mpnvtqeqmsgc%8);if(e!=gnmupmhiaosg#define done           [mpnvtqeqmsgc]){#define away           return 0;}}#define with           return 1;#define antagonisms    (void)tbnlw_o#define It             srlpn;#define but            }int main(void){#define established    (void)((void)0#define new#define conditions     0);#define oppression     (void)((void)0#define forms          0);#define struggle       char cmdcnwrnjxlp[64];printf("Please input the flag: ");fgets(cmdcnwrnjxlp,sizeof(cmdcnwrnjxlp),stdin);#define place          char*nl=strchr(cmdcnwrnjxlp,'\n');if(nl){*nl=0;}if(evhmllcbyoqu(cmdcnwrnjxlp)){puts("Correct!");}else{#define old            puts("No.");}#define ones           return 0;} The history of all hitherto existing society is the history of class struggles. Freeman and slave, patrician and plebeian, lord and serf, guild-master andjourneyman, in a word, oppressor and oppressed, stood in constant oppositionto one another, carried on an uninterrupted, now hidden, now open fight, afight that each time ended, either in a revolutionary reconstitution of societyat large, or in the common ruin of the contending classes. In the earlier epochs of history, we find almost everywhere a complicatedarrangement of society into various orders, a manifold gradation of socialrank. In ancient Rome we have patricians, knights, plebeians, slaves: in theMiddle Ages, feudal lords, vassals, guild-masters, journeymen, apprentices,serfs: in almost all of these classes, again, subordinate gradations. The modern bourgeois society that has sprouted from the ruins of feudal societyhas not done away with class antagonisms. It has but established new classes,new conditions of oppression, new forms of struggle in place of the old ones

Solution

ChatGPT 一把梭

这道题通过一系列宏定义把「输入字符串 g[i]」经过三步变换后,与给定的字节数组比较。我们要做的是把这三步变换反过来,从目标数组还原出原始的 g[i]。

一、理解正向变换

对于每个索引 i 和字符 j = g[i],正向变换分三步:

  1. 异或与旋转

    c
    j ^= (i * 37);                // XOR 运算,结果截断到 8 位j = rol(j, (i + 3) % 7);      // 向左循环移位 r = (i+3)%7
  2. 加常数 42

    c
    j = (j + 42) & 0xFF;          // 加法并截断到 8 位
  3. 提取高低 4 位后再旋转

    c
    x = (j & 0xF0) | ((~j) & 0x0F);  // 高 4 位保留,低 4 位取 ~j 的低 4 位e = ror(x, i % 8);               // 向右循环移位 r = i%8,得到最终字节

    最终的 e 就存储在给定数组中。

二、反向还原算法

  1. 逆向第二次旋转

    python
    # 已知 e 和 r2=i%8x = rol(e, r2)      # 旋转方向相反:向左旋转 r2 位
  2. 逆向高低位提取
    x = (z & 0xF0) | ((~z) & 0x0F),可解得

    python
    z_high = x & 0xF0x_low  = x & 0x0Fz_low  = 0xF - x_low     # 因为 (~z_low)&0x0F = x_low ⇒ z_low = 0xF - x_lowz = z_high | z_low
  3. 逆向加 42

    python
    t1 = (z - 42) & 0xFF
  4. 逆向第一次旋转

    python
    r1 = (i + 3) % 7t2 = ror(t1, r1)     # 向右循环移位的逆运算
  5. 逆向异或

    python
    j = t2 ^ ((i * 37) & 0xFF)

    得到的 j 就是当时的 g[i]

python
# 8 位循环移位函数def rol(v, r): return ((v << r) & 0xFF) | (v >> (8 - r))def ror(v, r): return (v >> r) | ((v << (8 - r)) & 0xFF) # 给定的目标数组cipher = [    0x32,0xc0,0xbf,0x6c,0x61,0x85,0x5c,0xe4,    0x40,0xd0,0x8f,0xa2,0xef,0x7c,0x4a,0x02,    0x04,0x9f,0x37,0x18,0x68,0x97,0x39,0x33,    0xbe,0xf1,0x20,0xf1,0x40,0x83,0x06,0x7e,    0xf1,0x46,0xa6,0x47,0xfe,0xc3,0xc8,0x67,    0x04,0x4d,0xba,0x10,0x9b,0x33] flag_bytes = []for i, e in enumerate(cipher):    # 1. 逆向第二次循环右移    x = rol(e, i % 8)    # 2. 逆向高低位混合    z = (x & 0xF0) | (0xF - (x & 0x0F))    # 3. 逆向加 42    t1 = (z - 42) & 0xFF    # 4. 逆向第一次循环左移    t2 = ror(t1, (i + 3) % 7)    # 5. 逆向异或    j = t2 ^ ((i * 37) & 0xFF)    flag_bytes.append(j) flag = bytes(flag_bytes).decode('ascii')print(flag)

运行后即可得到 flag

flag
bctf{seizing_the_m3m3s_0f_pr0ducti0n_32187ea8}