> For the complete documentation index, see [llms.txt](https://elijahchia.gitbook.io/ctf-blog/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://elijahchia.gitbook.io/ctf-blog/jelly-ctf-24/cherry-crypto.md).

# 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...

{% file src="/files/j6IHwjNhL54dCmoCHWJt" %}

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`:

<pre class="language-javascript"><code class="lang-javascript">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 &#x3C; 3; columnIndex++) {
    <a data-footnote-ref href="#user-content-fn-1">slotIndices[columnIndex] = (slotIndices[columnIndex] + slotSpins[columnIndex]) % m;</a>
  }
  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 &#x3C;= 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;
}
</code></pre>

When the script first runs, `m` is set to `slotSymbols.length`, which is the number of symbols available and is always `32768`.&#x20;

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`.&#x20;

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`.&#x20;

The equation determining the next indices is given by this line of code:

```javascript
 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:

$$
\[10992+19i+32j+347k,30978+22i+27j+349k,12520+19i+29j+353k]=\[0,0,0]
$$

$$
\[30983+19i+32j+347k,7390+22i+27j+349k,481+19i+29j+353k]=\[0,0,0]
$$

$$
\[25974+19i+32j+347k,26744+22i+27j+349k,9122+19i+29j+353k]=\[0,0,0]
$$

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.&#x20;

We can guess that when we get 3 cherries, `updatePlaintextDisplay()` will make the `spinCount0`, `spinCount1` and `spinCount2` elements show part of the flag.&#x20;

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.

```python
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:

```python
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)}')
```

[^1]: Change in slotIndices depends on slotSpins, which is set by playCoin
