BUP CTF Powered by Knight Squad - Qualification Round

Talk To Me – Reverse Engineering Challenge Writeup | BUP CTF Powered by Knight Squad - Qualification Round

Hi! I’m the author of this challenge. This is the official write-up that explains what the binary does, where to look in the code, and why those places matter. We’ll keep it practical: quick triage in Ghidra to understand structure, then hands-on confirmation and extraction with radare2 (plus a tiny helper script).

Binary Info

  • Format / Type: ELF64 (Linux), C++
  • Arch / Bits / Endian: x86-64, 64-bit, little-endian
  • Interpreter: /lib64/ld-linux-x86-64.so.2 (dynamically linked)
  • Compiler: GCC 15.2.1 (optimized, modern toolchain)
  • NX: true
  • RELRO: partial
  • PIE: true
  • Other flags: stripped (true), PIC (true), relocs (false), rpath NONE, crypto false (no linked crypto libs)

First run behavior

When you execute the binary, it’s quiet and ask for input (no banner, no prompt). If you type anything normal e.g. NomanProdhan — it instantly replies:

nope

and exits.

Diving into Ghidra

Time to open the binary in Ghidra and look at the decompiled code. Here is the decompiled code from ghidra :


/* WARNING: Globals starting with '_' overlap smaller symbols at the same address */

undefined8 FUN_001010c0(void)

{
  undefined4 *puVar1;
  uint *puVar2;
  char cVar3;
  short sVar4;
  short sVar5;
  short sVar6;
  short sVar7;
  short sVar8;
  short sVar9;
  short sVar10;
  short sVar11;
  short sVar12;
  short sVar13;
  short sVar14;
  short sVar15;
  short sVar16;
  short sVar17;
  short sVar18;
  short sVar19;
  undefined4 uVar20;
  undefined4 uVar21;
  undefined4 uVar22;
  undefined auVar23 [16];
  undefined auVar24 [16];
  undefined auVar25 [16];
  undefined auVar26 [16];
  undefined8 uVar27;
  char *pcVar28;
  size_t sVar29;
  undefined4 *puVar30;
  uint *puVar31;
  undefined8 uVar32;
  long lVar33;
  ulong uVar34;
  undefined8 *puVar35;
  uint uVar36;
  undefined8 *__s;
  undefined8 *puVar37;
  uint *puVar38;
  uint uVar39;
  undefined *puVar40;
  undefined4 *puVar41;
  undefined *puVar42;
  long in_FS_OFFSET;
  byte bVar43;
  int iVar44;
  int iVar58;
  undefined2 uVar59;
  undefined2 uVar60;
  undefined auVar46 [16];
  undefined auVar50 [16];
  undefined auVar54 [16];
  undefined auVar57 [16];
  undefined auVar61 [16];
  undefined auVar65 [16];
  undefined auVar69 [16];
  undefined auVar72 [16];
  undefined auVar74 [16];
  int iVar75;
  undefined2 uVar76;
  undefined2 uVar77;
  int iVar78;
  undefined2 uVar79;
  int iVar80;
  int iVar81;
  uint *local_2a8;
  undefined *local_2a0;
  undefined *local_298;
  undefined *local_288;
  undefined *local_280;
  undefined *local_278;
  undefined4 *local_268;
  undefined4 *local_260;
  undefined4 *local_258;
  uint *local_248;
  uint *local_240;
  uint *local_238;
  undefined4 *local_228;
  long local_220;
  long local_218;
  undefined4 local_208 [20];
  undefined4 local_1b8;
  undefined4 uStack_1b4;
  undefined4 uStack_1b0;
  undefined4 uStack_1ac;
  uint local_1a8;
  uint uStack_1a4;
  uint uStack_1a0;
  uint uStack_19c;
  undefined local_b9 [5];
  undefined4 uStack_b4;
  undefined4 uStack_b0;
  undefined4 uStack_ac;
  undefined3 local_a8;
  undefined8 uStack_a5;
  undefined5 uStack_9d;
  undefined local_98 [16];
  undefined local_88 [16];
  undefined local_78 [16];
  undefined local_68 [16];
  undefined local_58 [16];
  undefined local_48 [16];
  undefined8 local_38;
  long local_30;
  undefined auVar45 [12];
  undefined auVar47 [16];
  undefined auVar51 [16];
  undefined auVar48 [16];
  undefined auVar52 [16];
  undefined auVar55 [16];
  undefined auVar49 [16];
  undefined auVar53 [16];
  undefined auVar56 [16];
  undefined auVar62 [16];
  undefined auVar66 [16];
  undefined auVar63 [16];
  undefined auVar67 [16];
  undefined auVar70 [16];
  undefined auVar64 [16];
  undefined auVar68 [16];
  undefined auVar71 [16];
  undefined2 uVar73;
  
  bVar43 = 0;
  local_30 = *(long *)(in_FS_OFFSET + 0x28);
  __s = (undefined8 *)(local_b9 + 1);
  local_b9._1_4_ = 0;
  uStack_b4 = 0;
  uStack_b0 = 0;
  uStack_ac = 0;
  local_a8 = 0;
  uStack_a5 = 0;
  uStack_9d = 0;
  local_98 = (undefined  [16])0x0;
  local_88 = (undefined  [16])0x0;
  local_78 = (undefined  [16])0x0;
  local_68 = (undefined  [16])0x0;
  local_58 = (undefined  [16])0x0;
  local_48 = (undefined  [16])0x0;
  pcVar28 = fgets((char *)__s,0x80,stdin);
  if (pcVar28 != (char *)0x0) {
    sVar29 = strlen((char *)__s);
    if (sVar29 != 0) {
      if ((local_b9[sVar29] == '\n') || (local_b9[sVar29] == '\r')) {
        local_b9[sVar29] = 0;
        sVar29 = sVar29 - 1;
      }
      if (sVar29 == 0x1b) {
        local_2a8 = (uint *)operator.new(0x5b);
        uVar39 = 0x5b;
        local_2a0 = (undefined *)((long)local_2a8 + 0x5b);
        *(undefined *)local_2a8 = 0;
        puVar40 = (undefined *)((long)local_2a8 + 1);
        for (lVar33 = 0x5a; lVar33 != 0; lVar33 = lVar33 + -1) {
          *puVar40 = 0;
          puVar40 = puVar40 + (ulong)bVar43 * -2 + 1;
        }
        uVar36 = 0xffffffd3;
        lVar33 = 0;
        do {
                    /* WARNING (jumptable): Read-only address (ram,0x001020f0) is written */
                    /* WARNING (jumptable): Read-only address (ram,0x00102100) is written */
                    /* WARNING (jumptable): Read-only address (ram,0x00102110) is written */
          *(char *)((long)local_2a8 + lVar33) =
               (char)uVar36 * '\x03' + (char)uVar39 ^ (&DAT_00102060)[(uint)lVar33 & 7] ^
               (&DAT_00102080)[lVar33];
          lVar33 = lVar33 + 1;
          uVar39 = uVar39 + 0x3d ^ uVar36 >> 1 & 0x7f;
          uVar36 = uVar36 + 0x79 ^ uVar39 * 2;
        } while (lVar33 != 0x5b);
        uVar34 = (ulong)*(byte *)(local_2a8 + 1);
        local_298 = local_2a0;
        if (uVar34 + 0x25 < 0x5c) {
          puVar40 = (undefined *)((long)local_2a8 + 5);
          uVar39 = *local_2a8;
          puVar41 = (undefined4 *)(puVar40 + uVar34);
          if (uVar34 == 0) {
            local_288 = (undefined *)0x0;
            local_278 = (undefined *)0x0;
          }
          else {
            local_288 = (undefined *)operator.new(uVar34);
            local_278 = local_288 + uVar34;
            puVar42 = local_288;
                    /* WARNING (jumptable): Read-only address (ram,0x001020f0) is written */
                    /* WARNING (jumptable): Read-only address (ram,0x00102100) is written */
                    /* WARNING (jumptable): Read-only address (ram,0x00102110) is written */
            for (; uVar34 != 0; uVar34 = uVar34 - 1) {
              *puVar42 = *puVar40;
              puVar40 = puVar40 + (ulong)bVar43 * -2 + 1;
              puVar42 = puVar42 + (ulong)bVar43 * -2 + 1;
            }
          }
          puVar42 = local_278;
          puVar40 = local_288;
          local_280 = local_278;
          puVar30 = (undefined4 *)operator.new(0x20);
          uVar20 = puVar41[1];
          uVar21 = puVar41[2];
          uVar22 = puVar41[3];
          puVar37 = (undefined8 *)&local_1b8;
          puVar1 = puVar30 + 8;
          *puVar30 = *puVar41;
          puVar30[1] = uVar20;
          puVar30[2] = uVar21;
          puVar30[3] = uVar22;
          auVar74 = _DAT_001020e0;
          uVar20 = puVar41[5];
          uVar21 = puVar41[6];
          uVar22 = puVar41[7];
          lVar33 = 0x20;
          puVar30[4] = puVar41[4];
          puVar30[5] = uVar20;
          puVar30[6] = uVar21;
          puVar30[7] = uVar22;
          if (uVar39 == 0) {
            uVar39 = 0xc001d00d;
          }
          for (; puVar35 = (undefined8 *)&local_1b8, lVar33 != 0; lVar33 = lVar33 + -1) {
            *puVar37 = 0;
            puVar37 = puVar37 + (ulong)bVar43 * -2 + 1;
          }
          do {
            puVar37 = puVar35 + 2;
            iVar75 = auVar74._0_4_;
            iVar78 = auVar74._4_4_;
            iVar80 = auVar74._8_4_;
            iVar81 = auVar74._12_4_;
            auVar64._0_12_ = auVar74._0_12_;
            auVar64._12_2_ = auVar74._6_2_;
            auVar64._14_2_ = (short)((uint)(iVar78 + 4) >> 0x10);
            auVar63._12_4_ = auVar64._12_4_;
            auVar63._0_10_ = auVar74._0_10_;
            uVar77 = (undefined2)(iVar78 + 4);
            auVar63._10_2_ = uVar77;
            uVar73 = auVar74._4_2_;
            auVar62._10_6_ = auVar63._10_6_;
            auVar62._0_8_ = auVar74._0_8_;
            auVar62._8_2_ = uVar73;
            uVar76 = (undefined2)((uint)(iVar75 + 4) >> 0x10);
            auVar23._2_8_ = auVar62._8_8_;
            auVar23._0_2_ = uVar76;
            auVar23._10_6_ = 0;
            auVar72._0_2_ = auVar74._0_2_;
            auVar61._12_4_ = 0;
            auVar61._0_12_ = SUB1612(auVar23 << 0x30,4);
            auVar61 = auVar61 << 0x20;
            uVar59 = auVar74._8_2_;
            uVar79 = (undefined2)(iVar80 + 4);
            uVar60 = auVar74._12_2_;
            auVar68._0_12_ = auVar61._0_12_;
            auVar68._12_2_ = uVar76;
            auVar68._14_2_ = (short)((uint)(iVar80 + 4) >> 0x10);
            auVar67._12_4_ = auVar68._12_4_;
            auVar67._0_10_ = auVar61._0_10_;
            auVar67._10_2_ = auVar74._10_2_;
            auVar66._10_6_ = auVar67._10_6_;
            auVar66._0_8_ = auVar61._0_8_;
            auVar66._8_2_ = auVar74._2_2_;
            auVar24._2_8_ = auVar66._8_8_;
            auVar24._0_2_ = uVar79;
            auVar24._10_6_ = 0;
            auVar65._12_4_ = 0;
            auVar65._0_12_ = SUB1612(auVar24 << 0x30,4);
            auVar65 = auVar65 << 0x20;
            auVar74._0_4_ = iVar75 + 0x10;
            auVar74._4_4_ = iVar78 + 0x10;
            auVar74._8_4_ = iVar80 + 0x10;
            auVar74._12_4_ = iVar81 + 0x10;
            iVar44 = iVar75 + 8;
            iVar58 = iVar78 + 8;
            auVar45._0_8_ = CONCAT44(iVar58,iVar44);
            auVar45._8_4_ = iVar80 + 8;
            auVar71._0_12_ = auVar65._0_12_;
            auVar71._12_2_ = uVar79;
            auVar71._14_2_ = (short)(iVar81 + 4);
            auVar70._12_4_ = auVar71._12_4_;
            auVar70._0_10_ = auVar65._0_10_;
            auVar70._10_2_ = uVar77;
            auVar69._10_6_ = auVar70._10_6_;
            auVar69._0_8_ = auVar65._0_8_;
            auVar69._8_2_ = (short)(iVar75 + 4);
            auVar72._8_8_ = auVar69._8_8_;
            auVar72._6_2_ = uVar60;
            auVar72._4_2_ = uVar59;
            auVar72._2_2_ = uVar73;
            auVar49._12_2_ = (short)((uint)iVar58 >> 0x10);
            auVar49._0_12_ = auVar45;
            auVar49._14_2_ = (short)((uint)(iVar78 + 0xc) >> 0x10);
            auVar48._12_4_ = auVar49._12_4_;
            auVar48._0_10_ = auVar45._0_10_;
            uVar60 = (undefined2)(iVar78 + 0xc);
            auVar48._10_2_ = uVar60;
            auVar47._10_6_ = auVar48._10_6_;
            auVar47._8_2_ = (short)iVar58;
            auVar47._0_8_ = auVar45._0_8_;
            uVar59 = (undefined2)((uint)(iVar75 + 0xc) >> 0x10);
            auVar25._2_8_ = auVar47._8_8_;
            auVar25._0_2_ = uVar59;
            auVar25._10_6_ = 0;
            auVar57._0_2_ = (undefined2)iVar44;
            auVar46._12_4_ = 0;
            auVar46._0_12_ = SUB1612(auVar25 << 0x30,4);
            auVar46 = auVar46 << 0x20;
            auVar72 = auVar72 & _DAT_00102120;
            uVar73 = (undefined2)(iVar80 + 0xc);
            auVar53._0_12_ = auVar46._0_12_;
            auVar53._12_2_ = uVar59;
            auVar53._14_2_ = (short)((uint)(iVar80 + 0xc) >> 0x10);
            auVar52._12_4_ = auVar53._12_4_;
            auVar52._0_10_ = auVar46._0_10_;
            auVar52._10_2_ = (short)((uint)auVar45._8_4_ >> 0x10);
            auVar51._10_6_ = auVar52._10_6_;
            auVar51._0_8_ = auVar46._0_8_;
            auVar51._8_2_ = (short)((uint)iVar44 >> 0x10);
            auVar26._2_8_ = auVar51._8_8_;
            auVar26._0_2_ = uVar73;
            auVar26._10_6_ = 0;
            auVar50._12_4_ = 0;
            auVar50._0_12_ = SUB1612(auVar26 << 0x30,4);
            auVar50 = auVar50 << 0x20;
            auVar56._0_12_ = auVar50._0_12_;
            auVar56._12_2_ = uVar73;
            auVar56._14_2_ = (short)(iVar81 + 0xc);
            auVar55._12_4_ = auVar56._12_4_;
            auVar55._0_10_ = auVar50._0_10_;
            auVar55._10_2_ = uVar60;
            auVar54._10_6_ = auVar55._10_6_;
            auVar54._0_8_ = auVar50._0_8_;
            auVar54._8_2_ = (short)(iVar75 + 0xc);
            auVar57._8_8_ = auVar54._8_8_;
            auVar57._6_2_ = (short)(iVar81 + 8);
            auVar57._4_2_ = (short)auVar45._8_4_;
            auVar57._2_2_ = (short)iVar58;
            auVar57 = auVar57 & _DAT_00102120;
            sVar4 = auVar72._0_2_;
            sVar5 = auVar72._2_2_;
            sVar6 = auVar72._4_2_;
            sVar7 = auVar72._6_2_;
            sVar8 = auVar72._8_2_;
            sVar9 = auVar72._10_2_;
            sVar10 = auVar72._12_2_;
            sVar11 = auVar72._14_2_;
            sVar12 = auVar57._0_2_;
            sVar13 = auVar57._2_2_;
            sVar14 = auVar57._4_2_;
            sVar15 = auVar57._6_2_;
            sVar16 = auVar57._8_2_;
            sVar17 = auVar57._10_2_;
            sVar18 = auVar57._12_2_;
            sVar19 = auVar57._14_2_;
            *(char *)puVar35 = (0 < sVar4) * (sVar4 < 0xff) * auVar72[0] - (0xff < sVar4);
            *(char *)((long)puVar35 + 1) =
                 (0 < sVar5) * (sVar5 < 0xff) * auVar72[2] - (0xff < sVar5);
            *(char *)((long)puVar35 + 2) =
                 (0 < sVar6) * (sVar6 < 0xff) * auVar72[4] - (0xff < sVar6);
            *(char *)((long)puVar35 + 3) =
                 (0 < sVar7) * (sVar7 < 0xff) * auVar72[6] - (0xff < sVar7);
            *(char *)((long)puVar35 + 4) =
                 (0 < sVar8) * (sVar8 < 0xff) * auVar72[8] - (0xff < sVar8);
            *(char *)((long)puVar35 + 5) =
                 (0 < sVar9) * (sVar9 < 0xff) * auVar72[10] - (0xff < sVar9);
            *(char *)((long)puVar35 + 6) =
                 (0 < sVar10) * (sVar10 < 0xff) * auVar72[12] - (0xff < sVar10);
            *(char *)((long)puVar35 + 7) =
                 (0 < sVar11) * (sVar11 < 0xff) * auVar72[14] - (0xff < sVar11);
            *(char *)(puVar35 + 1) = (0 < sVar12) * (sVar12 < 0xff) * auVar57[0] - (0xff < sVar12);
            *(char *)((long)puVar35 + 9) =
                 (0 < sVar13) * (sVar13 < 0xff) * auVar57[2] - (0xff < sVar13);
            *(char *)((long)puVar35 + 10) =
                 (0 < sVar14) * (sVar14 < 0xff) * auVar57[4] - (0xff < sVar14);
            *(char *)((long)puVar35 + 0xb) =
                 (0 < sVar15) * (sVar15 < 0xff) * auVar57[6] - (0xff < sVar15);
            *(char *)((long)puVar35 + 0xc) =
                 (0 < sVar16) * (sVar16 < 0xff) * auVar57[8] - (0xff < sVar16);
            *(char *)((long)puVar35 + 0xd) =
                 (0 < sVar17) * (sVar17 < 0xff) * auVar57[10] - (0xff < sVar17);
            *(char *)((long)puVar35 + 0xe) =
                 (0 < sVar18) * (sVar18 < 0xff) * auVar57[12] - (0xff < sVar18);
            *(char *)((long)puVar35 + 0xf) =
                 (0 < sVar19) * (sVar19 < 0xff) * auVar57[14] - (0xff < sVar19);
            puVar35 = puVar37;
          } while (puVar37 != __s);
          puVar37 = (undefined8 *)local_b9;
          do {
            puVar35 = (undefined8 *)((long)puVar37 + -1);
            uVar39 = uVar39 << 0xd ^ uVar39;
            uVar39 = uVar39 >> 0x11 ^ uVar39;
            uVar39 = uVar39 << 5 ^ uVar39;
            uVar34 = (ulong)uVar39 % (ulong)(uint)((0x100 - (int)local_b9) + (int)puVar37);
            cVar3 = *(char *)puVar37;
            *(char *)puVar37 = *(char *)((long)&local_1b8 + uVar34);
            *(char *)((long)&local_1b8 + uVar34) = cVar3;
            puVar37 = puVar35;
          } while ((undefined8 *)&local_1b8 != puVar35);
          local_268 = puVar30;
          local_260 = puVar1;
          local_258 = puVar1;
          puVar31 = (uint *)operator.new(0x20);
          puVar2 = puVar31 + 8;
          *puVar31 = local_1b8 ^ 0x5a5a5a5a;
          puVar31[1] = uStack_1b4 ^ 0x5a5a5a5a;
          puVar31[2] = uStack_1b0 ^ 0x5a5a5a5a;
          puVar31[3] = uStack_1ac ^ 0x5a5a5a5a;
          puVar31[4] = local_1a8 ^ 0x5a5a5a5a;
          puVar31[5] = uStack_1a4 ^ 0x5a5a5a5a;
          puVar31[6] = uStack_1a0 ^ 0x5a5a5a5a;
          puVar31[7] = uStack_19c ^ 0x5a5a5a5a;
          local_248 = puVar31;
          local_240 = puVar2;
          local_238 = puVar2;
          local_228 = (undefined4 *)operator.new(0x1b);
          uVar27 = uStack_a5;
          uVar34 = 0;
          local_220 = (long)local_228 + 0x1b;
          *local_228 = local_b9._1_4_;
          local_228[1] = uStack_b4;
          local_228[2] = uStack_b0;
          local_228[3] = uStack_ac;
          uVar32 = CONCAT35(local_a8,CONCAT41(uStack_ac,uStack_b0._3_1_));
          puVar41 = local_208;
          for (lVar33 = 0xe; lVar33 != 0; lVar33 = lVar33 + -1) {
            *puVar41 = 0;
            puVar41 = puVar41 + (ulong)bVar43 * -2 + 1;
          }
          *(undefined8 *)((long)local_228 + 0xb) = uVar32;
          *(undefined8 *)((long)local_228 + 0x13) = uVar27;
          for (; local_218 = local_220, uVar34 < (ulong)((long)puVar42 - (long)puVar40);
              uVar34 = (ulong)((int)uVar34 + 1)) {
            if ((byte)puVar40[uVar34] < 0x10) {
                    /* WARNING: Could not recover jumptable at 0x00101515. Too many branches */
                    /* WARNING: Treating indirect jump as call */
              uVar32 = (*(code *)(&DAT_00102020 +
                                 *(int *)(&DAT_00102020 + (ulong)(byte)puVar40[uVar34] * 4)))();
              return uVar32;
            }
          }
          lVar33 = 0;
          do {
            if ((byte)((char)lVar33 * '\r' + 0xa6U ^ *(byte *)((long)puVar31 + lVar33) ^ 0x3c) !=
                *(byte *)((long)puVar30 + lVar33)) {
              pcVar28 = "nope";
              goto LAB_00101769;
            }
            lVar33 = lVar33 + 1;
          } while (lVar33 != 0x20);
          pcVar28 = "congrats!";
LAB_00101769:
          puts(pcVar28);
          do {
            puVar37 = (undefined8 *)((long)__s + 1);
            *(char *)__s = '\0';
            __s = puVar37;
          } while (puVar37 != &local_38);
          do {
            puVar38 = (uint *)((long)puVar31 + 1);
            *(undefined *)puVar31 = 0;
            puVar31 = puVar38;
          } while (puVar38 != puVar2);
          do {
            puVar41 = (undefined4 *)((long)puVar30 + 1);
            *(undefined *)puVar30 = 0;
            puVar30 = puVar41;
          } while (puVar41 != puVar1);
          FUN_00101950(&local_228);
          FUN_00101950(&local_248);
          FUN_00101950(&local_268);
          FUN_00101950(&local_288);
        }
        else {
          puts("nope");
        }
        FUN_00101950(&local_2a8);
        goto LAB_00101523;
      }
    }
    puts("nope");
  }
LAB_00101523:
  if (local_30 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}


void FUN_00101950(void **param_1)

{
  void *pvVar1;
  
  pvVar1 = *param_1;
  if (pvVar1 != (void *)0x0) {
    operator.delete(pvVar1,(long)param_1[2] - (long)pvVar1);
    return;
  }
  return;
}


From the decompiled entry function FUN_001010c0 (our “main” here), we can see it reads a line, trims \n/\r, and immediately enforces an exact-length gate:

pcVar28 = fgets((char *)__s, 0x80, stdin);
if (pcVar28 != (char *)0x0) {
  sVar29 = strlen((char *)__s);
  if (sVar29 != 0) {
    if ((local_b9[sVar29] == '\n') || (local_b9[sVar29] == '\r')) {
      local_b9[sVar29] = 0;
      sVar29 = sVar29 - 1;
    }
    if (sVar29 == 0x1b) {            // 0x1b == 27
      /* lots of task… */
    }
  }
  puts("nope");                      // any length ≠ 27 -> nope
}

So only 27-byte inputs can pass the first gate. That explains the instant “nope” on casual tests and fixes our search space.

Plan : Dump & Emulate The decompiled routine does a lot—SSE shuffles, an S-box, a tiny VM, and a masked compare. We don’t need to re-derive all of that. Instead, we’ll grab the decrypted blob at runtime and then emulate the tiny VM.

Dynamic Analysis

Open the binary in radare2 (debug mode):

r2 -d talk_to_me.ks

Run analysis and list functions:

aaa            # analyze all (functions/refs/BBs)
afl            # show function list

You’ll notice radare2 names the entry routine main even though Ghidra didn’t. Seek to it and disassemble:

s main         # jump to main
pdf            # print disassembly of the function

There’s a lot of operations in main, so we’ll follow a simple path. Since wrong-length inputs and wrong 27-byte inputs both end in nope, let’s find where that string lives and who calls it.

izz~nope

You’ll see something like:

44  0x0000200e 0x55f08bbdc00e 4   5    .rodata            ascii   nope

Find all cross-references :

axt 0x55f08bbdc00e

This returns a couple of call sites inside main as following :

main 0x55f08bbdb517 [DATA:r--] lea rdi, str.nope
main 0x55f08bbdb762 [DATA:r--] lea rdi, str.nope
main 0x55f08bbdb7ef [DATA:r--] lea rdi, str.nope

Set breakpoints at those xref addresses

db 0x55f08bbdb517
db 0x55f08bbdb762
db 0x55f08bbdb7ef

Now when you run with a bad input, you’ll break right before the program prints nope. From there you can scroll up a bit to see the surrounding logic and identify the final masked-compare loop and its buffers.

After setting breakpoints, run the program and feed any 27 characters, then press Enter. It’ll stop at one of the nope call:

dc
# type 27 chars (e.g., nnnnnnnnnnnnnnnnnnnnnnnnn) and hit Enter

If your input length is exactly 27, you should hit a breakpoint right before puts("nope"). From there, you can scroll a bit upwards in the disassembly to see the surrounding logic that leads into the final masked-compare loop and its two buffers.

When the breakpoint hit, I flipped to the disassembly and immediately noticed the VM dispatch: a bounds check, an opcode fetch, then an indirect jump through a switch table in .rodata. That confirms there’s a tiny interpreter chewing on a decrypted blob before the final compare. Here’s the slice we care about :

┌───────> 0x55f08bbdb200      4889cf         mov rdi, rcx
│ ╎│╎││││   0x55f08bbdb203      8d0452         lea eax, [rdx + rdx*2]
│ ╎│╎││││   0x55f08bbdb206      83e707         and edi, 7
│ ╎│╎││││   0x55f08bbdb209      01f0           add eax, esi
│ ╎│╎││││   0x55f08bbdb20b      83c63d         add esi, 0x3d           ; 61
│ ╎│╎││││   0x55f08bbdb20e      4132043a       xor al, byte [r10 + rdi]
│ ╎│╎││││   0x55f08bbdb212      89d7           mov edi, edx
│ ╎│╎││││   0x55f08bbdb214      41320408       xor al, byte [r8 + rcx]
│ ╎│╎││││   0x55f08bbdb218      83c279         add edx, 0x79           ; 121
│ ╎│╎││││   0x55f08bbdb21b      40d0ef         shr dil, 1
│ ╎│╎││││   0x55f08bbdb21e      41880409       mov byte [r9 + rcx], al
│ ╎│╎││││   0x55f08bbdb222      4883c101       add rcx, 1
│ ╎│╎││││   0x55f08bbdb226      31fe           xor esi, edi
│ ╎│╎││││   0x55f08bbdb228      8d3c36         lea edi, [rsi + rsi]
│ ╎│╎││││   0x55f08bbdb22b      31fa           xor edx, edi
│ ╎│╎││││   0x55f08bbdb22d      4883f95b       cmp rcx, 0x5b           ; '[' ; 91
│ └───────< 0x55f08bbdb231      75cd           jne 0x55f08bbdb200
│  │╎││││   0x55f08bbdb233      450fb66104     movzx r12d, byte [r9 + 4]
│  │╎││││   0x55f08bbdb238      498d442425     lea rax, [r12 + 0x25]
│  │╎││││   0x55f08bbdb23d      4883f85b       cmp rax, 0x5b           ; '[' ; 91
│ ┌───────< 0x55f08bbdb241      0f87a8050000   ja 0x55f08bbdb7ef
│ ││╎││││   0x55f08bbdb247      498d7105       lea rsi, [r9 + 5]
│ ││╎││││   0x55f08bbdb24b      458b01         mov r8d, dword [r9]
│ ││╎││││   0x55f08bbdb24e      4a8d2c26       lea rbp, [rsi + r12]
│ ││╎││││   0x55f08bbdb252      4d85e4         test r12, r12
│ ────────< 0x55f08bbdb255      0f84ca050000   je 0x55f08bbdb825
│ ││╎││││   0x55f08bbdb25b      4c89e7         mov rdi, r12
│ ││╎││││   0x55f08bbdb25e      44890424       mov dword [rsp], r8d
│ ││╎││││   0x55f08bbdb262      4889742408     mov qword [var_8h], rsi
│ ││╎││││   0x55f08bbdb267      e8d4fdffff     call sym operator new(unsigned long) ; sym.imp.operator_new_unsigned_long_
│ ││╎││││                                                              ; operator new(unsigned long)
│ ││╎││││   0x55f08bbdb26c      488b742408     mov rsi, qword [var_8h]
│ ││╎││││   0x55f08bbdb271      4c89e1         mov rcx, r12
│ ││╎││││   0x55f08bbdb274      448b0424       mov r8d, dword [rsp]
│ ││╎││││   0x55f08bbdb278      4889c7         mov rdi, rax
│ ││╎││││   0x55f08bbdb27b      4e8d1c20       lea r11, [rax + r12]
│ ││╎││││   0x55f08bbdb27f      4889442440     mov qword [var_40h], rax
│ ││╎││││   0x55f08bbdb284      4989c2         mov r10, rax
│ ││╎││││   0x55f08bbdb287      f3a4           rep movsb byte [rdi], byte [rsi]
│ ││╎││││   0x55f08bbdb289      4c895c2450     mov qword [var_50h], r11
│ ││╎││││   ; CODE XREF from main @ 0x55f08bbdb837(x)
│ ────────> 0x55f08bbdb28e      bf20000000     mov edi, 0x20           ; 32
│ ││╎││││   0x55f08bbdb293      4c89542410     mov qword [var_10h], r10
│ ││╎││││   0x55f08bbdb298      4489442408     mov dword [var_8h], r8d
│ ││╎││││   0x55f08bbdb29d      4c895c2448     mov qword [var_48h], r11
│ ││╎││││   0x55f08bbdb2a2      4c891c24       mov qword [rsp], r11
│ ││╎││││   0x55f08bbdb2a6      e895fdffff     call sym operator new(unsigned long) ; sym.imp.operator_new_unsigned_long_
│ ││╎││││                                                              ; operator new(unsigned long)
│ ││╎││││   0x55f08bbdb2ab      f30f6f4500     movdqu xmm0, xmmword [rbp]
│ ││╎││││   0x55f08bbdb2b0      448b442408     mov r8d, dword [var_8h]
│ ││╎││││   0x55f08bbdb2b5      488dbc2410..   lea rdi, [var_110h]
│ ││╎││││   0x55f08bbdb2bd      4c8d4820       lea r9, [rax + 0x20]
│ ││╎││││   0x55f08bbdb2c1      4889442460     mov qword [var_60h], rax
│ ││╎││││   0x55f08bbdb2c6      4889c6         mov rsi, rax
│ ││╎││││   0x55f08bbdb2c9      4c8b1c24       mov r11, qword [rsp]
│ ││╎││││   0x55f08bbdb2cd      0f1100         movups xmmword [rax], xmm0
│ ││╎││││   0x55f08bbdb2d0      f30f6f4510     movdqu xmm0, xmmword [var_120h]
│ ││╎││││   0x55f08bbdb2d5      4585c0         test r8d, r8d
│ ││╎││││   0x55f08bbdb2d8      b920000000     mov ecx, 0x20           ; 32
│ ││╎││││   0x55f08bbdb2dd      4c894c2470     mov qword [var_70h], r9
│ ││╎││││   0x55f08bbdb2e2      488dac2410..   lea rbp, [var_110h]
│ ││╎││││   0x55f08bbdb2ea      660f6f15ee..   movdqa xmm2, xmmword [0x55f08bbdc0e0] ; [0x55f08bbdc0e0:16]=-1
│ ││╎││││   0x55f08bbdb2f2      0f114010       movups xmmword [rax + 0x10], xmm0
│ ││╎││││   0x55f08bbdb2f6      b80dd001c0     mov eax, 0xc001d00d
│ ││╎││││   0x55f08bbdb2fb      66440f6f0d..   movdqa xmm9, xmmword [0x55f08bbdc0f0] ; [0x55f08bbdc0f0:16]=-1
│ ││╎││││   0x55f08bbdb304      66440f6f05..   movdqa xmm8, xmmword [str._b_b_b_b_f_f_f_f] ; [0x55f08bbdc100:16]=-1
│ ││╎││││   0x55f08bbdb30d      4c894c2468     mov qword [var_68h], r9
│ ││╎││││   0x55f08bbdb312      440f44c0       cmove r8d, eax
│ ││╎││││   0x55f08bbdb316      660f6f3df2..   movdqa xmm7, xmmword [0x55f08bbdc110] ; [0x55f08bbdc110:16]=-1
│ ││╎││││   0x55f08bbdb31e      31c0           xor eax, eax
│ ││╎││││   0x55f08bbdb320      f348ab         rep stosq qword [rdi], rax
│ ││╎││││   0x55f08bbdb323      660f6f2df5..   movdqa xmm5, xmmword [0x55f08bbdc120] ; [0x55f08bbdc120:16]=-1
│ ││╎││││   0x55f08bbdb32b      4c8b542410     mov r10, qword [var_10h]
│ ││╎││││   0x55f08bbdb330      4889e8         mov rax, rbp
│ ││╎││││   0x55f08bbdb333      660f6f35f5..   movdqa xmm6, xmmword [0x55f08bbdc130] ; [0x55f08bbdc130:16]=-1
│ ││╎││││   0x55f08bbdb33b      0f1f440000     nop dword [rax + rax]
│ ────────> 0x55f08bbdb340      660f6fda       movdqa xmm3, xmm2
│ ││╎││││   0x55f08bbdb344      660f6fca       movdqa xmm1, xmm2
│ ││╎││││   0x55f08bbdb348      660f6fc2       movdqa xmm0, xmm2
│ ││╎││││   0x55f08bbdb34c      4883c010       add rax, 0x10           ; 16
│ ││╎││││   0x55f08bbdb350      66410ffed9     paddd xmm3, xmm9
│ ││╎││││   0x55f08bbdb355      660f6fe2       movdqa xmm4, xmm2
│ ││╎││││   0x55f08bbdb359      660f61cb       punpcklwd xmm1, xmm3
│ ││╎││││   0x55f08bbdb35d      660f69c3       punpckhwd xmm0, xmm3
│ ││╎││││   0x55f08bbdb361      660ffee7       paddd xmm4, xmm7
│ ││╎││││   0x55f08bbdb365      660f6fd9       movdqa xmm3, xmm1
│ ││╎││││   0x55f08bbdb369      660f61c8       punpcklwd xmm1, xmm0
│ ││╎││││   0x55f08bbdb36d      660f69d8       punpckhwd xmm3, xmm0
│ ││╎││││   0x55f08bbdb371      660f6fc2       movdqa xmm0, xmm2
│ ││╎││││   0x55f08bbdb375      660ffed6       paddd xmm2, xmm6
│ ││╎││││   0x55f08bbdb379      66410ffec0     paddd xmm0, xmm8
│ ││╎││││   0x55f08bbdb37e      660f61cb       punpcklwd xmm1, xmm3
│ ││╎││││   0x55f08bbdb382      660f6fd8       movdqa xmm3, xmm0
│ ││╎││││   0x55f08bbdb386      660f61c4       punpcklwd xmm0, xmm4
│ ││╎││││   0x55f08bbdb38a      660fdbcd       pand xmm1, xmm5
│ ││╎││││   0x55f08bbdb38e      660f69dc       punpckhwd xmm3, xmm4
│ ││╎││││   0x55f08bbdb392      660f6fe0       movdqa xmm4, xmm0
│ ││╎││││   0x55f08bbdb396      660f69e3       punpckhwd xmm4, xmm3
│ ││╎││││   0x55f08bbdb39a      660f61c3       punpcklwd xmm0, xmm3
│ ││╎││││   0x55f08bbdb39e      660f61c4       punpcklwd xmm0, xmm4
│ ││╎││││   0x55f08bbdb3a2      660fdbc5       pand xmm0, xmm5
│ ││╎││││   0x55f08bbdb3a6      660f67c8       packuswb xmm1, xmm0
│ ││╎││││   0x55f08bbdb3aa      0f2948f0       movaps xmmword [rax - 0x10], xmm1
│ ││╎││││   0x55f08bbdb3ae      4839d8         cmp rax, rbx
│ ────────< 0x55f08bbdb3b1      758d           jne 0x55f08bbdb340
│ ││╎││││   0x55f08bbdb3b3      488d8dff00..   lea rcx, [rbp + 0xff]
│ ││╎││││   0x55f08bbdb3ba      bf00010000     mov edi, 0x100          ; 256
│ ││╎││││   0x55f08bbdb3bf      29cf           sub edi, ecx
│ ││╎││││   0x55f08bbdb3c1      0f1f4000       nop dword [rax]
│ ││╎││││   0x55f08bbdb3c5      66662e0f1f..   nop word cs:[rax + rax]
│ ────────> 0x55f08bbdb3d0      4489c2         mov edx, r8d
│ ││╎││││   0x55f08bbdb3d3      448d240f       lea r12d, [rdi + rcx]
│ ││╎││││   0x55f08bbdb3d7      4883e901       sub rcx, 1
│ ││╎││││   0x55f08bbdb3db      c1e20d         shl edx, 0xd
│ ││╎││││   0x55f08bbdb3de      4431c2         xor edx, r8d
│ ││╎││││   0x55f08bbdb3e1      89d0           mov eax, edx
│ ││╎││││   0x55f08bbdb3e3      c1e811         shr eax, 0x11
│ ││╎││││   0x55f08bbdb3e6      31d0           xor eax, edx
│ ││╎││││   0x55f08bbdb3e8      31d2           xor edx, edx
│ ││╎││││   0x55f08bbdb3ea      4189c0         mov r8d, eax
│ ││╎││││   0x55f08bbdb3ed      41c1e005       shl r8d, 5
│ ││╎││││   0x55f08bbdb3f1      4131c0         xor r8d, eax
│ ││╎││││   0x55f08bbdb3f4      4489c0         mov eax, r8d
│ ││╎││││   0x55f08bbdb3f7      41f7f4         div r12d
│ ││╎││││   0x55f08bbdb3fa      0fb64101       movzx eax, byte [rcx + 1]
│ ││╎││││   0x55f08bbdb3fe      440fb6a414..   movzx r12d, byte [rsp + rdx + 0x110]
│ ││╎││││   0x55f08bbdb407      44886101       mov byte [rcx + 1], r12b
│ ││╎││││   0x55f08bbdb40b      8884141001..   mov byte [rsp + rdx + 0x110], al
│ ││╎││││   0x55f08bbdb412      4839cd         cmp rbp, rcx
│ ────────< 0x55f08bbdb415      75b9           jne 0x55f08bbdb3d0
│ ││╎││││   0x55f08bbdb417      bf20000000     mov edi, 0x20           ; 32
│ ││╎││││   0x55f08bbdb41c      4c89542418     mov qword [var_18h], r10
│ ││╎││││   0x55f08bbdb421      4531ff         xor r15d, r15d
│ ││╎││││   0x55f08bbdb424      4c895c2410     mov qword [var_10h], r11
│ ││╎││││   0x55f08bbdb429      4c894c2408     mov qword [var_8h], r9
│ ││╎││││   0x55f08bbdb42e      48893424       mov qword [rsp], rsi
│ ││╎││││   0x55f08bbdb432      e809fcffff     call sym operator new(unsigned long) ; sym.imp.operator_new_unsigned_long_
│ ││╎││││                                                              ; operator new(unsigned long)
│ ││╎││││   0x55f08bbdb437      660f6f0501..   movdqa xmm0, xmmword [str.ZZZZZZZZZZZZZZZZ] ; [0x55f08bbdc140:16]=-1 ; "ZZZZZZZZZZZZZZZZ\x01\x1b\x03;,"
│ ││╎││││   0x55f08bbdb43f      bf1b000000     mov edi, 0x1b           ; r15
│ ││╎││││   0x55f08bbdb444      660f6f8c24..   movdqa xmm1, xmmword [var_110h]
│ ││╎││││   0x55f08bbdb44d      4c8d6020       lea r12, [rax + 0x20]
│ ││╎││││   0x55f08bbdb451      4889842480..   mov qword [var_80h], rax
│ ││╎││││   0x55f08bbdb459      4889c5         mov rbp, rax
│ ││╎││││   0x55f08bbdb45c      660fefc8       pxor xmm1, xmm0
│ ││╎││││   0x55f08bbdb460      4c89a42490..   mov qword [var_90h], r12
│ ││╎││││   0x55f08bbdb468      660fef8424..   pxor xmm0, xmmword [var_120h]
│ ││╎││││   0x55f08bbdb471      0f1108         movups xmmword [rax], xmm1
│ ││╎││││   0x55f08bbdb474      0f114010       movups xmmword [rax + 0x10], xmm0
│ ││╎││││   0x55f08bbdb478      4c89a42488..   mov qword [var_88h], r12
│ ││╎││││   0x55f08bbdb480      e8bbfbffff     call sym operator new(unsigned long) ; sym.imp.operator_new_unsigned_long_
│ ││╎││││                                                              ; operator new(unsigned long)
│ ││╎││││   0x55f08bbdb485      4c8b5c2410     mov r11, qword [var_10h]
│ ││╎││││   0x55f08bbdb48a      488b3424       mov rsi, qword [rsp]
│ ││╎││││   0x55f08bbdb48e      31d2           xor edx, edx
│ ││╎││││   0x55f08bbdb490      4989c0         mov r8, rax
│ ││╎││││   0x55f08bbdb493      4c8b542418     mov r10, qword [var_18h]
│ ││╎││││   0x55f08bbdb498      b90e000000     mov ecx, 0xe            ; 14
│ ││╎││││   0x55f08bbdb49d      660f6f8424..   movdqa xmm0, xmmword [var_210h]
│ ││╎││││   0x55f08bbdb4a6      48898424a0..   mov qword [var_a0h], rax
│ ││╎││││   0x55f08bbdb4ae      488d401b       lea rax, [rax + 0x1b]
│ ││╎││││   0x55f08bbdb4b2      4c8b4c2408     mov r9, qword [var_8h]
│ ││╎││││   0x55f08bbdb4b7      488dbc24c0..   lea rdi, [var_c0h]
│ ││╎││││   0x55f08bbdb4bf      410f1100       movups xmmword [r8], xmm0
│ ││╎││││   0x55f08bbdb4c3      4d29d3         sub r11, r10
│ ││╎││││   0x55f08bbdb4c6      f30f6f8424..   movdqu xmm0, xmmword [var_21bh]
│ ││╎││││   0x55f08bbdb4cf      48898424b0..   mov qword [var_b0h], rax
│ ││╎││││   0x55f08bbdb4d7      48898424a8..   mov qword [var_a8h], rax
│ ││╎││││   0x55f08bbdb4df      31c0           xor eax, eax
│ ││╎││││   0x55f08bbdb4e1      f3ab           rep stosd dword [rdi], eax
│ ││╎││││   0x55f08bbdb4e3      410f11400b     movups xmmword [r8 + 0xb], xmm0
│ ││╎││││   0x55f08bbdb4e8      488d3d310b..   lea rdi, [0x55f08bbdc020]
│ ││╎││││   0x55f08bbdb4ef      90             nop
│ ││╎││││   ; CODE XREF from main @ 0x55f08bbdb814(x)
│ ────────> 0x55f08bbdb4f0      89d0           mov eax, edx
│ ││╎││││   0x55f08bbdb4f2      4c39d8         cmp rax, r11
│ ────────< 0x55f08bbdb4f5      0f8322020000   jae 0x55f08bbdb71d
│ ││╎││││   0x55f08bbdb4fb      8d4a01         lea ecx, [rdx + 1]
│ ││╎││││   0x55f08bbdb4fe      41803c020f     cmp byte [r10 + rax], 0xf
│ ────────< 0x55f08bbdb503      0f8709030000   ja case.default.0x55f08bbdb515
│ ││╎││││   0x55f08bbdb509      410fb60402     movzx eax, byte [r10 + rax]
│ ││╎││││   0x55f08bbdb50e      48630487       movsxd rax, dword [rdi + rax*4]
│ ││╎││││   0x55f08bbdb512      4801f8         add rax, rdi
│ ││╎││││   ;-- switch:
│ ││╎││││   0x55f08bbdb515      ffe0           jmp rax                 ; switch table (16 cases) at 0x55f08bbdc020
│ │└───└──> 0x55f08bbdb517 b    488d3df00a..   lea rdi, str.nope       ; 0x55f08bbdc00e ; "nope"
│ │ ╎││ │   0x55f08bbdb51e      e84dfbffff     call sym.imp.puts       ; int puts(const char *s)
│ │ ╎││ │   ; CODE XREF from main @ 0x55f08bbdb805(x)
│ │ ╎││┌└─> 0x55f08bbdb523      488b842498..   mov rax, qword [var_298h]
│ │ ╎││╎    0x55f08bbdb52b      64482b0425..   sub rax, qword fs:[0x28]
│ │ ╎││╎┌─< 0x55f08bbdb534      0f8502030000   jne 0x55f08bbdb83c
│ │ ╎││╎│   0x55f08bbdb53a      4881c4a002..   add rsp, 0x2a0
│ │ ╎││╎│   0x55f08bbdb541      31c0           xor eax, eax
│ │ ╎││╎│   0x55f08bbdb543      5b             pop rbx
│ │ ╎││╎│   0x55f08bbdb544      5d             pop rbp
│ │ ╎││╎│   0x55f08bbdb545      415c           pop r12
│ │ ╎││╎│   0x55f08bbdb547      415e           pop r14
│ │ ╎││╎│   0x55f08bbdb549      415f           pop r15
│ │ ╎││╎│   0x55f08bbdb54b      c3             ret
│ │ ╎└└───> 0x55f08bbdb54c      c684141002..   mov byte [rsp + rdx + 0x210], 0
│ │ ╎  ╎│   0x55f08bbdb554      4889d0         mov rax, rdx
│ │ └─────< 0x55f08bbdb557      e91efcffff     jmp 0x55f08bbdb17a
..
│ ────────> 0x55f08bbdb71d      31d2           xor edx, edx
│ │   │╎│   0x55f08bbdb71f      b90d000000     mov ecx, 0xd            ; rcx
│ │  ┌────< 0x55f08bbdb724      eb28           jmp 0x55f08bbdb74e
..
│ │ ┌─────> 0x55f08bbdb740      4883c201       add rdx, 1
│ │ ╎││╎│   0x55f08bbdb744      4883fa20       cmp rdx, 0x20           ; 32
│ │┌──────< 0x55f08bbdb748      0f84cb000000   je 0x55f08bbdb819
│ ││╎││╎│   ; CODE XREF from main @ 0x55f08bbdb724(x)
│ ││╎└────> 0x55f08bbdb74e      89d0           mov eax, edx
│ ││╎ │╎│   0x55f08bbdb750      0fafc1         imul eax, ecx
│ ││╎ │╎│   0x55f08bbdb753      83e85a         sub eax, 0x5a           ; 90
│ ││╎ │╎│   0x55f08bbdb756      32441500       xor al, byte [rbp + rdx]
│ ││╎ │╎│   0x55f08bbdb75a      83f03c         xor eax, 0x3c           ; 60
│ ││╎ │╎│   0x55f08bbdb75d      3a0416         cmp al, byte [rsi + rdx]
│ ││└─────< 0x55f08bbdb760      74de           je 0x55f08bbdb740
│ ││  │╎│   ;-- rip:
│ ││  │╎│   0x55f08bbdb762 b    488d3da508..   lea rdi, str.nope       ; 0x55f08bbdc00e ; "nope"

If your address differs due to ASLR, just search for it after pdf main: look for the first movzx r12d, byte [r9 + 4] immediately following the loop that fills 0x5B bytes.

Break right before the blob is consumed

After the 0x5B-byte decode loop, the code reads a length byte from [r9+4] and then copies that many bytes as the VM bytecode. That’s the perfect place to pause and dump the whole plaintext blob.

In my run, that instruction is at:

0x55f08bbdb233    450fb66104     movzx r12d, byte [r9 + 4]

Set the breakpoint and run:

db 0x55f08bbdb233
dc

It will show nope instantly ! re-run the program and input 27-bytes and it will hit our new breakpoint.

You’ve hit the “post-decode, pre-use” breakpoint. At this point the program has already built a plaintext blob in memory and is about to consume it (VM + compare). That blob’s base pointer lives in r9. We’ll dump it so we can emulate everything.

Find the base address of the blob (in r9) with following command :

?v r9

You will get something like following :

0x562b86079730

Peek the first 0x40 bytes at r9 with the following command :

px 0x40 @ r9

You will get something like following :

- offset -      3031 3233 3435 3637 3839 3A3B 3C3D 3E3F  0123456789ABCDEF
0x562b86079730  fe0f dcba 3601 0100 0102 1b02 0003 0004  ....6...........
0x562b86079740  00a5 0e00 010b 010c 020f 02ef 0100 5509  ..............U.
0x562b86079750  001b 0100 aa09 001c 0100 1109 001d 0100  ................
0x562b86079760  2209 001e 0100 3309 001f 0040 6625 aeda  ".....3....@f%..

Read the 4-byte seed at r9

From the decompiled code, the blob base is treated as a uint and read with uVar39 = local2a8; right at the start. That is a 32-bit (4-byte) load, so the seed at the blob start is exactly 4 bytes. Immediately after, the code reads a single byte at (local2a8 + 1) (offset +4) for the VM length—confirming the layout. VM/permute step seeds a PRNG; we’ll need the same seed for emulation/unmasking

p8 4 @ r9

You will get something like followign :

fe0fdcba

Read the 1-byte VM length at r9+4 tells us how many bytes to copy as bytecode

p8 1 @ r9+4

You will get something like this :

36

The VM bytecode starting at r9+5 for vm_len (0x36) bytes**

wtf bytecode.bin 0x36 @ r9+5

You will get a file called bytecode.bin in your current directory. We won't need this to get flag. The final check uses a fixed mask over the 32-byte “expected” buffer; once you dump that buffer (goal.bin) you can unmask it and invert the S-box step with the PRNG seed, so the bytecode itself isn’t required.

Dump the 32-byte masked “goal” array.

wtf goal.bin 0x20 @ r9+5+0x36

You will get a file called goal.bin in your current directory. We will use this to get our flag.

Creating the Solver script

I didn’t hand-type the whole solver from scratch. I fed an LLM (ChatGPT) the facts I extracted with radare2, and asked it to draft a clean, beginner-friendly script:

What I provided to the LLM (from r2):

  1. The seed at r9 (p8 4 @ r9 → little-endian 0xBADC0FFE).
  2. The VM length byte at r9+4 (p8 1 @ r9+4 → 0x36 in my run).
  3. The bytecode region (starts at r9+5, length vm_len) — not required to recover the flag, but useful for referencing the constant writes.
  4. The masked 32-byte goal buffer at r9+5+vm_len (wtf goal.bin 0x20 @ r9+5+0x36).
  5. The verify loop mask formula from the disassembly (((0xA6 + 13*i) ^ 0x3C)).
  6. The S-box build (xorshift/Fisher–Yates) and the fallback seed 0xC001D00D.
  7. The tail constants written by opcode 0x09 in the bytecode (0x55, 0xAA, 0x11, 0x22, 0x33 to indices 27..31).

Here is the solver script :


#!/usr/bin/env python3
"""
Recover the 27-byte flag from the runtime dump `goal.bin`.

# What we dumped (from radare2 while hitting the r9 blob breakpoint)
- Seed (4 bytes, LE) at r9:      `p8 4 @ r9`  -> fe 0f dc ba  == 0xBADC0FFE
- VM length (1 byte) at r9+4:    `p8 1 @ r9+4` -> 0x36
- Masked goal (32 bytes) at:     `wtf goal.bin 0x20 @ r9+5+0x36`

You do NOT need the VM bytecode to recover the flag.

# Why this works (quick map to the decompiler)
- The program builds a PRNG S-box from `seed` (xorshift32 + Fisher-Yates).
  Ghidra: `if (uVar39 == 0) { uVar39 = 0xc001d00d; }` then xorshift loop + swap.
- It creates a 32-byte table from the S-box and XORs it with 0x5A.
  (Net effect for us: init_mem[i] = SBOX[i] ^ 0x5A)
- For each of the first 27 bytes, it XORs the table with (SBOX(input[i]) ^ 0xA5).
- Finally it compares the result to an “expected” 32-byte buffer, but with a mask:
    mask[i] = ((0xA6 + 13*i) & 0xFF) ^ 0x3C
  Decompiler loop:
    imul eax, 0x0d; sub 0x5a; xor [rbp+rdx]; xor 0x3c; cmp al, [rsi+rdx]

So if we:
  1) unmask the dumped goal (remove the per-index mask),
  2) subtract the 32-byte init table (SBOX[i] ^ 0x5A),
  3) invert the S-box,

…we recover the original 27 input bytes (the flag).
"""

from pathlib import Path

# ---- inputs you already dumped in r2 ----
GOAL_PATH = Path("goal.bin")     # 32 masked bytes from: wtf goal.bin 0x20 @ r9+5+0x36
SEED      = 0xBADC0FFE           # from: p8 4 @ r9 -> fe 0f dc ba (little-endian)
N_FLAG    = 27                   # required input length (0x1B)

# ---- constants lifted from the binary ----
# If seed == 0, the code replaces it with 0xC001D00D (fallback). We keep it for reference.
FALLBACK_SEED = 0xC001D00D       # seen in decomp: if (uVar39 == 0) uVar39 = 0xc001d00d

def xorshift32(x: int) -> int:
    """One step of the PRNG used in S-box generation (<<13, >>17, <<5)."""
    x ^= (x << 13) & 0xFFFFFFFF
    x ^= (x >> 17) & 0xFFFFFFFF
    x ^= (x << 5)  & 0xFFFFFFFF
    return x & 0xFFFFFFFF

def make_sbox(seed: int) -> list:
    """
    Build the 256-byte S-box via Fisher-Yates shuffling driven by xorshift32.
    Decompiler: the long loop that repeatedly computes a PRNG value, mods by
    the remaining range, and swaps bytes (classic Fisher-Yates).
    """
    s = seed if seed != 0 else FALLBACK_SEED
    a = list(range(256))
    for i in range(255, 0, -1):
        s = xorshift32(s)
        j = s % (i + 1)
        a[i], a[j] = a[j], a[i]
    return a

def invert_table(t: list) -> list:
    """Compute inverse mapping of an S-box: inv[t[i]] = i."""
    inv = [0] * 256
    for i, v in enumerate(t):
        inv[v] = i
    return inv

def unmask_goal(masked: bytes) -> bytes:
    """
    Remove the per-index compare mask seen in the final loop:
        al = ((rbp[i] ^ ((i*0x0d) - 0x5a)) ^ 0x3c) & 0xff
        cmp al, [rsi + i]
    Rearranged => goal[i] = expected[i] ^ (((0xA6 + 13*i) & 0xFF) ^ 0x3C).
    So to get expected[i], XOR with that mask again.
    """
    out = bytearray(32)
    for i, b in enumerate(masked[:32]):
        mask = ((0xA6 + 13 * i) & 0xFF) ^ 0x3C
        out[i] = b ^ mask
    return bytes(out)

def main():
    # 1) read & unmask the 32-byte “expected” array that the program compares against
    masked_goal = GOAL_PATH.read_bytes()[:32]
    expected = unmask_goal(masked_goal)

    # 2) rebuild the S-box and its inverse
    sbox = make_sbox(SEED)
    inv  = invert_table(sbox)

    # 3) rebuild the 32-byte init table (SBOX[i] ^ 0x5A), as the binary does
    init = bytes((sbox[i] ^ 0x5A) for i in range(32))

    # 4) invert the VM’s per-byte update:
    #    vm: mem[i] ^= ( SBOX(flag[i]) ^ 0xA5 )
    #    and mem starts as init[i]. After the VM, it equals expected[i].
    #    So: SBOX(flag[i]) = expected[i] ^ init[i] ^ 0xA5
    #    => flag[i] = inv[ expected[i] ^ init[i] ^ 0xA5 ]
    flag_bytes = bytearray()
    for i in range(N_FLAG):
        want = expected[i] ^ init[i] ^ 0xA5
        flag_bytes.append(inv[want])

    flag = bytes(flag_bytes)

    # print both ascii (if decodable) and hex for safety
    print("recovered (hex):", flag.hex())
    try:
        print("recovered (ascii):", flag.decode("utf-8"))
    except UnicodeDecodeError:
        print("recovered (ascii):", flag)

    # optional: sanity check on the last 5 bytes (constants in the binary from r9 dump)
    tail_expected = [0x55, 0xAA, 0x11, 0x22, 0x33]
    ok = all((init[27+i] ^ tail_expected[i]) == expected[27+i] for i in range(5))
    if not ok:
        print("[!] trailing-constant check failed (flag may still be correct)")

if __name__ == "__main__":
    main()

So that's it for this writeup. I hope you enjoyed the challenge and writeup. Happy reversing 🤟

0 people love this