>_ DigitalGuards
← all posts
2026-09-29 bug-bountybinancetrust-wallettron

Fixed in hours, paid in seven months: How Binance treats a bug that can steal all your funds

I reported a Trust Wallet Core bug that let a dApp show you one transaction and get your signature on another. Binance fixed it the day it was triaged, then took seven months to rate it P3 and pay $1,000.

In February 2026 I reported a bug in Trust Wallet Core to Binance’s bug bounty program on Bugcrowd. Trust Wallet Core is the open source library that signs transactions for the Trust Wallet mobile app and browser extension.

The Trust Wallet engineers fixed it the same day it was triaged. Getting it rated and paid took seven months. I submitted it as a P1. Binance offered P4 and $200, then raised it to P3 and $1,000, and that’s where it stayed.

The code and both fixes have been public on GitHub for months. This post covers the bug, and then how the report was handled.

How Tron signing is supposed to work

A Tron transaction has a raw_data field that contains everything: the contract type, the sender, the receiver, the amount, the expiry. The transaction ID (txID) is just sha256(protobuf_serialize(raw_data)). The wallet signs the txID, and the network validates the signature against a fresh hash of raw_data.

The txID is derived data. A wallet should always compute it itself, from the transaction content it just showed you.

How Trust Wallet actually did it

When a Tron dApp asked Trust Wallet to sign a transaction, it sent a JSON payload with three fields:

txID           -- the hash to sign
raw_data       -- the transaction details (shown to the user)
raw_data_hex   -- the same transaction as protobuf bytes

All three came from the dApp. The wallet never checked that sha256(raw_data_hex) == txID. The function that decided what to show the user read from raw_data_hex. The function that decided what to sign read from txID. They were independent code paths touching the same JSON, with no cross-validation.

The vulnerable code was in src/Tron/Signer.cpp. The function signaturePreimage() returned raw_data_hex (what the user sees), and signaturePreimageHash() returned txID (what gets signed). In the normal (non-dApp) code path, the hash was correctly computed as sha256(preimage). But the raw_json path bypassed that and returned the txID field verbatim.

This was introduced in commit 91ee958 (“Support compiling tron raw json from DApp”, July 2025) and the proto file even had a TODO comment about deprecating a separate txId field in favor of raw_json.txID, showing the plan was to rely on this untrusted field even more.

What it means in practice

A dApp puts a harmless transaction in raw_data and raw_data_hex, but sets txID to the hash of a completely different transaction. The wallet shows the harmless one. The user reads the confirmation screen, sees nothing wrong, and approves. The wallet signs the hash of the transaction the user never saw. The dApp takes that signature and broadcasts the real transaction.

The user doesn’t have to be careless. The user doesn’t have to ignore a warning. There is no warning. The confirmation screen shows exactly what the dApp wants it to show, and the signature covers something else entirely. That by itself is a fund theft bug, and nothing else has to go wrong for it to work.

How I found it

I was auditing the wallet-core repository commit by commit. The raw_json feature stood out because it introduced a second source of truth for the signing hash. I searched for any place where txID was validated against the actual transaction content and found none. From there it was straightforward: build a dApp that sends mismatched fields and see if the wallet catches it.

If you’re hunting on wallet code, this is the pattern to look for: any place where a derived value (a hash, a signature, an address) comes from an external source when the wallet should compute it itself. The non-dApp code path in this same file did it correctly. The bug was in the shortcut.

The fix

Both fixes are public:

  • PR #4666 removed the direct signing path altogether and dropped the txId and raw_json fields from the protobuf definition.
  • PR #4737, merged on 23 April 2026, rebuilt the raw JSON feature properly. The wallet now deserializes the JSON, re-serializes the transaction itself, computes sha256 over its own serialization, and rejects the request if it doesn’t match the supplied txID. The signing hash is never taken from external input.

What went right

I want to be fair about this part. Binance triaged the report three days after I submitted it. Once they’d asked me to confirm it on the latest version, a fix PR was up within the hour.

That’s a fast engineering response, and I said so at the time.

Timeline

  • 22 Feb: I submit the report as P1, with a live PoC against the Chrome extension, a video, the PoC dApp source, a C++ unit test and a patch.
  • 25 Feb: Triaged.
  • 26 Feb: Binance asks me to reproduce it on the latest version. I confirm it. Binance asks me to test PR #4666, and I confirm it fixes the issue. The report is marked Resolved, with a note that severity and bounty will follow “once the internal impact assessment is complete.”
  • 12 Mar: I ask for an update.
  • 25 Mar: “We will provide you with an update as soon as the internal impact assessment has concluded.”
  • 15 Apr: Binance offers P4, $200 in BNB, “based on our impact assessment and the prerequisites for this attack to be successful.” I dispute the rating the same day.
  • 17 Apr: Binance asks for the dApp code used in the video. It was already attached to the original submission. I attach it again.
  • 23 Apr: PR #4737 is merged into master. The same day, Binance tells me “the team is still looking into it.”
  • May and June: More follow-ups from me, and more “still looking into it” replies.
  • 8 Jun: Binance asks me to reproduce the issue on PR #4737, which had been merged six weeks earlier. I confirm it’s fixed.
  • 13 Jul: “This is still going through the evaluation process.”
  • 27 Jul: Binance raises it to P3, $1,000 in BNB, with word for word the same reason as before: “based on our impact assessment and the prerequisites for this attack to be successful.” I ask which prerequisite they mean.
  • 17 Sep: “The team reviewed this again and concluded that the would stay as is.” That’s a direct quote, including the missing word. No explanation.
  • 29 Sep: Paid.

What went wrong

The rating doesn’t match the impact. The only “prerequisite” is that a user connects to a dApp and approves a transaction, which is how people use a wallet every day. The confirmation screen exists so that the approval means something. This bug made it meaningless: the user could read the screen carefully and still lose everything.

The rating was never explained. I asked directly which prerequisite they were weighing. The answer was the same stock sentence twice, then “stay as is.” A severity dispute can’t get anywhere if one side won’t say what it’s basing its decision on.

Binance’s own engineering response contradicts the rating. The first fix deleted an entire signing path and removed fields from the public API. A second PR then rebuilt the feature with proper validation. Teams don’t ship two fixes in two months for a low impact bug.

It was slow, and it didn’t seem to be tracked. It took seven weeks to get the first offer and seven months to get paid. They asked for code that was already attached, and asked me to retest a fix that had been in master for six weeks. It felt like nobody was actually following the report.

Why I’m writing this

Researchers choose where to spend their time based on how programs treat them. The Trust Wallet engineers did good work here. But when a bug lets someone take a user’s funds with nothing more than a normal approval, and it’s rated P3 with no reasoning given, researchers learn that serious wallet bugs aren’t worth reporting to this program.

That’s bad for Trust Wallet’s users, who are the people the program exists to protect.

If you’re thinking about hunting on this program, expect bugs that need any user interaction to be rated low, however bad the outcome is. Document everything, keep your PoC self-contained, and expect to wait.

And to Binance: when you downgrade a report, say why. One specific sentence would have turned this into an ordinary disagreement.

← more from the blog work with us