Pastebin launched a little side project called VERYVIRAL.com, check it out ;-) Want more features on Pastebin? Sign Up, it's FREE!
Guest

Reverse Engineering: How WhatsApp (not) securing your data

By: a guest on Sep 12th, 2012  |  syntax: None  |  size: 12.16 KB  |  views: 59,730  |  expires: Never
download  |  raw  |  embed  |  report abuse  |  print
Text below is selected. Please press Ctrl+C to copy to your clipboard. (⌘+C on Mac)
  1. *****************************************************************************
  2. ******                                                                 ******
  3. ******    Reverse Engineering: How WhatsApp (not) securing your data   ******
  4. ******                                                                 ******
  5. *****************************************************************************
  6.  
  7.  
  8. ---> Moment of silence in memory of victims of 9/11 ---> ...........................................................
  9.  
  10.  
  11. Note: ATTENTION! This article is written in a kind of sarcastic manner. If you don't have a sense of humor it could be dangerous for your health, nerves, and self-confidence feeling. DON'T REED IT!
  12.  
  13.  
  14. *** DISCLAIMER ***
  15. THE RESEARCH IS BASED ON THE OWN SUBJECTIVE UNDERSTANDING OF MATTER AND IN NO WAY IMPLIES TO BE TRUE AND ACCURATE AS WELL TO COVER ALL ASPECTS.
  16.  
  17.  
  18.  
  19. *** Introduction ***
  20.  
  21. George Carlin said: "You wouldn't know it, from some of the things I've said over the years, but I like people... I do... I like people, but I like them in short bursts. I don't like people for extended periods of time. I'm all right with them for a little while, but once you get past around a minute, minute-and-a-half, I gotta get the f*** outta there. And my reason for this, my reason, the one that you wouldn't share possibly, I am very low tolerance level for stupid bullshit!"
  22.  
  23. And I would say I like WhatsApp, I really like it very much! But I have a very low tolerance level of bullshit. And if I hear WhatsApp guys say "your data is secure now" but don't say how secure (which cryptographic standards are used). Something tells my mind: "watch out, they try to bullshit you!".
  24.  
  25. Now! Do you know this joke? "What is the shortest prayer? – F*** it!"
  26.  
  27. That's exactly how I see the WhatsApp approach. They simply f*** the cryptographic best practices. You know... They just do "security-by-obscurity" or "cover-your-ass-security" or how they call it "commercially reasonable". They don't really protect your data, they just hide it. Oh, yeah, the master of disguise...
  28.  
  29. Well, I as a customer (the customer is a "king") then say back "screw you"! Just put in your statements "Your data is INSECURE" and this would be fine, but don't try to bullshit people!
  30.  
  31. However these are the reasons I did the research and publish the results. Enjoy!
  32.  
  33.  
  34.  
  35. *** WhatsApp Data Hiding ***
  36.  
  37. I. iOS Client Research
  38.  
  39. 0. Introduction
  40. Reverse engineered version of WhatsApp iOS client is v2.8.3 (2012/08/27)
  41.  
  42. 1. How encryption works?
  43.  
  44. 1.1 Cipher
  45. The data is being sent by WhatsApp generally designed to be encrypted on communication protocol (some kind of crippled XMPP) level. For binary stream encryption it uses RC4 symmetric cipher based on CCCryptor (1st concern 'cause it’s fast but old 1984(!) crap not recognized by NIST). Now remember the key word "symmetric", we will need it later.
  46.  
  47. 1.2 Key Handling
  48. RC4 requires a crypto key(stream)... What? Of course it is, as each cipher =) A "session key" is calculated as following (see CodeSnip #0):
  49. _sessionKey = PBKDF2(Password, Salt);
  50.  
  51. As you could see the key is derived from password and salt by PBKDF2 (based on HMAC-SHA1) algorithm. That's fine. The password is nothing else as device identifier "UDID" (2nd concern, see CodeSnip #1). The salt is a "session token" ("challenge") that provided by server at session negotiation and is used for all following crypto operations. This token is changed regularly on each session negotiation (see CodeSnip #4).
  52.  
  53. 1.3 Encryption Handling
  54. Well generally everything looks not bad. BUT! How do you protect/encrypt the "session token" while getting it from server?! Well, the answer is: it's not protected at all. So simple is that. Evidences? All encryption relevant methods have a boolean flag "encryption on/off". Now guess what? All right! It's off while session negotiation (3rd concern). See CodeSnip #6.
  55.  
  56. Ok, now what the attacker probably need the catch the session token while session negotiation and know the UDID, and of cause read this article ;-) A task for MacGyver? I think not... especially if to consider what AntiSec published recently =)) (~12 millions UDID, https://twitter.com/@AnonymousIRC)
  57.  
  58. Something else you have to know
  59. Messages/streams are signed with HMAC-SHA1 (CCHmac(kCCHmacAlgSHA1)) using the same "session key".
  60. URLs for server requests are hidden by encoding with passphrase aesEncodeWithPassphrase("landscape") (see CodeSnip #2)
  61. Data (current session token etc.) stored locally in NSUserDefaults are normally hidden by encoding with aesEncodeWithPassphrase(MD5("s.whatsapp.net")) (see CodeSnip #3)
  62.  
  63.  
  64. II. Android Client Research
  65.  
  66. 0. Introduction
  67. I have not focused so much on Android client. Because it is heavily obfuscated and requires significant more effort to reverse ( and also 'cause Android sucks =)) ) However I found something interesting. Analyzed version of WhatsApp Android client is v2.8.3383 (2012/no/idea).
  68.  
  69. 1. How encryption works?
  70.  
  71. 1.1. Cipher
  72. Well, what I found is an AES usage for en- and decrypting of data streams based on "javax.crypto" library. The obfuscated class name is "t4".
  73.  
  74. 1.2. Key Handling
  75. The key used for AES initialization above is pre-shared as 24 chars long (192 bits key length) ANSI encoded string constant and hidden by obfuscation (but was successfully DE obfuscated =)) ). This constant has obfuscated name "k" and stored in class called "x7" (see CodeSnip #6).
  76.  
  77. 1.3 Encryption Handling
  78. The most curious thing I found is... the piece of encryption code I described above is not used or let’s say not referenced across rest of client code. Reasons?
  79. 1st: My Android client analysis sucks -> nobody is perfect =))
  80. 2nd: Code is there but not used (not yet)
  81. 3rd: I suck again and didn't find any reference to the code because of heavy obfuscation
  82.  
  83.  
  84.  
  85. *** (Executive) Summary ***
  86.  
  87. WhatsApp decided to "protect" your data. But because of some unknown reasons (called "commercial reasonable effort") and probably lack of knowledge of the cryptographic best practice and Claude Shannon' works they only managed to hide your data (and not hide the crypto keys). And this only in the iOS client?
  88. IT IS NOT A SECURING - IT IS HIDING!
  89. However there are some best practices that could help make the product better, protect customer data, crypto keys and DON'T BULLSHIT PEOPLE!
  90.  
  91. Concern 1: Use of RC4
  92. Recommendation 1: Well, it's debatable, but I don't like RC4 on other hand I think that's only one available stream cipher implementation by CCCryptor. I believe it is possible to create a "secure enough" encryption on RC4 basis, if you are careful about how you use it, but I wouldn't recommend it. AES (128) with counter stream mode looks much better to me not only from security but also from performance view. (AES is better for small data blocks what short text messages actually are. Surprise-surprise! Look at this: "P. Prasithsangaree and P. Krishnamurthy (2003). Analysis of Energy Consumption of RC4 and AES Algorithms in Wireless LANs")
  93.  
  94. Concern 2: Use of UDID as part of symmetric crypto key
  95. Recommendation 2: STOP USING UDID - Apple advised! Use random generated symmetric key for data encryption along with asymmetric cryptography for keys exchange (hybrid cryptosystem, see #3 below).
  96.  
  97. Concern 3: Unprotected session key negotiation (symmetric encryption is good but how to protect key exchange between client and server)
  98. Recommendation 3: Heard about Diffie–Hellman key exchange? (based on the asymmetric cryptography )
  99.  
  100. @WhatsApp:  Security means Availability, Integrity, Confidentiality but NOT STENOGRAPHY!
  101.  
  102.  
  103.  
  104. *** P.S. ***
  105.  
  106. Thanks for your patience and do not let bullshit you! Peace!
  107. Independent Security Analyst
  108. 9B4BD395DC958F11A57FD0F1E26FCA3E0535E50B75AB2FC8765816E9F3CEEF9031C4AEF5A63AABA127A166F94CCACC640AEB725F8E191A61780AD1C11D51FEED
  109.  
  110. b00t55@yahoo.com
  111.  
  112.  
  113.  
  114. *** MIRROR ***
  115. http://snipurl.com/24yxr2r
  116.  
  117.  
  118.  
  119. *** CodeSnip #0 ***
  120.  
  121. // XMPPBinaryCoder - (void)setToken:(id) andPassword:(id)
  122. void __cdecl __XMPPBinaryCoder_setToken_andPassword__(struct XMPPBinaryCoder *self, SEL a2, id _token, id _password)
  123. {
  124.   struct XMPPBinaryCoder *this; // r4@1
  125.   this = self;
  126.   v5 = _password;  // Password
  127.   v6 = _token;     // Salt
  128.   objc_msgSend(self, "clean");
  129.   derived_key = objc_msgSend(&OBJC_CLASS___XMPPEncryption, "PBKDF2WithPassword:andSalt:", v5, v6); // Key Derivation Function
  130.   this->_sessionKey = (struct NSData *)objc_msgSend(derived_key, "retain");
  131.   this->_token = (struct NSData *)objc_msgSend(v6, "copy");
  132.   v8 = objc_msgSend(this->_encryption, "incoming");
  133.   objc_msgSend(v8, "reset:", this->_sessionKey);   // set crypto key for _INCOMING
  134.   v9 = objc_msgSend(this->_encryption, "incoming");
  135.   objc_msgSend(v9, "drop:", 256);                  // encryption rounds number, RC4 - standard 256
  136.   v10 = objc_msgSend(this->_encryption, "outgoing");
  137.   objc_msgSend(v10, "reset:", this->_sessionKey);  // set crypto key for _OUTGOING
  138.   v11 = objc_msgSend(this->_encryption, "outgoing");
  139.   objc_msgSend(v11, "drop:", 256);                                 // encryption rounds number, RC4 - standard 256
  140. }
  141.  
  142.  
  143. *** CodeSnip #1 ***
  144.  
  145. XMPPConnection - (id)macPassword
  146. id __cdecl __XMPPConnection_macPassword_(struct XMPPConnection *self, SEL a2)
  147. {
  148.   void *v2;
  149.   v2 = objc_msgSend(&OBJC_CLASS___UIDevice, "currentDevice");
  150.   return (id)objc_msgSend(v2, "deviceIdentifier");
  151. }
  152.  
  153. *** CodeSnip #2 ***
  154. _codeRequestURL = objc_msgSend(ptrToEncStr_1, "aesDecodeWithPassphrase:", CFSTR("landscape"));
  155. _registrationURL  = objc_msgSend(ptrToEncStr_2, "aesDecodeWithPassphrase:", CFSTR("landscape"));
  156.  
  157.  
  158. *** CodeSnip #3 ***
  159. id __cdecl __NSString_Crypto__encodedString_(struct NSString *self, SEL a2)
  160. {
  161.   v2 = objc_msgSend(self, "dataUsingEncoding:", NSUTF8StringEncoding); // NSUTF8StringEncoding = 4
  162.   v3 = objc_msgSend(CFSTR("s.whatsapp.net"), "md5");
  163.   v4 = objc_msgSend(v2, "aesEncodeWithPassphrase:", v3);
  164.   return (id)objc_msgSend(v4, "base64Encoded");
  165. }
  166.  
  167. *** CodeSnip #4 ***
  168.  
  169. XMPPStream - (void)processElements(id)
  170. void __cdecl __XMPPStream_processElements__(struct XMPPStream *self, SEL a2, id a3)
  171. {
  172.         // this = XMPPStream object
  173.         ...
  174.         objc_msgSend(this->_delegate, "logStanza:recv:", v8, 1);
  175.         v10 = objc_msgSend(v8, "name");
  176.         if ( (unsigned int)objc_msgSend(v10, "isEqualToString:", CFSTR("challenge")) & 0xFF )
  177.         {
  178.                 v11 = this;
  179.                 v12 = this->_binaryCoder;
  180.                 _sesstoken = objc_msgSend(v8, "value");
  181.                 v14 = objc_msgSend(v11, "xmppPassword"); // get global "password" = UDID
  182.                 _pass = objc_msgSend(v14, "dataValue");
  183.                 objc_msgSend(v12, "setToken:andPassword:", _sesstoken, _pass); // use challenge value as session token
  184.                 ...
  185.         }
  186.         ...
  187.        
  188.  
  189. *** CodeSnip #5 ***
  190. // XMPPBinaryCoder - (id)serialize:(id) encrypted:(char)
  191. id __cdecl __XMPPBinaryCoder_serialize_encrypted__(struct XMPPBinaryCoder *self, SEL a2, id a3, char _boolFlag)
  192. {
  193.   this = self;
  194.   // boolean flag, set to 0 when calling by XMPPStream onSocket:didConnectToHost:port &
  195.   bFlag = _boolFlag;  
  196.   v6 = a3;
  197.   v7 = objc_msgSend(&OBJC_CLASS___NSMutableData, "data");
  198.   data = objc_msgSend(this, "encodeList:", v6);   // serialize/encode
  199.   if ( data )                                  
  200.   {
  201.     ...
  202.     objc_msgSend(v7, "appendBytes:length:", &_data, 3, *(_DWORD *)&_data);
  203.     if ( bFlag ) // if true encrypt otherwise not
  204.     {
  205.       v11 = objc_msgSend(this->_encryption, "outgoing");
  206.       enc_data = objc_msgSend(v11, "process:", data); // encrypt DATA
  207.       data = enc_data;
  208.       v13 = objc_msgSend(                       // calculate HMAC
  209.               &OBJC_CLASS___XMPPEncryption,
  210.               "MACWithKey:andData:",
  211.               this->_sessionKey,
  212.               enc_data,
  213.               *(_DWORD *)&_dataLen);
  214.       objc_msgSend(v7, "appendData:", v13);
  215.       v14 = "appendData:";
  216.     }
  217.     else
  218.     {
  219.       v14 = "appendData:";
  220.     }
  221.     objc_msgSend(v7, v14, data);
  222.   }
  223.   return (id)v7;
  224. }
  225.        
  226.        
  227. *** CodeSnip #6 ***    
  228.        
  229. private CryptoEngine() {
  230.     try {
  231.  
  232.     SecretKeySpec aes192_key = new SecretKeySpec(x7.k.getBytes("ANSI"), "AES"); // AES key = x7.k = 24 bytes = 192 bits
  233.     this.Encryptor = Cipher.getInstance("AES");
  234.     this.Decryptor = Cipher.getInstance("AES");
  235.     this.Encryptor.init(EncryptMode, aes192_key);
  236.     this.Decryptor.init(DecryptMode, aes192_key);
  237.     this.EngineSuccessfullyInitialized = true;