Skip to content

Commit

Permalink
migrate to new build system; fixes in the way Pattern and Offsets are…
Browse files Browse the repository at this point in the history
… supplied
  • Loading branch information
monkeywave committed Oct 2, 2024
1 parent 3c5ee27 commit b3c1736
Show file tree
Hide file tree
Showing 23 changed files with 938 additions and 121 deletions.
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include requirements.txt
31 changes: 30 additions & 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.1.1-blue) [![PyPI version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=py&r=r&ts=1683906897&type=6e&v=1.2.1.1&x2=0)](https://badge.fury.io/py/friTap)
![version](https://img.shields.io/badge/version-1.2.2.0-blue) [![PyPI version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=py&r=r&ts=1683906897&type=6e&v=1.2.2.0&x2=0)](https://badge.fury.io/py/friTap)

The goal of this project is to help researchers to analyze traffic encapsulated in SSL or TLS. For details have a view into the [OSDFCon webinar slides](assets/friTapOSDFConwebinar.pdf) or in [this blog post](https://lolcads.github.io/posts/2022/08/fritap/).

Expand Down Expand Up @@ -51,6 +51,35 @@ $ sudo -E /home/daniel/.local/bin/friTap

More examples on using friTap can be found in the [USAGE.md](./USAGE.md). A detailed introduction using friTap on Android is under [EXAMPLE.md](./EXAMPLE.md) as well.

## 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**.

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

### Hooking by Byte Patterns

To solve this, we can use friTap with byte patterns to hook the desired functions. You can provide friTap with a JSON file that contains byte patterns for hooking specific functions, based on architecture and platform using the `--patterns <byte-pattern-file.json>` option.
In order to apply the apprioate hooks for the various byte patterns we distinguish between different hooking categories.
These categories include:

- Dump-Keys
- Install-Key-Log-Callback
- KeyLogCallback-Function
- SSL_Read
- SSL_Write

Each category has a primary and fallback byte pattern, allowing flexibility when the primary pattern fails.
For libraries like BoringSSL, where TLS functionality is often statically linked into other binaries, we developed a tool called [BoringSecretHunter](https://github.com/monkeywave/BoringSecretHunter). This tool automatically identifies the necessary byte patterns to hook BoringSSL by byte-pattern matching. Specifically, BoringSecretHunter focuses on identifying the byte patterns for functions in the Dump-Keys category, allowing you to extract encryption keys during TLS sessions with minimal effort. More about the different hooking categories can be found in [usage of byte-patterns in friTap](./USAGE.md#hooking-by-byte-patterns).

### Hooking by Offsets

Alternatively, you can use the `--offsets <offset-file.json>` option to hook functions using known offsets. friTap allows you to specify user-defined offsets (relative to the base address of the targeting SSL/socket library) or absolute virtual addresses for function resolution. This is done through a JSON file, which is passed using the `--offsets` parameter.

If the `--offsets` parameter is used, friTap will only overwrite the function addresses specified in the JSON file. For functions that are not specified, friTap will attempt to detect the addresses automatically (using symbols).


## Problems

Expand Down
100 changes: 96 additions & 4 deletions USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,18 @@ Now we can see and analyze all the packets live with Wireshark. As soon as we st

FriTap allows to specify user-defined offsets (starting from the base address of the ssl/socket library) and to specify absolute virtual addresses of ssl/socket functions for function resolution. For this a JSON file (see offsets_example.json) must be specified using the `--offsets` parameter. If the parameter is set, then friTap will overwrite only those addresses of those functions that were specified. For all functions for which nothing was specified, friTap will try to detect an address on its own.

The `address` field contains the offset or absolute address of the function specified function. The value must be specified as a hexadecimal string!
If the field `absolute` is set to true or the base address of the socket/ssl library cannot be found, then the values specified for `address` are interpreted as absolute addresses.
If the field `absolute` is set to false, then the values specified for `address` are interpreted as an **offset based on the library base address**.
The JSON file consists of the following fields:

- `address`: The offset or absolute address of the specified function, formatted as a hexadecimal string.
- `absolute`:
If `true`, the value in the `address` field is interpreted as an absolute address.
If `false`, the value is treated as an offset from the base address of the SSL/socket library.

If friTap cannot find the base address of the socket/SSL library, or if the `absolute` field is set to `true`, the specified addresses will be interpreted as absolute addresses.

### **Example**:
Suppose friTap recognizes the base address of the OpenSSL library, but does not find an export of the SSL_read and SSL_write functions, and you know the offsets of those same functions. In addition, if you know the absolute address of the required socket functions, the JSON file could look like this
Suppose friTap detects the base address of the OpenSSL library, but it fails to find exports for the `SSL_read` and `SSL_write` functions. If you know the offsets for these functions and the absolute addresses for certain socket functions, your JSON file could look like this:

```json
{
"openssl":{
Expand Down Expand Up @@ -110,3 +116,89 @@ Suppose friTap recognizes the base address of the OpenSSL library, but does not
}
}
```
## Hooking by Byte-Patterns

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.

To solve this, we can use friTap with byte patterns to hook the desired functions. You can provide friTap with a JSON file that contains byte patterns for hooking specific functions, based on architecture and platform.
Hooking Categories

We define different hooking categories for which specific byte patterns are used. These categories include:

Dump-Keys
Install-Key-Log-Callback
KeyLogCallback-Function
SSL_Read
SSL_Write

Each category has a primary and fallback byte pattern, allowing flexibility when the primary pattern fails.


### 1. Dump-Keys

This category is responsible for dumping keys directly from the process. The primary and fallback byte patterns in this category are used to hook functions that deal with key management and extraction. friTap provides than the parsing in order to extract the keys:

```json
"Dump-Keys": {
"primary": "AA BB CC DD EE FF ...",
"fallback": "FF EE DD CC BB AA ..."
}
```
Primary Pattern: Used to hook the function that allows key dumping.
Fallback Pattern: If the primary pattern fails, the fallback pattern is tried.

Our developed tool [BoringSecretHunter](https://github.com/monkeywave/BoringSecretHunter) can be used to automatically extract these patterns from a target library.

### 2. Install-Key-Log-Callback

This category installs a callback for logging TLS keys. It typically works alongside `KeyLogCallback-Function`. Both must be specified together in the JSON. As the name suggests it is responsbile for installing the keylog callback function:

```json
"Install-Key-Log-Callback": {
"primary": "11 22 33 44 55 66 ...",
"fallback": "66 55 44 33 22 11 ..."
}
```
Primary Pattern: Hook the function responsible for installing the key log callback.
Fallback Pattern: If the primary pattern fails, this fallback pattern is tried.

### 3. KeyLogCallback-Function

This category hooks the function that is triggered by the installed key log callback. It must be used alongside the Install-Key-Log-Callback category. It is also used for extracting the TLS key material but **no parsing** has to be done:

```json
"KeyLogCallback-Function": {
"primary": "77 88 99 AA BB CC ...",
"fallback": "CC BB AA 99 88 77 ..."
}
```
Primary Pattern: Hook the function where the key log callback processes keys.
Fallback Pattern: If the primary pattern fails, this fallback pattern is tried.

### 4. SSL_Read

This category hooks the SSL_Read function, which is responsible for reading encrypted SSL/TLS data. It works alongside the SSL_Write category.

```json
"SSL_Read": {
"primary": "AA 55 FF 00 11 22 ...",
"fallback": "22 11 00 FF 55 AA ..."
}
```
Primary Pattern: Hook the SSL_Read function.
Fallback Pattern: If the primary pattern fails, the fallback pattern is tried.

### 5. SSL_Write

This category hooks the SSL_Write function, which is responsible for writing encrypted SSL/TLS data. It must be used with the SSL_Read category.

```json
"SSL_Write": {
"primary": "BB CC DD EE FF 00 ...",
"fallback": "00 FF EE DD CC BB ..."
}
```
Primary Pattern: Hook the SSL_Write function.
Fallback Pattern: If the primary pattern fails, the fallback pattern is tried.
71 changes: 55 additions & 16 deletions agent/android/cronet_android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,39 +5,70 @@ import {PatternBasedHooking } from "../shared/pattern_based_hooking.js";
import { patterns, isPatternReplaced } from "../ssl_log.js"
import { devlog } from "../util/log.js";


export class Cronet_Android extends Cronet {
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 cronetModule = Process.findModuleByName(this.module_name);
const hooker = new PatternBasedHooking(cronetModule);

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






}

execute_hooks(){
Expand All @@ -49,13 +80,21 @@ export class Cronet_Android extends Cronet {

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

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

Expand Down
10 changes: 9 additions & 1 deletion agent/android/openssl_boringssl_android.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@

import {OpenSSL_BoringSSL } from "../ssl_lib/openssl_boringssl.js";
import { devlog } from "../util/log.js";
import { socket_library } from "./android_agent.js";

export class OpenSSL_BoringSSL_Android extends OpenSSL_BoringSSL {
Expand Down Expand Up @@ -48,13 +49,20 @@ export class OpenSSL_BoringSSL_Android extends OpenSSL_BoringSSL {

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

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

Expand Down
51 changes: 51 additions & 0 deletions agent/ios/cronet_ios.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@

import {Cronet } from "../ssl_lib/cronet.js";
import { socket_library } from "./ios_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 Cronet_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(){
const cronetModule = Process.findModuleByName(this.module_name);
const hooker = new PatternBasedHooking(cronetModule);

if (isPatternReplaced()){
devlog("Hooking libcronet functions by pattern");
hooker.hook_DumpKeys(this.module_name,"libcronet.so",patterns,(args: any[]) => {
this.dumpKeys(args[1], args[0], args[2]); // Unpack args into dumpKeys
});
}






}

execute_hooks(){
this.install_key_extraction_hook();
}

}


export function cronet_execute(moduleName:string, is_base_hook: boolean){
var cronet = new Cronet_Android(moduleName,socket_library,is_base_hook);
cronet.execute_hooks();

if (is_base_hook) {
const init_addresses = cronet.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;
}
}

}
4 changes: 3 additions & 1 deletion agent/ios/ios_agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { module_library_mapping, ModuleHookingType } from "../shared/shared_stru
import { log, devlog } from "../util/log.js";
import { getModuleNames, ssl_library_loader, invokeHookingFunction } from "../shared/shared_functions.js";
import { boring_execute } from "./openssl_boringssl_ios.js";
import { cronet_execute } from "./cronet_ios.js"


var plattform_name = "darwin";
Expand Down Expand Up @@ -57,7 +58,8 @@ function hook_iOS_SSL_Libs(module_library_mapping: { [key: string]: Array<[any,

export function load_ios_hooking_agent() {
module_library_mapping[plattform_name] = [
[/.*libboringssl\.dylib/, invokeHookingFunction(boring_execute)]]
[/.*libboringssl\.dylib/, invokeHookingFunction(boring_execute)],
[/.*cronet.*\.dylib/, invokeHookingFunction(cronet_execute)]]

hook_iOS_SSL_Libs(module_library_mapping, true);
hook_iOS_Dynamic_Loader(module_library_mapping, false);
Expand Down
Loading

0 comments on commit b3c1736

Please sign in to comment.