haxelion's den

nothing to see here






Enter The Matrix

This year I’m not elligible to participate in the Cyber Security Challenge Belgium as I’m not a student anymore so instead I’ve decided to contribute to the competition by writting a few challenges. Enter The Matrix was my contribution to the mobile security category.

I’m a bit disapointed no one managed to solve it … I guess no one was the one. I might have misjudged the difficulty. However it was not that hard, I swear. One java class and 7 KB of ARM code, that’s all.


The Java part

The java code is very simple, as I said it’s just one class: Terminal. The following are all java disassembly of classes.dex using jadx.

In the constructor we have one simple android message handler which, each time it’s called, displays a new character from this.text to this.terminal_text. Twelve iterations after the whole text has finished displaying, it closes the app.

this.terminalUpdater = new Handler() {
    public void handleMessage(Message msg) {
        if (Terminal.this.counter < Terminal.this.text.length()) {
                Terminal.this.text.substring(Terminal.this.counter, Terminal.this.counter + 1)
        } else if (Terminal.this.counter > Terminal.this.text.length() + 10) {
        Terminal terminal = Terminal.this;

So how is this message handler called? This is done in startPrinter by a simple timer scheduled to send an empty message every 100 milliseconds to our message handler thus progressively displaying this.text on the screen.

private void startPrinter() {
    this.text = "Wake up, Neo...\n";
    this.text += "                    \n";
    this.text += "The Matrix has you...\n";
    this.text += "     \n";
    this.text += "Follow the white rabbit.\n";
    this.text += "     \n";
    this.text += "Knock, knock, Neo.\n";
    this.text += "          \n";
    this.text += whiteRabbit();
    this.terminal_printer = new Timer();
    this.terminal_printer.schedule(new TimerTask() {
        public void run() {
    }, 1000, 100);

Where does the the text comes from? It’s just above in the function :-) However the last part of the text comes from the whiteRabbit function. And as you probably know, you need to follow the white rabbit to see how deep the rabbit hole goes ;)

public native String whiteRabbit();

So it’s a native function.

static {

And it’s in a library called matrix.

Enter The Matrix

Okay so in the lib directory you have a series of subdirectory for different architectures and inside each of them you’ll find libmatrix.so build for each of these architectures. You’ll notice you don’t have a x86 one because I’m not a nice person and I didn’t enable the intel architecture builds. The next assembly snippets come from disassembling lib/armeabi-v7a/libmatrix.so using Hopper, you could use objdump or radare2 as a free alternative.

Okay so the full qualified name of our java method is eu.haxelion.enterthematrix.Terminal.whiteRabbit and thus the symbol name we have to look for in the library is Java_eu_haxelion_enterthematrix_Terminal_whiteRabbit:

00000f14         ldr        r1, [r0]
00000f18         ldr        r2, = 0x1cfc        ; 0xf28,0x1cfc
00000f1c         ldr        r3, [r1, #0x29c]
00000f20         add        r1, pc, r2          ; "You are not the one."
00000f24         bx         r3

Oooooooh fuuuuuuuck …

The flag is missing …

I sent the wrong binary to the organizers …

So sorry :-(

Wait there’s something missing! This is a real screenshot from the app running on my smartphone:

Enter The Matrix Badboy

Where’s the last “Wake up!” coming from? We’re not looking at the right function!

The Red Pill

So how does the OS knowns the Java_eu_haxelion_enterthematrix_Terminal_whiteRabbit is at 0xf14? Simple, because it’s in the .dynsym section of the binary!

% readelf -s -W libmatrix.so

Symbol table '.dynsym' contains 57 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 00000000     0 FUNC    GLOBAL DEFAULT  UND __cxa_finalize@LIBC (2)
     2: 00000000     0 FUNC    GLOBAL DEFAULT  UND __cxa_atexit@LIBC (2)
     3: 00000000     0 FUNC    GLOBAL DEFAULT  UND mprotect@LIBC (2)
     4: 00000f14    24 FUNC    GLOBAL DEFAULT   12 Java_eu_haxelion_enterthematrix_Terminal_whiteRabbit

The dynamic loader ld, when loading the binary, uses the .dynsym tables to resolve the external
functions. But in between loading the library in memory and resolving symbol addresses using its .dynsym table, something can happen. And this thing are the library init functions. It’s normally used to initialize global objects and C runtime stuff. And sometimes to do evil stuff, like dynamic patching of the .dynsym table. This is the function at offset 0x1410.

This function is a bit big to paste below but you should notices calls to mprotect. This is because the .dynsym section is loaded in a READ | EXECUTE page and if we want to patch the table we have to change the permission to READ | WRITE using mprotect.

Now the patching happens at 0x14c0, in between two mprotect:

000014c0         str        r0, [r7]

If we trace back were the value comes from we find these two instructions which compute the address of the function at address 0xf50:

00001448         ldr        r1, = 0xfffffaf8      ; 0x1524
00001450         add        r1, pc, r1            ; sub_f50

So Java_eu_haxelion_enterthematrix_Terminal_whiteRabbit is actually at 0xf50.

Down the Rabbit Hole

The real whiteRabbit is a long function, but there is a very repetitive pattern of code:

00000f74         ldrb       r2, [r0], #0x1
00000f78     |-> ldrb       r3, [r1], #0x1
00000f7c     |   eor        r2, r3, r2
00000f80     |   strb       r2, [r0, #-0x1]
00000f84     |   ldrb       r2, [r0]
00000f88     |   add        r0, r0, #0x1
00000f8c     |   cmp        r2, #0x0
00000f90     |-- bne        0xf78

What does this do? This function XOR two array of bytes, one in r0 and the other in r1 and the result is placed back into r0. It exit when encountering a null byte. So it’s simply deciphering strings using a one time pad.

Let’s try to decrypt the first one:

import sys

def xor(a,b):
    c = ''
    for i in range(0,min(len(a),len(b))):
        c = c + chr(ord(a[i]) ^ ord(b[i]))
    return c

% python2 decrypt_otp.py 54fa617c7a5296693667c16f3f9ba0eb2f7f7811af43c9d63c83237cfb60ce82 \

That’s interesting. So all those repetitive XOR loop are simply string deobfuscation code. The reason for obfuscating the strings was to make the real whiteRabbit function harder to find.

Now you could deobfuscate every string or notice that the interesting part is the strcmp at the address 0x12bc. If the return value is 0 we jump to the block at address 0x1330 which decipher the following:

% python2 decrypt_otp.py b62f844fe2f9a5da83df266ede59dbf5ab49652d24e9 \

And that’s the flag, finally :-)

If you’re curious what the rest of the function is doing, it’s checking if one of the Google account linked with your phone is neo@gmail.com. Yes, Neo also uses Gmail.

An alternative solution was to either patch the jump in the library or use something like frida to change to return value of the strcmp. What you would have seen then is the screen below:

Enter The Matrix Goodboy

See you next year for something even more obscure and evil. In the meantime study your ARM ;-)

Creative Commons License

This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License .