tgalal / yowsup

The WhatsApp lib
GNU General Public License v3.0
7.06k stars 2.23k forks source link

Extracting WhatsApp password from Android devices #234

Closed whatsappnomedia closed 5 years ago

whatsappnomedia commented 10 years ago

This may be useful for someone:

WhatsApp stores the user credentials in a 'pw' file within the sandbox, while the 'me' file contains the Jabber ID. In addition, it stores on the SD card (in "Exteranal Storage/WhatsApp/Profile Pictures/.nomedia") a token that can be used for registration without confirmation via SMS.

Here's a small Python program to get you started:

from pbkdf2 import PBKDF2 from Crypto.Cipher import AES import base64 import time import javaobj import re

sauce1 = '\xc2\x99\x1e\xc2\x9b\x1d\x0c\xc2\xb8\xc3\xb7UdX\xc2\x98\xc2\x92\x03\xc2\x8bE\xc2\x97>x\xc3\x86\xc3\x95' sauce2 = 'S\x16\x0fR\x03\nD\xc2\x83\x10\xc2\x82\xc2\x9a\xc2\x8d\xc2\x8cF:!i)WA'

def do_decode(special_sauce, jabber_id, pw_file_content): salt = pw_file_content[2:6] iv = pw_file_content[6:22] encrypted_key = pw_file_content[22:] file_enc_key = PBKDF2(special_sauce + jabber_id, salt, 16).read(16) crypt = AES.new(file_enc_key, AES.MODE_OFB, iv) return crypt.decrypt(encrypted_key + '1'*12)[:20]

def decode_pw(mefname, pwfname): jabber_id = javaobj.JavaObjectUnmarshaller(open(mefname)).readObject().jabber_id pw = javaobj.JavaObjectUnmarshaller(open(pwfname)).readObject() pw_file_content = "".join([chr(c) if c>=0 else chr(c+256) for c in pw]) wa_key = do_decode(sauce1, jabber_id, pw_file_content) return base64.b64encode(wa_key)

C = re.compile("^([17]|2[07]|3[0123469]|4[013456789]|5[12345678]|6[0123456]|8[1246]|9[0123458]|\d{3})\d*?(\d{4,6})$")

def decode_nomedia(nomedia, pn, account): g = C.match(pn) e = g.group(1) + g.group(2) jabberid = e + account pw = javaobj.JavaObjectUnmarshaller(open(nomedia)).readObject() pw_file_content = "".join([chr(c) if c>=0 else chr(c+256) for c in pw]) return do_decode(sauce2, jabberid, pw_file_content)

print "%r" % (decode_nomedia(".nomedia", "12125551234", "your.google.account@gmail.com"),)

Want to back this issue? Post a bounty on it! We accept bounties via Bountysource.

whatsappnomedia commented 10 years ago

PS: the 'token' referred to above is the 'id' field for https://v.whatsapp.net/v2/code

shirioko commented 10 years ago

In addition, it stores on the SD card (in "Exteranal Storage/WhatsApp/Profile Pictures/.nomedia") a token that can be used for registration without confirmation via SMS.

Are you kidding me? That's so stupid >.< And here we were speculating about how to possibly retrieve/replicate/reverse engineer the registration ID...

whatsappnomedia commented 10 years ago

glad I could help ;)

Zeokat commented 10 years ago

I need to execute this python code on my Android device and do all the job, correct?

shirioko commented 10 years ago

No you need to extract the pw file from your Android device and run the code on your PC

whatsappnomedia commented 10 years ago

Note that both the 'pw' and '.nomedia' files are encrypted The provided code decrypts these files (the 'me' file is not encrypted).

debarko commented 10 years ago

Hey where is the pw and me file stored? How to retrieve them I found the .nomedia file, but couldn't find the pw and me file.

debarko commented 10 years ago

Also what is the 2nd parameter decode_nomedia(".nomedia", "12125551234", "your.google.account@gmail.com")

which is taken in as pn in the function definition

debarko commented 10 years ago

ok i am assuming that pn is the phone number. also i assume it includes the country code.

whatsappnomedia commented 10 years ago

pn is phone number - including country code the pw & me files are stored in the sandbox - not on the SD card

debarko commented 10 years ago

yeah i got the part that they are stored in sandbox. I just want to know how to access the sandbox, i want to fetch the files.

debarko commented 10 years ago

also i got something like |**_f\xfa\x0c\xacu\xdf\xdc****) *_* on running that file with my gmail account and phone number. Is it correct. Some encoding error is happening when i try to feed it to the yowsup library.

debarko commented 10 years ago

when i try to utf encode the stuff then it shows that there are some invalid starting bytes. :(

debarko commented 10 years ago

can you please explain where did you get the salt values?

debarko commented 10 years ago

@whatsappnomedia ^

debarko commented 10 years ago

any of you guys on IRC specially any one among @whatsappnomedia or @shirioko or @tgalal ?

shirioko commented 10 years ago

What is this "IRC" you speak of..?

debarko commented 10 years ago

Internet Relay Chat. Never heard of freenode servers? That's really interesting.. If you guys ever roam around in IRC then ping me on #yosapp channel I generally hang around there.

debarko commented 10 years ago

Hey @shirioko were you able to successfully use the above script to figure out the password of an existing account.

shirioko commented 10 years ago

IRC? That's the dark underground communication channel of the hacker scene. 2spooky4me

I've ported the script to C# but was unable to get a working password. I'll try using the original python script on my files to see if I get a different result.

debarko commented 10 years ago

I tried on my files and gave the password and id generated by the script to use yowsup but it failed. Auth fail.

debarko commented 10 years ago

@shirioko this is the same algorithm that is being used by Whatsapp Xtract. At least looked similar to me. http://forum.xda-developers.com/showthread.php?t=1583021

debarko commented 10 years ago

@whatsappnomedia dude were you successful in converting any workking password? I was confused only in one part I explain below: def decode_nomedia(nomedia, pn, account): g = C.match(pn) e = g.group(1) + g.group(2) jabberid = e + account pw = javaobj.JavaObjectUnmarshaller(open(nomedia)).readObject() pw_file_content = "".join([chr(c) if c>=0 else chr(c+256) for c in pw]) return do_decode(sauce2, jabberid, pw_file_content)

Here you create jabberid by using a sub part of phone number contac with email acc. But I think when I used yowsup I remember seeing jabberid being phone_number@s.whatsapp.net

So I am kind of confused. @shirioko can you help me solve this problem?

johnstern commented 10 years ago

Salt is extracted as follows (see the code):

salt = pw_file_content[2:6]

Seems the first 2 bytes (after unmarshalling) are not used @debarko: seems that for the 'nomedia' file, WhatsApp mixes in the Google account and phone number (after going through the regex) to create the encryption key for the file - I think 'jabberid' is incorrect. Got my hands on an Android and tried this on the '.nomedia' file - the original Python works (I was able to use the result as the 'id' parameter passed in request-code - no SMS verification). Not sure what WA version was on the phone. Note that 'nomedia' does not contain the password - you can only use it in registration to bypass the SMS verification (also, note that this unregisters Whatsapp on the phone - needed to re-register WhatsApp on the original Android phone :()

As for code being similar to Xtract: according to the Python code, the file encryption method is used for both the 'pw' file and the 'nomedia' file - but the details are different.

debarko commented 10 years ago

The id field which is returned by the above script is some bunch of values which definitely doesn't suit the yowsap id format in the file.

debarko commented 10 years ago

And if encryption is same then it shouldn't be a problem to find the pw from the file. But alas it doesn't work :( @johnstern

debarko commented 10 years ago

Join #yosapp on freenode to chat. @johnstern

johnstern commented 10 years ago

@debarko, Seems I've modified a bit the code - the original code didn't work. Oops. If you look at the decoding of 'pw', the returned value is in Base64 format:

return base64.b64encode(wa_key)

(I didn't try to decode 'pw' - getting to the sandbox seems non-trivial; at least, non-trivial for someone that knows nothing about Android). We need to do the same for id:

return  base64.b64encode(do_decode(sauce2, jabberid, pw_file_content))
debarko commented 10 years ago

@johnstern you can get your pw file from your android device easily if you have a rooted device in case of a rooted device just connect your phone to a machine with a terminal in terminal type the following commands (assuming you have android sdk installed)

  1. adb root
  2. adb pull /data/data/com.whatsapp/files/pw .
  3. adb pull /data/data/com.whatsapp/files/me .

now you should have both the files in your current directory. try if they work

i dont know how to pull these files for a non rooted device :(

johnstern commented 10 years ago

@debarko - don't have the Android anymore (and it ain't rooted)... Anyway, another small (but important detai): if you use the yowsup CLI app, it seems the 'id' goes through a processIdentity() function which performs passes it through MD5 - this will not work! you need to use the id as-is - it's already in the right format.

(I didn't use yowsup for my little experiment)

debarko commented 10 years ago

@johnstern ok cool! will try to improve on it.. and see when it works with existing apps

johnstern commented 10 years ago

@debarko, Oops - sorry. I think the base64 is wrong - it should be hex instead (it's quite late and doing this from memory...)

debarko commented 10 years ago

it's cool man! thanks for all the help @johnstern

shirioko commented 10 years ago

The "me" file only contains your phone number and not the Jabber ID, have you tried making it into a real JID by appending the domain to it? phonenumber@s.whatsapp.net

debarko commented 10 years ago

yes yes. i tried that. but to no gain.

johnstern commented 10 years ago

@debarko, I think that @whatsappnomedia's variable names are misleading. This is what the code seems to suggest: Both 'pw' and '.nomedia' files are encoded as follows (after unmarshalling): <some header - 2 bytes> <salt - 4 bytes> <iv - 16 bytes> <encrypted data - 20 bytes> In addition, we need a fixed "sauce" (if we have salt, why not sauce...) and the a "secret" that is unique per device. Now, we compute:

key = PBKDF2(sauce + secret, salt, 16).read(16)

that is - we concatenate the "sauce" and "secret" and use them as the passphrase for PBKDF2 using "salt" as salt, and HMAC, SHA1 as MAC and digest. The first 16 means we do 16 iterations of PBKDF2 (the 2nd just cuts the output at 128 bit - which is apparently the key size).

This key is then used to decrypt the 20 bytes encrypted data:

crypt = AES.new(key, AES.MODE_OFB, iv)
return crypt.decrypt(encrypted_data + '1'*12)[:20]

iv is what we read from the file. The weird '1'*12 seems to pad the length to 32 - as the output is then cut but to 20, it doesn't seem to make much sense (in OFB mode, the message to be ec/decrypted is xored with some stream generated by AES; the extra 12 bytes can't make any difference on the actual 20 bytes of data).

This is done for both 'pw' and '.nomedia' (code reuse, I guess...), but there are some differences:

>>> "0000".encode("hex")
'30303030'

Lastly, instead of looking at "sauce1" and "sauce2" as stings, we can look at the bytes (which I think would be more portable):

>>> sauce1 = '\xc2\x99\x1e\xc2\x9b\x1d\x0c\xc2\xb8\xc3\xb7UdX\xc2\x98\xc2\x92\x03\xc2\x8bE\xc2\x97>x\xc3\x86\xc3\x95'
>>> sauce2 = 'S\x16\x0fR\x03\nD\xc2\x83\x10\xc2\x82\xc2\x9a\xc2\x8d\xc2\x8cF:!i)WA'
>>> [ord(c) for c in sauce1]
[194, 153, 30, 194, 155, 29, 12, 194, 184, 195, 183, 85, 100, 88, 194, 152, 194, 146, 3, 194, 139, 69, 194, 151, 62, 120, 195, 134, 195, 149]
>>> [ord(c) for c in sauce2]
[83, 22, 15, 82, 3, 10, 68, 194, 131, 16, 194, 130, 194, 154, 194, 141, 194, 140, 70, 58, 33, 105, 41, 87, 65]

Hope this helps

johnstern commented 10 years ago

PS: if someone has a .nomedia file to play with, please post the 42 bytes here (along with the phone number and Google account) and let's see whether we can decrypt it.

WARNING: do not post details on your personal WhatsApp/phone.

shirioko commented 10 years ago

Here's my .nomedia file Base64 encoded:

rO0ABXVyAAJbQqzzF/gGCFTgAgAAeHAAAAAqAAJdgEBrOesj2nLvRhPHvTOvgOEZfpElLEJeh6Aydkc5gQo1cfpN4NU0

PN 316 474 85 673 maxkovaljov at gmail dot com

This is what my C# port makes of it:

53160F52030A44C28310C282C29AC28D92F780F7

but I was unable to use that identity to request a new password, and I was also unable to get Python to run on my PC >.< so let me know if the original Python code returns something different.

PS the phone number has now been registered to a new identity, so the extracted identity from the data I posted won't work anymore.

satup commented 10 years ago

I've tried extract your identity in Python and I've obtained three values

49202a6a49a3bb5b6d126d750063abc5---MD5
\xbd\xd4p\x1f\x17e\xe0\xa7\x19o\xebt4\xd5^oC\x95M--HEX (I think)
556f4e8bf1686252827fe7c527be8df11ecbf493--SHA1

So, check you if they're correct or not

I tried with my own files, and the pw works very well but I've not achieved to extract the id from the .nomedia

johnstern commented 10 years ago

This is what I get running the Python code:

python a.py
20bdd4701f1765e0a7196feb7434d55e6f43954d

(didn't try to see if it actually works).

This is what is sent into do_decode():

jabber_id: 31485673maxkovaljov@gmail.com
sause: 53160f52030a44c28310c282c29ac28dc28c463a2169295741
nomedia file: 00025d80406b39eb23da72ef4613c7bd33af80e1197e91252c425e87a032764739810a3571fa4de0d534

(hex encoded) Within do_decode(), these are the variable's values before the call to pbkdf2:

salt: 5d80406b
iv: 39eb23da72ef4613c7bd33af80e1197e
encrypted_key: 91252c425e87a032764739810a3571fa4de0d534

and the AES encryption key is:

file_enc_key: f341b410eefde993e2eda2f5c734e979
johnstern commented 10 years ago

As I've mentioned before, I think that to make this work you should use:

https://v.whatsapp.net/v2/code?cc=31&in=647485673&id=20bdd4701f1765e0a7196feb7434d55e6f43954d...
satup commented 10 years ago

I've obtained the same result you've obtained, but using this with my files, it doesn't work, so I think there's something wrong in the code, maybe the sauce or the jabberid

CODeRUS commented 10 years ago

stupid, but i can't find javaobj module for linux :)

johnstern commented 10 years ago

Hmm... the request code that I have seems to generate a bad token:

{u'status': u'fail', u'reason': u'bad_token'}

it should work for 2.11.151, though... So sorry, can't check further whether this works or not

johnstern commented 10 years ago

@satup, with your files, you should get a different result...

satup commented 10 years ago

@satup, with your files, you should get a different result...

Yes, I know, but I don't get my result (my identity)

CODeRUS commented 10 years ago

@droopy just learn python

droopy commented 10 years ago

@CODeRUS Thanks i have managed to do it myself as you seems only code for symbian jaja.

For decoding .nomedia which phone number format? same as appears inside "me" right? As i see 2 formats. a) international format b) local country format

Resutls shows: /// j\xa4\xd3.\xe2"\x06\xce\xeb"\xf5------- what to do with it? It is just one line. DO i need to replace this somewhere?

frannyvee commented 10 years ago

Can anyone help me get into a whatsapp account? or teach me to ? I would really appreciate it. E-mail me at comme.cartier@gmail.com

shirioko commented 10 years ago

Isn't that like... illegal and stuff?