-
Notifications
You must be signed in to change notification settings - Fork 33
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
Comments
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 To expedite the troubleshooting process, you might want to run BoringSecretHunter against Thanks again for your detailed insights and for your patience while we investigate this further. All the best, Daniel |
Hey, Thanks for the quick and really helpful response! I couldn't respond earlier, because my PSU died. Good catch on 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):
Even though it definitely contains some openssl symbols:
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. |
Hi @Diniboy1123, Thank you for the detailed feedback and insights! I had some time to explore the app and the Findings
Example invocation: frida -U -p $(frida-ps -Uai | grep -i "1.1.1" | awk '{print $1}') -l warp_hook.js Output:
// 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 |
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:
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: 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 |
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 Regarding your question about If you’re specifically interested in how friTap extracts the Additionally, with the latest update, you now have the flexibility to define custom hooks for 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 |
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 Find my redacted logs here. I didn't redact the first and last bytes so you can check for yourself. My script claims that 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
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" Thanks once again for all this! |
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:
Even then it doesn't seem to be hooked:
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. |
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 However, I also noticed that the wrong 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 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 Regarding your question about extracting the 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(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:
Additionally, I noticed that bssl::tls13_derive_handshake_secrets(bssl::SSL_HANDSHAKE*)+164 is being executed. This explains why we see different keys from different sources. The 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: 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
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 |
Hey, Seems like you really got almost everything right. I can also reproduce the 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 |
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: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: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:
Which is really unfortunate. :(
Any help would be very much appreciated!
The text was updated successfully, but these errors were encountered: