Now Reading
Heisting 20 Million {Dollars}’ Price of Magic: The Gathering Playing cards in a Single Request

Heisting 20 Million {Dollars}’ Price of Magic: The Gathering Playing cards in a Single Request

2023-05-04 20:00:07


With a bit little bit of math, decompilation, and understanding of laptop structure, we’re going to power a user-controlled arithmetic overflow to happen in Magic: The Gathering Area, and use it to purchase thousands and thousands of card packs for “free” (solely utilizing the beginning quantity of in-game forex given to new accounts).

However the thousands and thousands of {dollars} price of digital playing cards is not the reward right here. The reward, hopefully, is information.

Inform ’em Tai:


Digital buying and selling card video games have put nerds in a bind. With bodily TCGs, we used to have the ability to persuade our life companions, and ourselves, that in some imprecise manner we had been actually “investing” in collectibles that could possibly be liquidated if wanted. In recent times, although, digital card video games like Hearthstone and its ilk have laid the details naked for all to see: us card-game nerds are simply playing addicts with additional steps. It’s concerning the rush of opening the packs, child! Not possession of the playing cards.

Video games like Magic: The Gathering Area (MTGA) and Hearthstone are nonetheless massively in style and large monetary successes with none phantasm of shortage or worth in a secondary market. The playing cards “owned” and “opened” by every account are all simply numbers in a database someplace. That change in possession mannequin is a double-edged sword although. We nerds can change numbers in a database far more simply than we will rob a board sport store. So, let’s make the most of that!

Casing the joint

MTGA is a Unity sport, which means that it’s written in C#. C# decompiles extraordinarily cleanly, making reverse engineering and manipulating the sport logic a breeze. I lined this in additional of a how-to format in my last post, so I’ll skip it right here and simply get to the attention-grabbing half. Wanting on the buying logic for in-game retailer objects, the next perform is utilized by the sport to submit a purchase order request utilizing in-game forex:

// PurchaseV2Req is actually a JSON dictionary that later 
// will get marshalled and despatched to the sport server to make a purchase order
PurchaseV2Req purchaseV2Req = new PurchaseV2Req();
purchaseV2Req.listingId = merchandise.PurchasingId;

// IMPORTANT LINE 1 - Units amount being ordered
purchaseV2Req.purchaseQty = amount;

purchaseV2Req.payType = Mercantile.ToAwsPurchaseCurrency(paymentType, this._platform);

Client_PurchaseOption client_PurchaseOption = merchandise.PurchaseOptions.FirstOrDefault(
    (Client_PurchaseOption po) => po.CurrencyType == paymentType);

// IMPORTANT LINE 2 - Calculates value of order
purchaseV2Req.currencyQty = (
    (client_PurchaseOption != null) ? client_PurchaseOption.IntegerCost : 0) * amount;

purchaseV2Req.customTokenId = customTokenId;
PurchaseV2Req request = purchaseV2Req;

Once I took a take a look at this, it stood out to me {that a} request to buy one thing from the shop contains each the amount being ordered and a calculated worth of what the order ought to value. What’s extra, the worth is calculated by the shopper(!) by multiplying the unit worth of no matter is being bought by the amount being ordered. If that second necessary line is complicated to you, right here it’s in a extra readable manner:

if (client_PurchaseOption != null) {
    purchaseV2Req.currencyQty = client_PurchaseOption.IntegerCost * amount;
} else {
    purchaseV2Req.currencyQty = 0 * amount;

Seeing a client-side worth calculation, I started the traditional QA-engineer-beer-ordering workflow:

First I attempted messing with the purchaseQty discipline by setting it to a destructive quantity, simply to see if there was any bizarre habits there. My hope was that the logic for deducting a fee from my account serverside would look one thing like this:

accountBalance -= client_PurchaseOption.IntegerCost * amount

If I bought -1 card packs from the shop, at a worth of 200 gems (MTGA’s in-game forex), purchaseV2Req.currencyQty would equal -200. Subtracting that from my account steadiness would give me extra money!

This didn’t work. The server checks to just be sure you are ordering a amount better than 0, and prevents the acquisition from going via if not.

I then tried messing with currencyQty, the calculated worth. I believed this was going to be a winner and that I might be capable to buy no matter I wished for 0 gems. No cube there both. If I attempted to alter the calculated worth, I might get the next error again, resulting from a mismatch with a worth calculation carried out server-side:

  "code": "Store_IncorrectCurrency",
  "message": "Didn't buy itemizing as a result of currencyQuantity is just not equal to calculated worth"

Okay, bizarre. That implies that the shopper has to ship a accurately calculated worth within the buy order, as a result of the server validates the order by performing its personal worth calculation. Whereas this left me stumped as to why the client-side calculation even exists, it meant I could not simply inform the sport to offer me free playing cards, or playing cards at a destructive worth, or the rest.

However I wasn’t prepared to surrender but. The truth that I may see the logic of how this worth calculation was made allowed me to make some assumptions concerning the server-side examine:

  • The identical arithmetic is probably going used server-side to validate a purchase order request
  • It’s doubtlessly the precise identical implementation, which means no matter server-side software is receiving my requests can also be written in C#

Now that second bullet is a fairly large soar in reasoning, however I used to be keen to roll with it as a result of it opened up the chance to make a purchase order that was technically right, however nonetheless let me make out like a bandit.

The heist

Arithmetic overflow happens when the output of an operation is a worth greater than will be saved within the vacation spot information sort. It’s like chubby bunny, however with bits. In our case, the vacation spot is the int information sort.

In C#, an int is represented below the hood as 4 bytes, or 32 bits. In hex, the max worth of the 4-byte worth is 0xFFFFFFFF, or 11111111111111111111111111111111 in binary.

What occurs while you add 1 to 11111111111111111111111111111111?

+ 00000000000000000000000000000001

It ought to turn into 100000000000000000000000000000000, however that’s 33 bits – one bit greater than what our information sort permits. So as an alternative, probably the most vital bit is dropped, leaving us with 00000000000000000000000000000000. It rolls again over to zero.

Small apart: the int illustration of 0xFFFFFFFF is definitely -1 in C# resulting from the usage of two’s complement to permit the info sort to retailer destructive numbers. Which means the overflow form of works as supposed. While you add 1 to -1, each the underlying binary and the int illustration zero out. 0x00000000 = 0. However you needn’t fear about that. All it’s worthwhile to know is that if the output of an operation is larger than 0xFFFFFFFF, the output worth will basically be output % 0xFFFFFFFF.

So, with our new information of arithmetic overflows, do you see how we’re going to heist our Magic playing cards? Wanting again at this line of code, I see two issues:

purchaseV2Req.currencyQty = client_PurchaseOption.IntegerCost * amount;
  • There are no checks for overflows
  • The consumer controls a type of variables

Assuming that the server logic is comparable to what’s executed on the shopper, we should always be capable to overflow this integer by ordering an astronomically excessive variety of packs. Let’s plug some numbers in!

One pack of playing cards prices 200 gems, and we all know the max underlying worth is 0xFFFFFFFF. Subsequently, we will determine what number of packs we would have to order to overflow our order worth again round previous 0, and solely pay for the rest. A Python interpeter will do exactly high-quality:

>>> (0xFFFFFFFF/200) + 1 # add one to spherical as much as the closest int that can overflow

We add 1 to the quotient to get the most important complete quantity that can surpass the overflow, since Python all the time rounds down when casting floats to ints. This implies whereas we’re ordering 21 million packs, our fee will likely be as near 0 as feasibly potential. Doubtlessly below the worth of a single pack!

We may order plenty of packs increased than 21474837 as properly, however all that’s going to do is make the rest, the worth we’re going to pay, increased. That’s, till you order a quantity that, when modded with 0xFFFFFFFF, will wrap round to 0 once more – which might basically be across the space of multiples of 21474837.

Pulling it off

There’s one thing else I forgot to say. There is not any option to really submit bulk orders for an arbitrary variety of packs within the UI. There’s only a huge button to purchase a pack (and preset portions of 10, 25, and many others):


However that is no downside, now that we all know the order amount wanted to carry out the overflow, we will simply patch our binary with the suitable opcodes to have the amount hardcoded into our order! In C# it could seem like this:

See Also

// PurchaseV2Req is actually a JSON dictionary that later 
// will get marshalled and despatched to the sport server to make a purchase order
PurchaseV2Req purchaseV2Req = new PurchaseV2Req();
purchaseV2Req.listingId = merchandise.PurchasingId;

// Necessary Line 1 - Units amount being ordered
purchaseV2Req.purchaseQty = amount * 21474837;

purchaseV2Req.payType = Mercantile.ToAwsPurchaseCurrency(paymentType, this._platform);

Client_PurchaseOption client_PurchaseOption = merchandise.PurchaseOptions.FirstOrDefault(
    (Client_PurchaseOption po) => po.CurrencyType == paymentType);

// Necessary Line 2 - Calculates value of order
purchaseV2Req.currencyQty = (
    (client_PurchaseOption != null) ? client_PurchaseOption.IntegerCost : 0) * amount * 21474837;

purchaseV2Req.customTokenId = customTokenId;
PurchaseV2Req request = purchaseV2Req;

(And in case you are questioning why I did not simply recreate the acquisition request in python or one thing, it’s as a result of the store communication is over some type of socket. It wasn’t only a REST api and it did not appear price determining.)

So with the binary patched, lets click on our button and purchase a pack…

Bada-bing, bada-boom. With a single click on, over 20 million {dollars} price of Magic playing cards has been deposited to my account (for those who calculate the gems-to-dollars alternate price, a conservative estimate is every pack prices a bit over a greenback).

How a lot did that put us again? Properly, we will do the mathematics ourselves:

>>> (200 * 21474837) % 0xFFFFFFFF

105 gems! Lower than the price of a single pack. We will examine the acquisition logs simply to make sure although:

  "InventoryInfo": {
    "SeqId": 5,
    "Modifications": [
        "Source": "MercantilePurchase",
        "SourceId": "Packs-KHM-1-Listing",
        "InventoryGems": -104,
        "InventoryCustomTokens": {},
        "ArtStyles": [],
        "Avatars": [],
        "Sleeves": [],
        "Pets": [],
        "Emotes": [],
        "Decks": [],
        "DecksV2": [],
        "DeckCards": {},
        "Boosters": [
            "CollationId": 100022,
            "SetCode": "KHM",
            "Count": 21474837

Yup! In actual fact we get an additional gem off in comparison with our Python calculation. Undecided the place that one acquired added or misplaced between the 2 calculations.

Every account in MTGA begins with 250 gems which can be utilized to get hooked on the scrumptious sensation of opening packs. Which means you could possibly carry out this exploit proper off the bat with a brand new account, with out spending any cash. Now that is the way you actually begin filling out your assortment quick!

A remaining twist

One other twist right here, that I wasn’t anticipating, is that there are a finite variety of playing cards per set. There may be additionally a cap on the variety of copies of every card you may personal: you may solely open 4 copies of every card earlier than they turn into ineffective since you may’t use greater than 4 copies of a card in a deck. So what occurs while you open so many packs that you just attain the restrict? I arrange my autoclicker and discovered. As soon as you can’t acquire any extra playing cards, the packs as an alternative refund you gems.

free gems

And let me let you know, you hit the cardboard restrict looooooong earlier than you might be even via your first 10,000 packs for no matter set to procure. This then provides you an nigh-infinite trove of gems to exit and purchase 21 million packs of every of the opposite units with! Or purchase cosmetics, or take part in occasions, or no matter. MTGA simply turned actually free-to-play!

(Besides not, as a result of I reported this vulnerability to them and it has been patched. Shoutout to the WotC safety and engineering groups for being pretty to work with and patching this bug in a well timed method!)


I hope this has been an illustrative instance of the ability of a easy bug. Simply because a bug is easy, do not assume that it’s not there. Many of the loopy zero-click distant code execution exploits utilized by authorities businesses at present additionally stem from easy missed checks on user-controlled variables. Ian Beer, one of the crucial gifted vulnerability researchers on the earth, typically simply sits down and appears for memmove calls within the iOS kernel with controllable enter. This led to him discovering a wormable, zero-click, remote code execution exploit over radio. However that stuff is the massive leagues. For now, I’m content material simply with the ability to construct some new decks with my bug searching 🙂

P.S. — There may be additionally one thing to be mentioned right here concerning the worth of digital items. Frankly, I’m ambivalent. The fact is that digital items do and can live on, and issues like this are a aspect impact of that actuality. If you’re on the lookout for some extra ideas to chew on on this area, I like to recommend CONTENT: Selected Essays on Technology, Creativity, Copyright and the Future of the Future by Cory Doctorow. It’s revealed without cost in its entirety that hyperlink.

Recurse Center Webring

Source Link

What's Your Reaction?
In Love
Not Sure
View Comments (0)

Leave a Reply

Your email address will not be published.

2022 Blinking Robots.
WordPress by Doejo

Scroll To Top