# BabyEncoder - reverse We're basically a simple program that does this: * Reads 64 bytes from `/dev/urandom` into a variable called `buf` * Runs the following encryption loop ```c= for ( i = 0; i <= 63; ++i ) *((_BYTE *)buf + i) = *((_BYTE *)buf + i) % 0x5Fu + 32; ``` * This basically turns every value in `buf` into a value from 32 to 126 inclusive. * Runs another encoding function `sub_13C7` over a chunk of 8 bytes from `buf` eight times (ie `buf[0:8]`, `buf[8:16]`, etc. * The return value from `sub_13C7` is a double that's stored into the `v6` array as 128 bytes ```c= for ( j = 0; j <= 7; ++j ) sub_13C7( (unsigned int)&v6[128 * j], buf[j], BYTE1(buf[j]), BYTE2(buf[j]), BYTE3(buf[j]), BYTE4(buf[j]), BYTE5(buf[j]), BYTE6(buf[j]), HIBYTE(buf[j])); ``` * We are given the output of `v6` * If we guess the `buf` array correctly we get the flag (after the encoding loop from the second bullet point) ## sub_13C7 * Generate initial random value $\text{v32}$ between 0 and 254 inclusive * Generate 8 random numbers $r_j$, $0 \le r_j \le 254$ * Generate 128 values in an array as such ($0 \le i \le 127$): $$\text{arr[i]} = \sum_{j=1}^8 \left(\text{buf[j]}*\cos\left(\frac{2\pi}{128}*j*i + \frac{\pi}{64}*r_j\right)\right) + \text{v32} + x_i\\ = \sum_{j=1}^8 \left(\text{buf[j]}*\cos\left(\frac{\pi}{64}*(i*j + r_j)\right)\right) + \text{v32} + x_i$$ ## Fourier Series and DFT * According to https://en.wikipedia.org/wiki/Fourier_series#Table_of_common_Fourier_series, this formula is very similar to the formula for a Fourier Series $${\displaystyle s_{\scriptscriptstyle N}(x)={\frac {A_{0}}{2}}+\sum _{n=1}^{N}A_{n}\cdot \cos \left({\tfrac {2\pi }{P}}nx-\varphi _{n}\right)}$$ * The $x_i$ above is a random integer from 0 to 2 inclusive, it can be thought of as random noise added to the fourier series. * In exponential form (which helps when we're going to calculate the DFT) $${\displaystyle s_{_{N}}(x)=\sum _{n=-N}^{N}c_{n}\cdot e^{i2\pi nx/P}}$$ * Equating the variables from the 2 equations we have $$\frac{A_0}{2} = \text{v32}\\ A_n = \text{buf[j]}\\ x = i\\ n = j\\ N = 8\\ P = 128\\ \varphi _{n} = -\frac{\pi}{64}r_j\\ c_n = \frac {A_{n}}{2}e^{-i\varphi _{n}}$$ * The DFT (Discrete Fourier Transform) will give us a set of coeffcients $X_k$. The random noise is small and won't affect the coeffcients. * We can use these coefficients to define a new Fourier Series (https://en.wikipedia.org/wiki/Discrete_Fourier_transform#math_Eq.2) * Note $N = 128$ here, not to be confused in the above series where we defined $N = 8$. $${\displaystyle x_{n}={\frac {1}{N}}\sum _{k=0}^{N-1}X_{k}\cdot e^{i2\pi kn/N},\quad n\in \mathbb {Z} ,}$$ * Note that this Fourier Series is different than the one we have from the `sub_13C7` function * First, we have 128 terms in this expression, compared to the 9 we had in the other Fourier Series. * However, we can still recover the original coefficients by only looking at the first nine $X_k$ values. * This works because if we look at the exponential form of the Fourier Series and equate it to the Fourier Series the DFT defines for us, then $c_n = \frac{X_k}{128}$ * This basically means that the only relevant terms from the new Fourier Series are $k = 0$ to $k = 8$, accounting for the 8 cosine terms we have and the initial $A_0/2$ term from the Fourier Series of `sub_13C7`. * The DFT will still give us values for $X_k$ beyond $k = 8$, but they will be very small and negligible, which makes sense since our original series only has 9 terms. * We are going to use `numpy` to calculate the DFT, which gives us complex values, in order to get the amplitude and phase (which gives us $\text{buf[j]}$ and $r_j$), we have to convert from complex to polar notation. This is trivial with `np.abs` and `np.angle`. ## Code ```python= from pwn import * import struct import numpy as np #context.log_level = 'debug' def proof_of_work(tube): tube.recvline() poww = tube.recvline().strip(b"\n") p = int(poww[poww.index(b"mod ") + 4:poww.index(b" =")]) tube.recvuntil("Your answer: ") # calculate 2^(2^x) mod p x = int(poww[5:poww.index(b") mod ")]) tube.sendline(str(pow(2, pow(2, x), p))) #tube.interactive() def get_doubles(tube): v6 = [] tube.recvuntil("========START=======\n") doubles = tube.recvuntil("=========END========\n", drop=True) #print(len(doubles)) assert len(doubles) == 8192 for i in range(1024): v6.append(struct.unpack('<d', doubles[8*i:8*(i + 1)])[0]) correct_bufs = [] for xx in range(1024//128): fft = np.fft.fft(v6[128*xx:128*(xx + 1)])/128 amp = np.abs(fft) phase = np.angle(fft) #print(list(fft)) #print("v32", round(amp[0])) for i in range(1, 9): #print("buf[j]", round(amp[i]*2)) correct_bufs.append(int(round(amp[i]*2))) #print("r_j", round(phase[i]*64/np.pi)) #print(correct_bufs) #print(list(np.fft.fft(v6[:128])/128)) #print(list(np.fft.rfft(v6[:128])/128)) tube.send(bytes(correct_bufs)) print(tube.recv(1024)) r = remote("202.120.7.212", 20001) proof_of_work(r) get_doubles(r) ``` ## Flag `flag{R34l1y_3aSy_R3ve7s3_W31C000m3_T0_0CTF/TCTF_2022_G000d_Luck}`