[![stars](https://img.shields.io/github/stars/Impact-I/reFlutter)](https://github.com/Impact-I/reFlutter/stargazers)
<p align="center"><img src="https://user-images.githubusercontent.com/87244850/135659542-22bb8496-bf26-4e25-b7c1-ffd8fc0cea10.png" width="75%"/></p>
**Read more on the blog:** https://swarm.ptsecurity.com/fork-bomb-for-flutter/
This framework helps with Flutter apps reverse engineering using the patched version of the Flutter library which is already compiled and ready for app repacking. This library has snapshot deserialization process modified to allow you perform dynamic analysis in a convenient way.
Key features:
- `socket.cc` is patched for traffic monitoring and interception;
- `dart.cc` is modified to print classes, functions and some fields;
- display absolute code offset for functions
- contains minor changes for successfull compilation;
- if you would like to implement your own patches, manual Flutter code changes are supported using a specially crafted `Dockerfile`
### Supported engines
- Android: arm64, arm32;
- iOS: arm64;
- Release: Stable, Beta
### Install
```
# Linux, Windows, MacOS
pip3 install reflutter==0.8.0
```
### Usage
```console
impact@f:~$ reflutter main.apk
Please enter your Burp Suite IP: <input_ip>
SnapshotHash: 8ee4ef7a67df9845fba331734198a953
The resulting apk file: ./release.RE.apk
Please sign the apk file
Configure Burp Suite proxy server to listen on *:8083
Proxy Tab -> Options -> Proxy Listeners -> Edit -> Binding Tab
Then enable invisible proxying in Request Handling Tab
Support Invisible Proxying -> true
impact@f:~$ reflutter main.ipa
```
### Traffic interception
You need to specify the IP of your Burp Suite Proxy Server located in the same network where the device with the flutter application is. Next, you should configure the Proxy in `BurpSuite -> Listener Proxy -> Options tab`
- Add port: `8083`
- Bind to address: `All interfaces`
- Request handling: Support invisible proxying = `True`
<p align="center"><img src="https://user-images.githubusercontent.com/87244850/135753172-20489ef9-0759-432f-b2fa-220607e896b8.png" width="84%"/></p>
You don't need to install any certificates. On an Android device, you don't need root access as well. reFlutter also allows to bypass some of the flutter certificate pinning implementations.
### Usage on Android
The resulting apk must be aligned and signed. I use [uber-apk-signer](https://github.com/patrickfav/uber-apk-signer/releases/tag/v1.2.1)
`java -jar uber-apk-signer.jar --allowResign -a release.RE.apk`.
To see which code is loaded through DartVM, you need to run the application on the device. Note that you must manually find what `_kDartIsolateSnapshotInstructions` (ex. 0xB000 ) equals to using a binary search. reFlutter writes the dump to the root folder of the application and sets `777` permissions to the file and folder. You can pull the file with adb command
```console
impact@f:~$ adb -d shell "cat /data/data/<PACKAGE_NAME>/dump.dart" > dump.dart
```
<details>
<summary>file contents</summary>
```dart
Library:'package:anyapp/navigation/DeepLinkImpl.dart' Class: Navigation extends Object {
String* DeepUrl = anyapp://evil.com/ ;
Function 'Navigation.': constructor. (dynamic, dynamic, dynamic, dynamic) => NavigationInteractor {
Code Offset: _kDartIsolateSnapshotInstructions + 0x0000000000009270
}
Function 'initDeepLinkHandle':. (dynamic) => Future<void>* {
Code Offset: _kDartIsolateSnapshotInstructions + 0x0000000000412fe8
}
Function '_navigateDeepLink@547106886':. (dynamic, dynamic, {dynamic navigator}) => void {
Code Offset: _kDartIsolateSnapshotInstructions + 0x0000000000002638
}
}
Library:'package:anyapp/auth/navigation/AuthAccount.dart' Class: AuthAccount extends Account {
PlainNotificationToken* _instance = sentinel;
Function 'getAuthToken':. (dynamic, dynamic, dynamic, dynamic) => Future<AccessToken*>* {
Code Offset: _kDartIsolateSnapshotInstructions + 0x00000000003ee548
}
Function 'checkEmail':. (dynamic, dynamic) => Future<bool*>* {
Code Offset: _kDartIsolateSnapshotInstructions + 0x0000000000448a08
}
Function 'validateRestoreCode':. (dynamic, dynamic, dynamic) => Future<bool*>* {
Code Offset: _kDartIsolateSnapshotInstructions + 0x0000000000412c34
}
Function 'sendSmsRestorePassword':. (dynamic, dynamic) => Future<bool*>* {
Code Offset: _kDartIsolateSnapshotInstructions + 0x00000000003efb88
}
}
```
</details>
### Usage on iOS
Use the IPA file created after the execution of `reflutter main.ipa` command. To see which code is loaded through DartVM, you need to run the application on the device. reFlutter will print the dump file path to the Xcode console logs with the reFlutter tag
`Current working dir: /private/var/mobile/Containers/Data/Application/<UUID>/dump.dart`
Next, you will need to pull the file from the device
<p align="center"><img src="https://user-images.githubusercontent.com/87244850/135860648-a13ba3fd-93d2-4eab-bd38-9aa775c3178f.png" width="100%"/></p>
### Frida
The resulting offset from the dump can be used in the frida [script](https://github.com/Impact-I/reFlutter/blob/main/frida.js)
```
frida -U -f <package> -l frida.js
```
To get value for `_kDartIsolateSnapshotInstructions` you can use `readelf -Ws libapp.so` Where is the value you need in the `Value` field
### To Do
- [x] Display absolute code offset for functions;
- [ ] Extract more strings and fields;
- [x] Add socket patch;
- [ ] Extend engine support to Debug using Fork and Github Actions;
- [ ] Improve detection of `App.framework` and `libapp.so` inside zip archive
### Build Engine
The engines are built using [reFlutter](https://github.com/Impact-I/reFlutter/blob/main/.github/workflows/main.yml) in [Github Actions](https://github.com/Impact-I/reFlutter/actions) to build the desired version, commits and snapshot hashes are used from this [table](https://github.com/Impact-I/reFlutter/blob/main/enginehash.csv).
The hash of the snapshot is extracted from `storage.googleapis.com/flutter_infra_release/flutter/<hash>/android-arm64-release/linux-x64.zip`
<details>
<summary>release</summary>
[![gif](https://user-images.githubusercontent.com/87244850/135758767-47b7d51f-8b6c-40b5-85aa-a13c5a94423a.gif)](https://github.com/Impact-I/reFlutter/actions)
</details>
### Custom Build
If you would like to implement your own patches, manual Flutter code change is supported using specially crafted [Docker](https://hub.docker.com/r/ptswarm/reflutter)
```bash
git clone https://github.com/Impact-I/reFlutter && cd reFlutter
docker build -t reflutter -f Dockerfile .
```
Build command:
```bash
docker run -it -v "$(pwd):/t" -e HASH_PATCH=<Snapshot_Hash> -e COMMIT=<Engine_commit> reflutter
```
Example:
```bash
docker run -it -v "$(pwd):/t" -e HASH_PATCH=aa64af18e7d086041ac127cc4bc50c5e -e COMMIT=d44b5a94c976fbb65815374f61ab5392a220b084 reflutter
```
# Linux, Windows
EXAMPLE BUILD ANDROID ARM64:
```bash
docker run -e WAIT=300 -e x64=0 -e arm=0 -e HASH_PATCH=<Snapshot_Hash> -e COMMIT=<Engine_commit> --rm -iv${PWD}:/t reflutter
```
FLAGS:<br/>
-e x64=0 <disables building for x64 architecture, use to reduce building time><br/>
-e arm64=0 <disables building for arm64 architecture, use to reduce building time><br/>
-e arm=0 <disables building for arm32 architecture, use to reduce building time><br/>
-e WAIT=300 <the amount of time in seconds you need to edit source code><br/>
-e HASH_PATCH=[Snapshot_Hash] <here you need to specify snapshot hash which matches the engine_commit line of enginehash.csv table best. It is used for proper patch search in reFlutter and for successfull compilation><br/>
-e COMMIT=[Engine_commit] <here you specify commit for your engine version, take it from enginehash.csv table or from flutter/engine repo><br/>
Raw data
{
"_id": null,
"home_page": "https://github.com/Impact-I/reFlutter",
"name": "reflutter",
"maintainer": null,
"docs_url": null,
"requires_python": null,
"maintainer_email": null,
"keywords": "distutils setuptools egg pip requirements",
"author": "impact",
"author_email": "routeros7.1@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/8a/45/c9aec400585a1e159ee4869900c1c3d45f9ee451f66b22b5f599aeb98d1c/reflutter-0.8.0.tar.gz",
"platform": "any",
"description": "[![stars](https://img.shields.io/github/stars/Impact-I/reFlutter)](https://github.com/Impact-I/reFlutter/stargazers)\n\n<p align=\"center\"><img src=\"https://user-images.githubusercontent.com/87244850/135659542-22bb8496-bf26-4e25-b7c1-ffd8fc0cea10.png\" width=\"75%\"/></p>\n\n**Read more on the blog:** https://swarm.ptsecurity.com/fork-bomb-for-flutter/\n\nThis framework helps with Flutter apps reverse engineering using the patched version of the Flutter library which is already compiled and ready for app repacking. This library has snapshot deserialization process modified to allow you perform dynamic analysis in a convenient way.\n\nKey features:\n\n- `socket.cc` is patched for traffic monitoring and interception;\n- `dart.cc` is modified to print classes, functions and some fields;\n- display absolute code offset for functions\n- contains minor changes for successfull compilation;\n- if you would like to implement your own patches, manual Flutter code changes are supported using a specially crafted `Dockerfile`\n\n### Supported engines\n\n- Android: arm64, arm32;\n- iOS: arm64;\n- Release: Stable, Beta\n\n### Install\n\n```\n# Linux, Windows, MacOS\npip3 install reflutter==0.8.0\n```\n\n### Usage\n\n```console\nimpact@f:~$ reflutter main.apk\n\nPlease enter your Burp Suite IP: <input_ip>\n\nSnapshotHash: 8ee4ef7a67df9845fba331734198a953\nThe resulting apk file: ./release.RE.apk\nPlease sign the apk file\n\nConfigure Burp Suite proxy server to listen on *:8083\nProxy Tab -> Options -> Proxy Listeners -> Edit -> Binding Tab\n\nThen enable invisible proxying in Request Handling Tab\nSupport Invisible Proxying -> true\n\nimpact@f:~$ reflutter main.ipa\n```\n\n### Traffic interception\n\nYou need to specify the IP of your Burp Suite Proxy Server located in the same network where the device with the flutter application is. Next, you should configure the Proxy in `BurpSuite -> Listener Proxy -> Options tab`\n\n- Add port: `8083`\n- Bind to address: `All interfaces`\n- Request handling: Support invisible proxying = `True`\n<p align=\"center\"><img src=\"https://user-images.githubusercontent.com/87244850/135753172-20489ef9-0759-432f-b2fa-220607e896b8.png\" width=\"84%\"/></p>\n\nYou don't need to install any certificates. On an Android device, you don't need root access as well. reFlutter also allows to bypass some of the flutter certificate pinning implementations.\n\n### Usage on Android\n\nThe resulting apk must be aligned and signed. I use [uber-apk-signer](https://github.com/patrickfav/uber-apk-signer/releases/tag/v1.2.1)\n`java -jar uber-apk-signer.jar --allowResign -a release.RE.apk`.\nTo see which code is loaded through DartVM, you need to run the application on the device. Note that you must manually find what `_kDartIsolateSnapshotInstructions` (ex. 0xB000 ) equals to using a binary search. reFlutter writes the dump to the root folder of the application and sets `777` permissions to the file and folder. You can pull the file with adb command\n\n```console\nimpact@f:~$ adb -d shell \"cat /data/data/<PACKAGE_NAME>/dump.dart\" > dump.dart\n```\n\n<details>\n<summary>file contents</summary>\n\n```dart\nLibrary:'package:anyapp/navigation/DeepLinkImpl.dart' Class: Navigation extends Object {\nString* DeepUrl = anyapp://evil.com/ ;\n Function 'Navigation.': constructor. (dynamic, dynamic, dynamic, dynamic) => NavigationInteractor {\n\n Code Offset: _kDartIsolateSnapshotInstructions + 0x0000000000009270\n\n }\n\n Function 'initDeepLinkHandle':. (dynamic) => Future<void>* {\n\n Code Offset: _kDartIsolateSnapshotInstructions + 0x0000000000412fe8\n\n }\n\n Function '_navigateDeepLink@547106886':. (dynamic, dynamic, {dynamic navigator}) => void {\n\n Code Offset: _kDartIsolateSnapshotInstructions + 0x0000000000002638\n\n }\n\n }\n\nLibrary:'package:anyapp/auth/navigation/AuthAccount.dart' Class: AuthAccount extends Account {\nPlainNotificationToken* _instance = sentinel;\n\n Function 'getAuthToken':. (dynamic, dynamic, dynamic, dynamic) => Future<AccessToken*>* {\n\n Code Offset: _kDartIsolateSnapshotInstructions + 0x00000000003ee548\n\n }\n\n Function 'checkEmail':. (dynamic, dynamic) => Future<bool*>* {\n\n Code Offset: _kDartIsolateSnapshotInstructions + 0x0000000000448a08\n\n }\n Function 'validateRestoreCode':. (dynamic, dynamic, dynamic) => Future<bool*>* {\n\n Code Offset: _kDartIsolateSnapshotInstructions + 0x0000000000412c34\n\n }\n Function 'sendSmsRestorePassword':. (dynamic, dynamic) => Future<bool*>* {\n\n Code Offset: _kDartIsolateSnapshotInstructions + 0x00000000003efb88\n\n }\n }\n```\n\n</details>\n\n### Usage on iOS\n\nUse the IPA file created after the execution of `reflutter main.ipa` command. To see which code is loaded through DartVM, you need to run the application on the device. reFlutter will print the dump file path to the Xcode console logs with the reFlutter tag\n`Current working dir: /private/var/mobile/Containers/Data/Application/<UUID>/dump.dart`\nNext, you will need to pull the file from the device\n\n<p align=\"center\"><img src=\"https://user-images.githubusercontent.com/87244850/135860648-a13ba3fd-93d2-4eab-bd38-9aa775c3178f.png\" width=\"100%\"/></p>\n\n### Frida\n\nThe resulting offset from the dump can be used in the frida [script](https://github.com/Impact-I/reFlutter/blob/main/frida.js)\n\n```\nfrida -U -f <package> -l frida.js\n```\n\nTo get value for `_kDartIsolateSnapshotInstructions` you can use `readelf -Ws libapp.so` Where is the value you need in the `Value` field\n\n### To Do\n\n- [x] Display absolute code offset for functions;\n- [ ] Extract more strings and fields;\n- [x] Add socket patch;\n- [ ] Extend engine support to Debug using Fork and Github Actions;\n- [ ] Improve detection of `App.framework` and `libapp.so` inside zip archive\n\n### Build Engine\n\nThe engines are built using [reFlutter](https://github.com/Impact-I/reFlutter/blob/main/.github/workflows/main.yml) in [Github Actions](https://github.com/Impact-I/reFlutter/actions) to build the desired version, commits and snapshot hashes are used from this [table](https://github.com/Impact-I/reFlutter/blob/main/enginehash.csv).\nThe hash of the snapshot is extracted from `storage.googleapis.com/flutter_infra_release/flutter/<hash>/android-arm64-release/linux-x64.zip`\n\n<details>\n<summary>release</summary>\n \n[![gif](https://user-images.githubusercontent.com/87244850/135758767-47b7d51f-8b6c-40b5-85aa-a13c5a94423a.gif)](https://github.com/Impact-I/reFlutter/actions)\n \n</details>\n\n### Custom Build\n\nIf you would like to implement your own patches, manual Flutter code change is supported using specially crafted [Docker](https://hub.docker.com/r/ptswarm/reflutter)\n\n```bash\ngit clone https://github.com/Impact-I/reFlutter && cd reFlutter\ndocker build -t reflutter -f Dockerfile .\n```\n\nBuild command:\n\n```bash\ndocker run -it -v \"$(pwd):/t\" -e HASH_PATCH=<Snapshot_Hash> -e COMMIT=<Engine_commit> reflutter\n```\n\nExample:\n\n```bash\ndocker run -it -v \"$(pwd):/t\" -e HASH_PATCH=aa64af18e7d086041ac127cc4bc50c5e -e COMMIT=d44b5a94c976fbb65815374f61ab5392a220b084 reflutter\n```\n\n# Linux, Windows\n\nEXAMPLE BUILD ANDROID ARM64:\n```bash\ndocker run -e WAIT=300 -e x64=0 -e arm=0 -e HASH_PATCH=<Snapshot_Hash> -e COMMIT=<Engine_commit> --rm -iv${PWD}:/t reflutter\n```\n\nFLAGS:<br/>\n-e x64=0 <disables building for x64 architecture, use to reduce building time><br/>\n-e arm64=0 <disables building for arm64 architecture, use to reduce building time><br/>\n-e arm=0 <disables building for arm32 architecture, use to reduce building time><br/>\n-e WAIT=300 <the amount of time in seconds you need to edit source code><br/>\n-e HASH_PATCH=[Snapshot_Hash] <here you need to specify snapshot hash which matches the engine_commit line of enginehash.csv table best. It is used for proper patch search in reFlutter and for successfull compilation><br/>\n-e COMMIT=[Engine_commit] <here you specify commit for your engine version, take it from enginehash.csv table or from flutter/engine repo><br/>\n",
"bugtrack_url": null,
"license": "GPLv3+",
"summary": "Reverse Flutter",
"version": "0.8.0",
"project_urls": {
"Homepage": "https://github.com/Impact-I/reFlutter"
},
"split_keywords": [
"distutils",
"setuptools",
"egg",
"pip",
"requirements"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "805ffd1a5b5d279ee44e66bf7b521be9544a2f52cdd95c941bbdc1d6a4be1b0e",
"md5": "674a1524ffe989e229c655ecf32ced80",
"sha256": "097aab3ff868c1ff0f69ce725f1de62a352eba1777ea3009df093adb0e71c322"
},
"downloads": -1,
"filename": "reflutter-0.8.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "674a1524ffe989e229c655ecf32ced80",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 24936,
"upload_time": "2024-10-30T11:10:31",
"upload_time_iso_8601": "2024-10-30T11:10:31.016873Z",
"url": "https://files.pythonhosted.org/packages/80/5f/fd1a5b5d279ee44e66bf7b521be9544a2f52cdd95c941bbdc1d6a4be1b0e/reflutter-0.8.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "8a45c9aec400585a1e159ee4869900c1c3d45f9ee451f66b22b5f599aeb98d1c",
"md5": "0cc36c4db9fc541448587065bb5de256",
"sha256": "354683cdc8966ccd79d53a5e8ccfaa7e389ff90385bfde65235ffce406290d26"
},
"downloads": -1,
"filename": "reflutter-0.8.0.tar.gz",
"has_sig": false,
"md5_digest": "0cc36c4db9fc541448587065bb5de256",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 24813,
"upload_time": "2024-10-30T11:10:33",
"upload_time_iso_8601": "2024-10-30T11:10:33.956207Z",
"url": "https://files.pythonhosted.org/packages/8a/45/c9aec400585a1e159ee4869900c1c3d45f9ee451f66b22b5f599aeb98d1c/reflutter-0.8.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-10-30 11:10:33",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "Impact-I",
"github_project": "reFlutter",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "reflutter"
}