httptoolkit / frida-interception-and-unpinning

Frida scripts to directly MitM all HTTPS traffic from a target mobile application
https://httptoolkit.com/android/
GNU Affero General Public License v3.0
905 stars 178 forks source link

com.expressvpn.vpn not bypassed #37

Open ilyesgasmi opened 11 months ago

ilyesgasmi commented 11 months ago

No luck using the script on the expressvpn app. After running the script, HTTP Toolkit still is unable to view the https traffic. I tried reverse engineering the app and writing a frida script myself, but with no success. I wasn't able to figure out the functions needed to bypass the SSL verification. I would appreciate any help. Thank you

image

pimterry commented 11 months ago

Have you see https://httptoolkit.com/blog/android-reverse-engineering/? If the script doesn't work, the only option to intercept your traffic is to reverse engineer the app I'm afraid.

If this doesn't work, do please share any details you find along the way for each of the steps there (error logs for this failure, notable references to relevant system types, any other ADB output, results for the suggested strings to search for, etc). It's almost always possible to disable pinning, and the steps there should at least guide you to the relevant code. Do share anything you come up to here, once you manage to find the relevant code I'm happy to help put together a Frida hook to disable it.

ilyesgasmi commented 11 months ago

Yes, I have seen the blog post. It was very insightful and it is what lead me to try myself, but as I said earlier I haven't been able to make a functional script (or any script at all). Here are my attempts: Using a rooted memu emulator and connecting to frida using adb. I followed the blog post for all configuration.

I decompiled the app using jadx cli tool. I ended up with a sources and a ressources folder, unfortunately all with random package names:

image

Opening the files with vscode and using the search function, I found that there were many references to the certificate pinner:

image

Also, browsing through the code I found an interesting method that might have been useful:

image

There I encountered my first issue: My class was not being recognized:

image

I then used this script to view all the classes that were loaded

Java.perform(function() { var classes = Java.enumerateLoadedClassesSync(); classes.forEach(function(className) { console.log(className); }); });

And I was able to find my class!

image

After that, I tried again to run the script, and to my surprise the output changed. I got this output:

image

I have no idea if that means the script is properly hooked, and simply not working, if it didn't. Thank you for your help, I greatly appreciate your time.

ilyesgasmi commented 11 months ago

I also found ressource, suggesting the reverse engineering might be harder than it seems? https://rucore.net/en/a-guide-to-reverse-an-ios-app-using-expressvpn-as-an-example/

pimterry commented 11 months ago

Hmm, I'm not sure what would cause the missing class error. It might be that some classes are loaded somewhat dynamically, so weren't yet loaded in the app when you first ran the command, and were available later. You may need to start the app, and then delay hooking the methods until slightly later (maybe even by making it fail, and then hooking the methods afterwards).

Overall it looks like this app has obfuscated the sources of its libraries, not just its own code, which is unusual but does explain why the hooks here aren't working. One good thing though is that you can see that OkHTTP is included (some OkHTTP strings are shown in your find results). This is commonly used for HTTP in apps themselves (not so much in libraries) and so if it's included it's fairly likely that that's the main client they're using, and that's what you need to patch.

Although the existing OkHTTP hooks from this script won't work, because the class & method names have all been changed, if you can find the equivalent code in your app you should be able to use the same hook as in this script, just swapping out the class & method names. The check method our hooks patch originally looks like this - if you can find the equivalent in your obfuscated code, and then swap the obfuscated class & method names into the script, I think that should work.

I'd strongly recommend looking at the ADB logs too - that will often have exceptions listed when connections fail, and those may lead you directly to the right classes.

I have no idea if that means the script is properly hooked, and simply not working, if it didn't.

To tell if your hooks are working, you can:

ilyesgasmi commented 11 months ago

I think I found the equivalent in my code!

image

However, I am not sure on how to translate your script to work on my class and method names. Here is my best attempt at that:


// OkHTTPv3 (quadruple bypass)
try {
    // Bypass OkHTTPv3 {1}
    const okhttp3_Activity_1 = Java.use('s50.g');
    okhttp3_Activity_1.b.overload('java.lang.String', 'java.util.List').implementation = function (a, b) {
      console.log('  --> Bypassing OkHTTPv3 (list): ' + a);
      return;
    };
    console.log('[+] OkHTTPv3 (list)');
  } catch (err) {
    console.log('[ ] OkHTTPv3 (list)', err);
  }

  try {
    // Bypass OkHTTPv3 {2}
    // This method of CertificatePinner.check could be found in some old Android app
    const okhttp3_Activity_2 = Java.use('s50.g');
    okhttp3_Activity_2.b.overload('java.lang.String', 'java.security.cert.Certificate').implementation = function (a, b) {
      console.log('  --> Bypassing OkHTTPv3 (cert): ' + a);
      return;
    };
    console.log('[+] OkHTTPv3 (cert)');
  } catch (err) {
    console.log('[ ] OkHTTPv3 (cert)', err);
  }

  try {
    // Bypass OkHTTPv3 {3}
    const okhttp3_Activity_3 = Java.use('s50.g');
    okhttp3_Activity_3.b.overload('java.lang.String', '[Ljava.security.cert.Certificate;').implementation = function (a, b) {
      console.log('  --> Bypassing OkHTTPv3 (cert array): ' + a);
      return;
    };
    console.log('[+] OkHTTPv3 (cert array)');
  } catch (err) {
    console.log('[ ] OkHTTPv3 (cert array)', err);
  }

  try {
    // Bypass OkHTTPv3 {4}
    const okhttp3_Activity_4 = Java.use('s50.g');
    okhttp3_Activity_4['b$okhttp'].implementation = function (a, b) {
      console.log('  --> Bypassing OkHTTPv3 ($okhttp): ' + a);
      return;
    };
    console.log('[+] OkHTTPv3 ($okhttp)');
  } catch (err) {
    console.log('[ ] OkHTTPv3 ($okhttp)', err);
  }

I get these errors returned:

image

I believe this means that the method java.lang.String is obfuscated, and I need to find the new method name and replace it in the script. Am I on the right path? And if so, do you know where I could fine the obfuscated method name? It doesn't seem to be in the "check" method from okhttp. Thank you

pimterry commented 11 months ago

Nice! Yes that looks promising.

That error there is saying that the method name you have does match, but the argument types don't - you just need to change the argument types (as you're passing to overload() to match the error message (so replace the second argument, e.g java.util.List, with b50.a - I think java.lang.String is fine). In our hooks code, we cover multiple versions of the library, which is part of why there are multiple overloads - in this case since there is only one overload present (as the error message shows you) you'll only need one of those first three cases.

It does look like the b$okhttp property is wrong for the last case, I'm not totally sure how to fix that. For awkward cases, it's usually best to manually mess around with Frida to explore the code: connect with Frida, then run Java.use('s50.g'), and then see what properties and methods are available there.