Alpaca CTF SECCON CTF 13 決勝観戦CTF WriteUp
こんにちは、TRSUTDOCKのよもぎたです。
CTFはWriteUpを書くまでがCTFです。
というわけで、SECCON13電脳会議で行われたAlpaca CTFさんのSECCON CTF 13 決勝観戦CTFに参加してきました。解けた問題のWriteUpです。
Welcome!
FLAG形式の確認ですね。
Long Flag
出力からフラグを復元してください🐍
import os
from Crypto.Util.number import bytes_to_long
print(bytes_to_long(os.getenv("FLAG").encode()))
出力:
35...
bytes_to_long()
メソッドは、Python3.2以上では int.from_bytes(b'P', 'big')
で置き換えできるそうです。
というわけで試してみます。
$ python3
Python 3.12.3 (main, Feb 4 2025, 14:48:35) [GCC 13.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> print(int.from_bytes(b'Alpaca{example}','big'))
339698699736349728488459535622366589
訳が分からないのでもっと簡単にしてみます。
>>> print(int.from_bytes(b'A','big'))
65
>>> print(int.from_bytes(b'Al','big'))
16748
10進数の16748は16進数で0x416Cなので、なんとなくわからなくはありません。
試してみると、
>>> hex(int.from_bytes(b'Alpaca{Example}','big'))
'0x416c706163617b4578616d706c657d'
あー。はい。というわけで、
>>> hex(35...)
'0x416c706163617b...7d'
で、FLAGのASCIIコードが分かったので、文字列にして提出して一つ目ゲット!
Cookie
ある条件を満たすとフラグが得られるようです
import Fastify from "fastify";
import fastifyCookie from "@fastify/cookie";
const fastify = Fastify();
fastify.register(fastifyCookie);
fastify.get("/", async (req, reply) => {
reply.setCookie('admin', 'false', { path: '/', httpOnly: true });
if (req.cookies.admin === "true")
reply.header("X-Flag", process.env.FLAG);
return "can you get the flag?";
});
fastify.listen({ port: process.env.PORT, host: "0.0.0.0" });
curl
コマンドは-b
オプションでCookieを送信できます。
FLAGはヘッダで送信されてくるので、-I
オプションで表示します。
というわけで、
$ curl -I -b 'admin=true' http://34.170.146.252:6407/
HTTP/1.1 200 OK
x-flag: Alpaca{...}
content-type: text/plain; charset=utf-8
set-cookie: admin=false; Path=/; HttpOnly; SameSite=Lax
content-length: 21
Date: Sat, 01 Mar 2025 10:39:24 GMT
Connection: keep-alive
Keep-Alive: timeout=72
Beginner's Flag Printer
フラグを出力するアセンブリです🤖
.LC0:
.string "Alpaca{%x}\n"
main:
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR [rbp-4], 539232261
mov eax, DWORD PTR [rbp-4]
mov esi, eax
mov edi, OFFSET FLAT:.LC0
mov eax, 0
call printf
mov eax, 0
leave
ret
アセンブリわからん、まじなんもわからん。
というわけで、Copilotさんに聞いてみます。
次のアセンブリのLinuxでの実行結果を教えてください
おおお、きたーーー。と思って送信してみると、Wrongでした。
でも、なんとなく何をしているかはつかめました。
539232261
を16進数に変換すると 0x20240805
なので、Copilotさんの答えを修正してFALGゲットです。
アセンブリ分かるのに10進数と16進数の変換ミスるCopilotさんウケるw
AIさんには敬意を払いましょう。お茶目ですね、位にとどめておきます。
parseInt
a < b && parseInt(a) > parseInt(b) となるような a, b を見つけてください🐟
const rl = require("node:readline").createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question("Input a,b: ", input => {
const [a, b] = input.toString().trim().split(",").map(Number);
if (a < b && parseInt(a) > parseInt(b))
console.log(process.env.FLAG);
else
console.log(":(");
rl.close();
});
えーわからん。というわけで、問題プログラムを改造してテストプログラムを書いてみます。
const rl = require("node:readline").createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question("Input a,b: ", input => {
const [a, b] = input.toString().trim().split(",").map(Number);
console.log(a);
console.log(b);
console.log(parseInt(a));
console.log(parseInt(b));
if (a < b && parseInt(a) > parseInt(b))
console.log(process.env.FLAG);
else
console.log(":(");
rl.close();
});
いろいろ試行錯誤して、b
にIntで表現できないような数字を渡せばよいことが分かってきました。というわけで、
$ node test.js
Input a,b: 10,999999999999999999999999
10
1e+24
10
1
undefined
です。
1e+24
を1にしちゃうのって、よくある仕様なのかな?
play with memory
次のCプログラムが与えられました。
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
void print_flag() {
char flag[256];
int fd = open("./flag.txt", O_RDONLY);
if (fd < 0) { puts("./flag.txt not found"); return; }
write(1, flag, read(fd, flag, sizeof(flag)));
}
int main() {
setbuf(stdout, NULL);
int number = 0;
printf("input your number!: ");
scanf("%4s", &number);
if (number == 12345) {
print_flag();
} else {
printf("number: %d (0x%x)", number, number);
}
return 0;
}
number
が12345
になるような文字列を渡せばよいようです。桁数から考えて16進数にして渡せばよさそうです。
>>> hex(12345)
'0x3039'
コンパイルして実行してみます。
$ ./play-weth-memory
input your number!: 3039
number: 959655987 (0x39333033)
16進数をASCIIコードととらえて文字にして渡してやればよさそうです。
$ ./play-weth-memory
input your number!: 09
number: 14640 (0x3930)
あー。まぁここまでくれば、何を渡せばよいかわかります。というわけで、問題サーバに送信してFALGゲットです。
Flag Printer
フラグを出力するアセンブリです🤖
f(char*):
push rbp
mov rbp, rsp
...中略...
.LC0:
.string "Alpaca{%s}\n"
main:
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR [rbp-7], 1197424961
mov DWORD PTR [rbp-4], 4672071
lea rax, [rbp-7]
mov rdi, rax
call f(char*)
lea rax, [rbp-7]
mov rsi, rax
mov edi, OFFSET FLAT:.LC0
mov eax, 0
call printf
mov eax, 0
leave
ret
アセンブリです、というわけでまたCopilotさんに聞いてみました。
Copilotさんによると、f(char*)
はROT13変換しているそうです。あとは、変換元の文字列が分かればFALGゲットできそうですが、Copilotさんは聞くたびに違う文字列を言ってきます。あてになりません。
というわけで、自分でプログラムを書いてみます。紙に図も書きます。Grokさんにも聞いてみました。Grokさんもf(char*)
はROT13だと言ってきます。でも、どーしても、
mov DWORD PTR [rbp-7], 1197424961
mov DWORD PTR [rbp-4], 4672071
の結果、メモリにどのような値がどのように格納されるのか、わかりませんでした。
降参です!!(涙)
まぁf(char*)
がROT13じゃない可能性もありますが…
これからWriteUp書かれる方のを参考に、勉強したいと思います!
最後までお読みいただきありがとうございました。
Discussion