⬆️ ⬇️

Reverse engineering in-app purchases by Apple. (or "there" all too lazy)

Intro



Hi, Habr! You probably know about the recent events that have spread on the Internet as a “hack” of in-app apple shopping systems. So, it was not quite so. It was not even a burglary. And the key conclusions I drew:







So, I want to tell how and what was done, add a few sorts, and in general, try to direct thoughts in the right direction.

')

Technology



In the heyday of cloud and service infrastructures, a lot of things rely on the server part. And in vain. As practice has shown, both client developers and server developers are very lazy. Only in the case of the latter, this results in a big scandal.



So let's get started. In order to make a desired in-app purchase, you must perform from 4 to 6 requests to Apple servers, and up to as many requests to your server, if you validate the purchases on your server. I will not consider getting a shopping list from apple servers, but will directly consider the fact of purchase. The general plan of action is as follows:







Let us dwell on each of them:

1. What we give you is not important to you.


Receiving purchase parameters occurs GET request for p()-buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/offerAvailabilityAndInfoDialog p()-buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/offerAvailabilityAndInfoDialog , I didn’t figure out exactly how the numbers are generated, but they are reflected in $ _COOKIE ['Pod'] and most likely depend on user region.

By passing the following parameters to GET:



 'restrictionLevel' => '1000', // ? 'id' => '522704697', // ID  'versionId' => '7736106', //    'guid' => '074b684aa46990f92b60c374611e59a82xxxxxfe', //   GUID/UDID,     UDID,    'quantity' => '1', //  in-app ?   1 'offerName' => 'com.gameloft.TDKR.cashpack1', //  in-app  'lang' => 'en', // ? 'bid' => 'com.gameloft.TDKR', // bundle id  'bvrs' => '1.0.0', //   'icuLocale' => 'ru_RU' //    PLIST    




We get a plist in response. The answer is packed in gzip, so you must first unpack it:



 <?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>jingleDocType</key><string>inAppSuccess</string> <key>jingleAction</key><string>offerAvailabilityAndInfoDialog</string> <key>dsid</key><string></string> <key>dialog</key> <dict> <key>message</key><string>   ?</string> <key>explanation</key><string> -  $1.99?</string> <key>defaultButton</key><string>Buy</string> <key>okButtonString</key><string>!</string> <key>okButtonAction</key><dict> <key>kind</key><string>Buy</string> <key>buyParams</key><string>quantity=1&salableAdamId=525477928&appExtVrsId=7736106&bvrs=1.0.0&offerName=com.gameloft.TDKR.cashpack1&productType=A&appAdamId=522704697&price=1990&bid=com.gameloft.TDKR&pricingParameters=STDQ</string> <key>itemName</key><string>com.gameloft.TDKR.cashpack1</string> </dict> <key>cancelButtonString</key><string></string> </dict> </dict> </plist> 




Yes, I copied exactly as it is given, with monstrous line breaks and empty bytes after the end of PLIST. It feels like they wrote Krivorukov monkeys.

Here we are most interested in appAdamId from buyParams, and the rest for all applications is the same (what you get in get, you will get back + A & STDQ). The most fun thing is that applications almost spit on appAdamId. As written in the title , what is given to us is not important .



2. Where is gzip? (or how to shine a password from an apple id)


Next you need to make a purchase by performing a POST request for p()-buy.itunes.apple.com/WebObjects/MZBuy.woa/wa/inAppBuy p()-buy.itunes.apple.com/WebObjects/MZBuy.woa/wa/inAppBuy and transferring uncoded PLIST from your application to POST data. Yes, no URLencode or compression - just PLIST:



 <?xml_version' => '"1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>appAdamId</key> <string>522704697</string> <key>appDsid</key> <string>1341894157</string> <key>appExtVrsId</key> <string>7736106</string> <key>bid</key> <string>com.gameloft.TDKR</string> <key>bvrs</key> <string>1.0.0</string> <key>guid</key> <string>xxxxxxxxx</string> <key>offerName</key> <string>com.gameloft.TDKR.cashpack1</string> <key>price</key> <string>1990</string> <key>pricingParameters</key> <string>STDQ</string> <key>productType</key> <string>A</string> <key>quantity</key> <string>1</string> <key>salableAdamId</key> <string>525477928</string> </dict> </plist> 




If you have not used the appstore for a long time, the apple will answer you with a PLIST requesting authorization.

For authorization you need to send a GET or POST request to p()-buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/authenticate p()-buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/authenticate by sending the following normal POST (urlencode) or GET now:



  'appleId' => 'appleid', 'password' => '  , ', 'rmp' => '0', 'attempt' => '0', 'accountKind' => '0', 'guid' => 'xxxx' 




And in response, you get about yourself almost all the data, in uncompressed form. Just PLIST and all:



 <?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>accountInfo</key> <dict> <key>appleId</key><string>apleid</string> <key>accountKind</key><string>0</string> <key>address</key> <dict> <key>firstName</key><string></string> <key>lastName</key><string></string> </dict> </dict> <key>passwordToken</key><string>  ( 15 )</string> <key>clearToken</key><string> -  ( 15 )?</string> <key>is-cloud-enabled</key><string>false</string> <key>dsPersonId</key><string>ID ?</string> <key>creditDisplay</key><string></string> <key>creditBalance</key><string>1311811 (  ,   ,      )</string> <key>freeSongBalance</key><string>1311811 (  ,   ,      )</string> <key>status</key><integer>0</integer> </dict> </plist> 




PLIST clumsy again ...



Generate @ sign


Well, since you are logged in, having received such a PLIST, you should repeat the request for p()-buy.itunes.apple.com/WebObjects/MZBuy.woa/wa/inAppBuy p()-buy.itunes.apple.com/WebObjects/MZBuy.woa/wa/inAppBuy and in the end get a PLIST, indicating that yes, here is the data of your purchase:



 <?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>jingleDocType</key><string>inAppSuccess</string> <key>jingleAction</key><string>inAppBuy</string> <key>dsid</key><string></string> <key>download-queue-item-count</key><integer>1</integer> <key>app-list</key> <array> <dict> <key>item-id</key><integer>525477928</integer> <key>app-item-id</key><integer>522704697</integer> <key>version-external-identifier</key><integer>7736106</integer> <key>bid</key><string>com.gameloft.TDKR</string> <key>bvrs</key><string>1.0.0</string> <key>offer-name</key><string>com.gameloft.TDKR.cashpack1</string> <key>transaction-id</key><string>170000030394952</string> <key>original-transaction-id</key><string>170000030394952</string> <key>purchase-date</key><date>2012-07-28T14:30:19Z</date> <key>original-purchase-date</key><date>2012-07-28T14:30:19Z</date> <key>quantity</key><integer>1</integer> <key>receipt-data</key><data>base64 </data> </dict> </array> </dict> </plist> 




Here I think, judging by the above, everything is clear. Base64 is interesting, it consists of a coded NSDictionary:



 { "signature" = "AmJ2SQJx5yZI+t1XRiPBmRVxuoj8jatJkQ+VHCiMLA3Vek48A45NR02AJRNJkKG9+Ry3YgPBjZxifwnYZv1Ylm18NFblnmgDkValnktoL+5wFHcZZGN6//cmHs8p/RwV/rt/91XKVhNl4XIBimKjQQNfgHsDs6yju++DrKJE7uKsphMddKYfFE5rGXsAdBEjBwRIxexTevx3HLEFGAt1moKx509dhxtiIdDgJv2YaVs49B0uJvNdy6SMqNNLHsDLzDS9oZHAgMBAAGjcjBwMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUNh3o4p2C0gEYtTJrDtdDC5FYQzowDgYDVR0PAQH/BAQDAgeAMB0GA1UdDgQWBBSpg4PyGUjFPhJXCBTMzaN+mV8k9TAQBgoqhkiG92NkBgUBBAIFADANBgkqhkiG9w0BAQUFAAOCAQEAEaSbPjtmN4C/IB3QEpK32RxacCDXdVXAeVReS5FaZxc+t88pQP93BiAxvdW/3eTSMGY5FbeAYL3etqP5gm8wrFojX0ikyVRStQ+/AQ0KEjtqB07kLs9QUe8czR8UGfdM1EumV/UgvDd4NwNYxLQMg4WTQfgkQQVy8GXZwVHgbE/UC6Y7053pGXBk51NPM3woxhd3gSRLvXj+loHsStcTEqe9pBDpmG5+sk4tw+GK3GMeEN5/+e1QT9np/Kl1nj+aBw7C0xsy0bFnaAd1cSS6xdory/CUvM6gtKsmnOOdqTesbp0bs8sn6Wqs0C9dgcxRHuOMZ2tm8npLUm7argOSzQ=="; "purchase-info" = "base64   "; "pod" = ""; "signing-status" = "0"; } + VHCiMLA3Vek48A45NR02AJRNJkKG9 + Ry3YgPBjZxifwnYZv1Ylm18NFblnmgDkValnktoL + 5wFHcZZGN6 // cmHs8p / RwV / rt / 91XKVhNl4XIBimKjQQNfgHsDs6yju ++ DrKJE7uKsphMddKYfFE5rGXsAdBEjBwRIxexTevx3HLEFGAt1moKx509dhxtiIdDgJv2YaVs49B0uJvNdy6SMqNNLHsDLzDS9oZHAgMBAAGjcjBwMAwGA1UdEwEB / wQCMAAwHwYDVR0jBBgwFoAUNh3o4p2C0gEYtTJrDtdDC5FYQzowDgYDVR0PAQH / BAQDAgeAMB0GA1UdDgQWBBSpg4PyGUjFPhJXCBTMzaN + mV8k9TAQBgoqhkiG92NkBgUBBAIFADANBgkqhkiG9w0BAQUFAAOCAQEAEaSbPjtmN4C / IB3QEpK32RxacCDXdVXAeVReS5FaZxc + t88pQP93BiAxvdW / 3eTSMGY5FbeAYL3etqP5gm8wrFojX0ikyVRStQ + / AQ0KEjtqB07kLs9QUe8czR8UGfdM1EumV / UgvDd4NwNYxLQMg4WTQfgkQQVy8GXZwVHgbE / UC6Y7053pGXBk51NPM3woxhd3gSRLvXj + loHsStcTEqe9pBDpmG5 + sk4tw + GK3GMeEN5 { "signature" = "AmJ2SQJx5yZI+t1XRiPBmRVxuoj8jatJkQ+VHCiMLA3Vek48A45NR02AJRNJkKG9+Ry3YgPBjZxifwnYZv1Ylm18NFblnmgDkValnktoL+5wFHcZZGN6//cmHs8p/RwV/rt/91XKVhNl4XIBimKjQQNfgHsDs6yju++DrKJE7uKsphMddKYfFE5rGXsAdBEjBwRIxexTevx3HLEFGAt1moKx509dhxtiIdDgJv2YaVs49B0uJvNdy6SMqNNLHsDLzDS9oZHAgMBAAGjcjBwMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUNh3o4p2C0gEYtTJrDtdDC5FYQzowDgYDVR0PAQH/BAQDAgeAMB0GA1UdDgQWBBSpg4PyGUjFPhJXCBTMzaN+mV8k9TAQBgoqhkiG92NkBgUBBAIFADANBgkqhkiG9w0BAQUFAAOCAQEAEaSbPjtmN4C/IB3QEpK32RxacCDXdVXAeVReS5FaZxc+t88pQP93BiAxvdW/3eTSMGY5FbeAYL3etqP5gm8wrFojX0ikyVRStQ+/AQ0KEjtqB07kLs9QUe8czR8UGfdM1EumV/UgvDd4NwNYxLQMg4WTQfgkQQVy8GXZwVHgbE/UC6Y7053pGXBk51NPM3woxhd3gSRLvXj+loHsStcTEqe9pBDpmG5+sk4tw+GK3GMeEN5/+e1QT9np/Kl1nj+aBw7C0xsy0bFnaAd1cSS6xdory/CUvM6gtKsmnOOdqTesbp0bs8sn6Wqs0C9dgcxRHuOMZ2tm8npLUm7argOSzQ=="; "purchase-info" = "base64   "; "pod" = ""; "signing-status" = "0"; } 




We are interested in the signature. Here is its layout:



RECEIPTVERSION | SIGNATURE | CERTIFICATE SIZE | Certificate

1 byte 128 4 bytes ...



That is, normally, the certificate + signature in one bottle. OK, we put our certificate (just remember, the key length is 1024!), we sign with it, and voila, here’s a valid recipe for you. By the way, receipt_version + base64 is signed (purchase_info). And, by the way, unlike the Mac App Store recipe, there is only one certificate. And in the MAS recipe there is a chain there:



image



purchase_info consists of NSDictionary:

 { "original-purchase-date-pst" = "2012-07-28 07:30:19 America/Los_Angeles"; "purchase-date-ms" = "1343485819442"; "unique-identifier" = "xxxx"; "original-transaction-id" = "170000030394952"; "bvrs" = "1.0.0"; "app-item-id" = "522704697"; "transaction-id" = "170000030394952"; "quantity" = "1"; "original-purchase-date-ms" = "1343485819442"; "item-id" = "525477928"; "version-external-identifier" = "7736106"; "product-id" = "com.gameloft.TDKR.cashpack1"; "purchase-date" = "2012-07-28 14:30:19 Etc/GMT"; "original-purchase-date" = "2012-07-28 14:30:19 Etc/GMT"; "bid" = "com.gameloft.TDKR"; "purchase-date-pst" = "2012-07-28 07:30:19 America/Los_Angeles"; } 




and duplicates what is given in PLIST.



OH, RLY?


Suddenly it happened that you were given a transaction, but it did not pass? It happens, you say, for this you need to make another 1 GET request for p()-buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/inAppTransactionDone

and pass the transfer ID and GUID there:



 'transactionId' => '170000030394952', 'guid' => 'xxxxx', 




The following will return:



 <?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>jingleDocType</key><string>inAppSuccess</string> <key>jingleAction</key><string>inAppTransactionDone</string> <key>dsid</key><string>DSID (ID /?)</string> </dict> </plist> 




Everything! The purchase is completed, everyone is happy.

If you need to check the purchase from your server or from the application, use the apple mana , but it costs as much as everything above.



PS: Watch your head!




It is important to keep track of the headers and cookies, if you suddenly give away something, and not Apple Web Objects, there is no $ _COOKIE ['Pod'], then the application will curse and will not let you go any further.



Well, the very pulp, code!



You can pick it up with github . It is written crookedly, but it works. + Manual scan there.



Well, protection options






Currently, the service will not work precisely because the trust chain between certificates is rigidly set in the code from apple, => fake signatures will not work, but if you don’t check the signature, then everything will work.



And about androyd



The market breaks the connection, because certificates invalid. Who wants to help me, contact me, I have a couple of ideas.

Source: https://habr.com/ru/post/149207/



All Articles