Skip to content

Commit

Permalink
first support of Flutter - still not integrated for a plattform
Browse files Browse the repository at this point in the history
  • Loading branch information
monkeywave committed Oct 2, 2024
1 parent b3c1736 commit 8db416b
Show file tree
Hide file tree
Showing 5 changed files with 230 additions and 6 deletions.
6 changes: 2 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ This project was inspired by [SSL_Logger](https://github.com/google/ssl_logger )

Installation is simply a matter of `pip3 install fritap`. This will give you the `friTap` command. You can update an existing `friTap` installation with `pip3 install --upgrade friTap`.

Alternatively just clone the repository and run the `friTap.py` file or download the friTap standlone version from the release page.
Alternatively just clone the repository and run the `friTap.py` file.


## Usage
Expand Down Expand Up @@ -53,9 +53,7 @@ More examples on using friTap can be found in the [USAGE.md](./USAGE.md). A deta

## Hooking Libraries Without Symbols

In certain scenarios, the library we want to hook offers no symbols or is statically linked with other libraries, making it challenging to directly hook functions. For example:

Cronet (`libcronet.so`) and Flutter (`libflutter.so`) are often statically linked with **BoringSSL**.
In certain scenarios, the library we want to hook offers no symbols or is statically linked with other libraries, making it challenging to directly hook functions. For example Cronet (`libcronet.so`) and Flutter (`libflutter.so`) are often statically linked with **BoringSSL**.

Despite the absence of symbols, we can still use friTap for parsing and hooking.

Expand Down
101 changes: 101 additions & 0 deletions agent/android/flutter_android.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@

import { Flutter } from "../ssl_lib/flutter.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 Flutter_Android extends Flutter {
private default_pattern: { [arch: string]: { primary: string; fallback: string } };

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

this.default_pattern = {
"x64": {
primary: "55 41 57 41 56 41 55 41 54 53 48 83 EC 48 48 8B 47 68 48 83 B8 20 02 00 00 00 0F 84 FE 00 00 00", // Primary pattern
fallback: "55 41 57 41 56 41 55 41 54 53 48 83 EC 48 48 8B 47 68 48 83 B8 20 02 00 00 00" // Fallback pattern
},
"x86": {
primary: "55 53 57 56 83 EC 4C E8 00 00 00 00 5B 81 C3 A9 CB 13 00 8B 44 24 60 8B 40 34", // Primary pattern
fallback: "55 53 57 56 83 EC 4C E8 00 00 00 00 5B 81 C3 A9 CB 13 00 8B 44 24 60" // Fallback pattern
},
"arm64": {
primary: "3F 23 03 D5 FF C3 01 D1 FD 7B 04 A9 F6 57 05 A9 F4 4F 06 A9 FD 03 01 91 08 34 40 F9 08 11 41 F9 C8 07 00 B4", // Primary pattern
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 11 41 F9 E8 0F 00 B4" // Fallback pattern
},
"arm": {
primary: "2D E9 F0 43 89 B0 04 46 40 6B D0 F8 2C 01 00 28 49 D0", // Primary pattern
fallback: "2D E9 F0 43 89 B0 04 46 40 6B D0 F8 2C 01 00 28 49 D0" // Fallback pattern (right now we don't have any)
}
};
}



// Simulated JSON object (you can replace this with actual file loading)


private get_CPU_specific_pattern(): { primary: string; fallback: string } {
let arch = Process.arch.toString(); // Get architecture, e.g., "x64", "arm64"
if(arch == "ia32"){
arch = "x86"
}

if (this.default_pattern[arch]) {
return this.default_pattern[arch]; // Return the pattern for the architecture
} else {
throw new Error(`No patterns found for CPU architecture: ${arch}`);
}
}

install_key_extraction_hook(){
const flutterModule = Process.findModuleByName(this.module_name);
const hooker = new PatternBasedHooking(flutterModule);

if (isPatternReplaced()){
devlog("Hooking libflutter functions by patterns from JSON file");
hooker.hook_DumpKeys(this.module_name,"libflutter.so",patterns,(args: any[]) => {
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 Flutter
hooker.hookModuleByPattern(
this.get_CPU_specific_pattern(),
(args) => {
this.dumpKeys(args[1], args[0], args[2]); // Hook args passed to dumpKeys
}
);
}

}

execute_hooks(){
this.install_key_extraction_hook();
}

}


export function flutter_execute(moduleName:string, is_base_hook: boolean){
var flutter = new Flutter_Android(moduleName,socket_library,is_base_hook);
try {
flutter.execute_hooks();
}catch(error_msg){
devlog(`flutter_execute error: ${error_msg}`)
}

if (is_base_hook) {
try {
const init_addresses = flutter.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(`flutter_execute base-hook error: ${error_msg}`)
}
}

}
125 changes: 125 additions & 0 deletions agent/ssl_lib/flutter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { get_hex_string_from_byte_array, readAddresses } from "../shared/shared_functions.js";
import { devlog } from "../util/log.js";




export class Flutter {


// global variables
library_method_mapping: { [key: string]: Array<string> } = {};
addresses: { [libraryName: string]: { [functionName: string]: NativePointer } };
module_name: string;
is_base_hook: boolean;


constructor(public moduleName:string, public socket_library:String,is_base_hook: boolean ,public passed_library_method_mapping?: { [key: string]: Array<string> } ){
this.module_name = moduleName;
this.is_base_hook = is_base_hook;

if(typeof passed_library_method_mapping !== 'undefined'){
this.library_method_mapping = passed_library_method_mapping;
}else{
this.library_method_mapping[`*${socket_library}*`] = ["getpeername", "getsockname", "ntohs", "ntohl"]
}

this.addresses = readAddresses(moduleName,this.library_method_mapping);
}

get_client_random(s3_ptr: NativePointer, SSL3_RANDOM_SIZE: number): string {
if (!s3_ptr.isNull()) {
const client_random_ptr: NativePointer = s3_ptr.add(0x30); // Offset in s3 struct
//@ts-ignore
const client_random = Memory.readByteArray(client_random_ptr, SSL3_RANDOM_SIZE);

// Convert the byte array to a hex string
const hexClientRandom = get_hex_string_from_byte_array(new Uint8Array(client_random as ArrayBuffer));

return hexClientRandom;
} else {
devlog("[Error] s3 pointer is NULL");
return "";
}
}

get_client_random_from_ssl_struct(ssl_st_ptr: NativePointer): string {
const SSL3_RANDOM_SIZE = 32;
let offset_s3: number;

switch (Process.arch) {
case 'x64':
offset_s3 = 0x30;
break;
case 'arm64':
offset_s3 = 0x30;
break;
case 'ia32':
offset_s3 = 0x2C;
break;
case 'arm':
offset_s3 = 0x2C;
break;
default:
devlog("[Error] Unsupported architecture");
return "";
}

const s3_ptr = ssl_st_ptr.add(offset_s3).readPointer();
return this.get_client_random(s3_ptr, SSL3_RANDOM_SIZE);
}


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

let labelStr = '';
let client_random = '';
let secret_key = '';

// Read the label (the label pointer might contain a C string)
if (!labelPtr.isNull()) {
labelStr = labelPtr.readCString() ?? ''; // Read label as a C string
//devlog(`Label: ${labelStr}`);
} else {
devlog("[Error] Argument 'labelPtr' is NULL");
}

// Extract client_random from the SSL structure
if (!sslStructPtr.isNull()) {
client_random = this.get_client_random_from_ssl_struct(sslStructPtr)
}else {
devlog("[Error] Argument 'sslStructPtr' is NULL");
}

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

// Convert the byte array to a string of hex values
const hexKey = get_hex_string_from_byte_array(keyData);

secret_key = hexKey;
} else {
devlog("[Error] Argument 'key' is NULL");
}

//devlog("invoking ssl_log_secret() from BoringSSL statically linked into Cronet");
var message: { [key: string]: string | number | null } = {}
message["contentType"] = "keylog"
message["keylog"] = labelStr+" "+client_random+" "+secret_key;
send(message)
}

install_plaintext_read_hook(){
// TBD
}

install_plaintext_write_hook(){
// TBD
}

install_key_extraction_hook(){
// needs to be setup for the specific plattform
}
}
2 changes: 1 addition & 1 deletion friTap/about.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
# -*- coding: utf-8 -*-

__author__ = "Daniel Baier, Francois Egner, Max Ufer"
__version__ = "1.2.2.0"
__version__ = "1.2.2.1"
debug = False # are we running in debug mode?
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "friTap",
"version": "1.2.2.0",
"version": "1.2.2.1",
"description": "Frida agent for logging SSL traffic as plaintext and extracting SSL keys",
"private": true,
"main": "agent/ssl_log.ts",
Expand Down

0 comments on commit 8db416b

Please sign in to comment.