Reverse-engineering of "Lovebox" a React-Native app: High level approach (Part 2)
As we saw on the first part of this adventure, the Lovebox backend does not check if the box we're sending a message is allowed or not to receive a coloured message. Changing the hasColor value in the app response of an API request is all we need.
In order to change the value without using a proxy we have to modify the app.
Let's dig more on how React-Native works.
Looking in the files and searching online I discovered that the app was built using React-Native because in the assets folder there was a index.android.bundle file.
In the past the index.android.bundle was a uglified JS file that contained the entire React app. Now things are a little different because of Hermes, a JavaScript engine optimized for React Native, that is now used by default in the React-Native builds. It compiles the entire app into a bytecode that has a unique set of instructions with registers managment and such.
However, there is a problem.
The index.android.bundle is a binary file of zeros and ones. As a starting point, I needed a way to disassemble this file into a readable format so I could understand more.
Searching on GitHub I found hbctool a open-source tool for disassembling and assembling the Hermes bytecode. Unfortunately there are different version of Hermes and the tool from the original author doesn't support the version 94 that is used for the Lovebox app. BUT, looking at the differents forks I found this one that supported the version 94.
After installing the tool and running a simple command.
hbctool disasm .\hasm\ .\assets\index.android.bundle
I have three different files to start from.
hasm/
├─ instruction.hasm # has all the Hermes instructions.
├─ string.json # has all the strings from the Lovebox app.
├─ metadata.json # used from the hbctool.
The instruction.hasm file contains the Hermes bytecode in the following format.
Function<global>0(1 params, 19 registers, 0 symbols):
DeclareGlobalVar UInt32:32265
; Oper[0]: String(32265) '__BUNDLE_START_TIME__'
DeclareGlobalVar UInt32:37506
; Oper[0]: String(37506) '__DEV__'
DeclareGlobalVar UInt32:27612
; Oper[0]: String(27612) 'process'
DeclareGlobalVar UInt32:33860
; Oper[0]: String(33860) '__METRO_GLOBAL_PREFIX__'
CreateEnvironment Reg8:3
LoadThisNS Reg8:5
LoadConstString Reg8:4, UInt16:5759
; Oper[1]: String(5759) 'production'
GetById Reg8:0, Reg8:5, UInt8:1, UInt16:38842
; Oper[3]: String(38842) 'nativePerformanceNow'
JmpTrue Addr8:23, Reg8:0
GetGlobalObject Reg8:0
TryGetById Reg8:1, Reg8:0, UInt8:2, UInt16:13
; Oper[3]: String(13) 'Date'
GetById Reg8:0, Reg8:1, UInt8:3, UInt16:26774
; Oper[3]: String(26774) 'now'
As you see, this is the Hermes bytecode that uses different instructions that are described in the following link from another open-source project.
The string.json file contains all the Lovebox app strings in the following format.
{
"id": 5832,
"isUTF16": false,
"value": "Envoyer de l'amour"
},
{
"id": 5833,
"isUTF16": false,
"value": "Envoyer de l'amour sur une Lovebox"
},
{
"id": 5834,
"isUTF16": false,
"value": "LoveboxEverydayView.Terms.And"
},
Amazing, the tool worked and now I have a disassembled version of the app bytecode that is human readable. I can now modify instructions/strings and recompile everything up.
The high level approach: Modifying Strings
Do you remember the query from the first part?
{
"operationName": "me",
"variables": {},
"query": "..."
}
---
query me($timezone: Int) {
me(timezone: $timezone) {
__typename
_id
...
boxes {
__typename
_id
...
hasColor
...
}
...
}
}
After searching hasColor in the string.json file I found this.
{
"id": 2221,
"isUTF16": false,
"value": "boxes {\n _id\n color\n companyId\n signature\n picture\n nickname\n notifications {\n disableUntil,\n messageRead,\n heartReceived,\n }\n admin {\n _id\n firstName\n email\n }\n privacyPolicy\n pairingCode\n isConnected\n isAdmin\n hardware\n hasColor\n connectionDate\n macAddress\n channelsIds\n }"
},
This string contains the boxes { ... } part of the GraphQL me($timezone: Int) query. Most likely the app interpolates different strings to build up queries.
What if we could crack it so the hasColor is always true?
After looking in the GraphQL documentations for strange operators or behaviors a stroke of genius hit me: ALIASING
Recommended by LinkedIn
boxes {
...
isConnected
hasColor: _id
...
}
In this way you can query the _id field but give it the hasColor name.
But now you'll ask: We need a true bool value, not an ID. What's the point?
Well, what if the Lovebox app checks the bool value like this.
const user = #fetches user data using something we don't know.
if (user.hasColor) {
freeTheColors();
}
The user.hasColor can be any value except of 0 and pass the if condition. We don't really know what happens in the black box. This is just a guess.
Our _id disguised as hasColor would still unlock the colors even if the bool value in reality is a integer number. I might add that even if this wouldn't work we could alias another bool field like isConnected. (But probably get the coloured messages only if the Lovebox is actually connected to the internet).
I tested the query by modifing the API request using mitmproxy as I did in the first part. This time only changing the query fields in the request and not the hasColor in the response.
This was the response.
"data": {
"me": {
"__typename": "User",
"_id": "MySecretId",
...
"boxes": [
{
"__typename": "BoxSettings",
"_id": "1234567890",
...
"hasColor": "1234567890"
...
},
{
"__typename": "BoxSettings",
"_id": "987654321",
...
"hasColor": "987654321",
...
}
]
...
}
}
Aaaand it WORKED. I could send the coloured message to my Lovebox. We just have to change the query and recompile everything up.
After modifing the query in the string.json file, I've tried assembling the index.android.bundle with the following command.
hbctool asm .\hasm\ .\assets\index.android.bundle
hbctool complained that changing the string length is not supported in the assembler.
I fixed the error by removing 4 space characters in the query because 4 characters are added ( :, _, i, d ).
{
"id": 2221,
"isUTF16": false,
"value": "boxes {\n _id\n color\n companyId\n signature\n picture\n nickname\n notifications {\n disableUntil,\n messageRead,\n heartReceived,\n }\n admin {\n _id\n firstName\n email\n }\n privacyPolicy\n pairingCode\n isConnected\n isAdmin\n hardware\n hasColor:_id\n connectionDate\n macAddress\n channelsIds\n }"
},
I've recompiled the app using APKLab, installed on the emulator and BOOM.
The app is cracked. Colors are now enabled on all the Loveboxes.
From a security perspective, we don't really know who is responsible for this integer/bool "security flaw". Using the === compare operator in JS prevents this.
> hasColor ? "unlock colors" : "lock colors"
'unlock colors'
> hasColors === true ? "unlock colors" : "lock colors"
'lock colors'
But who knows? The GraphQL library used to fetch queries could be the problem too, parsing the query result and replacing the integer value with a bool true value.
In this part I achieved the goal without getting my hands dirty digging down in the Hermes bytecode.
This solutions seems to be elegant. But hey, I'm here to know things under the hood. This path is way too easy.
In the next part I'll try to leave the query as it is and modify the Hermes bytecode instruction to achieve the same result.
Stay tuned, the adventure continues!