Meowant Litterbox Reverse Engineering

Following up after the original meowant post Meowant Litter Box

I used mitm proxy and with some iteration was able to do something simple (turn auto-clean on and off).

Most of the http header fields don’t change (not filling out for privacy reasons):
content-type:
x-ubt-language:
x-ubt-deviceid:
accept:
authorization:
product:
x-ubt-appid:
priority:
accept-language:
accept-encoding:
content-length:
user-agent:

But I discovered that the last field:

x-ubt-sign: fbebd86b34e9f7e3915f01c93d4b2366 1776685892 2HR6c1gLG5 v2

appears to be dynamic per-request. I used mitmproxy to intercept, copied a fresh one into the httpclient I was using to debug, and success! I was able to toggle auto-clean off.

Anyone with more decryption experience than me have an interest in helping me reverse engineer what this field might be doing? Or have any ideas for how to auto-generate the contents of this field?

I’ll come back to this at some point and will post anything I figure out…

1 Like

super interested in how this goes! would love to get mine integrated but can't afford to have it out of commission for tinkering I'm unfamiliar with

Nice work isolating that, you've basically already found the one hard part. x-ubt-sign is a fairly standard request-signing scheme. It's four space-separated fields:

fbebd86b34e9f7e3915f01c93d4b2366   1776685892   2HR6c1gLG5   v2
  • fbebd86b... (32 hex chars) = an MD5 digest = the signature
  • 1776685892 = a Unix timestamp (seconds)
  • 2HR6c1gLG5 = a random 10-char nonce, generated fresh per request
  • v2 = the signing-scheme version

The timestamp + nonce are there to block replays (which is why a freshly-copied value works but a stale one eventually gets rejected), and the MD5 proves the app knows a secret key that's hardcoded in the app. So the digest is almost certainly something like:

MD5(app_secret + timestamp + nonce + request data)

with "request data" usually being the sorted params and/or the body, maybe the path/appid/deviceid.

The reason you can't regenerate it from mitmproxy captures alone: that secret never goes over the wire, so you have to pull it out of the app. Two ways, easiest first:

1. Frida. Hook the MD5 call and log what it hashes right before a request fires, that pre-image string is the whole recipe, secret and all:

Java.perform(() => {  const MD = Java.use('java.security.MessageDigest');  MD.update.overload('[B').implementation = function (b) {    try { console.log('MD5 in:', Java.use('java.lang.String').$new(b)); } catch (e) {}    return this.update(b);  };});

2. jadx. Decompile the APK and search strings for x-ubt-sign, getSign, appSecret, MD5, v2 to land on the header builder and the secret.

Once you've got the field order + secret, generating the header is trivial: current epoch, a random 10-char nonce, md5 it, space-join, append v2. The nonce can be anything; only the secret and the exact concatenation order matter.

Good Luck hunting!

1 Like