Rating:

# Automatic Rejection Machine
This is a classical flag checker but its an Aarch64 ELF.
Since qemu is a pain in the ass and I have no usable device to run the executable, we just reversed the binary and re-implemented it in Rust.

The binary basically takes the flag and apply some operations on it, and then it checks that the result matches some magic values. To solve it we just brute-force the value using the number of magic values matched as a """""side-channel"""".
```rust
use std::collections::HashMap;

const SBOX: [i16; 30] = [
0x0, 0x2, 0xB, 0x6, 0x4, 0x5, 0x3, 0x7, 0x8, 0x9, 0xA, 0x1, 0xC, 0x16, 0x18, 0xF, 0x11, 0x10, 0x12, 0x13, 0x17, 0x14, 0xD, 0x15, 0xE, 0x1D, 0x1C, 0x1B, 0x1A, 0x19
];

fn shuffle_flag(mut flag: Vec<u8>) -> Vec<u8> {
let mut sbox = SBOX.to_vec();
for i in 0..0x1E {
let mut j = i;
while sbox[j] > -1 {
flag.swap(i, sbox[j] as usize);
let tmp = sbox[j];
sbox[j] -= 0x1E;
j = tmp as usize;
}
}
flag
}

const WTF: [u64; 6] = [
0x9e3779b917181920,
0x9e3779b912881288,
0xdeadbeeffeedbeef,
0x1badb002facecafe,
0xFEEDFACE08920892,
0xCAFEFEED12401240,
];

fn f1(flag: Vec<u8>) -> Vec<u64> {
let mut wtf = WTF.to_vec();
let mut res = Vec::new();

for i in 0..(flag.len() - 2) {
wtf[0] =
(flag[i + 2] as u64) << 0x10 |
(flag[i + 1] as u64) << 8 |
flag[i] as u64 |
0xaabbccdd11000000;
let (a, b) = f2(&wtf);
res.push(a);
res.push(b);
}

res
}

fn f2(wtf: &Vec<u64>) -> (u64, u64) {
let mut res1 = wtf[0];
let mut res2 = wtf[1];
let mut c = wtf[2..].to_vec();

for i in 0..0x10 {
c[i & 3] =
c[0] + c[1] + (
c[2] + c[3] ^ c[0] << (c[2 & 0x3F])
);
let tmp = c[i & 3];
res1 += (tmp + res2) * 0x200 ^ tmp - res2 ^ (tmp + res2) >> 0xe;
res2 += (tmp + res1) * 0x200 ^ tmp - res1 ^ (tmp + res1) >> 0xe;
}

(res1, res2)
}

const MAGIC: [u64; 60] = [
0xb4d8846071ac9ee5,
0x1e1ff00814e134fe,
0x6b198e7941b7002e,
0xbc6fa839efe36443,
0xc3c71ad9a664b6c3,
0x5692a2f09c98d986,
0xf084a1a59cd01e68,
0xbc52e78a7e4df2df,
0xda219d93290b91a8,
0x5703d0286fa5d32f,
0x6274b1b118da82b2,
0xa746ebfb0954ebbc,
0x5f6df7bd4f1967a2,
0x16d5b5bdee98cf8e,
0x52e8b6df7e62e39a,
0x99f9455fb0c8d933,
0x5ffd82d53af933d,
0xff9084a16ff0141c,
0xe17c5f0781d52f9b,
0x1a0f4431548e51d1,
0xf2e8573d8f0f01dd,
0x250039177f4def91,
0x8851491ecbc7af7c,
0xad427c6695b91d24,
0x5e0071d97d98d094,
0x264dda52b0c37b03,
0xa5811271d6d7c428,
0xe0133fc719f34136,
0xe508ace2412b2633,
0x74321a3e9face34c,
0xff5b8a59e8ebf70b,
0x76275a516f88c986,
0x1604d76f74599cc4,
0xf744bcd8f2016f58,
0xa0b6a7a0239e4ea7,
0xf1efc57f15cb9ab4,
0xb0d1ad4fb4ed946a,
0x81ca31324d48e689,
0xe6a9979c51869f49,
0xa666637ee4bc2457,
0x6475b6ab4884b93c,
0x5c033b1207da898f,
0xb66dc7e0dec3443e,
0xe4899c99cfa0235c,
0x3b7fd8d4d0dcaf6b,
0xb1a4690db34a7a7c,
0x8041d2607129adab,
0xa6a1294a99894f1a,
0xdde37a1c4524b831,
0x3bc8d81de355b65c,
0x6c61ab15a63ad91e,
0x8fa4e37f4a3c7a39,
0x268b598404e773af,
0x74f4f040ae13f867,
0x4df78e91fd682404,
0xabe1fc425a9a671a,
0x1bb06615c8a31dd5,
0x9f56e9aef2fa5d55,
0x239dcf030b3ce09b,
0x24556a34b61ca99,
];

const INV_SBOX: [usize; 30] = [0, 11, 1, 6, 4, 5, 3, 7, 8, 9, 10, 2, 12, 22, 24, 15, 17, 16, 18, 19, 21, 23, 13, 20, 14, 29, 28, 27, 26, 25];

fn main() {
// start from a plausible input this is important because the f1 function
// depends on the next 3 chars in the flag (once shuffled) so we need the `p` and the `m``
// to be already right otherwise the bruteforcing won't work.
let mut input = b"ptm{EFGHIJKLMNOPQRSTUVWXYZabc}".to_vec();

// for each carachter
for i in 1..30 {
let mut tests = HashMap::new();

// bruteforce the next carachter
for c in "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&\\'()*+,-./:;<=>?@[\\]^_`{|}~".bytes() {
// set the current carachter
input[INV_SBOX[i]] = c;

// simlate the binary
let clone_input = input.clone();
let flag = shuffle_flag(input.clone());
let flag = f1(flag);

// count how many magic values the current input matches
let mut count = 0;
for i in 0..56 {
if flag[i] == MAGIC[i] {
count += 1;
}
}

// save it into the possible solutions map
tests.insert(clone_input, count);
}

// extract the input which matches the most magics
let (new_input, count) = tests.iter().max_by(|(_flag1, count1), (_flag2, count2)| count1.cmp(count2)).unwrap();

// print it and use it for the next iteration
println!("{} {}", String::from_utf8(new_input.clone()).unwrap(), count);
input = new_input.clone();

}
}
```
Which give the result:
```
$ time cargo run --release
ptm{EFGHIJKuMNOPQRSTUVWXYZabc} 2
ptm{EFGHIJKuMNOPQRSTUVWXYZabc} 2
ptm{EF0HIJKuMNOPQRSTUVWXYZabc} 4
ptm{5F0HIJKuMNOPQRSTUVWXYZabc} 6
ptm{5m0HIJKuMNOPQRSTUVWXYZabc} 10
ptm{5m0HIJKuMNOPQRSTUVWXYZabc} 10
ptm{5m0lIJKuMNOPQRSTUVWXYZabc} 12
ptm{5m0l_JKuMNOPQRSTUVWXYZabc} 14
ptm{5m0l_cKuMNOPQRSTUVWXYZabc} 16
ptm{5m0l_chuMNOPQRSTUVWXYZabc} 20
ptm{5m0l_chuMNOPQRSTUVWXYZabc} 20
ptm{5m0l_chunNOPQRSTUVWXYZabc} 22
ptm{5m0l_chunNOPQRSTUV3XYZabc} 24
ptm{5m0l_chunNOPQRSTUV3XuZabc} 26
ptm{5m0l_chunNO_QRSTUV3XuZabc} 28
ptm{5m0l_chunNO_QmSTUV3XuZabc} 30
ptm{5m0l_chunNO_5mSTUV3XuZabc} 32
ptm{5m0l_chunNO_5m0TUV3XuZabc} 34
ptm{5m0l_chunNO_5m0lUV3XuZabc} 36
ptm{5m0l_chunNO_5m0lU53XuZabc} 38
ptm{5m0l_chunNO_5m0lU53cuZabc} 40
ptm{5m0l_chunkO_5m0lU53cuZabc} 42
ptm{5m0l_chunkO_5m0l_53cuZabc} 44
ptm{5m0l_chunk5_5m0l_53cuZabc} 48
ptm{5m0l_chunk5_5m0l_53cuZabc} 48
ptm{5m0l_chunk5_5m0l_53cuZaby} 50
ptm{5m0l_chunk5_5m0l_53cuZa7y} 52
ptm{5m0l_chunk5_5m0l_53cuZ17y} 54
ptm{5m0l_chunk5_5m0l_53cur17y} 56
cargo run --release 0.07s user 0.01s system 98% cpu 0.073 total
```

Original writeup (https://gist.github.com/zommiommy/507abb7be594e213bd114146634390a0).