Keep up with HMAC key changes

Issue #14 on hold
Christian Fibich repo owner created an issue

The HMAC key changes with every version of the Android app. We need to extract the key for each app version from the libhmac.so to enable registration of new device-uids.

The key was once just obfuscated by XORing it with the APK Sig Hash, now (since ~4.19.1) they use a more elaborate scheme containing shuffling bytes around etc.

Comments (38)

  1. Former user Account Deleted

    hmm.. i had no success in extracting the hmac key from libhmac.so either. Will check with a debugger like IDA now.

    EDIT: have fun 4.19.1 swbBCdBLdtvSqgflkjyrvVwiVHMZSQDQzQWsPiMg

  2. Nils Borrmann

    Hey guys, does the 4.19.1 key work for you? The latest working version is 4.18.2 for me. (Though this will probably be deprecated by the server soon.)

    It looks like the signing procedure has been changed in that update. There's now a native function sign(String str, String str2, byte[] bArr), which is a lot more complicated than the old one. Though I haven't looked at the disassembly in detail.

  3. Christian Fibich reporter

    I confirm that new registration currently only works for me with the key for 4.18.2, which I just pushed. The key Dominic posted in the pull request does not work for me for new registration.

    I had some success generating valid HMACs using the new libhmac.so as a black box. Didn't figure out how it works until now, this is what I got until now:

    There are three functions which need to be called:

    init() register(String str) sign(String str, String str2, byte[] bArr)

    From the decompiled APK, I learned:

    register() takes a String containing method and path, e.g., "GET@/api/path"

    sign() takes... 1. the APK signature hash ("a4a8d4d7b09736a0f65596a868cc6fd620920fb0") 2. the String passed to register() 3. the stringified request as generated by _sign_request() in OJOC/Connection.py, converted to Byte[]

  4. Nils Borrmann

    Were you able to call into the .so library from python? I tried, but never managed to do that.

    Or did you just embed it in an Android project?

  5. Christian Fibich reporter

    I just made a dummy Android app. Originally I tried it from Java under Linux, but that didn't work because libhmac.so needs the Android libc.

  6. Christian Fibich reporter

    Can someone confirm this hypothesis:

    If you don't call the

    register(<HTTP Method>@<Path>)
    

    function exported by libhmac.so before calling the

    sign(<APK Sig Hash>,<HTTP Method>@<Path>,<Req>)
    

    you get

    HMAC-SHA1(key="swbBCdBLdtvSqgflkjyrvVwiVHMZSQDQzQWsPiMg", msg=<Req>)
    

    as a result?

    My data:

    <req> := "GET%api.go-tellm.com%443%/api/v3/user/config%b81aebfa-fc44-46d7-8307-699d00ac5d0f%2016-09-28T09:17:30Z%%"
    <HTTP Method>@<Path> := "GET@/api/v3/user/config"
    <APK Sig Hash> := "a4a8d4d7b09736a0f65596a868cc6fd620920fb0"
    

    sign() returns e04c35179e1c72a2be04223b1fb7f2a881f5d225 when register() is called beforehand

    sign() returns 6f1e48edc2f0b37d1169e4a58a411f7979046fb4 when register() is not called beforehand.

    HMAC-SHA1(key="swbBCdBLdtvSqgflkjyrvVwiVHMZSQDQzQWsPiMg", msg=<Req>) returns 6f1e48edc2f0b37d1169e4a58a411f7979046fb4

  7. User c3b18

    I can confirm with my own implementation of a proxy app that a4a8d4d7b09736a0f65596a868cc6fd620920fb0 is the correct current APK Signature used by the App and:

    1. sign() returns 6f1e48edc2f0b37d1169e4a58a411f7979046fb4 when register() is not called beforehand.
    2. HMAC-SHA1(key="swbBCdBLdtvSqgflkjyrvVwiVHMZSQDQzQWsPiMg", msg=<Req>) returns 6f1e48edc2f0b37d1169e4a58a411f7979046fb4

    I'm currently struggling to get a different sign() result when calling register() beforehand

  8. User c3b18

    I am currently thoroughly analyzing the libhmac exports and I'm positive that they are analyzing the Stacktrace in the second method called by init(). For Jodel 4.19.1 this method is at 0x4800 in the x86 libhmac.so. I will keep you posted. Next step is reversing their cheap xor-encryption of some strings they used.

  9. Christian Fibich reporter

    Concerning the XOR-Encryption, I have had some success with the 4.20.1 armeabi Version.

    0x4ca6 contains the encrypted string ()Ljava/lang/Thread;

    0x4ffa contains ()Ljava/lang/String; and getClassName

    0x5c2c contains (Ljava/lang/String;)[B, getBytes, and javax/crypto/spec/SecretKeySpec

  10. Christian Fibich reporter

    Seems like they're doing the entire HMAC by calling Java methods from their C++ library.

    You can see the un"encrypted" method and class strings in the armeabi libhmac.so up to (at least) 4.15.2:

    // From Java_com_[...]_sign() (0x5948 - 0x5adb)
    [...]
        g108 = (int32_t)"HmacSHA1";
    [...]
        ((int32_t (*)(int32_t, int32_t, int32_t))(v29 & -2))(v28, (int32_t)"HmacSHA1", v29); // this might be conversion to a Java.lang.String
    [...]
        _Z11call_staticP7_JNIEnvPKcS2_S2_z((int32_t *)g114, (int32_t)"javax/crypto/Mac", (int32_t)"getInstance", (int32_t)"(Ljava/lang/String;)Ljavax/crypto/Mac;", v19, 0, 0, v18, a3);
        _Z16call_constructorP7_JNIEnvPKcS2_z((int32_t *)g114, (int32_t)"javax/crypto/spec/SecretKeySpec", (int32_t)"([BLjava/lang/String;)V", v9, v19, 0, 0, v18, a3, a5, v4, v6, v17, v16, 0, 0);
        _Z10invokeVoidP7_JNIEnvP8_jobjectPKcS4_z((int32_t *)g114, g115, (int32_t)"init", (int32_t)"(Ljava/security/Key;)V", v19, 0, 0, v18, a3, a5, v4, v6, v17, v16, 0, 0, 0);
    [...]
    

    Calls to functions like

    _ZNSt4priv8_Rb_treeISsSt4lessISsESt4pairIKSsiENS_10_Select1stIS5_EENS_11_MapTraitsTIS5_EESaIS5_EE13insert_uniqueENS_17_Rb_tree_iteratorIS5_S9_EERKS5_()
    

    from Java_com_[...]_sign() in 4.20.1 have me guessing if they use some kind of tree where they store the registered API calls, or keys generated from them...

  11. User c3b18

    That's exactly my observation. 4.20.3 is the current APK version and the libhmac in there still works like that. Though, if you look closely at the sign method, there are execution paths where the clientsecret is calculated instead of using the hardcoded one.

    The function you mentioned demangles to std::priv::_Rb_tree<std::string, std::less<std::string>, std::pair<std::string const, int>, std::priv::_Select1st<std::pair<std::string const, int> >, std::priv::_MapTraitsT<std::pair<std::string const, int> >, std::allocator<std::pair<std::string const, int> > >::insert_unique(std::priv::_Rb_tree_iterator<std::pair<std::string const, int>, std::priv::_MapTraitsT<std::pair<std::string const, int> > >, std::pair<std::string const, int> const&) which is most likely a long way to write std::map<std::string, int>::insert.

  12. Christian Fibich reporter

    In the meantime, I extracted the HMAC secret and HTTP headers from Wodel, which seems to allow new registration with the pre-4.19.1-HMAC calculation.

  13. User c3b18

    I have just verified that the HMAC secret is now derived from the device-id. I'll keep you posted on how the calculation works.

  14. Christian Fibich reporter

    Great! I'm curious about how they get hold of the device id. It's not part of any of the obvious parameters to the exported functions, is it?

  15. User c3b18

    By intercepting most of the Java API-Calls I've gotten hold of the SecretClientKey Bytes that work for my deviceid. Changing data in the payload and recalculating the HMAC works. But if I change the device-id in the payload, my calculated HMAC is no longer correct. From the disassembly haven't yet figured out how they do that. The only input to the other clientsecret-calculation routine I've seen was the key provided to the sign method. The device-id does not seem to be part of any obvious parameters.

  16. User c3b18

    4.21.1 ClientSecret: MowxMGuZnoXYgVoAlqmcgUPxdbszEBpBXpjpccbg

    The ClientSecet is NOT derived from the deviceid, that was a mistake on my side. The register method is doing nothing else but inserting the key into a std::map. The map is then used in the sign method to find out which API call should use the new hash. Since all API calls use the new hash, this is pretty useless.

  17. Nils Borrmann

    I can confirm that this client secret works for version 4.21.1.

    Weird that it's just such a simple change. I had expected them to change the hash method and obscure it some more.

  18. Christian Fibich reporter

    Confirmed that MowxMGuZnoXYgVoAlqmcgUPxdbszEBpBXpjpccbg works for 4.21.1. Great work, @jangarcia!

  19. Christian Fibich reporter

    I added the key for 4.27.0 on 2016-11-16 in commit 2199688, but incorrectly used 4.27.1 as version string. Worked fine, though. This is now fixed.

  20. xcircle

    i decompiled the v4.31.1 and this code is different than the older code (for the hmac decrypter) the char numbers don't have a hex number. And the numbers sometimes negative and othertimes postivi :/ Do you have some ideas for decryption?

    decompiled code:

    // Address range: 0x4b70 - 0x4b78
    void Java_com_jodelapp_jodelandroidv3_api_HmacInterceptor_init(int32_t a1) {
        // 0x4b70
        int32_t v1;
        int32_t v2;
        function_4b79(v1, 0, v2);
    }
    
    // Address range: 0x4b79 - 0x4c15
    void function_4b79(int32_t a1, int32_t a2, int32_t a3) {
        // 0x4b79
        int32_t v1;
        g4 = v1 + (int32_t)&g12;
        int32_t v2;
        g5 = v2;
        function_4c20(a1, a2);
        *(char *)(g4 + (int32_t)&g32) = -87;
        *(int32_t *)(g4 + (int32_t)&g31) = -0x4871070e;
        *(char *)(g4 + (int32_t)&g34) = 22;
        *(int32_t *)(g4 + (int32_t)&g33) = -0x61ff101e;
        *(char *)(g4 + (int32_t)&g36) = 6;
        *(int32_t *)(g4 + (int32_t)&g35) = 0x40b31106;
        *(char *)(g4 + (int32_t)&g38) = 96;
        *(int32_t *)(g4 + (int32_t)&g37) = -0x73d1c6e7;
        *(char *)(g4 + (int32_t)&g40) = 61;
        *(int32_t *)(g4 + (int32_t)&g39) = 0x1336f946;
        *(char *)(g4 + (int32_t)&g42) = 97;
        *(int32_t *)(g4 + (int32_t)&g41) = -0x485cb585;
        *(char *)(g4 + (int32_t)&g44) = -113;
        *(int32_t *)(g4 + (int32_t)&g43) = 0x69c012c6;
        *(char *)(g4 + (int32_t)&g46) = -92;
        *(int32_t *)(g4 + (int32_t)&g45) = 0x36c88e36;
    }
    

    //

    EDIT: I see you've updated OJOC with the new HMAC. could you say us, how you've got the hmac? :)

  21. Christian Fibich reporter

    Basically you need to do the following:

    • Get IDAPro & HexRays or
    • Do the following to the decompiled code:
    1. Take the lines that assign *(char *)(g4 + <offset>) and *(int32_t *)(g4 + <offset>)
    2. Sort the lines according to their g[34][0-9] offset
    3. Replace g4 + (int32_t)&g[34][0-9] with g4 + i
    4. After the lines that cast g4 + i to (int32_t *), add i+=4;
    5. After the lines that cast g4 + i to (char *), add i+=1;
    6. Now you have a C snippet which sets up the encrypted key in a buffer called g4. You can decrypt it by calling decode(g4); from /ojoc-x86-keyhack/decrypt.c
    7. Add a C main function around it, compile & link with /ojoc-x86-keyhack/decrypt.c
    8. Execute the compiled program, it should output the key.

    Pipe the lines from 1.) to the following bash script and you get a suitable C main function

    #!/bin/bash
    
    echo """#include <stdint.h>
    #include <stdio.h>
    #include \"decrypt.h\"
    
    int main (void) {
       int i=0;
       uint8_t g4[40];
    """
    
    sed -r 's/(.*&g([0-9]+).*)/\/* \2 *\/ \1/' | \
    sort | \
    sed -r '
    s/g4 \+ .*\) =/g4+i) =/
    s/(.*\*\(int32_t \*\).*)/    \1\n    i+=4;/
    s/(.*\*\(char \*\).*)/    \1\n    i+=1;/'
    
    echo """    return decode(g4);
    }"""
    

    The decode() function shuffles and XORs the key to retrieve the valid HMAC key.

  22. Christian Zöller

    Does somebody know which parameters do affect the access token generation? I have a token from my phone with version 4.32.2 but I can't create a valid HMAC key signing with 4.31.1 and changing the X-Client-Type accordingly (not using OJOC but an own implementation in nodejs, 477 return code from API). Using the OJOC generated access token the HMAC signing works perfectly. So I think I need to create the HMAC with the 4.32.2 secret. Could someone do me a favour and share the 4.32.2 secret?

  23. Christian Fibich reporter

    Pushed ff6e0f1 with key for 4.32.2

    Don't know about access token generation as it's completely server side.

    You send a POST request to /users/ with the following data JSON-encoded:

        {'client_id': <APP's ID>,'device_uid': <32 Bytes as Hex, your account #>, 'location': {'city':<city>, 'country':<2-Char country code>, 'loc_accuracy': <some #>, 'loc_coordinates': {'lat':<latitude in degrees>, 'lng':<longitude in degrees>}}}
    

    The server just replies with an access token and that's it

  24. Christian Zöller

    Thanks for the prompt response and the secret! Signing with this one immediately solved the 477. But I'm not able to explain why (but my guess about the version was right somehow). I will try to generate a new token with this API endpoint you mentioned next time my requests are being rejected. Or I will take a closer look how to extract the secret by myself.

    Please let me know if somebody needs an js implementation of the HMAC signing stuff someday.

  25. Christian Fibich reporter

    You always get the 447 ("Signed request expected") when the server-side validation of the HMAC failed.

    This happens, for example, if you use the wrong X-Client-Type for the respective HMAC key. Each HMAC key is bound to a specific version of the Android App, which is reflected in the X-Client-Type header.

  26. Leon

    Hey guys, I am beta tester of the android app and managed to extract the key for android 4.37.2:

    OjZvbmHjcGoPhz6OfjIeDRzLXOFjMdJmAIplM7Gq

  27. Maximilian Schirmer

    Hi there,

    any chance one of you might come up with a newer one? 4.42.4 seems to be expired and it looks like your ojoc-keyhack would need to be run on an android emulator / device? :)

  28. Christian Fibich reporter

    Hi,

    I've had a chat with the Jodel people, and since they have come under Spam attacks I've decided against releasing any further HMAC keys directly here. Also, It would get kinda time consuming.

    The repo https://bitbucket.org/cfib90/ojoc-keyhack/ contains everything you need to derive the key from an Android app.

    In Readme.md in the x86 folder of that repo you'll find a step-by-step guide for the current encryption scheme. It just requires a machine running Linux. Anything from a Raspi to a VM or a physical machine will do.

    If you have any problems, you open an issue there.

  29. Log in to comment