Cherry (crypto)
A Starknight hacked an old slot machine and turned it into something strange?! I heard that you win a secret message if you manage to get triple cherries, but...
This challenge gives us a slot machine, and our goal is to make all the slots become cherries. The main logic of the program is in script.js
:
let slotSymbols = [];
let m = 0; // gets initialised to 32768
let awascii32 = 'awjelyhosiumpcntbdfgr.,!{}_/;CTF';
let spinMode = 0; // either 0, 1 or 2
let spinCounts = [0,0,0]; // how much we've spun in each mode. reset by reset()
let slotIndices = [0,0,0]; // the current index of the character being displayed
let slotSpins = [0,0,0]; // depends on spinMode
let ciphertextIndex = 0; // modified by change()
fetch('static/slotsymbols.txt')
.then(response => response.text())
.then(text => {
slotSymbols = text.split(/(?:)/u);
m = slotSymbols.length;
}).then(() => {
playCoin(0);
reset();
});
function change() {
ciphertextIndex = (ciphertextIndex + 1) % 3;
reset();
}
function reset() {
if (ciphertextIndex == 0) slotIndices = [10992, 30978, 12520];
else if (ciphertextIndex == 1) slotIndices = [30983, 7390, 481];
else if (ciphertextIndex == 2) slotIndices = [25974, 26744, 9122];
spinCounts = [0,0,0];
updatePlaintextDisplay();
doSpinCleanup();
}
function spin() {
const buttons = document.querySelectorAll('.button');
buttons.forEach((b) => b.disabled = true);
spinCounts[spinMode] += 1;
updateModeDisplay();
updatePlaintextDisplay();
doSpinAnimation();
for (let columnIndex = 0; columnIndex < 3; columnIndex++) {
}
setTimeout(() => {
doSpinCleanup();
buttons.forEach((b) => b.disabled = false);
}, 1)
}
function playCoin(n){
spinMode = n;
if (n == 0) slotSpins = [ 19, 22, 19];
else if (n == 1) slotSpins = [ 32, 27, 29];
else if (n == 2) slotSpins = [347, 349, 353];
updateModeDisplay();
}
function updatePlaintextDisplay() {
let decToAwascii32 = (x) => {return awascii32.charAt(x % 32) + awascii32.charAt((x >> 5) % 32) + awascii32.charAt((x >> 10) % 32)};
document.getElementById('spinCount0').innerHTML = decToAwascii32(spinCounts[0]);
document.getElementById('spinCount1').innerHTML = decToAwascii32(spinCounts[1]);
document.getElementById('spinCount2').innerHTML = decToAwascii32(spinCounts[2]);
}
/**
*
* NOTE: Everything below this point is purely visual and not needed to solve the challenge.
*
*/
function createSymbolElement(symbol) {
const div = document.createElement('div');
div.classList.add('symbol');
div.textContent = symbol;
return div;
}
function doSpinAnimation() {
const slots = document.querySelectorAll('.slot');
slots.forEach((slot, columnIndex) => {
const symbols = slot.querySelector('.symbols');
symbols.style.transition = 'none';
symbols.style.top = '0';
symbols.replaceChildren();
for (let i = slotIndices[columnIndex]; i <= slotIndices[columnIndex] + slotSpins[columnIndex]; i++) {
symbols.appendChild(createSymbolElement(slotSymbols[i % m]))
}
symbols.offsetHeight;
symbols.style.transition = '';
const symbolHeight = symbols.querySelector('.symbol')?.clientHeight;
const offset = -(symbols.childElementCount - 1) * symbolHeight;
symbols.style.top = `${offset}px`;
});
}
function doSpinCleanup() {
const slots = document.querySelectorAll('.slot');
slots.forEach((slot, columnIndex) => {
const symbols = slot.querySelector('.symbols');
symbols.style.transition = 'none';
symbols.style.top = '0';
symbols.replaceChildren(createSymbolElement(slotSymbols[slotIndices[columnIndex]]));
symbols.offsetHeight;
symbols.style.transition = '';
});
}
function updateModeDisplay() {
document.getElementById('spinMode').innerHTML = spinMode;
}
When the script first runs, m
is set to slotSymbols.length
, which is the number of symbols available and is always 32768
.
Initially, before spinning, the characters we are shown depends on cipherTextIndex
. Its value can be 0
, 1
, or 2
. Depending on its value, reset()
gives us a different combination of initial indexes to use in slotIndices
.
When we spin()
, the change of indices also depends on slotSpins
. slotSpins
is set by the playCoin()
function. We can play with either 0
, 1
, or 2
coins, and each gives us a different array slotSpins
.
The equation determining the next indices is given by this line of code:
slotIndices[columnIndex] = (slotIndices[columnIndex] + slotSpins[columnIndex]) % m;
Upon inspecting slotsymbols.txt
, we can see that the cherry is at index 0 since it is the first symbol in the file. So our objective is to make slotIndices[0] = 0, slotIndices[1] = 0 and slotIndices[2] = 0
.
Let's formulate the problem. For each of the 3 cipherTextIndex
values, we can spin with 0 coins i
times, spin with 1 coin j
times, and spin with 2 coins k
times. Hence our equations are:
For each equation, we can solve for i
, j
and k
to find out how many times to roll in each coin state to get 3 cherries.
We can guess that when we get 3 cherries, updatePlaintextDisplay()
will make the spinCount0
, spinCount1
and spinCount2
elements show part of the flag.
Hence I used z3 to solve the above constraints, then input the individual i j k
values into decToAwascii32
to get the characters of the flag.
from z3 import *
m = 32768
i, j, k = Int('i'), Int('j'), Int('k')
i_cpy, j_cpy, k_cpy = i, j, k
s = Solver()
# solve each equation
s.add((10992 + 19 * i_cpy + 32 * j_cpy + 347 * k_cpy) % m == 0)
s.add((30978 + 22 * i_cpy + 27 * j_cpy + 349 * k_cpy) % m == 0)
s.add((12520 + 19 * i_cpy + 29 * j_cpy + 353 * k_cpy) % m == 0)
# [k = 25598, i = 36962, j = 29860]
# s.add((30983 + 19 * i_cpy + 32 * j_cpy + 347 * k_cpy) % m == 0)
# s.add((7390 + 22 * i_cpy + 27 * j_cpy + 349 * k_cpy) % m == 0)
# s.add((481 + 19 * i_cpy + 29 * j_cpy + 353 * k_cpy) % m == 0)
# [k = 14158, i = 698597, j = 433210]
# s.add((25974 + 19 * i_cpy + 32 * j_cpy + 347 * k_cpy) % m == 0)
# s.add((26744 + 22 * i_cpy + 27 * j_cpy + 349 * k_cpy) % m == 0)
# s.add((9122 + 19 * i_cpy + 29 * j_cpy + 353 * k_cpy) % m == 0)
# [k = 26344, i = 86118, j = 101684]
s.add(i_cpy >= 0)
s.add(j_cpy >= 0)
s.add(k_cpy >= 0)
print(s.check())
print(s.model())
awascii32 = 'awjelyhosiumpcntbdfgr.,!{}_/;CTF'
def decToAwascii32(x):
return awascii32[x % 32] + awascii32[(x >> 5) % 32] + awascii32[(x >> 10) % 32]
def printFlagHelper(i, j, k):
print(f"{decToAwascii32(i)}{decToAwascii32(j)}{decToAwascii32(k)}")
printFlagHelper(36962, 29860, 25598)
printFlagHelper(698597, 433210, 14158)
printFlagHelper(86118, 101684, 26344)
#jellyCTF{you_won_cherries!}
The intended solve using matrices is given here:
from math import gcd
import sympy
a = sympy.Matrix([[19, 32, 347],
[22, 27, 349],
[19, 29, 353]])
b1 = sympy.Matrix([10992,30978,12520])
b2 = sympy.Matrix([30983,7390,481])
b3 = sympy.Matrix([25974,26744,9122])
m = 32768
def solve(b):
det = int(a.det())
if gcd(det, m) == 1:
ans = ((pow(det, -1, m) * a.adjugate()) @ b) % m
return ans
else:
print("don't know")
def decToAwascii32(x):
awascii32 = r'awjelyhosiumpcntbdfgr.,!{}_/;CTF'
return awascii32[x % 32] + awascii32[(x >> 5) % 32] + awascii32[(x >> 10) % 32]
x1,x2,x3 = solve(b1)
print(f'{decToAwascii32(m-x1)}{decToAwascii32(m-x2)}{decToAwascii32(m-x3)}')
x1,x2,x3 = solve(b2)
print(f'{decToAwascii32(m-x1)}{decToAwascii32(m-x2)}{decToAwascii32(m-x3)}')
x1,x2,x3 = solve(b3)
print(f'{decToAwascii32(m-x1)}{decToAwascii32(m-x2)}{decToAwascii32(m-x3)}')
Last updated