Palantine Pack (SunshineCTF 2025)
Palantine Pack (SunshineCTF 2025)
Steps:
- Investigate binary and identify the transform pipeline in main.
- Note the binary reads palantinepackflag.txt, then three class to an expand routine with a shuffle, and one earlier flipBits pass.
- Obserse the ouput is printed and written to flag.txt. This is a Ciphertext, not the flag.
- Derive exact inverse functions for expand and flipBits.
- Apply inverse expand three times, then inverse flipBits once, and decode the bytes as UTF-8
- Recover the plaintext flag in format sunshine{…}
Detailed Explanation:
We are given an x86_64 ELF that performs a layred bitwise transform on the contents of a local file, the prints and saves the bytes.
Key functions:
flipBits(buf, n)
- Alternates two operations across
0..n-1- even index:
b = ~b - odd index:
b = b ^ Kwith K starting at 0x69, thenK += 0x20after each odd index
- even index:
expand(buf, n)
- Produces 2*n bytes by mixing nibbles with a running key K that evolves as
K = K * 0x0b mod 256. It also toggles a flag for each byte. For each source byte x it emits two bytes, placing x’s high and low nibbles in complement positions and filling the other nibble positions from K.
The pipeline in main:
read(“palatinepackflag.txt”) -> buf length L+1 including newline flipBits(buf, L+1) buf1 = expand(buf, L+1)
buf2 = expand(buf1, 2(L+1))
buf3 = expand(buf2, 4(L+1))
print buf3 write buf3 to “flag.txt”
Therefore, the encryption is:
cipher = expand(expand(expand(flipBits(plain))))
We need to invert this to get the flag.
Inversion
For expand
- Forward for each source byte x with toggle t and key K:
- if t is False:
out0 = (x & 0x0F) | (K << 4)out1 = (x & 0xF0) | (K >> 4)
- if t is True:
out0 = (x & 0xF0) | (K >> 4)out1 = (x & 0x0F) | (K << 4)
- if t is False:
- The nibble from x is always preserved in one nibble of out0 and the complement nibble of out1. So we can recover x without knowing K:
- if t is False:
x = (out1 & 0xF0) | (out0 & 0x0F) - if t is True:
x = (out0 & 0xF0) | (out1 & 0x0F)
- if t is False:
For flipBits
- Forward index i:
- even i:
b = ~b - odd i:
b = b ^ K, thenK = (K + 0x20) mod 256
- even i:
With this we can make a minimal solve:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
def inv_expand(buf: bytes) -> bytes:
out = bytearray()
toggle = False
for i in range(0, len(buf), 2):
b0, b1 = buf[i], buf[i+1]
if toggle:
x = ((b0 & 0xF0) | (b1 & 0x0F)) & 0xFF
else:
x = ((b1 & 0xF0) | (b0 & 0x0F)) & 0xFF
out.append(x)
toggle = not toggle
return bytes(out)
def inv_flip(buf: bytes) -> bytes:
out = bytearray()
toggle = False
k = 0x69
for b in buf:
if toggle:
out.append(b ^ k)
k = (k + 0x20) & 0xFF
else:
out.append((~b) & 0xFF)
toggle = not toggle
return bytes(out)
# usage on the file that the binary outputs
with open("flag.txt", "rb") as f:
c = f.read()
for _ in range(3):
c = inv_expand(c)
p = inv_flip(c)
print(p.rstrip(b"\x00").decode("utf-8"))
Output : sunshine{C3A5ER_CR055ED_TH3_RUB1C0N}
This post is licensed under CC BY 4.0 by the author.