Skip to content

Commit

Permalink
improving hooks in BoringSSL (ssl_log_secrets()); improved the way we…
Browse files Browse the repository at this point in the history
… can use byte patterns for hooking; symbols now used as an backup solution when we don't identify the target functions using the exports
  • Loading branch information
monkeywave committed Feb 15, 2025
1 parent ced43c9 commit f37f477
Show file tree
Hide file tree
Showing 10 changed files with 442 additions and 34 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
</p>

# friTap
![version](https://img.shields.io/badge/version-1.2.9.1-blue) [![PyPI version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=py&r=r&ts=1683906897&type=6e&v=1.2.9.1&x2=0)](https://badge.fury.io/py/friTap)
![version](https://img.shields.io/badge/version-1.3.0.0-blue) [![PyPI version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=py&r=r&ts=1683906897&type=6e&v=1.3.0.0&x2=0)](https://badge.fury.io/py/friTap)

friTap is a powerful tool designed to assist researchers in analyzing network traffic encapsulated in SSL/TLS. With its ability to automate key extraction, friTap is especially valuable when dealing with malware analysis or investigating privacy issues in applications. By simplifying the process of decrypting and inspecting encrypted traffic, friTap empowers researchers to uncover critical insights with ease.

Expand Down
59 changes: 56 additions & 3 deletions agent/android/android_agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { conscrypt_native_execute } from "./conscrypt.js";
import { flutter_execute } from "./flutter_android.js";
import { s2ntls_execute } from "./s2ntls_android.js";
import { mono_btls_execute } from "./mono_btls_android.js";
import { patterns, isPatternReplaced } from "../ssl_log.js"
import { pattern_execute } from "./pattern_android.js"

var plattform_name = "linux";
var moduleNames: Array<string> = getModuleNames();
Expand Down Expand Up @@ -74,24 +76,75 @@ function hook_native_Android_SSL_Libs(module_library_mapping: { [key: string]: A

}

function loadPatternsFromJSON(jsonContent: string): any {
try {
let data = JSON.parse(jsonContent);
return data;
} catch (error) {
devlog("[-] Error loading or parsing JSON pattern: "+ error);
return null;
}
}

// currently the support this only on Android systems
function install_pattern_based_hooks(){
try{
let data = loadPatternsFromJSON(patterns);
if (data !== null && data.modules) {
for (const moduleName in data.modules) {
if (Object.prototype.hasOwnProperty.call(data.modules, moduleName)) {
console.log("[*] Module name:", moduleName);
module_library_mapping[plattform_name] = [
[moduleName, invokeHookingFunction(pattern_execute)]];

hook_native_Android_SSL_Libs(module_library_mapping, true);
}
}
}

}catch(e){

}

//console.log("data: \n"+data);
/*
for (const moduleName in data.modules) {
/*if (Object.prototype.hasOwnProperty.call(data.modules, moduleName)) {
console.log("[*] Module name:", moduleName);
}
}*/

/*
const hooker = new PatternBasedHooking(cronetModule);
hooker.hook_DumpKeys(this.module_name,"libcronet.so",patterns,(args: any[]) => {
devlog("Installed ssl_log_secret() hooks using byte patterns.");
this.dumpKeys(args[1], args[0], args[2]); // Unpack args into dumpKeys
});
*/
}


export function load_android_hooking_agent() {
module_library_mapping[plattform_name] = [
[/.*libssl_sb.so/, invokeHookingFunction(boring_execute)],
[/.*libssl\.so/, invokeHookingFunction(boring_execute)],
[/libconscrypt_gmscore_jni.so/, invokeHookingFunction(conscrypt_native_execute)], // inspired from https://github.com/PiRogueToolSuite/pirogue-cli/blob/debian-12/pirogue_cli/frida-scripts/log_ssl_keys.js#L55
[/ibconscrypt_jni.so/, invokeHookingFunction(conscrypt_native_execute)],
[/.*cronet.*\.so/, invokeHookingFunction(cronet_execute)],
[/.*monochrome.*\.so/, invokeHookingFunction(cronet_execute)],
[/.*flutter.*\.so/, invokeHookingFunction(flutter_execute)],
[/.*libgnutls\.so/, invokeHookingFunction(gnutls_execute)],
[/.*libwolfssl\.so/, invokeHookingFunction(wolfssl_execute)],
[/.*libnss[3-4]\.so/,invokeHookingFunction(nss_execute)],
[/libmbedtls\.so.*/, invokeHookingFunction(mbedTLS_execute)],
[/.*libs2n.so/, invokeHookingFunction(s2ntls_execute)],
[/.*mono-btls.*\.so/, invokeHookingFunction(mono_btls_execute)]];
[/.*mono-btls.*\.so/, invokeHookingFunction(mono_btls_execute)],
[/.*cronet.*\.so/, invokeHookingFunction(cronet_execute)],
[/.*monochrome.*\.so/, invokeHookingFunction(cronet_execute)]];//,
//[/.*libwarp_mobile.*\.so/, invokeHookingFunction(cronet_execute)]];

install_java_hooks();
hook_native_Android_SSL_Libs(module_library_mapping, true);
hook_Android_Dynamic_Loader(module_library_mapping, false);
if (isPatternReplaced()){
install_pattern_based_hooks();
}
}
45 changes: 41 additions & 4 deletions agent/android/cronet_android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,31 +44,68 @@ export class Cronet_Android extends Cronet {
if (isPatternReplaced()){
devlog("Hooking libcronet functions by patterns from JSON file");
hooker.hook_DumpKeys(this.module_name,"libcronet.so",patterns,(args: any[]) => {
devlog("Installed ssl_log_secret() hooks using byte patterns.");
this.dumpKeys(args[1], args[0], args[2]); // Unpack args into dumpKeys
});
}else{
// This are the default patterns for hooking ssl_log_secret in BoringSSL inside Cronet
hooker.hookModuleByPattern(
get_CPU_specific_pattern(this.default_pattern),
(args) => {
devlog("Installed ssl_log_secret() hooks using byte patterns.");
this.dumpKeys(args[1], args[0], args[2]); // Hook args passed to dumpKeys
}
);
}

return hooker;

}

// instead of relying on pattern we check if the target module has a symbol of ssl_log_secret()
execute_symbol_based_hooking(hooker){
// Capture the dumpKeys function with the correct 'this'
let dumpKeysFunc = this.dumpKeys.bind(this);

if(hooker.no_hooking_success){
let symbols = Process.getModuleByName(this.module_name).enumerateSymbols().filter(exports => exports.name.toLowerCase().includes("ssl_log"));
if(symbols.length > 0){
devlog("Installed ssl_log_secret() hooks using sybmols.");
try{
Interceptor.attach(symbols[0].address, {
onEnter: function(args) {
dumpKeysFunc(args[1], args[0], args[2]);
}
});

}catch(e){
// right now we ingore error's here
}
}


}

}

execute_hooks(){
this.install_key_extraction_hook();
// hooking ssl_log_secret() from BoringSSL
let hooker_instance = this.install_key_extraction_hook();

return hooker_instance;
}

}


export function cronet_execute(moduleName:string, is_base_hook: boolean){
var cronet = new Cronet_Android(moduleName,socket_library,is_base_hook);
let cronet = new Cronet_Android(moduleName,socket_library,is_base_hook);
try {
cronet.execute_hooks();
let hooker = cronet.execute_hooks();
// wait 1 sec before we continue
setTimeout(function() {
cronet.execute_symbol_based_hooking(hooker);
}, 1000);
}catch(error_msg){
devlog(`cronet_execute error: ${error_msg}`)
}
Expand All @@ -85,4 +122,4 @@ export function cronet_execute(moduleName:string, is_base_hook: boolean){
}
}

}
}
93 changes: 93 additions & 0 deletions agent/android/pattern_android.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@

import {Cronet } from "../ssl_lib/cronet.js";
import { socket_library } from "./android_agent.js";
import {PatternBasedHooking } from "../shared/pattern_based_hooking.js";
import { patterns, isPatternReplaced } from "../ssl_log.js"
import { devlog } from "../util/log.js";


export class Pattern_Android extends Cronet {

constructor(public moduleName:string, public socket_library:String, is_base_hook: boolean){
super(moduleName,socket_library,is_base_hook);
}

install_key_extraction_hook(){
if(isPatternReplaced){
const patternModuleName = Process.findModuleByName(this.module_name);
const hooker = new PatternBasedHooking(patternModuleName);

hooker.hook_DumpKeys(this.module_name,this.module_name,patterns,(args: any[]) => {
devlog(`Installed ssl_log_secret() hooks using byte patterns for module ${this.module_name}`);
this.dumpKeys(args[1], args[0], args[2]); // Unpack args into dumpKeys
});

return hooker;
}else{
return null;
}

}

// instead of relying on pattern we check if the target module has a symbol of ssl_log_secret()
execute_symbol_based_hooking(hooker){
// Capture the dumpKeys function with the correct 'this'
let dumpKeysFunc = this.dumpKeys.bind(this);

if(hooker.no_hooking_success){
let symbols = Process.getModuleByName(this.module_name).enumerateSymbols().filter(exports => exports.name.toLowerCase().includes("ssl_log"));
if(symbols.length > 0){
devlog("Installed ssl_log_secret() hooks using sybmols.");
try{
Interceptor.attach(symbols[0].address, {
onEnter: function(args) {
dumpKeysFunc(args[1], args[0], args[2]);
}
});

}catch(e){
// right now we ingore error's here
}
}


}

}

execute_boring_ssl_log_secret_hooks(){
// hooking ssl_log_secret() from BoringSSL
let hooker_instance = this.install_key_extraction_hook();
return hooker_instance;
}

}


export function pattern_execute(moduleName:string, is_base_hook: boolean){
let pattern_BoringSSL = new Pattern_Android(moduleName,socket_library,is_base_hook);
try {
let hooker = pattern_BoringSSL.execute_boring_ssl_log_secret_hooks();
if(hooker != null){
// wait 1 sec before we continue
setTimeout(function() {
pattern_BoringSSL.execute_symbol_based_hooking(hooker);
}, 1000);
}
}catch(error_msg){
devlog(`pattern_execute error: ${error_msg}`)
}

if (is_base_hook) {
try {
const init_addresses = pattern_BoringSSL.addresses[moduleName];
// ensure that we only add it to global when we are not
if (Object.keys(init_addresses).length > 0) {
(global as any).init_addresses[moduleName] = init_addresses;
}
}catch(error_msg){
devlog(`pattern_execute base-hook error: ${error_msg}`)
}
}

}
7 changes: 6 additions & 1 deletion agent/shared/pattern_based_hooking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,15 @@ export function get_CPU_specific_pattern(default_pattern : { [arch: string]: { p

export class PatternBasedHooking {
found_ssl_log_secret: boolean;
no_hooking_success: boolean;
module: Module;
private patterns: any = {};
private rescannedRanges: Set<string> = new Set(); // Set to keep track of memory ranges that have been rescanned

constructor(module: Module) {
this.found_ssl_log_secret = false;
this.module = module;
this.no_hooking_success = true;
}

private createRegexFromModule(moduleName: string): RegExp {
Expand Down Expand Up @@ -69,6 +71,7 @@ export class PatternBasedHooking {
Memory.scan(moduleBase, moduleSize, pattern, {
onMatch: (address) => {
this.found_ssl_log_secret = true;
this.no_hooking_success = false;
log(`Pattern found at (${pattern_name}) address: ${address}`);
log(`Pattern based hooks installed.`);

Expand All @@ -93,6 +96,7 @@ export class PatternBasedHooking {
this.hookByPatternOnlyReadableParts(patterns, "fallback_pattern", onMatchCallback, (pattern_success_alt) => {
if (!pattern_success_alt) {
devlog("None of the patterns worked. You may need to adjust the patterns.");
this.no_hooking_success = true;
}
});
}
Expand Down Expand Up @@ -179,6 +183,7 @@ export class PatternBasedHooking {
this.hookByPattern(patterns, "fallback_pattern", onMatchCallback, (pattern_success_alt) => {
if (!pattern_success_alt) {
devlog("None of the patterns worked. You may need to adjust the patterns.");
this.no_hooking_success = true;
}
});
}
Expand All @@ -199,7 +204,7 @@ export class PatternBasedHooking {
private invoke_pattern_based_hooking(action: keyof ActionPatterns, module_name: string, platform: string, arch: string, hookCallback: (args: any[]) => void){
var action_specific_patterns = this.get_action_specific_pattern(module_name, platform, arch,action);

devlog(`Using ${action} patterns for ${platform} and ${arch}`);
devlog(`Using ${action} patterns for ${platform} on ${arch}`);
this.hookModuleByPattern(action_specific_patterns, hookCallback);
}

Expand Down
30 changes: 29 additions & 1 deletion agent/ssl_lib/cronet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ export class Cronet {


dumpKeys(labelPtr: NativePointer, sslStructPtr: NativePointer, keyPtr: NativePointer): void {
const KEY_LENGTH = 32; // Assuming key length is 32 bytes
const MAX_KEY_LENGTH = 64;
const RANDOM_KEY_LENGTH = 32;

let labelStr = '';
let client_random = '';
Expand All @@ -93,6 +94,33 @@ export class Cronet {
}

if (!keyPtr.isNull()) {
let KEY_LENGTH = 0;
let calculatedKeyLength = 0;

// Iterate through the memory to determine key length
while (calculatedKeyLength < MAX_KEY_LENGTH) {
//@ts-ignore
const byte = Memory.readU8(keyPtr.add(calculatedKeyLength)); // Read one byte at a time


if (byte === 0) { // Stop if null terminator is found (optional, adjust as needed)
if(calculatedKeyLength < 20){
calculatedKeyLength++;
continue;
}
break;
}
calculatedKeyLength++;
}

if (calculatedKeyLength > 24 && calculatedKeyLength <= 40) {
KEY_LENGTH = 32; // Closest match is 32 bytes
} else if (calculatedKeyLength >= 46 && calculatedKeyLength <=49) {
KEY_LENGTH = 48; // Closest match is 48 bytes
}else{
KEY_LENGTH = 32; // fall back size
}

//@ts-ignore
const keyData = Memory.readByteArray(keyPtr, KEY_LENGTH); // Read the key data (KEY_LENGTH bytes)

Expand Down
Loading

0 comments on commit f37f477

Please sign in to comment.