Tags: react 

Rating:

Reactor - hacktivitycon 2021

  • Category: Mobile - Reversing
  • Points: 383
  • Solves: 103
  • Solved by: drw0if

Description

We built this app to protect the reactor codes

Solution

We are provided with an android apk file, installing it on an emulator we are provided with the following screen:

main_screen

If we provide a pin we can see a string popping out:

decoded_screen

This string seems not to be the flag, tho.

Since it is an android application we can unpack it with apktool:

apktool d reactor.apk

Opening the apk file with jadx-gui and looking inside the com.reactor.MainActivity we can notice that the MainActivity extends the ReactActivity class: we are dealing with a react native application. Those applications aren't compiled to native language so we can find the js code somewhere: in fact digging inside the asset folder we can spot a file called index.android.bundle full of js code!

This is a huge block with more than 30k lines so we can't read it all. Searching for the string Insert the pin we can identify our component code:

__d(function(g, r, i, a, m, e, d) {
    Object.defineProperty(e, "__esModule", {
        value: !0
    }), e.default = void 0;

    var t = r(d[0])(r(d[1])),
        n = (function(t, n) {
            if (!n && t && t.__esModule) return t;
            if (null === t || "object" != typeof t && "function" != typeof t) return {
                default: t
            };
            var l = u(n);
            if (l && l.has(t)) return l.get(t);
            var o = {},
                f = Object.defineProperty && Object.getOwnPropertyDescriptor;
            for (var c in t)
                if ("default" !== c && Object.prototype.hasOwnProperty.call(t, c)) {
                    var p = f ? Object.getOwnPropertyDescriptor(t, c) : null;
                    p && (p.get || p.set) ? Object.defineProperty(o, c, p) : o[c] = t[c]
                } o.default = t, l && l.set(t, o);
            return o
        })(r(d[2])),
        l = r(d[3]);

    function u(t) {
        if ("function" != typeof WeakMap) return null;
        var n = new WeakMap,
            l = new WeakMap;
        return (u = function(t) {
            return t ? l : n
        })(t)
    }

    var o = function() {
        var u = (0, n.useState)(''),
            o = (0, t.default)(u, 2),
            f = o[0],
            c = o[1],
            p = (0, n.useState)(''),
            s = (0, t.default)(p, 2),
            y = s[0],
            v = s[1];
        return n.default.createElement(l.ScrollView, null, n.default.createElement(l.Text, {
            style: {
                fontSize: 45,
                marginTop: 30,
                textAlign: "center"
            }
        }, "\u2622\ufe0f Reactor \u2622\ufe0f"), n.default.createElement(l.Text, {
            style: {
                padding: 10,
                fontSize: 18,
                textAlign: "center"
            }
        }, "Insert the pin to show the reactor codes."), n.default.createElement(l.TextInput, {
            style: {
                height: 40,
                fontSize: 15,
                textAlign: "center"
            },
            placeholder: "PIN",
            keyboardType: "number-pad",
            maxLength: 4,
            onChangeText: function(t) {
                return v(t)
            },
            onSubmitEditing: function(t) {
                c((0, r(d[4]).decrypt)(t.nativeEvent.text)), v("")
            },
            defaultValue: y
        }), n.default.createElement(l.Text, {
            style: {
                padding: 10,
                fontSize: 18,
                textAlign: "center"
            }
        }, f))
    };
    e.default = o
}, 399, [3, 23, 125, 1, 400]);

We can notice that the component has an event handler for the onSubmitEditing event, this callback uses the method decrypt on the textbox content!

Searching for something called decrypt we can find:

__d(function(g, r, _i, a, m, e, d) {
    var t = r(d[0])(r(d[1])),
        n = "U1VTUE5aVFVXDVEBUFoHDlZcAQYDXApTAg8GA1RaBlQCCVMGB0Q=";

    function o(t, n) {
        for (var o = '', c = t; c.length < n.length;) c += c;
        for (var f = 0; f < n.length; ++f) o += String.fromCharCode(c.charCodeAt(f) ^ n.charCodeAt(f));
        return o
    }
    m.exports.encrypt = function(n, c) {
        return t.default.encode(o(n, c))
    }, m.exports.decrypt = function(c) {
        return o(c, t.default.decode(n))
    }
}, 400, [3, 401]);

Once again the decrypt function calls a decode method, that can be spotted here:

__d(function(g, r, _i, a, m, e, d) {
    Object.defineProperty(e, "__esModule", {
        value: !0
    }), e.default = void 0;
    var t = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=',
        n = {
            encode: function(n) {
                var c, h, i, o, A, f = [],
                    u = "",
                    l = "",
                    s = 0;
                do {
                    i = (c = n.charCodeAt(s++)) >> 2, o = (3 & c) << 4 | (h = n.charCodeAt(s++)) >> 4, A = (15 & h) << 2 | (u = n.charCodeAt(s++)) >> 6, l = 63 & u, isNaN(h) ? A = l = 64 : isNaN(u) && (l = 64), f.push(t.charAt(i) + t.charAt(o) + t.charAt(A) + t.charAt(l)), c = h = u = "", i = o = A = l = ""
                } while (s < n.length);
                return f.join('')
            },
            encodeFromByteArray: function(n) {
                var c, h, i, o, A, f = [],
                    u = "",
                    l = "",
                    s = 0;
                do {
                    i = (c = n[s++]) >> 2, o = (3 & c) << 4 | (h = n[s++]) >> 4, A = (15 & h) << 2 | (u = n[s++]) >> 6, l = 63 & u, isNaN(h) ? A = l = 64 : isNaN(u) && (l = 64), f.push(t.charAt(i) + t.charAt(o) + t.charAt(A) + t.charAt(l)), c = h = u = "", i = o = A = l = ""
                } while (s < n.length);
                return f.join('')
            },
            decode: function(n) {
                var c, h, i, o, A = "",
                    f = "",
                    u = "",
                    l = 0;
                if (/[^A-Za-z0-9\+\/\=]/g.exec(n)) throw new Error("There were invalid base64 characters in the input text.\nValid base64 characters are A-Z, a-z, 0-9, '+', '/',and '='\nExpect errors in decoding.");
                n = n.replace(/[^A-Za-z0-9\+\/\=]/g, "");
                do {
                    c = t.indexOf(n.charAt(l++)) << 2 | (i = t.indexOf(n.charAt(l++))) >> 4, h = (15 & i) << 4 | (o = t.indexOf(n.charAt(l++))) >> 2, f = (3 & o) << 6 | (u = t.indexOf(n.charAt(l++))), A += String.fromCharCode(c), 64 != o && (A += String.fromCharCode(h)), 64 != u && (A += String.fromCharCode(f)), c = h = f = "", i = o = u = ""
                } while (l < n.length);
                return A
            }
        };
    e.default = n
}, 401, []);

Putting everything togheter and changing some variable names the relevant code is:

var n = "U1VTUE5aVFVXDVEBUFoHDlZcAQYDXApTAg8GA1RaBlQCCVMGB0Q=";
var t = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='

function decode(n) {
    var c, h, i, o, A = "",
        f = "",
        u = "",
        l = 0;
    if (/[^A-Za-z0-9\+\/\=]/g.exec(n)) throw new Error("There were invalid base64 characters in the input text.\nValid base64 characters are A-Z, a-z, 0-9, '+', '/',and '='\nExpect errors in decoding.");
    n = n.replace(/[^A-Za-z0-9\+\/\=]/g, "");
    do {
        c = t.indexOf(n.charAt(l++)) << 2 | (i = t.indexOf(n.charAt(l++))) >> 4, h = (15 & i) << 4 | (o = t.indexOf(n.charAt(l++))) >> 2, f = (3 & o) << 6 | (u = t.indexOf(n.charAt(l++))), A += String.fromCharCode(c), 64 != o && (A += String.fromCharCode(h)), 64 != u && (A += String.fromCharCode(f)), c = h = f = "", i = o = u = ""
    } while (l < n.length);
    return A
}

function o(key, n) {
    for (var o = '', c = key; c.length < n.length;) c += c;
    for (var f = 0; f < n.length; ++f) o += String.fromCharCode(c.charCodeAt(f) ^ n.charCodeAt(f));
    return o
}

function decrypt(key) {
    return o(key, decode(n))
}

A PIN code is usually compose by 4 digits, so we brute-force it with:

for(i = 0; i < 10; i++){
    for(j = 0; j < 10; j++){
        for(k = 0; k < 10; k++){
            for(l = 0; l < 10; l++){
                var key = "" + i + j + k + l;
                a = decrypt(key);
                if(a.includes("flag"))
                    console.log(key + " " + a)
            }
        }
    }
}

and we got the flag!

flag{cfbb4c6ec59ce316e8d7644ac4c70a12}
Original writeup (https://github.com/r00tstici/writeups/tree/master/hacktivitycon_2021/reactor).