8/3 から開催されていた n00bz CTF 2024 に 0nePadding で参加して最終順位 28 位でした。
超優秀なチームメンバーのおかげでほぼ全完できました(惜しかった!
今回は解いた問題数が多いのでざっくり Writeup を書きます。
もくじ
- Vacation(Rev)
- Brain(Rev)
- FlagChecker(Rev)
- Plane(Forensic)
- Wave(Forensic)
- Vinegar(Crypto)
- Vinegar 2(Crypto)
- RSA(Crypto)
- Random(Crypto)
- Tail(OSINT)
- The Gang(OSINT)
- The Gang 2(OSINT)
- The Gang 3(OSINT)
- The Gang 4(OSINT)
- Pastebin(OSINT)
- PastebinX(OSINT)
- Passwordless(Web)
- Addition(Misc)
- Subtraction(Misc)
- Think Outside the Box(Pwn)
- まとめ
Vacation(Rev)
My friend told me they were going on vacation, but they sent me this weird PowerShell script instead of a postcard! Author: 0xBlue
問題バイナリとして以下のスクリプトと暗号文が与えられます。
$bytes = [System.Text.Encoding]::ASCII.GetBytes((cat .\flag.txt))
[System.Collections.Generic.List[byte]]$newBytes = @()
$bytes.ForEach({
$newBytes.Add($_ -bxor 3)
})
$newString = [System.Text.Encoding]::ASCII.GetString($newBytes)
echo $newString | Out-File -Encoding ascii .\output.txt
m33ayxeqln\sbqjp\twk\{lq~
コードを見てわかる通り 3 で XOR すると復号できます。
Brain(Rev)
Help! A hacker said that this “language” has a flag but I can’t find it! Author: NoobMaster
問題バイナリとして以下の BrainFuck コードが与えられます。
>+++++++++++[<++++++++++>-]<[-]>++++++++[<++++++>-]<[-]>++++++++[<++++++>-]<[-]>++++++++++++++[<+++++++>-]<[-]>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++[<++>-]<[-]>+++++++++++++++++++++++++++++++++++++++++[<+++>-]<[-]>+++++++[<+++++++>-]<[-]>+++++++++++++++++++[<+++++>-]<[-]>+++++++++++[<+++++++++>-]<[-]>+++++++++++++[<++++>-]<[-]>+++++++++++[<++++++++++>-]<[-]>+++++++++++++++++++[<+++++>-]<[-]>+++++++++++[<+++++++++>-]<[-]>++++++++[<++++++>-]<[-]>++++++++++[<++++++++++>-]<[-]>+++++++++++++++++[<+++>-]<[-]>+++++++++++++++++++[<+++++>-]<[-]>+++++++[<+++++++>-]<[-]>+++++++++++[<++++++++++>-]<[-]>+++++++++++++++++++[<+++++>-]<[-]>++++++++++++++[<+++++++>-]<[-]>+++++++++++++++++++[<++++++>-]<[-]>+++++++++++++[<++++>-]<[-]>+++++++[<+++++++>-]<[-]>+++++++++++[<++++++++++>-]<[-]>+++++++++++++++++[<++++++>-]<[-]>+++++++[<++++++>-]<[-]>+++++++++++[<+++++++++>-]<[-]>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++[<+>-]<[-]>+++++++++++[<+++>-]<[-]>+++++++++++++++++++++++++[<+++++>-]<[-]
例によって以下のスクリプトで C コードに置き換えます。
参考:bftoc/bftoc.py at master · paulkaefer/bftoc · GitHub
結果は以下のようになりました。
/* This is a translation of bf.txt, generated by bftoc.py (by Paul Kaefer)
* It was generated on Saturday, August 03, 2024 at 03:20AM
*/
#include <stdio.h>
void main(void)
{
int size = 1000;
int tape[size];
int i = 0;
/* Clearing the tape (array) */
for (i=0; i<size; i++)
tape[i] = 0;
int ptr = 0;
ptr += 1;
tape[ptr] += 11;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 10;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
}
ptr += 1;
tape[ptr] += 8;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 6;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
}
ptr += 1;
tape[ptr] += 8;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 6;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
}
ptr += 1;
tape[ptr] += 14;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 7;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
}
ptr += 1;
tape[ptr] += 61;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 2;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
}
ptr += 1;
tape[ptr] += 41;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 3;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
}
ptr += 1;
tape[ptr] += 7;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 7;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
}
ptr += 1;
tape[ptr] += 19;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 5;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
}
ptr += 1;
tape[ptr] += 11;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 9;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
}
ptr += 1;
tape[ptr] += 13;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 4;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
}
ptr += 1;
tape[ptr] += 11;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 10;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
}
ptr += 1;
tape[ptr] += 19;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 5;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
}
ptr += 1;
tape[ptr] += 11;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 9;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
}
ptr += 1;
tape[ptr] += 8;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 6;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
}
ptr += 1;
tape[ptr] += 10;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 10;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
}
ptr += 1;
tape[ptr] += 17;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 3;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
}
ptr += 1;
tape[ptr] += 19;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 5;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
}
ptr += 1;
tape[ptr] += 7;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 7;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
}
ptr += 1;
tape[ptr] += 11;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 10;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
}
ptr += 1;
tape[ptr] += 19;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 5;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
}
ptr += 1;
tape[ptr] += 14;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 7;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
}
ptr += 1;
tape[ptr] += 19;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 6;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
}
ptr += 1;
tape[ptr] += 13;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 4;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
}
ptr += 1;
tape[ptr] += 7;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 7;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
}
ptr += 1;
tape[ptr] += 11;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 10;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
}
ptr += 1;
tape[ptr] += 17;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 6;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
}
ptr += 1;
tape[ptr] += 7;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 6;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
}
ptr += 1;
tape[ptr] += 11;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 9;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
}
ptr += 1;
tape[ptr] += 107;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 1;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
}
ptr += 1;
tape[ptr] += 11;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 3;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
}
ptr += 1;
tape[ptr] += 25;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 5;
ptr += 1;
tape[ptr] -= 1;
}
ptr -= 1;
while (tape[ptr] != 0)
{
tape[ptr] -= 1;
}
}
このコードは、主に以下のパートで成り立っています。
ptr += 1;
tape[ptr] += 11;
while (tape[ptr] != 0)
{
ptr -= 1;
tape[ptr] += 10;
ptr += 1;
tape[ptr] -= 1;
}
これは Flag の各文字の検証を行っています。
なので以下の Solver で Flag を取得しました。
case = """tape[ptr] += 11
tape[ptr] += 10
tape[ptr] += 8
tape[ptr] += 6
tape[ptr] += 8
tape[ptr] += 6
tape[ptr] += 14
tape[ptr] += 7
tape[ptr] += 61
tape[ptr] += 2
tape[ptr] += 41
tape[ptr] += 3
tape[ptr] += 7
tape[ptr] += 7
tape[ptr] += 19
tape[ptr] += 5
tape[ptr] += 11
tape[ptr] += 9
tape[ptr] += 13
tape[ptr] += 4
tape[ptr] += 11
tape[ptr] += 10
tape[ptr] += 19
tape[ptr] += 5
tape[ptr] += 11
tape[ptr] += 9
tape[ptr] += 8
tape[ptr] += 6
tape[ptr] += 10
tape[ptr] += 10
tape[ptr] += 17
tape[ptr] += 3
tape[ptr] += 19
tape[ptr] += 5
tape[ptr] += 7
tape[ptr] += 7
tape[ptr] += 11
tape[ptr] += 10
tape[ptr] += 19
tape[ptr] += 5
tape[ptr] += 14
tape[ptr] += 7
tape[ptr] += 19
tape[ptr] += 6
tape[ptr] += 13
tape[ptr] += 4
tape[ptr] += 7
tape[ptr] += 7
tape[ptr] += 11
tape[ptr] += 10
tape[ptr] += 17
tape[ptr] += 6
tape[ptr] += 7
tape[ptr] += 6
tape[ptr] += 11
tape[ptr] += 9
tape[ptr] += 107
tape[ptr] += 1
tape[ptr] += 11
tape[ptr] += 3
tape[ptr] += 25
tape[ptr] += 5"""
flag = ""
arr = case.splitlines()
for i,a in enumerate(arr):
w = int(a.split(" ")[-1])
if i % 2 == 0:
count = w
else:
flag += chr(count*w)
# n00bz{1_c4n_c0d3_1n_br41nf*ck!}
FlagChecker(Rev)
Why did the macros hide its knowledge? Because it didn’t want anyone to “excel”! Note: char21 is the SAME as char22 Note 2: The correct flag has ALL LOWERCASE, NUMBERS, n00bz{] AND UNDERSCORES (There’s two underscores in the entire flag) Author: NoobMaster
問題バイナリとして与えられた FlagChecker.xlsm
から olevba でスクリプトを抽出します。
olevba FlagChecker.xlsm > out.txt
抽出したスクリプトは以下の通りでした。
Sub FlagChecker()
Dim chars(1 To 24) As String
guess = InputBox("Enter the flag:")
If Len(guess) <> 24 Then
MsgBox "Nope"
End If
char_1 = Mid(guess, 1, 1)
char_2 = Mid(guess, 2, 1)
char_3 = Mid(guess, 3, 1)
char_4 = Mid(guess, 4, 1)
char_5 = Mid(guess, 5, 1)
char_6 = Mid(guess, 6, 1)
char_7 = Mid(guess, 7, 1)
char_8 = Mid(guess, 8, 1)
char_9 = Mid(guess, 9, 1)
char_10 = Mid(guess, 10, 1)
char_11 = Mid(guess, 11, 1)
char_12 = Mid(guess, 12, 1)
char_13 = Mid(guess, 13, 1)
char_14 = Mid(guess, 14, 1)
char_15 = Mid(guess, 15, 1)
char_16 = Mid(guess, 16, 1)
char_17 = Mid(guess, 17, 1)
char_18 = Mid(guess, 18, 1)
char_19 = Mid(guess, 19, 1)
char_20 = Mid(guess, 20, 1)
char_21 = Mid(guess, 21, 1)
char_22 = Mid(guess, 22, 1)
char_23 = Mid(guess, 23, 1)
char_24 = Mid(guess, 24, 1)
If Asc(char_1) Xor Asc(char_8) = 22 Then
If Asc(char_10) + Asc(char_24) = 176 Then
If Asc(char_9) - Asc(char_22) = -9 Then
If Asc(char_22) Xor Asc(char_6) = 23 Then
If (Asc(char_12) / 5) ^ (Asc(char_3) / 12) = 130321 Then
If char_22 = char_11 Then
If Asc(char_15) * Asc(char_8) = 14040 Then
If Asc(char_12) Xor (Asc(char_17) - 5) = 5 Then
If Asc(char_18) = Asc(char_23) Then
If Asc(char_13) Xor Asc(char_14) Xor Asc(char_2) = 121 Then
If Asc(char_14) Xor Asc(char_24) = 77 Then
If 1365 = Asc(char_22) Xor 1337 Then
If Asc(char_10) = Asc(char_7) Then
If Asc(char_23) + Asc(char_8) = 235 Then
If Asc(char_16) = Asc(char_17) + 19 Then
If Asc(char_19) = 107 Then
If Asc(char_20) + 501 = (Asc(char_1) * 5) Then
If Asc(char_21) = Asc(char_22) Then
MsgBox "you got the flag!"
End If
End If
End If
End If
End If
End If
End If
End If
End If
End If
End If
End If
End If
End If
End If
End If
End If
End If
End Sub
24 文字の Flag 文字列から各文字を取り出し、様々な検証を行っているようです。
これを Z3Py で書き起こすと以下のようになりました。
from z3 import *
flag = [BitVec(f"flag[{i}]", 64) for i in range(25)]
s = Solver()
for i in range(25):
s.add(And(
(flag[i] >= 0x21),
(flag[i] <= 0x7e)
))
s.add(flag[1] == ord("n"))
s.add(flag[2] == ord("0"))
s.add(flag[3] == ord("0"))
s.add(flag[4] == ord("b"))
s.add(flag[5] == ord("z"))
s.add(flag[6] == ord("{"))
s.add(flag[24] == ord("}"))
s.add(flag[1] ^ flag[8] == 22)
s.add(flag[10] + flag[24] == 176)
s.add(flag[9] - flag[22] == -9)
s.add(flag[22] ^ flag[6] == 23)
s.add(flag[22] == flag[11])
# s.add(UDiv(flag[12],5) ** 4 == 130321)
s.add(flag[12] == 95)
s.add(flag[15] * flag[8] == 14040)
s.add(flag[12] ^ (flag[17] - 5) == 5)
s.add(flag[18] == flag[23])
s.add(flag[13] ^ flag[14] ^ flag[2] == 121)
s.add(flag[14] ^ flag[24] == 77)
s.add(flag[22] ^ 1337 == 1365)
s.add(flag[10] == flag[7])
s.add(flag[23] + flag[8] == 235)
s.add(flag[16] == flag[17] + 19)
s.add(flag[19] == 107)
s.add(flag[20] + 501 == flag[1] * 5)
s.add(flag[21] == flag[22])
while s.check() == sat:
m = s.model()
for i in range(1,25):
c = flag[i]
print(chr(m[c].as_long()),end="")
print("")
# n00bz{3xc3l_y0ur_sk1lls}
VBA で ^
が XOR じゃなくて累乗を意味するということに気づかず少しハマりました。
Plane(Forensic)
So many plane-related challenges! Why not another one? The flag is the latitude, longitude of the place this picture is taken from, rounded upto two decimal places. Example: n00bz{55.51,-20.27} Author: NoobMaster
exif に座標が埋まってました。
Flag は以下。
n00bz{13.37,-13.37}
Wave(Forensic)
The Wave is not audible, perhaps corrupted? Note: Wrap the flag in n00bz{}. There are no spaces in the flag and it is all lowercase. Author: NoobMaster
破損した WAV ファイルが与えられます。
マジックナンバーなどが 0 に置き換えられてるようです。
適当に取得したサンプルの WAV ファイルは以下の通りでした。
これを参考に破損した WAV ファイルを修復します。
修復した WAV ファイルを再生するとモールス信号でしたので、以下のサイトで解読しました。
参考:Morse Code Adaptive Audio Decoder | Morse Code World
正しい Flag は以下でした。
n00bz{beepbopmorsecode}
Vinegar(Crypto)
Can you decode this message? Note: Wrap the decrypted text in n00bz{}
以下のテキストが与えられます。
Encrypted flag: nmivrxbiaatjvvbcjsf
Key: secretkey
問題名の通りヴィジュネル暗号(Vigenere cipher)でしたので、以下のサイトで解析しました。
正しい Flag は以下でした。
n00bz{vigenerecipherisfun}
Vinegar 2(Crypto)
Never limit yourself to only alphabets! Author: NoobMaster
問題バイナリとして以下の Solver が与えられます。
今回も換字式暗号のようですが、変換テーブルが独自に実装されているようです。
alphanumerical = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*(){}_?'
matrix = []
for i in alphanumerical:
matrix.append([i])
idx=0
for i in alphanumerical:
matrix[idx][0] = (alphanumerical[idx:len(alphanumerical)]+alphanumerical[0:idx])
idx += 1
flag=open('../src/flag.txt').read().strip()
key='5up3r_s3cr3t_k3y_f0r_1337h4x0rs_r1gh7?'
assert len(key)==len(flag)
flag_arr = []
key_arr = []
enc_arr=[]
for y in flag:
for i in range(len(alphanumerical)):
if matrix[i][0][0]==y:
flag_arr.append(i)
for y in key:
for i in range(len(alphanumerical)):
if matrix[i][0][0]==y:
key_arr.append(i)
for i in range(len(flag)):
enc_arr.append(matrix[flag_arr[i]][0][key_arr[i]])
encrypted=''.join(enc_arr)
f = open('enc.txt','w')
f.write(encrypted) # *fa4Q(}$ryHGswGPYhOC{C{1)&_vOpHpc2r0({
そこで、これを復号する以下の Solver を作成しました。
alphanumerical = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*(){}_?'
matrix = []
for i in alphanumerical:
matrix.append([i])
idx=0
for i in alphanumerical:
matrix[idx][0] = (alphanumerical[idx:len(alphanumerical)]+alphanumerical[0:idx])
idx += 1
flag=r"*fa4Q(}$ryHGswGPYhOC{C{1)&_vOpHpc2r0({"
key='5up3r_s3cr3t_k3y_f0r_1337h4x0rs_r1gh7?'
assert len(key)==len(flag)
flag_arr = []
key_arr = []
resolve_arr = []
for y in key:
for i in range(len(alphanumerical)):
if matrix[i][0][0]==y:
key_arr.append(i)
for i,f in enumerate(flag):
for j,arr in enumerate(matrix):
if arr[0][key_arr[i]] == f:
resolve_arr.append(j)
break
for n in resolve_arr:
print(alphanumerical[n],end="")
print("")
# n00bz{4lph4num3r1c4l_1s_n0t_4_pr0bl3m}
RSA(Crypto)
The cryptography category is incomplete without RSA. So here is a simple RSA challenge. Have fun! Author: noob_abhinav
問題バイナリとして以下が与えられます。
e = 3
n = 135112325288715136727832177735512070625083219670480717841817583343851445454356579794543601926517886432778754079508684454122465776544049537510760149616899986522216930847357907483054348419798542025184280105958211364798924985051999921354369017984140216806642244876998054533895072842602131552047667500910960834243
c = 13037717184940851534440408074902031173938827302834506159512256813794613267487160058287930781080450199371859916605839773796744179698270340378901298046506802163106509143441799583051647999737073025726173300915916758770511497524353491642840238968166849681827669150543335788616727518429916536945395813
e が 3 であり、c が n より小さいため、c の 3 乗根をとることにしました。
import gmpy2
from Crypto.Util.number import long_to_bytes, bytes_to_long
e = 3
n = 135112325288715136727832177735512070625083219670480717841817583343851445454356579794543601926517886432778754079508684454122465776544049537510760149616899986522216930847357907483054348419798542025184280105958211364798924985051999921354369017984140216806642244876998054533895072842602131552047667500910960834243
c = 13037717184940851534440408074902031173938827302834506159512256813794613267487160058287930781080450199371859916605839773796744179698270340378901298046506802163106509143441799583051647999737073025726173300915916758770511497524353491642840238968166849681827669150543335788616727518429916536945395813
assert c < n
a,b=gmpy2.iroot(c,e)
print(long_to_bytes(a))
# n00bz{crypt0_1s_1nc0mpl3t3_w1th0ut_rs4!!}
上記の Solver で Flag を取得できます。
Random(Crypto)
I hid my password behind an impressive sorting machine. The machine is very luck based, or is it?!?!?!? Author: Connor Chang
問題バイナリとして以下のスクリプトが与えられます。
リモートサーバーではこのコードをコンパイルしたプログラムが稼働しているようです。
#include<chrono>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<string>
#include<fstream>
#include<thread>
#include<map>
using namespace std;
bool amazingcustomsortingalgorithm(string s) {
int n = s.size();
for (int i = 0; i < 69; i++) {
cout << s << endl;
bool good = true;
for (int i = 0; i < n - 1; i++)
good &= s[i] <= s[i + 1];
if (good)
return true;
random_shuffle(s.begin(), s.end());
this_thread::sleep_for(chrono::milliseconds(500));
}
return false;
}
int main() {
string s;
getline(cin, s);
map<char, int> counts;
for (char c : s) {
if (counts[c]) {
cout << "no repeating letters allowed passed this machine" << endl;
return 1;
}
counts[c]++;
}
if (s.size() < 10) {
cout << "this machine will only process worthy strings" << endl;
return 1;
}
if (s.size() == 69) {
cout << "a very worthy string" << endl;
cout << "i'll give you a clue'" << endl;
cout << "just because something says it's random mean it actually is" << endl;
return 69;
}
random_shuffle(s.begin(), s.end());
if (amazingcustomsortingalgorithm(s)) {
ifstream fin("flag.txt");
string flag;
fin >> flag;
cout << flag << endl;
}
else {
cout << "UNWORTHY USER DETECTED" << endl;
}
}
amazingcustomsortingalgorithm 関数では、入力した文字列を random_shuffle
関数でシャッフルし、ソートされた文字列が生成されれば Flag を取得できます。
何度か操作を行うと、random_shuffle
関数によるシャッフルのパターンが固定化されていることに気づきます。
そこで、ABCDEFGHIJK をシャッフルした文字列と対応する位置の文字を手動で置き換え、1 回目のシャッフル結果が ABCDEFGHIJK となる文字列を用意しました。
これを入力すると Flag を取得できます。
Tail(OSINT)
Here’s a picture of a plane’s tail. Can you find the airline’s hub (the airport where they mostly operate from). Use the three letter airport IATA code and wrap it in n00bz{}. Example: n00bz{SFO}
以下の画像が与えられます。
airplain blue flower
などで検索するとこの模様の飛行機が見つかります。
正しい Flag は n00bz{PPT}
でした。
The Gang(OSINT)
John Doe has been suspected of creating a gang. The members of team n00bzUnit3d also seem associated with it. Can you find out if John Doe has recently joined the team? You might find what you are looking for ;) P.S.: The team website might help
n00bzUnit3d のチームサイトから以下のユーザページを見つけます。
参考:Authors
The Gang 2(OSINT)
You may have gotten your first flag, but it’s just the beginning! John Doe, as overconfident as he is, has left you with a riddle. Maybe it hides some secrets? Continue where you left off last time. Flag will already by wrapped in n00bz{} Author: NoobMaster
以下の文を縦読みすると、ユーザ名が JOHN HACKER DOE
であることがわかります。
これを X で探すと、Flag が見つかりました。
Flag は n00bz{5t0p_ch4s1ng_m3_4f2d1a7d}
です。
The Gang 3(OSINT)
Can you find out where the OG meetup point is? The flag is in the format n00bz{lat,long} with upto 3 decimal places, rounded. Continue where you left off… Note: Wikipedia can be wrong sometimes ;) Author: NoobMaster
ツイートを詳しく読むと、AES 暗号文と復号のためのヒントが与えられます。
過去の n00bzCTF の John Doe の関連問題を探すと、AES の復号に必要な情報が見つかります。
参考:n00bzCTF-OfficialWriteups/osint/john_doe at main · n00bzUnit3d/n00bzCTF-OfficialWriteups
これを復号すると Discord サーバの URL がでてきました。
このサーバに参加すると、インドの Bengaluru 周辺の 110 フィートの銅像で合流する予定であることがわかります。
この銅像の人物は空港の名前の由来にもなっているらしいです。
これは、以下の銅像でした。
参考:Nadaprabhu Kempegowda Statue - Google Maps
この銅像の座標が正しい Flag n00bz{13.199,77.682}
になります。
The Gang 4(OSINT)
Can you find more information about his flight? Time to close John Doe’s case one and for all! Author: NoobMaster Note: Flag format: n00bz{DateOfFlight(DD/MM/YYYY)FlightNumberIATAairportCodeOfDepartureIATAairportCodeofArrivalACTUALtimeOfDepartureACTUALtimeOfArrivalGateofDepartureAirplaneModel} Example: n00bz{26/06/2024AA1337DFWSFO13:3715:5132AAirbusA319-400} Please use the above format to format your flag.
Discord サーバ内の会話から、John Doe が乗った飛行機に関する情報を収集します。
これらの情報から以下のサイトにたどり着くことができます。
正しい Flag は n00bz{03/08/2024_AI506_DEL_BLR_10:03_12:44_30_AirbusA350-900}
でした。
Pastebin(OSINT)
Just go to my pastebin which was created long time back. Username: abhinav654321
与えられたユーザ名で Pastebin を検索します。
参考:Nothing here - Pastebin.com
この投稿の過去のコンテンツを Wayback Machine で探すと Flag を特定できます。
参考:n00bz{l0ngt1m3ag0m34nsw4yb4ck} - Pastebin.com
PastebinX(OSINT)
All I need is the User ID said the boss. Help the poor employee to find the id. Flag format - n00bz{1d}. If id is “hhjkh876897” then flag will be n00bz{1dhhjkh876897}
Pastebin の投稿から、@Abhinav78082932
という X アカウントの ID?を特定する問題であると予想できます。
しかし、ここで完全にハマりました。
最終的にチームメンバーが Twitter アカウントの内部 ID?のようなものが存在することに気づき、正しい Flag を特定できました。
参考:Twitter ID Finder and Converter (100% Working ✅)
Passwordless(Web)
Tired of storing passwords? No worries! This super secure website is passwordless! Author: NoobMaster
問題バイナリとして以下のコードが与えられます。
#!/usr/bin/env python3
from flask import Flask, request, redirect, render_template, render_template_string
import subprocess
import urllib
import uuid
global leet
app = Flask(__name__)
flag = open('/flag.txt').read()
leet=uuid.UUID('13371337-1337-1337-1337-133713371337')
@app.route('/',methods=['GET','POST'])
def main():
global username
if request.method == 'GET':
return render_template('index.html')
elif request.method == 'POST':
username = request.values['username']
if username == 'admin123':
return 'Stop trying to act like you are the admin!'
uid = uuid.uuid5(leet,username) # super secure!
return redirect(f'/{uid}')
@app.route('/<uid>')
def user_page(uid):
if uid != str(uuid.uuid5(leet,'admin123')):
return f'Welcome! No flag for you :('
else:
return flag
if __name__ == '__main__':
app.run(host='0.0.0.0', port=1337)
ユーザ名 admin123 の UUID を URL に含む場合に Flag が返されるため、手動で生成した UUID を URL に付与して直接アクセスしました。
Addition(Misc)
My little brother is learning math, can you show him how to do some addition problems? Author: Connor Chang
なんか足し算の計算をするらしいです。
import time
import random
questions = int(input("how many questions do you want to answer? "))
for i in range(questions):
a = random.randint(0, 10)
b = random.randint(0, 10)
yourans = int(input("what is " + str(a) + ' + ' + str(b) + ' = '))
print("calculating")
totaltime = pow(2, i)
print('.')
time.sleep(totaltime / 3)
print('.')
time.sleep(totaltime / 3)
print('.')
time.sleep(totaltime / 3)
if yourans != a + b:
print("You made my little brother cry ðŸ˜")
exit(69)
f = open('/flag.txt', 'r')
flag = f.read()
print(flag[:questions])
totaltime = pow(2, i)
で Sleep させられる時間が膨大過ぎるので、正攻法では解けません。
最終的に print(flag[:questions])
で Flag を表示できればいいため、単に -1 を入力して Flag を取得しました。
from pwn import *
# Set context
# context.log_level = "debug"
target = remote("24.199.110.35", 42189, ssl=False)
# Exploit
q = -1
target.recvuntil(b"how many questions do you want to answer? ")
target.sendline(str(q))
print(target.recvline())
# Finish exploit
target.interactive()
target.clean()
Subtraction(Misc)
My little brother is learning math, can you show him how to do some subtraction problems? Author: Connor Chang
今度は引き算らしいです。
696969 個のランダムに生成された偶数の配列を任意の整数 c から引いた結果の絶対値をとる操作を最大 20 回繰り返した結果、配列内のすべての値を同じ値にすることができれば Flag を取得できます。
import random
n = 696969
a = []
for i in range(n):
a.append(random.randint(0, n))
a[i] -= a[i] % 2
print(' '.join(list(map(str, a))))
for turns in range(20):
c = int(input())
for i in range(n):
a[i] = abs(c - a[i])
if len(set(a)) == 1:
print(open('/flag.txt', 'r').read())
break
少し悩んだ末に、最終的に配列内の要素の最大値を 1 に近づけていく操作ができればよいことに気づきました。
そこで、以下の Solver で最大値と最小値の平均をとる操作を繰り返していくことで Flag を取得できました。
from pwn import *
# context.log_level = "debug"
# target = process(TARGET_PATH)
target = remote("challs.n00bzunit3d.xyz", 10274, ssl=False)
r = target.recvline().decode()
arr = []
for n in r.split(" "):
arr.append(int(n))
# Exploit
n = len(arr)
for turns in range(20):
min_a = min(arr)
max_a = max(arr)
c = (min_a + max_a) // 2
for i in range(n):
arr[i] = abs(c - arr[i])
target.sendline(str(c).encode())
if len(set(arr)) == 1:
print("found")
break
print(target.recvline())
# Finish exploit
target.interactive()
target.clean()
Think Outside the Box(Pwn)
You cannot beat my Tic-Tac-Toe bot! If you do, you get a flag! Author: NoobMaster
○× ゲームの後攻でゲームに勝利すると Flag を得られます。
しかし、普通にプレイしても後攻だと引き分けにしかできません。
また、入力をとる箇所も 1 箇所しかないため、Attack Surface が一切ありません。
最終的に、チームメンバーが -1 を入力すると board_limit_0x3 s> y && board_limit_0x3 s> x
の検証をバイパスするとともに、Bot の操作をバグらせることができることに気づきました。
int32_t main(int32_t argc, char** argv, char** envp)
{
void* fsbase
int64_t rax = *(fsbase + 0x28)
int32_t board_limit_0x3 = 3
int16_t board
__builtin_strncpy(dest: &board, src: "____X____", n: 9)
puts(str: "Welcome to Tic-Tac-Toe! In order…")
printBoard(&board)
int32_t N
int32_t M
move(&board, board_limit_0x3, stage_count: 1, &N, &M, 0, 1)
printBoard(&board)
int32_t K
int32_t L
move(&board, board_limit_0x3, stage_count: 2, &K, &L, N, M)
printBoard(&board)
int32_t var_30
int32_t var_2c
move(&board, board_limit_0x3, stage_count: 3, &var_30, &var_2c, K, L)
printBoard(&board)
void var_28
void var_24
move(&board, board_limit_0x3, stage_count: 4, &var_28, &var_24, var_30, var_2c)
if (rax == *(fsbase + 0x28))
return rax.d - (*(fsbase + 0x28)).d
__stack_chk_fail()
noreturn
}
int64_t move(char* board, int32_t board_limit_0x3, int32_t stage_count, int32_t* arg4, int32_t* arg5, int32_t arg6, int32_t arg7)
{
void* fsbase
int64_t rax = *(fsbase + 0x28)
printf(format: "Move: ")
int32_t y // カンマ区切りでy,x
int32_t x
__isoc99_scanf(format: "%d,%d", &y, &x)
if (board_limit_0x3 s> y && board_limit_0x3 s> x && board[sx.q(y) * 3 + sx.q(x)] != 'X' && board[sx.q(y) * 3 + sx.q(x)] != 'O')
board[sx.q(y) * 3 + sx.q(x)] = 0x4f
int32_t is_win
is_win.b = checkWin(board, board_limit_0x3) != 0
if (is_win.b != 0)
printBoard(board)
printf(format: "You won! Flag: ")
void buf
fgets(&buf, n: 0x26, fp: fopen(filename: "flag.txt", mode: &data_3080))
printf(format: &data_308b, &buf)
exit(status: 0)
noreturn
printBoard(board)
if (stage_count == 1)
puts(str: "Bot turn!")
int32_t var_68
int32_t var_64
firstmove(board, y, x, &var_68, &var_64)
*arg4 = var_68
*arg5 = var_64
if (stage_count == 2)
puts(str: "Bot turn!")
int32_t var_60
int32_t var_5c
secondmove(board, y, x, &var_60, &var_5c, arg6, arg7, board_limit_0x3)
*arg4 = var_60
*arg5 = var_5c
if (stage_count == 3)
puts(str: "Bot turn!")
uint64_t board_limit_0x3_1 = zx.q(board_limit_0x3)
uint64_t var_b8_2 = zx.q(arg7)
void var_58
void var_54
thirdmove(board, y, x, &var_58, &var_54, arg6)
if (stage_count == 4)
puts(str: "Bot turn!")
uint64_t board_limit_0x3_2 = zx.q(board_limit_0x3)
uint64_t var_b8_3 = zx.q(arg7)
fourthmove(board, y)
if (rax == *(fsbase + 0x28))
return rax - *(fsbase + 0x28)
__stack_chk_fail()
noreturn
puts(str: "Invalid move!")
exit(status: 0)
noreturn
}
○× ゲームのテーブルは線形の文字列で管理されているため、-1 を入力に使用することで Bot の動きを止めつつ複数のマスを埋めることでゲームに勝利し、Flag を取得しました。
まとめ
たくさんの問題を解いた。
全体的に難易度は易しめでサクサク解けたので楽しかったです。