venomous0x / WhatsAPI

Interface to WhatsApp Messenger
2.59k stars 2.14k forks source link

WP7 token register: old_version #423

Closed AmirGolan85 closed 10 years ago

AmirGolan85 commented 11 years ago

Hey, i tried to register after my first account got blocked and got this messaeg from the server:

stdClass Object ( [status] => fail [reason] => old_version )

I changed back to Android Token and it worked.. So please update the API or fix the WP7 token.. Thanks :)

jonnywilliamson commented 11 years ago

Maybe you could fix the WP7 token?

shirioko commented 11 years ago

AFAIK @JayFoxRox is the only one around here with the skills to do this. HE IS THE CHOSEN ONE!

jonnywilliamson commented 11 years ago

@JayFoxRox - We miss you! (Or at least your assembly knowledge!) ;)

Dynogic commented 11 years ago

@shirioko,

As I recall from reading previous issues, the token must be extracted from WhatsAppNative.dll?

shirioko commented 11 years ago

That's correct.

Dynogic commented 11 years ago

Mind uploading the latest unencrypted XAP?

shirioko commented 11 years ago

@JayFoxRox didn't have the time yet to patch the native dll, so in the meantime I've added a small service to my upcoming website (http://mywapi.nl) to generate code request tokens for WP7 2.10.506.0

https://mywapi.nl/?in={number without cc}

e.g.

https://mywapi.nl/?in=650568134

I won't promise that it will stay up, it's not quite "stable" at the moment :') The website is basically an online version of MissVenom (running MightyVenom, an extended version) which allows you to capture registration details. I still need to add a user backend to login and retrieve their password, as well as a number verification mechanism to verify the ownership of a number before getting the captured password.

Dynogic commented 11 years ago

@shirioko, had a look at WhatsAppNative...

GUID's are now in the WAobs segment (everything is) (GUID's use to be in the data section). That mean's they're obfuscated ... IByteBuffer's GUID in WhatsApp.dll is != to what it was on a dll-dump on the previous version.

.WAobs:0008F3E8 IByBufGUIDOb    DCB 0x47 ; G            ; DATA XREF: .data:OffIByBufGUIDObo
.WAobs:0008F3E9                 DCB 0x45 ; E
.WAobs:0008F3EA                 DCB 0xA4 ; ñ
.WAobs:0008F3EB                 DCB 0x55 ; U
.WAobs:0008F3EC                 DCB 0xC1 ; -
.WAobs:0008F3ED                 DCB 0x51 ; Q
.WAobs:0008F3EE                 DCB 0xF1 ; ±
.WAobs:0008F3EF                 DCB 0x1C
.WAobs:0008F3F0                 DCB 0xE1 ; ß
.WAobs:0008F3F1                 DCB 0xF3 ; =
.WAobs:0008F3F2                 DCB 0xDF ; ¯
.WAobs:0008F3F3                 DCB 0x61 ; a
.WAobs:0008F3F4                 DCB 0x92 ; Æ
.WAobs:0008F3F5                 DCB 0xFD ; ²
.WAobs:0008F3F6                 DCB  0xD
.WAobs:0008F3F7                 DCB 0xD8 ; +
        public static void Main (string[] args)
        {
            var guidBytes = "47 45 A4 55 C1 51 F1 1C  E1 F3 DF 61 92 FD 0D D8";

            var guidBytesParts = guidBytes.Split (' ');

            List<byte> bytes = new List<byte>();
            foreach (var guidBytePart in guidBytesParts)
            {
                if (string.IsNullOrWhiteSpace(guidBytePart))
                    continue;

                var @byte = byte.Parse(guidBytePart, NumberStyles.HexNumber, CultureInfo.InvariantCulture);
                bytes.Add(@byte);
            }

            Console.WriteLine (new Guid(bytes.ToArray()));
        }

Not equal.

Also, interesting enough... a bunch of Function calls are not in WAOb too... Lol

I assume they're using a commercial ob'er. Not really familiar with ARM Ob'ers... but there's no VTable hook to change these GUIDs around spontaneously (fuck - that's what I would of done). Consequently, the VTable refers to to the GUID in WAobs directly. Therefore, there must be something at DLL load that converts all obbed data into real data. Haven't located this yet... but let's pray to the Lords that it calls Win32's cryptography functions (then it'll be easy to reverse engineer). Otherwise I'll have to rewrite in C/Assembly and gcc on a Raspberry Pi or Emulator...

Also, GetToken in this version doesn't rely on Win32's cryptography functions, NewBuild: newbuild OldBuild that Jay patched & you ran: oldbuild

... 10 minutes later after a washroom break ...

GetToken is sub_187D4 ... after snooping around it more. I looked at it for a few minutes, and it looks to be using this structure: http://msdn.microsoft.com/en-us/library/windows/desktop/ms737530(v=vs.85).aspx

So... I'm pretty positive GetToken is connecting to the internet...

I'll figure out how to de-ob the WaOb strings tomorrow. I see there's xref's between them and GetToken ... so one of them must be a host... then it should be smooth sailing.

Which means, your patching of returning the token may not work anymore (as the token sits on the server (further explains why there's no encryption in GetToken)). Unless, of course, Jay did already and I seem to be wrong!

That is all! Thank God for that Logic Design class I took last semester.

Edit: if all else fails... we're hacking the Java version! Attaching a debugger or modifying the SMALI code should work. I use to do a lot of BCEL/ASM work... so it shouldn't be too tough. xD

shirioko commented 11 years ago

What if I told you that I can generate tokens in airplane mode? screen capture

Dynogic commented 11 years ago

Oooooooooo :P

Still probing the DLL with a patched DLL ? How does GetToken in WhatsAppNative exactly work then ?

I'll see if I can extract that algo.

Dynogic commented 11 years ago

@shirioko does the probing still work if you reload the app? i.e, rebooting your phone in airplane mode?

shirioko commented 11 years ago

Yep

Dynogic commented 11 years ago

@shirioko ahh, I see... my structures were wrong,

http://msdn.microsoft.com/en-us/library/windows/desktop/aa365740(v=vs.85).aspx

... it must open a file.

"Still probing the DLL with a patched DLL ? How does GetToken in WhatsAppNative exactly work then ?"

shirioko commented 11 years ago

WhatsAppNative.dll appears to be using a new (proprietary?) hashing algorithm according to Jannik. So in the meantime you can either place your bets on Android credentials or use WP7 credentials and generate the WP7 code request token using my web service. I mean how often are you really going to need to request an SMS code?? :')

The service crashes quite a lot though so I need to keep a close eye on it. Hopefully I'll be able to fix this soon.

PS I've encrypted the WP7 archives on my server, don't want any nosy people looking at the files I obtained by breaking WhatsApp's EULA haha

Dynogic commented 11 years ago

^^ send me the encryption key?

dynogic@gmail.com

What is Jannik's Github name?

shirioko commented 11 years ago

@Dynogic email sent, and https://github.com/JayFoxRox

Dynogic commented 11 years ago

@shirioko, Still working on a reverse :') I'll brainstorm with @JayFoxRox .. the prefix still seems to be located in WAObs... just a matter of decrypting WAObs I think, then we can just do an entire segment dump. And we now have GUIDs as a regression test.

Dynogic commented 11 years ago

@shirioko , thanks!

Dynogic commented 11 years ago

@shirioko , it'll be nice if you can look at the reply I sent you over email =)

JayFoxRox commented 11 years ago

I said they probably use a new hash algo. Not entirely sure though. The algorithm is not too complicated either but we would have to dump the constants.

Here are my findings (Figured out these without Dynogics post above, so partial confirmation about his findings):

.text:000187D4: IMiscGetToken .text:00014078 getIByteBufferDataPointer .text:00017B54 printError .text:0001B3B4 The new algo which I assume to be a new hash, parameters: getTokenParameter, getTokenParameterLength, outputPointer, outputLengthPointer .text:00014354 createIByteBuffer_ .text:00014040 setIByteBufferDataPointers

I don't have time to work on WhatsApp at the moment. I'll be back when things get more serious (the other token not working) or in a couple of weeks maybe. At a first glance I didn't see any function imports - so the hash algo is completly made of simple logic. I first assumed it was a cloneByteBuffer.

Dynogic commented 11 years ago

@JayFoxRox

Okay... My encryption findings:

DllImport calls .text:00017B54 , which looks as if it decrypts the WAObs. This, in turn calls a Cipher function, .text:00017B54. This Cipher function seems to be used for A LOT (including multiple times in GetToken - i'll get to that). To an xref on it - the tree is massive. So, I believe WAObs are all decrypted here - which explains why GUID's are still able to resolve at runtime.

Now, the GetToken function is more difficult than it looks at first glance. I started documenting on a piece of paper, and I ended up just loosing track. xD

Firstly, GetToken get's passed in an unencrypted byte buffer (I can see this from the managed code). GetByteBufferPointer is then called that writes out to

#0x2C+var_1C
#0x2C+var_18
#0x2C+var_24

In GetByteBufferPointer, an obbed constant array, a constant 0x80070057 (i think it's a cipher/decipher control), an unobbed constant array are loaded in, and the pointer to GetIBufferBytes - then the Cipher is called. Same Cipher that's in DLLImport. Control flow REALLY get's ridiculous in the Cipher... just outside the Cipher routine, LR is set to a pointer (dword_95008) that points to something that gets changed dynamically (check the ref count on this - it's written to just before every call to Cipher - including in DLLImport). As soon as cipher is finished, you get this weird stack unravel:

LDMFD   SP!, {R3,LR}
ADD     SP, SP, #0x10

Which then throws control flow to =dword_95008.

... then weird stuff happens and I am clueless without a debugger. But I would guess it does something (set variables on the stack - in GetTokens case: var_1c, var_18, var_24) and then just jumps back into its parent routine (GetToken in this case).

Once we're back in GetToken... yet another encryption method runs on the data :

.text:0001B3B4 sub_1B3B4

This one has several nested routines just like Cipher too.

Then after that, everything get's wrapped by Cipher yet AGAIN (using the obbed prefix constant array, an unobbed constant array, and a constant). You can verify the prefix from the weird offset calculations it does. Same length as what we're use to.

Also, to note, there's a stack offset (44bytes) in GetToken. This looks to be used by the Cipher. But I haven't closely examined everything else the Cipher uses. Maybe it's used by whatever =dword_95008 points too.

So... that's it. I give up :( This thing has been seriously obfuscated.

I'm just going to hook the Java code and call it at my disposal. That's a lot easier. :D:D:D

AmirGolan85 commented 11 years ago

Hey, the server gives me this message back for voice and for sms: stdClass Object ( [status] => sent [length] => 6 [method] => voice [retry_after] => 1805 )

But i cant get any voice call or sms from the servers.. (Using the android token) any idea why?

shirioko commented 11 years ago

Oh look another update for WhatsApp this morning. 2.10.523.0 Let's see what they changed this time.

Dynogic commented 11 years ago

@AmirGolan85 , I'm working on it... hooking the Java client as we speak. Will be done by tomorrow. It's extremely easy. I'm refactoring Java decompiled code like a mad dog with BCEL/ASM.

Dynogic commented 11 years ago

@shirioko :D:DD:D:D

Dynogic commented 11 years ago

@AmirGolan85 , I'll try to make it a web service... as I'm just going to bootstrap the Java bytecode that dex2jar converted for me.

shirioko commented 11 years ago

It's funny how extremely capable people like Jake and Jannik just pop up out of nowhere without any back story :')

Dynogic commented 11 years ago

@shirioko I change my username on such a frequent basis ;) I've been sued before :'(

Dynogic commented 11 years ago

Lawl. I like how JD just bombs on the BuildHash

private static byte[] a(File paramFile)
  {
    // Byte code:
    //   0: aload_0
    //   1: invokevirtual 434   java/io/File:exists ()Z
    //   4: ifeq +82 -> 86
    //   7: aload_0
    //   8: invokevirtual 438   java/io/File:length ()J
    //   11: lconst_0
    //   12: lcmp
    //   13: ifle +73 -> 86
    //   16: new 440    java/io/ObjectInputStream
    //   19: dup
    //   20: new 442    java/io/FileInputStream
    //   23: dup
    //   24: aload_0
    //   25: invokespecial 443  java/io/FileInputStream:<init>  (Ljava/io/File;)V
    //   28: invokespecial 446  java/io/ObjectInputStream:<init>    (Ljava/io/InputStream;)V
    //   31: astore_1
    //   32: aload_1
    //   33: invokevirtual 450  java/io/ObjectInputStream:readObject    ()Ljava/lang/Object;
    //   36: checkcast 382  [B
    //   39: checkcast 382  [B
    //   42: astore 6
    //   44: aload_1
    //   45: ifnull +7 -> 52
    //   48: aload_1
    //   49: invokevirtual 451  java/io/ObjectInputStream:close ()V
    //   52: aload 6
    //   54: areturn
    //   55: astore 7
    //   57: aload 7
    //   59: invokestatic 453   com/whatsapp/pbb:d  (Ljava/lang/Throwable;)V
    //   62: aload 6
    //   64: areturn
    //   65: astore_2
    //   66: aconst_null
    //   67: astore_1
    //   68: getstatic 209  com/whatsapp/ik:z   [Ljava/lang/String;
    //   71: bipush 15
    //   73: aaload
    //   74: aload_2
    //   75: invokestatic 406   com/whatsapp/pbb:b  (Ljava/lang/String;Ljava/lang/Throwable;)V
    //   78: aload_1
    //   79: ifnull +7 -> 86
    //   82: aload_1
    //   83: invokevirtual 451  java/io/ObjectInputStream:close ()V
    //   86: aconst_null
    //   87: areturn
    //   88: astore 5
    //   90: aload 5
    //   92: invokestatic 453   com/whatsapp/pbb:d  (Ljava/lang/Throwable;)V
    //   95: goto -9 -> 86
    //   98: astore_3
    //   99: aconst_null
    //   100: astore_1
    //   101: aload_1
    //   102: ifnull +7 -> 109
    //   105: aload_1
    //   106: invokevirtual 451 java/io/ObjectInputStream:close ()V
    //   109: aload_3
    //   110: athrow
    //   111: astore 4
    //   113: aload 4
    //   115: invokestatic 453  com/whatsapp/pbb:d  (Ljava/lang/Throwable;)V
    //   118: goto -9 -> 109
    //   121: astore_3
    //   122: goto -21 -> 101
    //   125: astore_2
    //   126: goto -58 -> 68
    //
    // Exception table:
    //   from   to  target  type
    //   48 52  55  java/io/IOException
    //   16 32  65  java/lang/Exception
    //   82 86  88  java/io/IOException
    //   16 32  98  finally
    //   105    109 111 java/io/IOException
    //   32 44  121 finally
    //   68 78  121 finally
    //   32 44  125 java/lang/Exception
  }
shirioko commented 11 years ago

Isn't the build hash just the MD5 of classes.dex?

Dynogic commented 11 years ago

Yep. But I have an old version of JD-Gui. I quit Java about a year ago :dancers: I see a new version is out, but the download server is down for it. =( JD-Gui crashed half-way through on me on source export... so half my files are missing. xD

ik.java contains on WA2.10.222 contains the cryptography functions.

private static byte[] a(File paramFile, String paramString)
  {
    byte[] arrayOfByte1 = a(paramFile);
    if (arrayOfByte1 != null)
    {
      try
      {
        if (arrayOfByte1.length < 20 + (16 + (4 + b)))
          throw new InvalidParameterException(paramFile.toString() + z[80]);
      }
      catch (Exception localException)
      {
        paramFile.delete();
        throw new RuntimeException(localException);
      }
      byte[] arrayOfByte2 = new byte[b];
      System.arraycopy(arrayOfByte1, 0, arrayOfByte2, 0, b);
      int m = 0 + b;
      if ((arrayOfByte2[0] != 0) || (arrayOfByte2[1] != 1))
        throw new InvalidParameterException(paramFile.toString() + z[78]);
      byte[] arrayOfByte3 = new byte[4];
      System.arraycopy(arrayOfByte1, m, arrayOfByte3, 0, 4);
      int n = m + 4;
      byte[] arrayOfByte4 = new byte[16];
      System.arraycopy(arrayOfByte1, n, arrayOfByte4, 0, 16);
      int i1 = n + 16;
      SecretKeySpec localSecretKeySpec = new SecretKeySpec(a(paramString, arrayOfByte3), z[79]);
      Cipher localCipher = Cipher.getInstance(z[79]);
      localCipher.init(2, localSecretKeySpec, new IvParameterSpec(arrayOfByte4));
      byte[] arrayOfByte5 = localCipher.doFinal(arrayOfByte1, i1, arrayOfByte1.length - i1);
      return arrayOfByte5;
    }
    return null;
  }

  private static byte[] a(String paramString, byte[] paramArrayOfByte)
    throws NoSuchAlgorithmException
  {
    return new SecretKeySpec(a(paramString.getBytes(), paramArrayOfByte, 16, 128), z[2]).getEncoded();
  }

  private static byte[] a(byte[] paramArrayOfByte1, byte[] paramArrayOfByte2, int paramInt1, int paramInt2)
  {
    tb localtb = new tb();
    localtb.a(paramArrayOfByte1, paramArrayOfByte2, paramInt1);
    return ((rb)localtb.a(paramInt2)).a();
  }

  public static byte[] a(byte[] paramArrayOfByte, byte[][] paramArrayOfByte1)
  {
    boolean bool = DialogToastActivity.f;
    try
    {
      Mac localMac = Mac.getInstance(z[76]);
      localMac.init(new SecretKeySpec(paramArrayOfByte, z[76]));
      int m = paramArrayOfByte1.length;
      int n = 0;
      do
      {
        if (n >= m)
          break;
        localMac.update(paramArrayOfByte1[n]);
        n++;
      }
      while (!bool);
      byte[] arrayOfByte = localMac.doFinal();
      return arrayOfByte;
    }
    catch (GeneralSecurityException localGeneralSecurityException)
    {
    }
    throw new RuntimeException(localGeneralSecurityException);
  }

And there's a few more... but that should be the meat of it.

In verifynumber.class,

static bu a(String paramString1, String paramString2, Locale paramLocale, String paramString3, String paramString4)
    throws IOException, HttpException, JSONException, Exception
  {
    String str1 = z[6];
    Object localObject = z[6];
    String str2 = App.fb.getNetworkOperator();
    String str3 = ik.a(paramString2);
    Matcher localMatcher;
    if (str2 != null)
    {
      localMatcher = Pattern.compile(z[8]).matcher(str2);
      if (localMatcher.find())
        str1 = localMatcher.group(1);
    }
    while (true)
    {
      try
      {
        Locale localLocale = Locale.US;
        String str5 = z[9];
        Object[] arrayOfObject = new Object[1];
        arrayOfObject[0] = Integer.valueOf(localMatcher.group(2));
        String str6 = String.format(localLocale, str5, arrayOfObject);
        localObject = str6;
        str4 = str1;
        return zt.a(paramString1, paramString1 + paramString2, paramString2, (String)localObject, str4, paramString3, paramString4, str3);
      }
      catch (NumberFormatException localNumberFormatException)
      {
        pbb.d(z[7] + localNumberFormatException.toString());
        if (!DialogToastActivity.f)
          break label216;
      }
      pbb.b(z[10] + str2);
      label216: String str4 = str1;
    }
  }

LOL at the non-obbed name... that's where the request is made ... zt contains all the WebRequest code (like WARequest.py).

The return value bu, has a member of type vh... which lists everything that can go wrong. including the infamous... you are blocked. (honestly, fuck these guys).

Also, these guys have a full list of rom-names (you get a warning for using a custom rom). I loled when I saw those strings being injected.

Dynogic commented 11 years ago

@shirioko .. check your emailz!

Dynogic commented 11 years ago

Alright. I should have a proof of concept pretty quickly up. WhatsApp uses a pathetic Java obber.... things are already clear as snow. =)

Dynogic commented 11 years ago

^^ And maybe, I'll write an updater too... =)

atans commented 11 years ago

@Dynogic fighting...

Dynogic commented 11 years ago

@atans more like... sleeping zzzz

atans commented 11 years ago

@Dynogic I can give you some account for test! Orz....

Dynogic commented 11 years ago

Tomorrow... when I can send one to my second phone :P

I'll implement the Java hooks now. My brain is fried after all that ARM research.

dynogic@gmail.com

Message me =)

Dynogic commented 11 years ago

@atans

atans commented 11 years ago

@Dynogic Sent.

shirioko commented 11 years ago

Meh. 523 only addled datatype double to BinarySettings and some code cleanups. Another update without a noteworthy changelog. Oh and a new WhatsAppNative.dll

Dynogic commented 11 years ago

@shirioko @JayFoxRox @atans et al:

Cracked. It was quite trivial after I got all the obbed code mapped out. If anyone is curious on how it was done, reply here and I'll be happy to explain. I just don't feel like typing up a report as on now. :/

Tested against my phone for SMS and Voice... working. Also testing some bullshit number, and it said "sent". I''m not sure if it works completely for new registrations though.

MNC and MCC it does send (contrary to the WP version). So be careful of that. ID is also some static value... is this secure? Or no? It also sends a reason=self-send-jailbroken. Even though my phone isn't jailbroken. LOL

Token is still wrapped up the same way. I rebuilt a small part of the WA's Dalvik bytecode to make sure. One interesting thing to note: There seems to be two longHashes (you'll know what the longHash is when you look at the code below). There's a flag (e)

    if (App.e == 3)
    {
      return mfb.q;
    }
    else
    {
    return mfb.r;
    }

Where if e is is 3, then a different longHash is used to calculate the token. This is only triggered though if some exception occurs on App creation. Haven't explored it much... so I don't really know.

Credit me if used. Regards.

RegisterRequest should work as usual.

Code:

using System;
using System.Security.Cryptography;
using System.Text;
using System.IO;

namespace CodeRequest
{
    class MainClass
    {
        static string _dexHash = "8ac295a1bc5c0df1147db1ad2172b776";
        static string _longHash = "30820332308202f0a00302010202044c2536a4300b06072a8648" + 
            "ce3804030500307c310b3009060355040613025553311330110603550408130a43616" + 
                "c69666f726e6961311430120603550407130b53616e746120436c617261311630" + 
                "14060355040a130d576861747341707020496e632e31143012060355040b130b4" + 
                "56e67696e656572696e67311430120603550403130b427269616e204163746f6e" + 
                "301e170d3130303632353233303731365a170d3434303231353233303731365a3" + 
                "07c310b3009060355040613025553311330110603550408130a43616c69666f72" + 
                "6e6961311430120603550407130b53616e746120436c617261311630140603550" + 
                "40a130d576861747341707020496e632e31143012060355040b130b456e67696e" + 
                "656572696e67311430120603550403130b427269616e204163746f6e308201b83" + 
                "082012c06072a8648ce3804013082011f02818100fd7f53811d75122952df4a9c" + 
                "2eece4e7f611b7523cef4400c31e3f80b6512669455d402251fb593d8d58fabfc" + 
                "5f5ba30f6cb9b556cd7813b801d346ff26660b76b9950a5a49f9fe8047b1022c2" + 
                "4fbba9d7feb7c61bf83b57e7c6a8a6150f04fb83f6d3c51ec3023554135a16913" + 
                "2f675f3ae2b61d72aeff22203199dd14801c70215009760508f15230bccb292b9" + 
                "82a2eb840bf0581cf502818100f7e1a085d69b3ddecbbcab5c36b857b97994afb" + 
                "bfa3aea82f9574c0b3d0782675159578ebad4594fe67107108180b449167123e8" + 
                "4c281613b7cf09328cc8a6e13c167a8b547c8d28e0a3ae1e2bb3a675916ea37f0" + 
                "bfa213562f1fb627a01243bcca4f1bea8519089a883dfe15ae59f06928b665e80" +
                "7b552564014c3bfecf492a0381850002818100d1198b4b81687bcf246d41a8a72" +
                "5f0a989a51bce326e84c828e1f556648bd71da487054d6de70fff4b49432b6862" +
                "aa48fc2a93161b2c15a2ff5e671672dfb576e9d12aaff7369b9a99d04fb29d2bb" +
                "bb2a503ee41b1ff37887064f41fe2805609063500a8e547349282d15981cdb58a" +
                "08bede51dd7e9867295b3dfb45ffc6b259300b06072a8648ce3804030500032f0" +
                "0302c021400a602a7477acf841077237be090df436582ca2f0214350ce0268d07" +
                "e71e55774ab4eacd4d071cd1efad";

        public static void Main(string[] args)
        {
            var countryCode = "1"; //country code
            var number = ""; //number without cc
            var mnc = ""; //mnc ... check wikipedia or phone (three digits - if less, zero-pad in front until length is of 3)
            var mcc = ""; //mcc ... "
            var useSMS = true; //true for sms or false for voice.
            var token = CalculateMD5Hash(
                System.Text.Encoding.UTF8.GetBytes(_longHash + _dexHash + number));

            var codeRequest = new Disa.Framework.WhatsApp.HTTP.CodeRequest(countryCode, number, mnc, mcc, useSMS, token);
            Console.WriteLine(codeRequest.Send());
        }

        public static string CalculateMD5Hash(byte[] bytes)
        {
            var md5 = System.Security.Cryptography.MD5.Create();
            var hash = md5.ComputeHash(bytes);

            var sb = new StringBuilder();
            for (int i = 0; i < hash.Length; i++)
            {
                sb.Append(hash[i].ToString("x2"));
            }
            return sb.ToString();
        }
    }
}
using System;

namespace Disa.Framework.WhatsApp.HTTP
{
    public class CodeRequest : WaRequest
    {
        public CodeRequest(string countryCode, string phoneNumber, string mnc, 
                           string mcc, bool useSms, string token) 
            : base("https://v.whatsapp.net/v2/code", RequestType.Get, 
                   "WhatsApp/2.10.222 Android/4.2.2 Device/LGE-Nexus_4")
        {
            Attributes.Add(new Attribute("cc", countryCode));
            Attributes.Add(new Attribute("in", phoneNumber));
            Attributes.Add(new Attribute("id", "abcdef0123456789"));
            Attributes.Add(new Attribute("lg", "en"));
            Attributes.Add(new Attribute("lc", "US"));
            Attributes.Add(new Attribute("mnc", mnc));
            Attributes.Add(new Attribute("mcc", mcc));
            Attributes.Add(new Attribute("method", useSms ? "sms" : "voice"));
            Attributes.Add(new Attribute("reason", "self-send-jailbroken"));
            Attributes.Add(new Attribute("token", token));
        }
    }
}
using System;
using System.Collections.Generic;
using System.Net;
using System.Text;
using System.IO;

namespace Disa.Framework.WhatsApp.HTTP
{
    public class WaRequest
    {
        public enum RequestType { Get, Post };

        readonly bool _useAttributes;
        string _url;
        string _rawData = "";
        readonly RequestType _requestType = RequestType.Get;
        readonly List<Attribute> _attributes = new List<Attribute>();
        readonly List<HeaderItem> _headerItems = new List<HeaderItem>();
        WebResponse _response;
        private string _userAgent { get; set; }

        public WaRequest(string url, RequestType requestType, string userAgent, bool useAttributes = true)
        {
            _url = url;
            _requestType = requestType;
            _useAttributes = useAttributes;
            _userAgent = userAgent;
            //Ignore any SSL errors.
            ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
        }

        public string Url
        {
            get
            {
                return _url;
            }
            set
            {
                _url = value;
            }
        }

        public WebResponse Response
        {
            get
            {
                return _response;
            }
            set
            {
                _response = value;
            }
        }

        public List<Attribute> Attributes
        {
            get
            {
                return _attributes;
            }
        }

        public void AddAttribute(Attribute a)
        {
            _attributes.Add(a);
        }

        public string RawData
        {
            get
            {
                return _rawData;
            }
            set
            {
                _rawData = value;
            }
        }

        public string Send()
        {
            return _requestType == RequestType.Get ? GetRequest() : PostRequest();
        }

        public string GetRequest()
        {
            string sendData;
            if (_useAttributes)
            {
                var data = new StringBuilder();
                foreach (var a in _attributes)
                {
                    data.Append(String.Format("{0}={1}&", a.Key, a.Token));
                }
                sendData = data.ToString();
            }
            else
            {
                sendData = _rawData;
            }

            var request = (HttpWebRequest)WebRequest.Create(_url + "?" + sendData);
            request.Timeout = 10000;
            request.Proxy = null;
            request.UserAgent = _userAgent;

            using (_response = request.GetResponse())
            {
                return new StreamReader(_response.GetResponseStream()).ReadToEnd();
            }
        }

        public void AddHeaders(params HeaderItem[] items)
        {
            _headerItems.AddRange(items);
        }

        public void ClearHeaders()
        {
            _headerItems.Clear();
        }

        public string PostRequest()
        {
            var httpWReq = (HttpWebRequest)WebRequest.Create(_url);
            httpWReq.Proxy = null;
            httpWReq.Timeout = 10000;

            byte[] dataBytes;
            if (_useAttributes)
            {
                var data = new StringBuilder();
                foreach (var a in _attributes)
                {
                    data.Append(String.Format("{0}={1}&", a.Key, a.Token));
                }
                dataBytes = Encoding.Default.GetBytes(data.ToString());
            }
            else
            {
                dataBytes = Encoding.Default.GetBytes(_rawData);
            }

            httpWReq.Method = "POST";
            httpWReq.UserAgent = _userAgent;
            foreach (var item in _headerItems)
            {
                switch (item.Name)
                {
                    case "Accept":
                        httpWReq.Accept = item.Value;
                        break;
                    case "Content-Type":
                        httpWReq.ContentType = item.Value;
                        break;
                    case "Content-Length":
                        httpWReq.ContentLength = dataBytes.Length;
                        break;
                    default:
                        httpWReq.Headers.Add(item.Name, item.Value);
                        break;
                }
            }

            if (dataBytes.Length != 0)
            {
                using (var stream = httpWReq.GetRequestStream())
                {
                    stream.Write(dataBytes, 0, dataBytes.Length);
                }
            }

            using (_response = httpWReq.GetResponse())
            {
                using (var sr = new StreamReader(_response.GetResponseStream()))
                {
                    return sr.ReadToEnd();
                }
            }
        }

        public class HeaderItem
        {
            public string Name {get; private set;}
            public string Value {get; private set;}

            public HeaderItem(string name, string value)
            {
                Name = name;
                Value = value;
            }
        }

        public class Attribute
        {
            public Attribute(string key, string token)
            {
                Key = key;
                Token = token;
            }

            public string Key { get; set; }

            public string Token { get; set; }
        }
    }
}
atans commented 11 years ago

Good job! @Dynogic, Love YOU.

devloic commented 11 years ago

Well done . You are fast. Once you have enjoyed a fully deserved rest I think we would all be glad to know more about how you made it so for the next update we can put our hands in the dirt too.

atans commented 11 years ago

@Dynogic Is this token right?

(Amazing, It's very simple in php)

class Token
{
   const DEHASH = "8ac295a1bc5c0df1147db1ad2172b776";
   const LONGHASH = "30820332308202f0a00302010202044c2536a4300b06072a8648ce3804030500307c310b3009060355040613025553311330110603550408130a43616c69666f726e6961311430120603550407130b53616e746120436c61726131163014060355040a130d576861747341707020496e632e31143012060355040b130b456e67696e656572696e67311430120603550403130b427269616e204163746f6e301e170d3130303632353233303731365a170d3434303231353233303731365a307c310b3009060355040613025553311330110603550408130a43616c69666f726e6961311430120603550407130b53616e746120436c61726131163014060355040a130d576861747341707020496e632e31143012060355040b130b456e67696e656572696e67311430120603550403130b427269616e204163746f6e308201b83082012c06072a8648ce3804013082011f02818100fd7f53811d75122952df4a9c2eece4e7f611b7523cef4400c31e3f80b6512669455d402251fb593d8d58fabfc5f5ba30f6cb9b556cd7813b801d346ff26660b76b9950a5a49f9fe8047b1022c24fbba9d7feb7c61bf83b57e7c6a8a6150f04fb83f6d3c51ec3023554135a169132f675f3ae2b61d72aeff22203199dd14801c70215009760508f15230bccb292b982a2eb840bf0581cf502818100f7e1a085d69b3ddecbbcab5c36b857b97994afbbfa3aea82f9574c0b3d0782675159578ebad4594fe67107108180b449167123e84c281613b7cf09328cc8a6e13c167a8b547c8d28e0a3ae1e2bb3a675916ea37f0bfa213562f1fb627a01243bcca4f1bea8519089a883dfe15ae59f06928b665e807b552564014c3bfecf492a0381850002818100d1198b4b81687bcf246d41a8a725f0a989a51bce326e84c828e1f556648bd71da487054d6de70fff4b49432b6862aa48fc2a93161b2c15a2ff5e671672dfb576e9d12aaff7369b9a99d04fb29d2bbbb2a503ee41b1ff37887064f41fe2805609063500a8e547349282d15981cdb58a08bede51dd7e9867295b3dfb45ffc6b259300b06072a8648ce3804030500032f00302c021400a602a7477acf841077237be090df436582ca2f0214350ce0268d07e71e55774ab4eacd4d071cd1efad";

    public static function generate($number)
    {
        //return md5(utf8_encode(self::LONGHASH . self::DEHASH . $number));
        return md5(self::LONGHASH . self::DEHASH . $number);
    }
}

echo Token::generate(12345679);
// ead69804b4b5da4c7455d1d20c8f0dc0
devloic commented 11 years ago

@atans token is "ead69804b4b5da4c7455d1d20c8f0dc0" too using yowsup-cli with number (1)12345679

@Dynogic looks good with yowsup-cli , no more bad_token nor old_version ( got retry_after: 3543 ) on code request... let's try again in one hour or so

atans commented 11 years ago

@Dynogic @devloic I was test two real number Got {"status":"sent","length":6,"method":"sms","retry_after":1805} and failed to receive sms code too

Dynogic commented 11 years ago

@atans, its working for @devloic and me. why aren't you getting a code?

this is incredibly weird =(

Dynogic commented 11 years ago

@atans, look at the second code piece. make sure your PHP implementation is sending all those attributes.

atans commented 11 years ago

@Dynogic Could you give a US's mcc and mnc example for me ? US's mcc 310 and mnc 000, right ?

China's mcc 460, mnc 000 is not working for me https://v.whatsapp.net/v2/code?cc=86&in=13650101233&lc=cn&lg=en&mcc=460&mnc=000&method=sms&reason=self-send-jailbroken&id=01fb96b840b9e83a30f28e4db581f7de&token=eba8822ee1a5ec6146da7e1aac9d910e