Objective

image-20210110214715784

Terminal - The Elf Code

Challenge

In the top left corner of the Dining Room, Ribb Bonbowford stands next to an arcade game, The Elf Code:

Hello - my name is Ribb Bonbowford. Nice to meet you!

Are you new to programming? It’s a handy skill for anyone in cyber security.

This challenge centers around JavaScript. Take a look at this intro and see how far it gets you!

Ready to move beyond elf commands? Don’t be afraid to mix in native JavaScript.

Trying to extract only numbers from an array? Have you tried to filter?

Maybe you need to enumerate an object’s keys and then filter?

Getting hung up on number of lines? Maybe try to minify your code.

Is there a way to push array items to the beginning of an array? Hmm…

image-20210110215127008

There’s a series of seven JavaScript-based challenges that involve moving my elf around a grid, avoiding obsticles and answering challenges to progress. Each level is solved with a limited number of Javascript lines.

#1

Program the elf to the end goal in no more than 2 lines of code and no more than 2 elf commands.

image-20210110215418329
elf.moveLeft(10)
elf.moveUp(10)

Completed in 2 elf statements.

#2

Program the elf to the end goal in no more than 5 lines of code and no more than 5 elf command/function execution statements in your code.

image-20210110215510245

To turn off the Yeeter, I’ll need to move to the level, call elf.get_lever(0), add two to the returned value, and submit it with elf.pull_lever(result).

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

Completed in 5 elf statements.

#3

Program the elf to the end goal in no more than 4 lines of code and no more than 4 elf command/function execution statements in your code.

image-20210110215720762

I need to make sure to get all the lollipops this time. This level introduces the elf.moveTo() function:

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

Complete in 2 elf statements.

#4

Program the elf to the end goal in no more than 7 lines of code and no more than 6 elf command/function execution statements in your code.

image-20210110215919109
for (i = 0; i < 4; i++) {
  elf.moveLeft(3);
  elf.moveUp(11);
  elf.moveLeft(3);
  elf.moveDown(11);
}

Completed in 4 elf statements.

#5

Program the elf to the end goal in no more than 10 lines of code and no more than 5 elf command/function execution statements in your code.

image-20210110220127436

The munchkin can be disabled by asking with elf.ask_munch(1), taking the returned array, and returning only the elements which are strings.

elf.moveTo(lollipop[1])
elf.moveTo(lollipop[0])
elf.tell_munch(elf.ask_munch(0).filter(elem => typeof elem === 'number'));
elf.moveUp(2)

Completed in 5 elf statements.

#6

Program the elf to the end goal in no more than 15 lines of code and no more than 7 elf command/function execution statements in your code.

image-20210110220627705

To solve the munchkin, I’ll need to get a json object from him, and return the name of the key that holds the value “lollipop”.

for (i = 0; i < 4; i++) {
  elf.moveTo(lollipop[i]);
}
elf.moveLeft(8);
elf.moveUp(2);
var from_munch = elf.ask_munch(0);
const keys = Object.keys(from_munch);
for (key of keys) {
  if (from_munch[key] == "lollipop") {
    elf.tell_munch(key);
  }
}
elf.moveUp(2);

Completed in 6 elf statements.

#7 - Bonus

Program the elf to the end goal in no more than 25 lines of code and no more than 10 elf command/function execution statements in your code.

image-20210110220846633

This level is full of challenges to disable. To pull the lever, I just need to elf.pull_lever(#) while standing on that lever. The munchkin is much more complex. I need to write a function that takes one argument that is an array of arrays, and returns the sum of all numbers in those arrays.

function func(input) {
  sum = 0
  input.forEach(outer => outer.forEach(inner => {
    if (typeof(inner) == 'number') {
      sum += inner;
    }
  }))
  return sum;
}

var moves = [elf.moveDown, elf.moveLeft, elf.moveUp, elf.moveRight];
for (i = 0; i < 2; i++) {
  for (j = 0; j < 4; j++) {
    moves[j](1 + j + (4 * i))
    elf.pull_lever(4 * i + j)
  }
}
elf.moveUp(2);
elf.moveLeft(4);
elf.tell_munch(func);
elf.moveUp(2)

To make it through the swirl without using too many lines or elf commands, I used an array of move functions and nested for loops.

Completed in 6 elf statements.

#8 - Bonus

Program the elf to the end goal in no more than 40 lines of code and no more than 10 elf command/function execution statements in your code.

image-20210110222013067

For each of the levers, I must submit the sum of the values from all the levers before it.

For the munchkin, I must give it a function that will look through an array of dicts, finding the key of the value lollipop.

function func(input) {
  for (var i = 0; i < input.length; i++) {
    keys = Object.keys(input[i]);
    for (var j = 0; j < keys.length; j++) {
      if (input[i][keys[j]] == 'lollipop') {
        return keys[j];
      }
    }
  }
}

elf.moveRight(1);
var lever_sums = [0, 0, 0, 0, 0, 0];
lever_sums[0] = elf.get_lever(0);
elf.pull_lever(lever_sums[0]);
move_fns = [elf.moveRight, elf.moveLeft]
for (var i = 1; i < 6; i++) {
  elf.moveUp(2);
  move_fns[i % 2](2 * i + 1)
  lever_sums[i] = lever_sums[i - 1] + elf.get_lever(i)
  elf.pull_lever(lever_sums[i])
}
elf.moveUp(2);
elf.tell_munch(func);
elf.moveRight(11);

Completed in 9 elf statements.

image-20210110222225486

Defeat Fingerprint Sensor

Hints

Ribb suggests I might use my JavaScript skills on the Santavator:

Wow - are you a JavaScript developer? Great work!

Hey, you know, you might use your JavaScript and HTTP manipulation skills to take a crack at bypassing the Santavator’s S4.

No badget hints for this one.

Santavator Floor 3

In objective 4, I looked at the JavaScript for the Santavator and how to bypass the S4 stream to enable the buttons. There’s extra security around Santa’s Office, floor 3. When that button is pushed, instead of going there, a fingerprint sensor is revealed:

image-20210111061141059

Clicking on it as a non-Santa player just makes a buzz noise. As Santa, it works and I’m taken to Santa’s Office.

JavaScript

Previous Work from Obj 4

The Santavator panel is run by script loaded from elevator.kringlecastle.com, specifically app.js:

image-20210111062819111

In Objective 4, I looked at adding the powered class to each button but changing the checks in the renderTraps function:

const renderTraps = () => {
  TRAPS.forEach((points, index) => {
    const fillLevel = pl.Math.clamp(PARTICLE_COUNTS[index].length / trapTargetCounts[index], 0, 1);
    const steppa = Math.floor(fillLevel / (1 / wireSteps[index]));
    wireElements[index].style.backgroundPosition = `0 ${ -wireElements[index].clientHeight * steppa }px`;
    ledElements[index].classList[fillLevel === 1 ? 'add' : 'remove']('on');
    powered[index] = fillLevel === 1;
  });
  btn1.classList[powered[2] ? 'add' : 'remove']('powered');
  btn3.classList[powered[2] ? 'add' : 'remove']('powered');
  btn2.classList[powered[2] && powered[0] && hasToken('workshop-button') ? 'add' : 'remove']('powered');
  btnr.classList[powered[2] && powered[0] ? 'add' : 'remove']('powered');
  btn4.classList[powered[2] && powered[1] && powered[0] ? 'add' : 'remove']('powered');
  
};

Just from this I can tell that btn4 is the floor 3 button, as it’s the only one that requires all three S4 streams to turn on. I can just change that in the Chrome dev tools to:

btn4.classList['add']('powered');

And now that button is powered:

image-20210111063148158

Additional RE

Looking around a bit more, I see where the buttons are set to the btn variables, and how clicks are handled:

const btn1 = document.querySelector('button[data-floor="1"]');
const btn2 = document.querySelector('button[data-floor="1.5"]');
const btn3 = document.querySelector('button[data-floor="2"]');
const btn4 = document.querySelector('button[data-floor="3"]');
const btnr = document.querySelector('button[data-floor="r"]');

btn1.addEventListener('click', handleBtn);
btn2.addEventListener('click', handleBtn);
btn3.addEventListener('click', handleBtn);
btn4.addEventListener('click', handleBtn4);
btnr.addEventListener('click', handleBtn);

The handle function for btn4 is different than the others. The handleBtn function gets the floor from the button pushed, and then makes an AJAX web call:

const handleBtn = event => {
  const targetFloor = event.currentTarget.attributes['data-floor'].value;
  $.ajax({
    type: 'POST',
    url: POST_URL,
    dataType: 'json',
    contentType: 'application/json',
    data: JSON.stringify({ 
      targetFloor,
      id: getParams.id,
    }),
    success: (res, status) => {
      if (res.hash) {
        __POST_RESULTS__({
          resourceId: getParams.id || '1111',
          hash: res.hash,
          action: `goToFloor-${targetFloor}`,
        });
      }
    }
  });
}

The handleBtn4 function has that same AJAX call, but there are additional checks before:

const handleBtn4 = () => {
  const cover = document.querySelector('.print-cover');
  cover.classList.add('open');

  cover.addEventListener('click', () => {
    if (btn4.classList.contains('powered') && hasToken('besanta')) {
      $.ajax({
        type: 'POST',
        url: POST_URL,
        dataType: 'json',
        contentType: 'application/json',
        data: JSON.stringify({ 
          targetFloor: '3',
          id: getParams.id,
        }),
        success: (res, status) => {
          if (res.hash) {
            __POST_RESULTS__({
              resourceId: getParams.id || '1111',
              hash: res.hash,
              action: 'goToFloor-3',
            });
          }
        }
      });
    } else {
      __SEND_MSG__({
        type: 'sfx',
        filename: 'error.mp3',
      });
    }
  });
};

First, it add the class open to the cover object, and it adds an event listener for a click. When the fingerprint reader is clicked, there’s one more if before making the AJAX call that takes the player to Santa’s Office:

 if (btn4.classList.contains('powered') && hasToken('besanta')) {

hastoken is a one line function that checks if a string is in the array tokens that is created when the page loads:

let tokens = (getParams.tokens || '').split(',');
...[snip]...

const hasToken = name => tokens.indexOf(name) !== -1;

Modify

There are many ways to modify this to bypass the check. I can add a line before the check that adds that token to tokens:

  cover.addEventListener('click', () => {
    tokens.push('besanta');
    if (btn4.classList.contains('powered') && hasToken('besanta')) {
      $.ajax({

Or just change the check:

  cover.addEventListener('click', () => {
    if (btn4.classList.contains('powered')) {
      $.ajax({

On running, I find myself in Santa’s Office:

image-20210111065620647

Tinsel Upatree is surprised:

GOSHGOLLY

How did you get in here??

I mean, hey, I’m impressed you made it in here, but you’ve got to leave!

Breaking into Santa’s office might mean immediate membership on the wrong side of the Naughty/Nice List.