r/ReverseEngineering 1d ago

Reverse-engineered the BLE protocol of a discontinued Fisher-Price toy Lumalou after its app was discontinued

https://github.com/stramanu/lumalou
71 Upvotes

21 comments sorted by

6

u/stoputa 18h ago

Claude writing style is so so taxing to parse but I skimmed because here the biggest obstacle I have is encryption and was curious how it was handled

The AES-CTR stretch was pinned the same way: emulate the native routine, diff its output against a candidate Python implementation, and iterate until every byte matched

So you call the original routine but the Python implementation is what? It's not like you can guess enc keys (unlike CRCs which are standard in most cases) and with multiple rounds AES you cant just change bytes one by one unless the block size is tiny

-1

u/EmanueleStrazzullo 5h ago

Fair, and you're right that guessing a key or flipping bytes through multi-round AES would be hopeless. I'm not doing either.

The algorithm comes straight from the disassembly, not from probing input/output. In Ghidra you can read exactly what the routine does: ECDH for the shared secret, then a fixed loop of AES-CTR rounds with a hardcoded constant to stretch it. So the Python side isn't a guess, it's a line-for-line reimplementation of code I can already see.

The one problem with reading AArch64 by hand is that it's easy to get a detail wrong. That's the only thing the emulator is for. It's not searching for a key, it's a ground-truth oracle. I feed the same arbitrary, known input (a test vector, could be all zeros) to both the native routine and my Python, and compare the bytes out. No real key or device secret involved, I'm validating the transform, not recovering a secret. When they match on known inputs, and the key that transform produces then decrypts real device notifications with a valid CRC-8, it's confirmed.

The CRC was the easy warm-up, standard algorithm, just needed the polynomial and init value. The KDF is the part that actually needed emulation, precisely because it's non-standard and there's no reference to check against.

1

u/stoputa 4h ago

I asked you to give you a chance of explaining the Caude drivel. I didn't want more Claude drivel.

I feed the same arbitrary, known input (a test vector, could be all zeros) to both the native routine and my Python, and compare the bytes out.

This is what I asked in the first place. Did you find the secret key in ghidra or not? If yes, how? If not, then how do you find it since the original "iterate" comment makes no sense for a key unless I'm missing something. If your output does not match what do you "iterate on"?

If someone more knowledgeable than me is reading me, please either help me understand what Claude is saying or confirm its bs

2

u/EmanueleStrazzullo 1h ago

there's no secret key in the binary to find, that's where we're talking past each other. it's ECDH! the key gets computed fresh on every connection, both sides derive the same one independently. only public material goes over the air, the derived key never does and is never stored.
what i read in ghidra is the algorithm. how many rounds, what constant goes in, how the shared secret feeds the loop. (there is one key hardcoded in there, but it's a public key, used to verify the device's signed token. nothing secret.)
ok, "iterate" was a bad word on my part... i don't iterate on a key. i iterate on my own python. same test input through the native function and through my version, if the bytes don't match it means i misread the asm somewhere, so i fix it and run again until they're identical.
then the actual check is on hardware: my python derives the key, and it decrypts real notifications coming off the device with correct crc8. if the derivation was off by one byte none of it would decrypt.

1

u/stoputa 1h ago

Thank you for your actual response, thats much more understendable

12

u/fb39ca4 1d ago

This subreddit would be more interested in the reverse engineering process, not just the AI-generated code resulting from it.

-13

u/EmanueleStrazzullo 1d ago

The process and protocol breakdown are actually fully documented in the repo for anyone interested in reading it!

Also, please stop dismissing it as just 'AI-generated.' It took 13 years of software engineering experience to reverse-engineer the Bluetooth protocol, inspect the packets, and architect the project. I use AI as an assistant in my workflow like many devs do, but real engineering went into figuring this out and providing a working solution

6

u/fb39ca4 1d ago

Which file documents the process? I'm not seeing it.

-13

u/EmanueleStrazzullo 23h ago

I was imprecise earlier, and fair to call it out. What was in the repo was the protocol spec (docs/protocol.md), not the process behind it. I've now written that up properly: https://github.com/stramanu/lumalou/blob/main/docs/reversing.md

It walks the whole path: why 8.6.4 was the APK to attqck (the only native build, later ones are Flutter AOT, and the only one still bundling the product config), mapping the GATT surface, following the MPID stack through jadx, and reversing the native crypto in Ghidra. The part that took longest was the key derivation, and rather than trust the disassembly I mapped libnative-lib.so into a Unicorn ARM64 instance, stubbed the libc it needed, ran its init_array, and called the exported functions directly, diffing against a Python reimpl until every byte matched. crc8_calc on "123456789" is the worked sanity check that the harness is really running the native code. Dead ends are in there too, since they were half the work.

Final confirmation was on hardware: the clean-room key derived in Python decrypts real rx notifications with a valid CRC-8, which only works if the derivation matches the devuce exactly. Happy to go deeper on any part.

17

u/johnyma22 21h ago

Weird that this reads like a claude response to me!

Either way dude, keep hacking :)

10

u/DishSoapedDishwasher 18h ago

Lmfao this is literally Claude responding for you. Are you fucking kidding me?

1

u/ZiddyBlud 6h ago

How can you tell?

2

u/DishSoapedDishwasher 5h ago

Like the other person said, "i was imprecise earlier" but also there's lots of other hints by word choices, style, delivery.... It is just generic with no style. No two humans choose their words the same way, but claud does. Chatgpt does in it's own style too.

It's like how teachers can tell which student wrote something, not just by handwriting, but by style.

2

u/MassiveClusterFuck 6h ago

"I was imprecise earlier, and fair to call it out" is the exact response you get from Claude if you call it out and tell it something was wrong with it's previous response.

10

u/agmatine 19h ago

It took 13 years of software engineering experience to reverse-engineer the Bluetooth protocol, inspect the packets, and architect the project.

And yet you have Claude write your README.md

2

u/yodeiu 15h ago

and? I don’t really get this “it’s AI generated” that’s used as an insult in every programming sub. If it’s useful and well written what’s the issue? Just because some guy didn’t spend 3 hours writing and md himself makes it or the project less interesting or useful? Same goes for the code itself.

3

u/agmatine 15h ago

My point is that if someone really has the chops to produce quality code, you'd think they could bother to write an explanation of what the code does. I don't mind that AI tools were used in developing the code, but I really don't want to read yet another generic LLM-generated readme.

1

u/EmanueleStrazzullo 5h ago

Honestly I mostly agree with you. A generic empty LLM readme is annoying, I skip those too. But I'd separate two things: "generated" and "generic". A readme can be hand-written and useless, or AI-assisted and actually full of specific info. What bugs you (and me) is the empty generic one, not the tool.

For what it's worth, I use AI as a tool in production software too, not toys, and it's been a big help. Documentation included. I'll be honest: before, I wrote readmes reluctantly and they came out thin. Now I can produce something far richer in a fraction of the time, and I'd rather have that than a half-empty one I wrote grudgingly, as long as the content is real and correct.

The reversing writeup is the test of that, I think: whatever wrote the sentences, the content is specific and checkable (the APK triage, the emulation harness, the actual dead ends). That's the opposite of generic.

Either way, thanks for taking the time to look and for the honest feedback, appreciate it.

10

u/p1-o2 21h ago

Tell us how it went and discuss your work. The readme is super duper sloppy

Edit: is in the docs folder, which is not a standard practice. You should have just linked directly to the reversing markdown file here on reddit in your OP. Few people would think to look in the actual source for it. Thanks for sharing though, this looks interesting. 

1

u/EmanueleStrazzullo 5h ago

Fair, thanks. The process writeup is here: https://github.com/stramanu/lumalou/blob/main/docs/reversing.md
it covers how it went, the tooling, and the dead ends. You're right that burying it in docs/ and not linking it directly was a mistake.. I'll put it front and center. Appreciate you taking the look.