Holiday Hack Challenge 2023 Report Cody Travis <cwtravis@gmail.com>
Top

Luggage Lock

Difficulty:

Description:

Help Garland Candlesticks on the Island of Misfit Toys get back into his luggage by finding the correct position for all four dials


Solution

Solution

I watched Chris Elgee's video on this and this is a fairly realistic challenge. I found that using the QWER keys and Space Bar worked the best with testing resistance.

I found that its best to practice with one or two wheels at first, and then work your way up to four wheels.

The technique is to push the button down 1 or two clicks, spin a wheel and find which number is sticking more than others. If there are no numbers sticking more, then increase the button pressure by clicking it in one time. Once you are happy with a dial, move on to the next dial. You can start the pressure back at the first click too. I also found it was easier to work right to left.

If this is too hard for you, then you can always brute force!


Brute Force

Brute Force

This is another one where brute force is possible. There are only 4 wheels, each with 10 possibilities. That means that brute force is possible with only 9999 maximum guesses. We can do better than that though. When you launch a luggage lock session, the game responds with an array of probabilities. These probabilities represent, for each position, how likely each number could be the correct guess. With a little scripting I was able to make a correct guess using this probabilities array on the first try almost every time.

Example:

  </>
Javascript
[
	0.05666666666666667,
	0.03,
	0.13,
	0.13666666666666666,
	0.7766666666666666,
	0.13333333333333333,
	0.25333333333333335,
	0.3133333333333333,
	0.32666666666666666,
	0.22
]
Example Probability Array

This example is for one wheel. In a four wheel example there would four such arrays. In the above example the number 4 has the highest probability value of 0.776~ which means its the most likely to be the correct number.

The script does this for all four wheels and makes the guess. As long as you put in your "playerId" from the URL of the challenge. It will even complete the objective for you in your badge.

  </>
Python
import socketio
import requests
import time
import sys
import random
from itertools import product 

playerID = "YOUR PLAYER ID HERE"

timeout = 5
wheels = 4

last_guess = -1
start = False
quit = False
probabilities = []
guesses = []
numbers = []

for i in range(0, 10):
    numbers.append(i)
    
combinations = [p for p in product(numbers, repeat=wheels)]

data = {
    "wheels": wheels,
    "playerID": playerID
}

resp = requests.post("https://lockdecode.com/game", data=data)
print(f"Making POST request to server: {resp.status_code}")
cookie = resp.headers["set-cookie"]
print(f"Cookie: {cookie}")

sio = socketio.Client()
#sio = socketio.Client(logger=True, engineio_logger=True)

def sort_by_probability(combos, prob):
    print(prob)
    combos_with_probability = []
    for combo in combos:
        probability_sum = 0
        guess = ''.join(str(x) for x in combo)
        for x in range(0, len(prob)):
            probability_sum += prob[x][combo[x]]
        obj = {
            "guess": guess,
            "probability": probability_sum
        }
        combos_with_probability.append(obj)
    combos_with_probability = sorted(combos_with_probability, key=lambda x: x['probability'], reverse=True)
    return combos_with_probability

@sio.event
def connect():
    print("Socket Connected")
    print(f"Starting game with {wheels} wheels")
    sio.emit("message", {"Type": "GameStart", "Wheels": wheels})

@sio.event
def connect_error(data):
    print("The connection failed!")

@sio.event
def disconnect():
    print("Socket Disconnected")
   
@sio.on('message')
def message(event, data=""):
    global last_guess, start, quit, combinations, probabilities, guesses
    if event["Type"] == "Open":
        if event["Success"] == "True":
            print(f"Combination: {last_guess} - SUCCESS!")
            quit = True
        else:
            print(f"Combination: {last_guess} - FAILURE")
        last_guess = -1
    if event["Type"] == "Setup":
        probabilities = event["Probabilities"]
        guesses = sort_by_probability(combinations, probabilities)
        start = True
    #print(event)

sio.connect("https://lockdecode.com", headers={"Cookie": cookie})

#Wait til we get setup response
while not start:
    time.sleep(0.001)

print("Highest Probability Guesses:")
print("Guess\tProbability")
for i in range(0,10):
    print(f"{guesses[i]['guess']}\t{round(guesses[i]['probability'], 4)}/{wheels}")
print()
try:
    for guess in guesses:
        guess_str = guess["guess"]
        print(f"Making guess {guess_str}")
        msg = {
            "Type": "Open",
            "Combo": guess_str
        }
        last_guess = guess_str
        start = time.time()
        elapsed = 0
        sio.emit("message", msg)
        while last_guess == guess_str:
            if quit:
                sys.exit(0)
            time.sleep(0.001)
            elapsed = time.time()-start
            if elapsed > timeout:
                print(f"Combination: {last_guess} - TIMEOUT")
                last_guess = -1
                
except KeyboardInterrupt as e:
    print("Keyboard Interrupt. Exiting")
    sys.exit(0)

print("Combination not found")
Brute Force Script

Here is the example output:

  </>
Bash

Making POST request to server: 200
Cookie: PlayinAndPickin=.eJyrVgrPSE3NKVayMtFRykxRslJKMjOyTEpLMtM1MjQx1jWxSDPWtbS0MNFNMTQxSjRJNUs1NDdQ0lEqLU4tIl59LQCu-hsg.ZZkK6g.cAD_QLCoerVt2PDmohOyH97nVJQ; Secure; HttpOnly; Path=/; SameSite=None
websocket-client package not installed, only polling transport is available
Socket Connected
Starting game with 4 wheels

Highest Probability Guesses:
Guess   Probability
4640    3.0433/4
4647    2.6833/4
4641    2.67/4
4648    2.6567/4
4643    2.6233/4
4620    2.61/4
4690    2.5433/4
4600    2.5367/4
4660    2.53/4
4630    2.5267/4

Making guess 4640
Combination: 4640 - SUCCESS!
Brute Force Output

If your playerID is correct the objective will complete automatically!