While writing my previous post about CyberTruckChallenge19 I noticed that it’s possible intercept individual instructions in native code in addition to just functions. With this new knowledge I decided take on the third part of the CyberTruck challenge one more time.

This requires a bit more knowledge about x64 assembly and Intel’s Introduction to x64 Assembly was of great help to me.

Getting the hard coded key

As we remember from last time, a pointer to the hard coded key is passed to strlen. Looking at the corresponding assembly we can see that the LEA instruction (load effective address) is used to load the pointer to the key into the RDI register. If you’re not familiar with the LEA instruction here’s a good Youtube video I found that explains the basics of it.

LEA loads the pointer pointing to the key into RDI
LEA loads the pointer pointing to the key into RDI

This means that if we intercept the following instruction (MOV at 0x760) we can just pick up the key from RDI, so let’s do that.

  const SECRET_LENGTH = 32;
  var keyHook = Interceptor.attach(Module.findBaseAddress('libnative-lib.so').add(0x760), {
    onEnter: function(args) {
      console.log("Challenge3 key: " + this.context.rdi.readUtf8String(SECRET_LENGTH));
      keyHook.detach();
    }
  })

Getting the dynamic secret

The dynamic secret is generated by xor:ing the key with some data, so now we need to find the xor operation in the assembly.

The secret generation
The secret generation

Here we can see the XOR instruction at 0x7bd which xor:s the content of ECX and EDX and put the result in ECX. So like before we intercept the instruction after XOR which is on 0x7bf and pick up the result from ECX.

One small complication here is that Frida only provide access to the R*X registers and not the E*X registers. But the E registers is just the lower 4 bytes of the R registers, so we can just look at RCX instead.

  var secret = "";
  var len = 0;
  var secretHook = Interceptor.attach(Module.findBaseAddress('libnative-lib.so').add(0x7bf), {
    onEnter: function(args) {
      if (len++ < SECRET_LENGTH) {
        secret += String.fromCharCode(this.context.rcx);
      } else {
        console.log("Challenge3 secret: " + secret);
        secretHook.detach();
      }
    }
  });

With this in place we can run our script and extract both secrets quick and easy.

Challenge3 solved again
Challenge3 solved again

Caveats with attaching to instructions

I noticed that it’s not possible to use Interceptor.attach on both a function and an instruction inside that function at the same time. If you try to do this the app will crash when the instruction is executed.

It also seems like it’s not possible to attach to two instructions immediately following each other. Whenever I tried this the app crashed when the second instruction was hit. Intercepting two separate instructions with at least one other instruction in between them has worked fine for me though.

Full code is available on GitHub