Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot dump quic keys on Android #38

Closed
Diniboy1123 opened this issue Dec 28, 2024 · 9 comments
Closed

Cannot dump quic keys on Android #38

Diniboy1123 opened this issue Dec 28, 2024 · 9 comments
Assignees
Labels
bug Something isn't working

Comments

@Diniboy1123
Copy link

Diniboy1123 commented Dec 28, 2024

Hi,

First of all this is probably one of the most precisely written frida tools out there, I found a gem just now with it. Thanks a lot for developing such a useful tool.

However I can't seem to get it working with Cloudflare warp. It's a free app, you download it, launch it and in the Settings -> Advanced -> Connection options -> Tunnel protocol I picked MASQUE.

In MASQUE mode they are essentially using connect-ip from this RFC which runs through quic so there is definitely TLS 1.3 involved. I can confirm that by using wireshark and inspecting the encrypted packets.

If I run the tool using friTap -m -k keys.log -v -s com.cloudflare.onedotonedotonedotone -do -p test.pcap --full_capture I can see a bunch of keys captured:

[*] libssl.so found & will be hooked on Android!
[***] The module "libssl.so" has 554 exports.
[***] Found SSL_read 0x7c2fcd8120
[***] Found SSL_write 0x7c2fcd8590
[***] Found SSL_get_fd 0x7c2fcd91d0
[***] Found SSL_get_session 0x7c2fce1640
[***] Found SSL_SESSION_get_id 0x7c2fce10c0
[***] Found SSL_new 0x7c2fcd7230
[***] Found SSL_CTX_set_keylog_callback 0x7c2fcdb3e0
[***] Found getpeername 0x7d3677f580
[***] Found getsockname 0x7d3677f540
[***] Found ntohs 0x7d36776d30
[***] Found ntohl 0x7d36776d20
[*] Android dynamic loader hooked.
[*] Logging pcap to test.pcap
[*] Logging keylog file to keys.log
[***] ProviderInstaller could not be found, although it has been loaded
[***] Remaining: AndroidNSSP version 1.0,AndroidOpenSSL version 1.0,CertPathProvider version 1.0,AndroidKeyStoreBCWorkaround version 1.0,BC version 1.68,HarmonyJSSE version 1.0,AndroidKeyStore version 1.0
[***] invoking keylog_callback from OpenSSL_BoringSSL
CLIENT_RANDOM REDACTED REDACTED
[***] invoking keylog_callback from OpenSSL_BoringSSL
CLIENT_RANDOM REDACTED REDACTED
[***] invoking keylog_callback from OpenSSL_BoringSSL
CLIENT_RANDOM REDACTED REDACTED
[***] invoking keylog_callback from OpenSSL_BoringSSL
CLIENT_RANDOM REDACTED REDACTED
[***] invoking keylog_callback from OpenSSL_BoringSSL
CLIENT_HANDSHAKE_TRAFFIC_SECRET REDACTED REDACTED
[***] invoking keylog_callback from OpenSSL_BoringSSL
SERVER_HANDSHAKE_TRAFFIC_SECRET REDACTED REDACTED
[***] invoking keylog_callback from OpenSSL_BoringSSL
CLIENT_TRAFFIC_SECRET_0 REDACTED REDACTED
[***] invoking keylog_callback from OpenSSL_BoringSSL
SERVER_TRAFFIC_SECRET_0 REDACTED REDACTED
[***] invoking keylog_callback from OpenSSL_BoringSSL
EXPORTER_SECRET REDACTED REDACTED
[***] invoking keylog_callback from OpenSSL_BoringSSL
CLIENT_HANDSHAKE_TRAFFIC_SECRET REDACTED REDACTED
[***] invoking keylog_callback from OpenSSL_BoringSSL
SERVER_HANDSHAKE_TRAFFIC_SECRET REDACTED REDACTED
[***] invoking keylog_callback from OpenSSL_BoringSSL
CLIENT_TRAFFIC_SECRET_0 REDACTED REDACTED
[***] invoking keylog_callback from OpenSSL_BoringSSL
SERVER_TRAFFIC_SECRET_0 REDACTED REDACTED
[***] invoking keylog_callback from OpenSSL_BoringSSL
EXPORTER_SECRET REDACTED REDACTED
[***] invoking keylog_callback from OpenSSL_BoringSSL
CLIENT_HANDSHAKE_TRAFFIC_SECRET REDACTED REDACTED
[***] invoking keylog_callback from OpenSSL_BoringSSL
SERVER_HANDSHAKE_TRAFFIC_SECRET REDACTED REDACTED
[***] invoking keylog_callback from OpenSSL_BoringSSL
CLIENT_TRAFFIC_SECRET_0 REDACTED REDACTED
[***] invoking keylog_callback from OpenSSL_BoringSSL
SERVER_TRAFFIC_SECRET_0 REDACTED REDACTED
[***] invoking keylog_callback from OpenSSL_BoringSSL
EXPORTER_SECRET REDACTED REDACTED
[***] invoking keylog_callback from OpenSSL_BoringSSL
CLIENT_RANDOM REDACTED REDACTED
[***] invoking keylog_callback from OpenSSL_BoringSSL
CLIENT_HANDSHAKE_TRAFFIC_SECRET REDACTED REDACTED
[***] invoking keylog_callback from OpenSSL_BoringSSL
SERVER_HANDSHAKE_TRAFFIC_SECRET REDACTED REDACTED
[***] invoking keylog_callback from OpenSSL_BoringSSL
CLIENT_TRAFFIC_SECRET_0 REDACTED REDACTED
[***] invoking keylog_callback from OpenSSL_BoringSSL
SERVER_TRAFFIC_SECRET_0 REDACTED REDACTED
[***] invoking keylog_callback from OpenSSL_BoringSSL
EXPORTER_SECRET REDACTED REDACTED

So I seemingly end up with some keys, but I assume these are for some other connection, like maybe DNS over HTTPS that happens already in the tunnel or during some API calls, as I cannot seem to decrypt packets using any of these keys in wireshark...

I wrote a go implementation of a similar quic based project where I defined a KeyLogWriter and that dumped these keys:

CLIENT_HANDSHAKE_TRAFFIC_SECRET REDACTED REDACTED
SERVER_HANDSHAKE_TRAFFIC_SECRET REDACTED REDACTED
CLIENT_TRAFFIC_SECRET_0 REDACTED REDACTED
SERVER_TRAFFIC_SECRET_0 REDACTED REDACTED

And that's enough for wireshark to properly decrypt all data. Therefore I am guessing that the quic crypto either isn't done by the statically linked OpenSSL inside the libnativetunnel.so, but maybe it's rust's own thing OR we are actually missing something in friTap to handle quic and openssl properly.

The binary also has some string:

SSLKEYLOGFILE environment variable detected, but no keys will be written due to compilation without the capture_keylogs feature

Which is really unfortunate. :(

Any help would be very much appreciated!

@monkeywave
Copy link
Collaborator

Hi @Diniboy1123 ,

Thank you for reaching out and for the detailed problem description.

Regarding the issue, I wanted to clarify: is the OpenSSL library statically linked inside the libnativetunnel.so binary, which is part of the Cloudflare Warp app? From the provided output, it seems that friTap is only hooking the libssl.so library, which is dynamically linked to the Cloudflare Warp app. This might explain why we are not capturing the keys for the QUIC connection.

To expedite the troubleshooting process, you might want to run BoringSecretHunter against libnativetunnel.so. If it identifies anything related to the key material or TLS hooks, it could be a valuable lead for us to proceed further.

Thanks again for your detailed insights and for your patience while we investigate this further.

All the best,

Daniel

@monkeywave monkeywave self-assigned this Dec 30, 2024
@monkeywave monkeywave added the bug Something isn't working label Dec 30, 2024
@Diniboy1123
Copy link
Author

Diniboy1123 commented Jan 1, 2025

Hey,

Thanks for the quick and really helpful response! I couldn't respond earlier, because my PSU died.

Good catch on libssl.so, I didn't even notice it, assumed it's just printing that so name, because it recognized openssl patterns in the app (but I thought it comes from the libnativetunnel.so). Yes, OpenSSL is statically compiled into the rust libnativetunnel.so library. It doesn't seem to have stripped symbol names either (which makes sense).

But I have tried running the mentioned tool before writing the issue, just forgot to paste the result in. Now re-ran it on my server (no new lines, because of my terminal emulator on phone):

Identifying the ssl_log_secret() function for extracting key material using Frida.                                    Version: 0.8 by Daniel Baier                                                                                          [*] Start analyzing binary libnativetunnel.so (CPU Architecture: ARM64). This might take a while ...                  [*] Looking for SERVER_HANDSHAKE_TRAFFIC_SECRET            [*] Trying fallback approach with String CLIENT_RANDOM     [-] No functions found using the string.                   ssl_log_secret() function not found.                       === Finished analyzing libnativetunnel.so ===

Even though it definitely contains some openssl symbols:

$ readelf -a binaries/libnativetunnel.so | grep SSL_connect                            67572: 0000000000b101fc    40 FUNC    LOCAL  HIDDEN    14SSL_connect

I am not actually sure whether its openssl or rustls used for the connection though. But if we could hook openssl manually, that would already be huge. Once my new PSU arrives, I will try to figure out the addresses using Ghidra and try to feed it to friTap manually.

@monkeywave
Copy link
Collaborator

Hi @Diniboy1123,

Thank you for the detailed feedback and insights! I had some time to explore the app and the libnativetunnel.so binary further, and here’s what I’ve found:


Findings

  1. QUIC Implementation:
    It appears that quiche-tokio and quiche are used for the QUIC implementation in the app. quiche internally relies on BoringSSL for its cryptographic operations (reference).

  2. BoringSSL Compilation & Updates:
    I’ve updated BoringSecretHunter so it can now successfully identify the ssl_log_secret() function in the target application. I’ve also written a short Frida script (warp_hook.js) that installs a hook for this function. However, despite the hook being installed, it is never invoked during runtime.

Example invocation:

   frida -U -p $(frida-ps -Uai | grep -i "1.1.1" | awk '{print $1}') -l warp_hook.js

Output:

  ____
/ _  |   Frida 16.2.1 - A world-class dynamic instrumentation toolkit
| (_| |
> _  |   Commands:
/_/ |_|       help      -> Displays the help system
. . . .       object?   -> Display information about 'object'
. . . .       exit/quit -> Exit
. . . .
. . . .   More info at https://frida.re/docs/home/
. . . .
. . . .   Connected to Pixel 5 (id=09011FDD4007DJ)
Attaching...
BoringSSL module not found.
Hooking dlopen
Hooking android_dlopen_ext
[Pixel 5::PID::1130 ]-> [android_dlopen_ext] Loading library: /data/app/~~sMdWLz6GHUjGpRXPLPJXJQ==/com.cloudflare.onedotonedotonedotone-WzdZfb0wt63A6ML1C4epqg==/split_config.arm64_v8a.apk!/lib/arm64-v8a/libnativetunnel.so
[Dynamic Load] libnativetunnel.so loaded dynamically.
Module Base Address: 0x77b4c8c000
Module Size: 17985536
[*] Start hooking on arch: arm64
Pattern found at (ssl_log_secret()): 0x77b579a978
Analyzing address: 0x77b579a978
Page Info:
Base Address: 0x77b5339000
Size: 10272768
Protection: r-x
/data/app/~~sMdWLz6GHUjGpRXPLPJXJQ==/com.cloudflare.onedotonedotonedotone-WzdZfb0wt63A6ML1C4epqg==/split_config.arm64_v8a.apk
  1. BoringSSL Hooking I explored the binary further and identified some BoringSSL-related functions. Unfortunately, while the hook for ssl_log_secret() installs correctly, it is never invoked during runtime, which suggests the function might not be used in the current implementation.

  2. Hooking quiche Functions: I suspect that hooking some of the quiche functions could yield better results. However, attempts to hook these functions consistently crash the app and Frida. This may be due to how these functions are invoked or optimized in this implementation.
    Below is the script I used:

// Hook the libnativetunnel.so library
function hookQuicheFunctions() {
    var moduleName = "libnativetunnel.so";
    var module = Process.getModuleByName(moduleName);
    if (!module) {
        console.log(moduleName + " not found in memory.");
        return;
    }
    console.log(moduleName + " loaded at base address: " + module.base);

    // Enumerate symbols in the module
    var symbols = module.enumerateSymbols();
    //var symbols = module.enumerateExports();
    symbols.forEach(function (symbol) {
        try {
            if (symbol.name.includes("bssl") || symbol.name.includes("quiche")) {
                // Check if symbol.address is not zero
                console.log("Hooking: " + symbol.name + " at " + symbol.address);

                // Hook the function
                Interceptor.attach(symbol.address, {
                    onEnter: function (args) {
                        console.log("[quiche function] Called: " + symbol.name);
                    },
                    onLeave: function (retval) {
                        console.log("[quiche function] Returned: " + retval);
                    }
                });
            }
        } catch (e) {
            console.log('[!] Error in onLeave: ' + e.message);
        }
    });
}

Unfortunately, I don’t have time to investigate this any further at the moment. If you manage to get more insights or progress on hooking quiche functions, feel free to share your findings. I hope the updates to BoringSecretHunter and the Frida script provide a good starting point for further exploration.

Let me know if you make any progress or have additional questions. I’d be happy to help where I can :-)

All the best,

Daniel

@Diniboy1123
Copy link
Author

Diniboy1123 commented Feb 14, 2025

Hi @monkeywave,

Sorry for the delays, I tried a few things after your post, but didn't have too much time to dig into this more in depth either. What surprised me is that I couldn't get functions inside libnativetunnel to fire as well. First I thought that maybe the lib is loaded twice into memory and we are picking the wrong one, but no matter what, I couldn't get any of the methods to get called. Then I took a look at the Java app and figured that nativetunnel is only used for 3 functions. It is simply not used for anything else. The actual logic is in libwarp_mobile.so. Sorry for overlooking!

Then I modified your script: https://gist.github.com/Diniboy1123/595202e17214b4ff3ade5aaa8da6b6b2

And I can definitely see some interesting values:

[*] successfully hooked ssl_log_secret()
a2: CLIENT_HANDSHAKE_TRAFFIC_SECRET
a3: [object ArrayBuffer]
eeREDACTEDb4
[*] successfully hooked ssl_log_secret() on_leave
[*] successfully hooked ssl_log_secret()
a2: SERVER_HANDSHAKE_TRAFFIC_SECRET
a3: [object ArrayBuffer]
b1REDACTEDd6
[*] successfully hooked ssl_log_secret() on_leave
[*] successfully hooked ssl_log_secret()
a2: CLIENT_TRAFFIC_SECRET_0
a3: [object ArrayBuffer]
b0REDACTED2f
[*] successfully hooked ssl_log_secret() on_leave
[*] successfully hooked ssl_log_secret()
a2: SERVER_TRAFFIC_SECRET_0
a3: [object ArrayBuffer]
f0REDACTED3b
[*] successfully hooked ssl_log_secret() on_leave
[*] successfully hooked ssl_log_secret()
a2: EXPORTER_SECRET
a3: [object ArrayBuffer]
8eREDACTED25
[*] successfully hooked ssl_log_secret() on_leave

They even look like valid length secrets to me. However they are nothing like what friTap dumps when I am running it the same time on the app. Maybe friTap extracts wrong ones? Also, I can see that boringssl has a client_random field in the ssl struct: https://github.com/google/boringssl/blob/8c6b0c04f19f5a6a13af3e29a6a4bb6784cb2bd7/ssl/ssl_lib.cc#L191 But I am not sure how can I read that exactly.

So I am a little further, but also not too much, because I still can't decrypt with these.

I was hoping that if I craft a keylog file manually from the wireshark dump of the random value:

Image

And the values I got from ssl_log_secrets(), I can get wireshark to decrypt my dump, but unfortunately it still can't...

What's the reason why you don't use ssl_log_secret() in your hooks BTW?

@monkeywave
Copy link
Collaborator

Hi @Diniboy1123,

thank you for taking the time to share your findings in such detail! Based on your insights, we have updated friTap to incorporate this information.

I ran some tests on my research device using the latest version of friTap (1.3.0.0) and observed different keys being printed, including the client_random. This suggests that the latest update may resolve the issue you encountered. I’d recommend trying again with friTap 1.3.0.0 to see if you can now extract the correct keys and successfully decrypt the traffic.

Regarding your question about ssl_log_secret(), friTap already hooks this function, primarily through the Cronet ([1]) and Flutter ([2]) hooks. Most invocations of ssl_log_secret() occur within these hooks, which facilitate the extraction of relevant secrets.

If you’re specifically interested in how friTap extracts the client_random from ssl_log_secret(), you can find the implementation details here: client_random dumping in friTap ([3]).

Additionally, with the latest update, you now have the flexibility to define custom hooks for ssl_log_secret() using the --patterns pattern.json parameter. We’ve also improved BoringSecretHunter to address an issue related to short patterns, making pattern generation more reliable.

I’d suggest giving the latest version of friTap a try and letting me know if this resolves the issue. If you’re still experiencing problems, feel free to reach out—I’d be happy to help further :-)

Looking forward to your feedback. 😊

All the best,

Daniel

References

[1] Cronet Hooks in friTap
[2] Flutter Hooks in friTap
[3] Implementation of client_random Dumping

@Diniboy1123
Copy link
Author

Diniboy1123 commented Feb 15, 2025

Thanks for getting back so quick. I am already ashamed to bother you so often, but I still don't think the implementation is correct or my script is getting the secrets wrong.

So I have done 3 things at the same time.

First I cloned the repo to make sure it's the latest version I have (but then also tried the one from pip, same issue) and ran the following command to launch the Warp app and simultaneously start capturing network traffic as well as secret keys:

fritap -m -k keys.log -v -s com.cloudflare.onedotonedotonedotone -do -p test.pcap --full_capture

(For some reason not doing a full capture produces empty logs, but I assume that could be because of the twisty VPN setup here)

Secondly I took the script I linked earlier that I modified slightly to accompany your research:

let ssl_ptr = Memory.readPointer(args[0]);
console.log("ssl: " + ssl_ptr);
let s3_ptr = Memory.readPointer(ssl_ptr.add(0x30));
console.log("s3: " + s3_ptr);
let client_random_ptr = Memory.readPointer(s3_ptr.add(0x30));
console.log("client_random: " + client_random_ptr);

That one was attached to the already running process:

frida -U -p $(frida-ps -Uai | grep -i "1.1.1" | awk '{print $1}') -l warp_hook.js

Finally I enabled the tunnel inside the app. What I immediately see is that warp_hook.js almost immediately spits out some values, while fritap takes a while (I am guessing those might even be completely unrelated TLS contexts) to spit out some credentials.

Find my redacted logs here. I didn't redact the first and last bytes so you can check for yourself. My script claims that CLIENT_HANDSHAKE_TRAFFIC_SECRET is 0eREDACTEDfaf. If I grep for that in the other log, I get 0 matches.

So I am jumping to the conclusion that either of these scripts is getting something wrong or fritap doesn't even hook that specific address. I don't even see it hooking the libwarp_mobile.so file in the logs nor the ssl_log_secrets address:

[*] libssl.so found & will be hooked on Android!
[***] The module "libssl.so" has 554 exports.
[***] Found SSL_read 0x792e662120
[***] Found SSL_write 0x792e662590
[***] Found SSL_get_fd 0x792e6631d0
[***] Found SSL_get_session 0x792e66b640
[***] Found SSL_SESSION_get_id 0x792e66b0c0
[***] Found SSL_new 0x792e661230
[***] Found SSL_CTX_set_keylog_callback 0x792e6653e0
[***] Found SSL_CTX_new 0x792e660d80
[***] Found getpeername 0x7a12d55580
[***] Found getsockname 0x7a12d55540
[***] Found ntohs 0x7a12d4cd30
[***] Found ntohl 0x7a12d4cd20
[***] The module "libssl.so" has 554 exports.

In the meantime I opened the pcap file again that the script dumped from the device. I can see a completely different random value there than any of the values fritap dumps. Can you confirm that the first column that contains hex values (so technically speaking the absolute second column, which is seemingly the same value in the same session for all secrets) should match with what I see in wireshark inside the clienthello packet's random value? If so, they aren't matching with any of the hex values fritap dumps. :(

Anyway back to "my modified" warp_hook script. Many thanks for the pointer, it is really useful. However as you can see in my logs, the last pointer to client_random seems to be wrong. I am probably doing something wrong... My device is arm64, so I thought I need 0x30 from ssl to s3 and 0x30 again for client_random. That seems to produce an illegal access once trying to read and you can see from the pointer size that something is wrong too. How did you figure all these offsets? Static analysis in some disassembler?

Thanks once again for all this!

@Diniboy1123
Copy link
Author

Diniboy1123 commented Feb 15, 2025

Additionally, with the latest update, you now have the flexibility to define custom hooks for ssl_log_secret() using the --patterns pattern.json parameter. We’ve also improved BoringSecretHunter to address an issue related to short patterns, making pattern generation more reliable

This sounds really useful, I will give it a try and share my findings in a bit. Update: Tried it, see my findings below.

Okay, tried it really quick. Not sure if I did it right, but used this content:

{
    "openssl":{
        "ssl_log_secrets": {
            "address":"0x481ea4",
            "absolute":false
        }
    }
}

Address was calculated from the other script output and double checked from IDA:

Found BoringSSL Module in: libwarp_mobile.so
Module Base Address: 0x78c62de000
Module Size: 7401472
[*] Start hooking on arch: arm64
Hooking dlopen
Hooking android_dlopen_ext
Pattern found at (ssl_log_secret()): 0x78c675fea4

Even then it doesn't seem to be hooked:

spawning com.cloudflare.onedotonedotonedotone
[*] doing full capture on Android
[***] loading friTap frida script: _ssl_log.js
[*] applying hooks at offset {
    "openssl":{
        "ssl_log_secrets": {
            "address":"0x481ea4",
            "absolute":false
        }
    }
}

[*] Running Script on Android
[*] libssl.so found & will be hooked on Android!
[***] The module "libssl.so" has 554 exports.
[***] Found SSL_read 0x792e662120
[***] Found SSL_write 0x792e662590
[***] Found SSL_get_fd 0x792e6631d0
[***] Found SSL_get_session 0x792e66b640
[***] Found SSL_SESSION_get_id 0x792e66b0c0
[***] Found SSL_new 0x792e661230
[***] Found SSL_CTX_set_keylog_callback 0x792e6653e0
[***] Found SSL_CTX_new 0x792e660d80
[***] Found getpeername 0x7a12d55580
[***] Found getsockname 0x7a12d55540
[***] Found ntohs 0x7a12d4cd30
[***] Found ntohl 0x7a12d4cd20
[***] The module "libssl.so" has 554 exports.

I have a feeling that this could be happening, because maybe the module only hooks symbols once its launched and not later? In my case libwarp_mobile is only loaded once I start the tunnel. Ideally I want to listen to every single new module that gets loaded by linker64 and hook those really quick.

Edit: Last two comments merged to avoid the spam.

@monkeywave
Copy link
Collaborator

Hi @Diniboy1123,

thanks again for your thorough testing and valuable feedback :-) I really appreciate your patience and all the effort you’re putting into this.

I realized that while testing something different, I had unintentionally commented out the lines responsible for hooking ssl_log_secret() from libwarp_mobile.so. This has now been fixed in the latest version of friTap, so just using the latest pip version should allow you to hook ssl_log_secret() as expected.

However, I also noticed that the wrong CLIENT_RANDOM values are still being printed, which remains an issue I haven't fully resolved yet.

You’re right that sometimes the warp app crashes when trying to extract secrets. At this point, I’m not entirely sure what’s causing the instability—whether it's an issue with Frida, memory access, or something else inside the app’s TLS stack.

Regarding your question, you're absolutely correct that the CLIENT_RANDOM is visible in the ClientHello message of the TLS handshake.
For instance when I hook the warp app (on a Pixel5 with Android 13 on ARM64) I have the following results with friTap:

fritap -m -k warpkeys_final.log -p warp_final.pcap --full_capture -do -v 1.1.1.1
Start logging
Press Ctrl+C to stop logging

[*] capturing whole traffic of target app
[*] Attaching to the first available USB device...
[*] Successfully attached to the mobile device.
[*] doing full capture on Android
[***] loading friTap frida script: _ssl_log.js
[*] Running Script on Android
[*] libssl.so found & will be hooked on Android!
[***] The module "libssl.so" has 887 exports.
[***] Found SSL_read 0x6db84903dc
[***] Found SSL_write 0x6db8490814
[***] Found SSL_get_fd 0x6db84911f0
[***] Found SSL_get_session 0x6db8499078
[***] Found SSL_SESSION_get_id 0x6db8498be8
[***] Found SSL_new 0x6db848f628
[***] Found SSL_CTX_set_keylog_callback 0x6db849367c
[***] Found SSL_CTX_new 0x6db848f1a4
[***] Found getpeername 0x7082274f10
[***] Found getsockname 0x7082274ef0
[***] Found ntohs 0x708226e6e0
[***] Found ntohl 0x708226e6d8
[***] The module "libssl.so" has 887 exports.
[*] libwarp_mobile.so found & will be hooked on Android!
[***] Found getpeername 0x7082274f10
[***] Found getsockname 0x7082274ef0
[***] Found ntohs 0x708226e6e0
[***] Found ntohl 0x708226e6d8
[***] Trying Pattern: {"primary":"3F 23 03 D5 FF ?3 01 D1 FD 7B 0? A9 F6 57 0? A9 F4 4F 0? A9 FD ?3 0? 91 08 34 40 F9 08 1? 41 F9 ?8 0? 00 B4","fallback":"3F 23 03 D5 FF 03 02 D1 FD 7B 04 A9 F7 2B 00 F9 F6 57 06 A9 F4 4F 07 A9 FD 03 01 91 08 34 40 F9 08 ?? 41 F9 E8 0F 00 B4"}
[***] Module Base Address: 0x6d6128f000
[***] Module Size: 7401472
[*] Android dynamic loader hooked.
[*] Logging pcap to warp_final.pcap
[*] Logging keylog file to warpkeys_final.log
[***] Primary pattern failed, trying fallback pattern...
[***] None of the patterns worked. You may need to adjust the patterns.
[***] ProviderInstaller could not be found, although it has been loaded
[***] Remaining: AndroidNSSP version 1.0,AndroidOpenSSL version 1.0,CertPathProvider version 1.0,AndroidKeyStoreBCWorkaround version 1.0,BC version 1.68,HarmonyJSSE version 1.0,AndroidKeyStore version 1.0
[!] The extracted CLIENT_RANDOM from libwarp_mobile.so is currently not working correctly.
[***] Installed ssl_log_secret() hooks using sybmols.
CLIENT_HANDSHAKE_TRAFFIC_SECRET E2AF968D048DD97636EC5769BF1127F3BF4893D9E39C0DF73FE7E3AC1FD215D0 1EE21CA289F272278D816FF8848BCBB182AE0AACFA230A90BDA8C3168334999EBD6BC0200FB3C8F35B483CA0B6EF8BF4
SERVER_HANDSHAKE_TRAFFIC_SECRET E2AF968D048DD97636EC5769BF1127F3BF4893D9E39C0DF73FE7E3AC1FD215D0 AD78263341EADB0999061E83E15594D333DB3F0B940E8AABC276868457D062EBB76E96A1689ED769849FBDAB3B78CD28
CLIENT_TRAFFIC_SECRET_0 E2AF968D048DD97636EC5769BF1127F3BF4893D9E39C0DF73FE7E3AC1FD215D0 A6C68739C28008594ABD71C5938575DD0152E2B8FE031E813BBE1FA24184B3AC490419DCD1FEED072E3C396FA2606A5E
SERVER_TRAFFIC_SECRET_0 E2AF968D048DD97636EC5769BF1127F3BF4893D9E39C0DF73FE7E3AC1FD215D0 B16A8AAB88301096AEC47AAF70930C9D117920E06D113A1687F1F702EEF5E3A1DA2DFD94F0A9093169342627947D6B09
EXPORTER_SECRET E2AF968D048DD97636EC5769BF1127F3BF4893D9E39C0DF73FE7E3AC1FD215D0 C866D0B2D2015BCD6B453D01B723391C76DF1FE1BDDCE919A91C2E5E7ED3BE38
[***] invoking keylog_callback from OpenSSL_BoringSSL
CLIENT_HANDSHAKE_TRAFFIC_SECRET e70882187609269b63386ff95c12bb416dd1c351b7b2939dddc5bafd5558e770 aceec09952d2abfc3bced27dea9b7c74cc3efe5e3b2f0d171794a3a7fa8a07e2
[***] invoking keylog_callback from OpenSSL_BoringSSL
SERVER_HANDSHAKE_TRAFFIC_SECRET e70882187609269b63386ff95c12bb416dd1c351b7b2939dddc5bafd5558e770 45eb57441066bf86c9c9bcf28d343cec0013e2136e8cb44f02328d6345fc6f43
[***] invoking keylog_callback from OpenSSL_BoringSSL
CLIENT_TRAFFIC_SECRET_0 e70882187609269b63386ff95c12bb416dd1c351b7b2939dddc5bafd5558e770 7ae6a99b73c60e5acb99940bec7d21deb9d5a9efedfe70762d25e80d40b36329
[***] invoking keylog_callback from OpenSSL_BoringSSL
SERVER_TRAFFIC_SECRET_0 e70882187609269b63386ff95c12bb416dd1c351b7b2939dddc5bafd5558e770 7abb54e27968dbdf59701406a24db776dc11dfde82f38ec71f1d722520c26a08
[***] invoking keylog_callback from OpenSSL_BoringSSL
EXPORTER_SECRET e70882187609269b63386ff95c12bb416dd1c351b7b2939dddc5bafd5558e770 7baaac91eb2c8434517efdddd845de444904d37f4ff0759d9cb0abcf68606e4b
[***] invoking keylog_callback from OpenSSL_BoringSSL
CLIENT_HANDSHAKE_TRAFFIC_SECRET 3c5e205f079ec3b36df782054907e845cd2c475a599c32a4017f190042d71827 f5472165175fb991a44008d084ceba723390146b2dc289866a739eae74bbaaf2
[***] invoking keylog_callback from OpenSSL_BoringSSL
SERVER_HANDSHAKE_TRAFFIC_SECRET 3c5e205f079ec3b36df782054907e845cd2c475a599c32a4017f190042d71827 a9d0f539927e327dabc9a6be95187cffa8b9018ecbc8f02b9abc805f7302bb26
[***] invoking keylog_callback from OpenSSL_BoringSSL
CLIENT_TRAFFIC_SECRET_0 3c5e205f079ec3b36df782054907e845cd2c475a599c32a4017f190042d71827 122361d5fd4493fb612379e3dbef5bb7be715ca2e9ba41dcbdad68544056dfa1
[***] invoking keylog_callback from OpenSSL_BoringSSL
SERVER_TRAFFIC_SECRET_0 3c5e205f079ec3b36df782054907e845cd2c475a599c32a4017f190042d71827 d99dc5313f89732081ab53892b6ac9b6f3ec6186804653c450ac04b7cc108d11
[***] invoking keylog_callback from OpenSSL_BoringSSL
EXPORTER_SECRET 3c5e205f079ec3b36df782054907e845cd2c475a599c32a4017f190042d71827 9b322e7594be574a829df56d40544fb4bb262041ecc1dfb42802e6c6d2d5909f
^C
[*] Ctrl+C detected. Cleaning up...
[*] pulling capture from device: /data/local/tmp/_warp_final.pcap: 1 file pulled, 0 skipped. 29.8 MB/s (676355 bytes in 0.022s)
[*] full mobile capture safed to _warp_final.pcap
[*] remember that the full capture won't contain any decrypted TLS traffic. In order to decrypt it use the logged keys from warpkeys_final.log
[*] friTap not trace the sockets in use (--socket_tracing option not enabled)
[*] The resulting PCAP _warp_final.pcap will contain all trafic from the device.
[*] Attempting to detach from Frida process...
[*] Successfully detached from Frida process.
[*] Detached friTap from process successfully.

And when I look into the TLS Client Hello Messages I can see some of the CLIENT_RANDOM values:

tshark -r _warp_final.pcap -Y "tls.handshake.type == 1" -T fields -e tls.handshake.random

3b373cee79ca8b01fd26d14719c2f38833b3d64f42c69a603422359d8c2ab6e8
39bb76c281d55d996e50a3abf98a92775df43caf466b50a3482d7ca22399c335
e70882187609269b63386ff95c12bb416dd1c351b7b2939dddc5bafd5558e770
3c5e205f079ec3b36df782054907e845cd2c475a599c32a4017f190042d71827

Unfortunately, I found that none of the extracted CLIENT_RANDOM values match what ssl_log_secret() prints, which is unexpected and suggests that something is still off with the extraction process.

Regarding your question about extracting the CLIENT_RANDOM value I parsed the TLS struct offsets using IDA Pro in combination with the BoringSSL source code (reference).

In the disassembly, I observed the following sequence:

MOV             X19, X0  
...  
LDR             X23, [X19,#0x30]  
BL              CBB_add_space  
CBZ             W0, loc_482048  

MOV             X8, XZR  
ADD             X9, X23, #0x30 ; '0'  
ADRL            X23, _ZZN4bsslL11cbb_add_hexEP6cbb_stNS_4SpanIKhEEE8hextable  

This strongly resembles the ssl_log_secret() function:

ssl_log_secret(const SSL *ssl, const char *label, Span<const uint8_t> secret)
...
cbb_add_hex_consttime(cbb.get(), ssl->s3->client_random);

Looking at IDA Pro, I can confirm that Quiche is being used, but internally, it still invokes BoringSSL, meaning it shouldn't be an issue for FriTap to extract the secrets:

_ZN6quiche3tls9Handshake4init17hda4accacbd7fad00E --> 
SSL_set_connect_state --> 
_ZN4bssl20ssl_client_handshakeEPNS_13SSL_HANDSHAKEE -->
_ZN4bssl22tls13_client_handshakeEPNS_13SSL_HANDSHAKEE --> 
_ZN4bssl30tls13_derive_handshake_secretsEPNS_13SSL_HANDSHAKEE --> 
_ZN4bssl14ssl_log_secretEPK6ssl_stPKcNS_4SpanIKhEE

Additionally, I noticed that okhttp3 (okhttp3.internal.connection.RealConnection.connectTls) is being used, which leads to Conscrypt (com.android.org.conscrypt.ConscryptEngineSocket.doHandshake+108). That, in turn, calls into /apex/com.android.conscrypt/lib64/libssl.so, where:

bssl::tls13_derive_handshake_secrets(bssl::SSL_HANDSHAKE*)+164

is being executed.

This explains why we see different keys from different sources. The client_random from Conscrypt (libssl.so) does appear in the PCAP (see above output), while the one from libwarp_mobile.so does not.


Unfortunately, in the coming weeks, I won’t have time to dive deeper into this. However, I’m still very curious about what’s going wrong with hooking and extracting all secrets correctly, so this remains an area where friTap can be improved.

To experiment with key dumping, I created a side project on Frida Codeshare:
Android TLS Keylogger

This script is useful for playing around with dumping TLS secrets, but I’ve noticed that the target application crashes quite often when using it—which is another issue that needs further investigation.

Final Thoughts

  • Try the latest friTap version (via pip), as it should now correctly hook ssl_log_secret() from libwarp_mobile.so.
  • There are still issues with extracting the correct CLIENT_RANDOM, and I suspect there are some subtle structural changes in BoringSSL.
  • Investigating crashes: The app sometimes crashes during key extraction, and I’m still unsure why.
  • Different key sources: We’re seeing TLS keys from multiple sources (Conscrypt, BoringSSL inside Quiche, etc.), making it difficult to track down the exact mismatch.

I'm looking forward to your further findings—this is an interesting challenge, and any additional insights you gather would be super helpful :-)

All the best,

Daniel

@Diniboy1123
Copy link
Author

Hey,

Seems like you really got almost everything right. I can also reproduce the CLIENT_RANDOM mismatch, but the secrets seem to work perfectly. I now have a fully working own client. :)

Thanks a lot for all your hard work, this was one of the most impressive interactions I have ever experienced on Github. Both rapid and extremely helpful. This is a fantastic tool, keep up the good work!

I am gonna close this for now and gonna return to figuring out the right offsets for the client_random sometime in the future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants