ppati000 / jodelstats

jodelstats.com: Displays the most popular posts on Jodel (jodel-app.com) using its (private) API
MIT License
24 stars 5 forks source link

Jodel auth #1

Open denysvitali opened 8 years ago

denysvitali commented 8 years ago

Has anyone had any success in authenticating with Jodel via HMAC? I digged into the Jodel apk and found the following:

private String calculateHMac(Request var1, String var2) {
        String var7;
        try {
            URI var4 = var1.DB();
            StringBuilder var5 = new StringBuilder(var1.Ee());
            var5.append("%").append(var4.getHost()).append("%").append(String.valueOf(this.port)).append("%").append(var4.getPath());
            var5.append("%");
            String var3 = var1.dJ("Authorization");
            if(!TextUtils.isEmpty(var3)) {
                var5.append(var3.split(" ")[1]);
            }

            var5.append("%").append(var2);
            appendQuery(var5, var4.getQuery());
            appendBody(var5, var1);
            var3 = var5.toString();
            Mac var8 = Mac.getInstance("HmacSHA1");
            SecretKeySpec var9 = new SecretKeySpec(this.secret, "HmacSHA1");
            var8.init(var9);
            var7 = hex(var8.doFinal(var3.getBytes("UTF-8")));
        } catch (Throwable var6) {
            Crashlytics.logException(var6);
            var7 = "";
        }

        return var7;
    }
private native String generate(String var1);

generate() is called from a native file (C C++, libhmac.so) which decompiled leads to the following:

//
// This file was generated by the Retargetable Decompiler
// Website: https://retdec.com
// Copyright (c) 2016 Retargetable Decompiler <info@retdec.com>
//

#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>

// ------------------------ Structures ------------------------

struct struct_0 {
    int32_t e0;
    int32_t e1;
    char e2[4];
    int32_t e3;
    int32_t e4;
};

// ------------------- Function Prototypes --------------------

int32_t ___cxa_finalize(int32_t a1);
int32_t _ftext(int32_t a1, int32_t a2);
char * client_secret(void);
int32_t function_568(char * a1, int32_t a2);
void function_758(void);
int32_t get_generated_key(void);
int32_t get_random_key(void);
int32_t Java_com_jodelapp_jodelandroidv3_api_HmacInterceptor_generate(struct struct_0 * a1, int32_t a2, int32_t a3);

// --------------------- Global Variables ---------------------

int32_t g1 = 0; // $a0
int32_t g2 = 0; // $a3
int32_t g3 = 0; // $gp
int32_t g4 = 0; // $ra
int32_t g5 = 0; // $t9
int32_t g6 = 0; // $v0
int32_t g7 = 0x11000; // 0x11000
int32_t g8 = 0xa002000;

// ------------------------ Functions -------------------------

// Address range: 0x530 - 0x567
int32_t _ftext(int32_t a1, int32_t a2) {
    int32_t v1 = g5; // bp+538
    g3 = v1 + 0x18b00;
    ___cxa_finalize(g7);
    return *(int32_t *)(v1 + 0x10b18);
}

// Address range: 0x568 - 0x58f
int32_t function_568(char * a1, int32_t a2) {
    int32_t v1 = *(int32_t *)(g5 + 0x10ae4); // bp+580
    return __cxa_atexit((void (*)())v1, a1, (char *)&g7);
}

// Address range: 0x590 - 0x5a7
int32_t get_random_key(void) {
    // 0x590
    g3 = g5 + 0x18aa0;
    g6 = *(int32_t *)(g5 + 0x10ac0);
    return (int32_t)"\x18\x6e\x2e\x76\x09\x7d\x08\x42\x23\x76\x4a\x45\x45\x4e\x38\x7f\x30\x5e\x47\x42\x49\x5e\x29\x6c\x67\x4b\x30\x35\x5a\x29\x33\x75\x7c\x47\x6f\x73\x5f\x3f\x0b\x53";
}

// Address range: 0x5a8 - 0x61f
int32_t get_generated_key(void) {
    int32_t v1 = g5;
    g5 = get_random_key;
    int32_t v2 = g1; // $s0
    get_random_key();
    g3 = v1 + 0x18a88;
    int32_t * v3 = (int32_t *)(v1 + 0x10aa8);
    int32_t result = *v3 + 0x1080;
    int32_t v4 = result;
    *(char *)v4 = *(char *)v2 ^ *(char *)g6;
    int32_t v5 = v4 + 1; // bp+604
    int32_t v6 = g6 + 1; // bp+604
    g6 = v6;
    // branch -> 0x5ec
    while (v5 != *v3 + 0x10a8) {
        // 0x5ec
        v4 = v5;
        v2++;
        *(char *)v4 = *(char *)v2 ^ *(char *)v6;
        v5 = v4 + 1;
        v6 = g6 + 1;
        g6 = v6;
        // continue -> 0x5ec
    }
    // 0x608
    return result;
}

// Address range: 0x620 - 0x6ab
char * client_secret(void) {
    int32_t v1 = g5; // bp+628
    g5 = get_generated_key;
    int32_t v2 = get_generated_key(); // bp+640
    g3 = v1 + 0x18a10;
    char * mem = malloc(g8); // bp+654
    int32_t v3 = (int32_t)mem;
    int32_t v4 = 0;
    // branch -> 0x664
    while (true) {
        int32_t v5 = v4 - v4 % 4; // bp+668
        int32_t v6 = (-1 - v4) % 4; // bp+674
        uint32_t v7 = *(int32_t *)(v5 + v2); // bp+678
        int32_t v8 = 8 * v6; // bp+680
        g2 = v8;
        int32_t v9 = v4 + 1;
        *(char *)(v5 + v3 + v6) = (char)(v7 >> v8);
        if (v9 == 40) {
            // 0x694
            *(char *)(v3 + 40) = 0;
            return mem;
        }
        // 0x664
        v4 = v9;
        // branch -> 0x664
    }
}

// Address range: 0x6ac - 0x757
int32_t Java_com_jodelapp_jodelandroidv3_api_HmacInterceptor_generate(struct struct_0 * a1, int32_t a2, int32_t a3) {
    int32_t v1 = g5 + 0x18984;
    g3 = v1;
    int32_t v2 = a1->e0;
    g6 = v2;
    int32_t v3 = *(int32_t *)(v2 + 676);
    g5 = v3;
    g4 = 1776;
    int32_t v4 = (int32_t)a1; // $s0
    ((int32_t (*)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t))v3)(g1, a3, 0, g2, v1, 0);
    g5 = (int32_t)client_secret;
    int32_t v5 = *(int32_t *)(*(int32_t *)v4 + 668); // $s3
    g1 = g6;
    char * v6 = client_secret(); // bp+704
    g5 = v5;
    g4 = 1820;
    g1 = v4;
    ((int32_t (*)())v5)();
    int32_t v7 = *(int32_t *)(*(int32_t *)v4 + 680); // bp+724
    g5 = v7;
    g1 = v4;
    g4 = 1848;
    ((int32_t (*)(int32_t))v7)(v4);
    return (int32_t)v6;
}

// Address range: 0x758 - 0x75f
void function_758(void) {
    // 0x758
    return;
}

// Address range: 0x760 - 0x79f
int32_t ___cxa_finalize(int32_t a1) {
    int32_t v1 = *(int32_t *)(g3 - 0x7ff0); // bp+760
    g5 = v1;
    g4 = 1904;
    ((int32_t (*)())v1)();
    int32_t v2 = *(int32_t *)(g3 - 0x7ff0); // bp+770
    g5 = v2;
    g4 = 1920;
    ((int32_t (*)())v2)();
    int32_t v3 = *(int32_t *)(g3 - 0x7ff0); // bp+780
    g5 = v3;
    g4 = 1936;
    ((int32_t (*)())v3)();
    return g5;
}

// --------------- Dynamically Linked Functions ---------------

// int __cxa_atexit(void(*func)(void *), void * arg, void * dso_handle);
// void * malloc(size_t);

// --------------------- Meta-Information ---------------------

// Detected compiler/packer: gcc (4.9)
// Detected functions: 8
// Decompiler release: v2.1.2 (2016-01-27)
// Decompilation date: 2016-06-13 22:47:23

I tried to call some functions in it, but I always get segmentation faults (for example one at 0x0000555555554933 in get_generated_key () at main.cpp:75)

hydroid7 commented 8 years ago

Did you try to modify the android app with smali?

A good resource for smali: http://androidcracking.blogspot.de

I think they don't want let us build our own bots 😁

denysvitali commented 8 years ago

The problem is that, like for Pokémon Go they created a string that has to be sent on every request. This string is created with the app signature and with a key generated by an arm7 library multi-architecture shared object called libhmac.so, which is then called by the Java app itself (IIRC there is also a check to ensure that the lib is called from com.tellm.android.app)

nborrmann commented 8 years ago

I debugged this some time ago. Pretty easy actually.

  1. attach a debugger to the android app
  2. capture the output of the native generate() function to get the secret for the hash function (No need to reverse it lol)
  3. rebuild the calculateHmac() function

I ended up with this (This is outdated. It doesn't seem to work for creating new accounts, but still does for re-authenticating old accounts.):

    def _sign_request(self, method, url, headers, payload=None):
        timestamp = datetime.datetime.utcnow().isoformat()[:-7] + "Z"

        req = [method,
               urlparse(url).netloc,
               "443",
               urlparse(url).path,
               self.access_token if self.access_token else "",
               timestamp]
        req.extend(sorted(urlparse(url).query.replace("=", "%").split("&")))
        req.append(payload if payload else "")

        secret = bytearray([108, 67, 89, 78, 86, 75, 110, 104, 71, 120, 118, 111, 120, 101, 111, 119, 104, 108, 82, 80,
                            69, 99, 76, 82, 87, 120, 75, 106, 76, 77, 69, 101, 105, 116, 113, 98, 114, 110, 97, 82])
        signature = hmac.new(secret, "%".join(req).encode("utf-8"), sha1).hexdigest().upper()

        headers['X-Authorization'] = 'HMAC ' + signature
        headers['X-Client-Type'] = 'android_4.5.11'
        headers['X-Timestamp'] = timestamp
        headers['X-Api-Version'] = '0.1'

For making this work again, you probably only need to get the new secret from the generate() function.