frooky 0.1.0__tar.gz → 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {frooky-0.1.0 → frooky-0.1.1}/PKG-INFO +64 -242
- frooky-0.1.1/README.md +113 -0
- {frooky-0.1.0 → frooky-0.1.1}/frooky/cli.py +6 -0
- {frooky-0.1.0 → frooky-0.1.1}/frooky/frida_runner.py +25 -0
- {frooky-0.1.0 → frooky-0.1.1}/frooky.egg-info/PKG-INFO +64 -242
- {frooky-0.1.0 → frooky-0.1.1}/pyproject.toml +1 -1
- frooky-0.1.0/README.md +0 -291
- {frooky-0.1.0 → frooky-0.1.1}/LICENSE +0 -0
- {frooky-0.1.0 → frooky-0.1.1}/frooky/__init__.py +0 -0
- {frooky-0.1.0 → frooky-0.1.1}/frooky/__main__.py +0 -0
- {frooky-0.1.0 → frooky-0.1.1}/frooky/android/android_decoder.js +0 -0
- {frooky-0.1.0 → frooky-0.1.1}/frooky/android/base_script.js +0 -0
- {frooky-0.1.0 → frooky-0.1.1}/frooky/android/native_decoder.js +0 -0
- {frooky-0.1.0 → frooky-0.1.1}/frooky/ios/base_script.js +0 -0
- {frooky-0.1.0 → frooky-0.1.1}/frooky/resources.py +0 -0
- {frooky-0.1.0 → frooky-0.1.1}/frooky.egg-info/SOURCES.txt +0 -0
- {frooky-0.1.0 → frooky-0.1.1}/frooky.egg-info/dependency_links.txt +0 -0
- {frooky-0.1.0 → frooky-0.1.1}/frooky.egg-info/entry_points.txt +0 -0
- {frooky-0.1.0 → frooky-0.1.1}/frooky.egg-info/requires.txt +0 -0
- {frooky-0.1.0 → frooky-0.1.1}/frooky.egg-info/top_level.txt +0 -0
- {frooky-0.1.0 → frooky-0.1.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: frooky
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: Frida-powered hook runner based on JSON hook files.
|
|
5
5
|
Author-email: Carlos Holguera <holguera.cybersec@gmail.com>, Stefan Bernhardsgrütter <stefan.bernhardsgruetter@redguard.ch>
|
|
6
6
|
License: GNU GENERAL PUBLIC LICENSE
|
|
@@ -711,283 +711,105 @@ Dynamic: license-file
|
|
|
711
711
|
|___/
|
|
712
712
|
```
|
|
713
713
|
|
|
714
|
-
|
|
714
|
+
`frooky` is a [Frida](https://www.frida.re/)-based dynamic analysis tool for Android and iOS apps based on JSON hook files.
|
|
715
|
+
|
|
716
|
+
[](https://pypi.python.org/pypi/frooky)
|
|
717
|
+
|
|
718
|
+
- Hook Java/Kotlin methods and native C/C++ functions
|
|
719
|
+
- Simple JSON hook file format
|
|
720
|
+
- Support for method overloads and stack trace capturing
|
|
721
|
+
- Argument capturing with various data types
|
|
722
|
+
- Filtering hooks by argument values or stack trace patterns
|
|
723
|
+
- Output events in JSON Lines format for easy processing
|
|
724
|
+
|
|
725
|
+
See more in [docs/usage.md](docs/usage.md).
|
|
715
726
|
|
|
716
727
|
## Installation
|
|
717
728
|
|
|
729
|
+
Simply install via pip and you'll get the `frooky` CLI tool:
|
|
730
|
+
|
|
718
731
|
```bash
|
|
719
|
-
|
|
732
|
+
pip3 install frooky
|
|
720
733
|
```
|
|
721
734
|
|
|
722
735
|
## Usage
|
|
723
736
|
|
|
737
|
+
Create a hook file (e.g., `hooks.json`) as described in [docs/usage.md](docs/usage.md), then run `frooky` with the desired options:
|
|
738
|
+
|
|
724
739
|
```bash
|
|
725
740
|
# Attach by app name
|
|
726
|
-
frooky -U -n "My App" hooks.json
|
|
741
|
+
frooky -U -n "My App" --platform android hooks.json
|
|
727
742
|
|
|
728
743
|
# Spawn and add multiple hook files (hooks are merged)
|
|
729
|
-
frooky -U -f com.example.app
|
|
744
|
+
frooky -U -f com.example.app --platform android storage.json crypto.json
|
|
730
745
|
```
|
|
731
746
|
|
|
732
747
|
See `frooky -h` for more options.
|
|
733
748
|
|
|
734
|
-
##
|
|
749
|
+
## Example
|
|
735
750
|
|
|
736
|
-
|
|
751
|
+
We'll use the OWASP MAS [MASTG-DEMO-0072](https://mas.owasp.org/MASTG/demos/android/MASVS-CRYPTO/MASTG-DEMO-0072/MASTG-DEMO-0072/) app to demonstrate hooking a cryptographic key generation method.
|
|
737
752
|
|
|
738
|
-
|
|
753
|
+
First you need to create a hook file, e.g., `crypto.json`:
|
|
739
754
|
|
|
740
755
|
```json
|
|
741
756
|
{
|
|
742
|
-
"category": "
|
|
757
|
+
"category": "CRYPTO",
|
|
743
758
|
"hooks": [
|
|
744
759
|
{
|
|
745
|
-
"class": "
|
|
746
|
-
"
|
|
760
|
+
"class": "android.security.keystore.KeyGenParameterSpec$Builder",
|
|
761
|
+
"method": "$init",
|
|
762
|
+
"maxFrames": 10
|
|
747
763
|
}
|
|
748
764
|
]
|
|
749
765
|
}
|
|
750
766
|
```
|
|
751
767
|
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
#### Simple Method Hook
|
|
755
|
-
|
|
756
|
-
```json
|
|
757
|
-
{
|
|
758
|
-
"class": "java.io.File",
|
|
759
|
-
"method": "exists"
|
|
760
|
-
}
|
|
761
|
-
```
|
|
762
|
-
|
|
763
|
-
#### Multiple Methods
|
|
764
|
-
|
|
765
|
-
```json
|
|
766
|
-
{
|
|
767
|
-
"class": "java.io.FileOutputStream",
|
|
768
|
-
"methods": ["write", "close", "flush"]
|
|
769
|
-
}
|
|
770
|
-
```
|
|
771
|
-
|
|
772
|
-
#### Method Overloads
|
|
773
|
-
|
|
774
|
-
Specify exact method signatures using `overloads`:
|
|
775
|
-
|
|
776
|
-
```json
|
|
777
|
-
{
|
|
778
|
-
"class": "java.io.FileOutputStream",
|
|
779
|
-
"method": "write",
|
|
780
|
-
"overloads": [
|
|
781
|
-
{ "args": ["[B"] },
|
|
782
|
-
{ "args": ["[B", "int", "int"] },
|
|
783
|
-
{ "args": ["int"] }
|
|
784
|
-
]
|
|
785
|
-
}
|
|
786
|
-
```
|
|
787
|
-
|
|
788
|
-
#### Stack Traces
|
|
789
|
-
|
|
790
|
-
Control stack trace depth with `maxFrames`:
|
|
791
|
-
|
|
792
|
-
```json
|
|
793
|
-
{
|
|
794
|
-
"class": "javax.crypto.Cipher",
|
|
795
|
-
"method": "doFinal",
|
|
796
|
-
"maxFrames": 10
|
|
797
|
-
}
|
|
798
|
-
```
|
|
799
|
-
|
|
800
|
-
### Native Hooks
|
|
801
|
-
|
|
802
|
-
Native hooks intercept C/C++ functions. Set `native: true` and specify the symbol.
|
|
803
|
-
|
|
804
|
-
#### Basic Native Hook
|
|
805
|
-
|
|
806
|
-
```json
|
|
807
|
-
{
|
|
808
|
-
"native": true,
|
|
809
|
-
"symbol": "open",
|
|
810
|
-
"module": "libc.so"
|
|
811
|
-
}
|
|
812
|
-
```
|
|
813
|
-
|
|
814
|
-
#### Argument Descriptors
|
|
815
|
-
|
|
816
|
-
Define how arguments should be captured:
|
|
817
|
-
|
|
818
|
-
```json
|
|
819
|
-
{
|
|
820
|
-
"native": true,
|
|
821
|
-
"symbol": "write",
|
|
822
|
-
"module": "libc.so",
|
|
823
|
-
"args": [
|
|
824
|
-
{ "name": "fd", "type": "int32" },
|
|
825
|
-
{ "name": "buf", "type": "bytes", "length": 256 },
|
|
826
|
-
{ "name": "count", "type": "int32" }
|
|
827
|
-
]
|
|
828
|
-
}
|
|
829
|
-
```
|
|
830
|
-
|
|
831
|
-
#### Dynamic Length from Another Argument
|
|
832
|
-
|
|
833
|
-
Use `lengthInArg` to read length from another argument:
|
|
834
|
-
|
|
835
|
-
```json
|
|
836
|
-
{
|
|
837
|
-
"native": true,
|
|
838
|
-
"symbol": "send",
|
|
839
|
-
"module": "libc.so",
|
|
840
|
-
"args": [
|
|
841
|
-
{ "name": "sockfd", "type": "int32" },
|
|
842
|
-
{ "name": "buf", "type": "bytes", "lengthInArg": 2 },
|
|
843
|
-
{ "name": "len", "type": "int32" },
|
|
844
|
-
{ "name": "flags", "type": "int32" }
|
|
845
|
-
]
|
|
846
|
-
}
|
|
847
|
-
```
|
|
848
|
-
|
|
849
|
-
#### Capture Return Values
|
|
850
|
-
|
|
851
|
-
Set `returnValue: true` on the last argument:
|
|
852
|
-
|
|
853
|
-
```json
|
|
854
|
-
{
|
|
855
|
-
"native": true,
|
|
856
|
-
"symbol": "read",
|
|
857
|
-
"module": "libc.so",
|
|
858
|
-
"args": [
|
|
859
|
-
{ "name": "fd", "type": "int32" },
|
|
860
|
-
{ "name": "buf", "type": "bytes", "lengthInArg": 2 },
|
|
861
|
-
{ "name": "count", "type": "int32" },
|
|
862
|
-
{ "name": "result", "type": "int32", "returnValue": true }
|
|
863
|
-
]
|
|
864
|
-
}
|
|
865
|
-
```
|
|
866
|
-
|
|
867
|
-
#### Outbound Parameters
|
|
868
|
-
|
|
869
|
-
Use `direction: "out"` for output parameters that should be read after the function returns:
|
|
768
|
+
Then run `frooky` with the hook file against your target app:
|
|
870
769
|
|
|
871
|
-
```
|
|
872
|
-
|
|
873
|
-
"native": true,
|
|
874
|
-
"symbol": "CCCrypt",
|
|
875
|
-
"module": "libcommonCrypto.dylib",
|
|
876
|
-
"args": [
|
|
877
|
-
{ "name": "op", "type": "int32" },
|
|
878
|
-
{ "name": "alg", "type": "int32" },
|
|
879
|
-
{ "name": "dataOut", "type": "bytes", "length": 256, "direction": "out" },
|
|
880
|
-
{ "name": "dataOutMoved", "type": "pointer", "direction": "out" }
|
|
881
|
-
]
|
|
882
|
-
}
|
|
770
|
+
```bash
|
|
771
|
+
frooky -U -n "MASTestApp" --platform android crypto.json
|
|
883
772
|
```
|
|
884
773
|
|
|
885
|
-
|
|
774
|
+
Output (pretty-printed for readability):
|
|
886
775
|
|
|
887
|
-
|
|
776
|
+
> Events are written to the output file in JSON Lines format (one JSON object per line, known as NDJSON). You can easily pretty-print it e.g. using `jq . output.json`.
|
|
888
777
|
|
|
889
778
|
```json
|
|
890
779
|
{
|
|
891
|
-
"
|
|
892
|
-
"
|
|
893
|
-
"
|
|
894
|
-
"
|
|
895
|
-
|
|
780
|
+
"id": "14535033-08ea-4063-897c-eacd4a885d8b",
|
|
781
|
+
"type": "hook",
|
|
782
|
+
"category": "CRYPTO",
|
|
783
|
+
"time": "2026-01-14T16:02:21.782Z",
|
|
784
|
+
"class": "android.security.keystore.KeyGenParameterSpec$Builder",
|
|
785
|
+
"method": "$init",
|
|
786
|
+
"instanceId": 35486102,
|
|
787
|
+
"stackTrace": [
|
|
788
|
+
"android.security.keystore.KeyGenParameterSpec$Builder.<init>(Native Method)",
|
|
789
|
+
"org.owasp.mastestapp.MastgTest.generateKey(MastgTest.kt:97)",
|
|
790
|
+
"org.owasp.mastestapp.MastgTest.mastgTest(MastgTest.kt:41)",
|
|
791
|
+
"org.owasp.mastestapp.MainActivityKt.MainScreen$lambda$12$lambda$11(MainActivity.kt:101)",
|
|
792
|
+
"org.owasp.mastestapp.MainActivityKt.$r8$lambda$Pm6AsbKBmypP53K-UABM21E_Xxk(Unknown Source:0)",
|
|
793
|
+
"org.owasp.mastestapp.MainActivityKt$$ExternalSyntheticLambda3.run(D8$$SyntheticClass:0)",
|
|
794
|
+
"java.lang.Thread.run(Thread.java:1012)"
|
|
795
|
+
],
|
|
796
|
+
"inputParameters": [
|
|
797
|
+
{
|
|
798
|
+
"declaredType": "java.lang.String",
|
|
799
|
+
"value": "MultiPurposeKey"
|
|
800
|
+
},
|
|
801
|
+
{
|
|
802
|
+
"declaredType": "int",
|
|
803
|
+
"value": 15
|
|
804
|
+
}
|
|
805
|
+
],
|
|
806
|
+
"returnValue": [
|
|
807
|
+
{
|
|
808
|
+
"declaredType": "void",
|
|
809
|
+
"value": "void"
|
|
810
|
+
}
|
|
896
811
|
]
|
|
897
812
|
}
|
|
898
813
|
```
|
|
899
814
|
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
Only capture events when the call stack contains specific patterns:
|
|
903
|
-
|
|
904
|
-
```json
|
|
905
|
-
{
|
|
906
|
-
"native": true,
|
|
907
|
-
"symbol": "SSL_write",
|
|
908
|
-
"module": "libssl.so",
|
|
909
|
-
"filterEventsByStacktrace": ["com.example.network", "okhttp3"]
|
|
910
|
-
}
|
|
911
|
-
```
|
|
912
|
-
|
|
913
|
-
#### Debug Mode
|
|
914
|
-
|
|
915
|
-
Enable verbose logging for troubleshooting:
|
|
916
|
-
|
|
917
|
-
```json
|
|
918
|
-
{
|
|
919
|
-
"native": true,
|
|
920
|
-
"symbol": "problematic_function",
|
|
921
|
-
"module": "libfoo.so",
|
|
922
|
-
"debug": true
|
|
923
|
-
}
|
|
924
|
-
```
|
|
925
|
-
|
|
926
|
-
### Argument Types
|
|
927
|
-
|
|
928
|
-
| Type | Description |
|
|
929
|
-
|------|-------------|
|
|
930
|
-
| `string` | Null-terminated C string |
|
|
931
|
-
| `int32` | 32-bit signed integer |
|
|
932
|
-
| `uint32` | 32-bit unsigned integer |
|
|
933
|
-
| `int64` | 64-bit signed integer |
|
|
934
|
-
| `pointer` | Memory address |
|
|
935
|
-
| `bytes` | Raw bytes (requires `length` or `lengthInArg`) |
|
|
936
|
-
| `bool` | Boolean value |
|
|
937
|
-
| `double` | 64-bit floating point |
|
|
938
|
-
| `CFData` | iOS CFData object |
|
|
939
|
-
| `CFDictionary` | iOS CFDictionary object |
|
|
940
|
-
|
|
941
|
-
### iOS Objective-C Hooks
|
|
942
|
-
|
|
943
|
-
Hook Objective-C methods using `objClass` and `symbol`:
|
|
944
|
-
|
|
945
|
-
```json
|
|
946
|
-
{
|
|
947
|
-
"native": true,
|
|
948
|
-
"objClass": "NSURLSession",
|
|
949
|
-
"symbol": "dataTaskWithRequest:completionHandler:"
|
|
950
|
-
}
|
|
951
|
-
```
|
|
952
|
-
|
|
953
|
-
## Output Format
|
|
954
|
-
|
|
955
|
-
Events are written to the output file in JSON Lines format (one JSON object per line, know as NDJSON). You can easily pretty-print it e.g. using `jq . output.json`.
|
|
956
|
-
|
|
957
|
-
Example event (pretty-printed for clarity):
|
|
958
|
-
|
|
959
|
-
```json
|
|
960
|
-
{
|
|
961
|
-
"id": "0117229c-b034-4676-ba33-075fc27922ba",
|
|
962
|
-
"type": "hook",
|
|
963
|
-
"category": "STORAGE",
|
|
964
|
-
"time": "2026-01-18T16:17:25.470Z",
|
|
965
|
-
"class": "android.app.SharedPreferencesImpl$EditorImpl",
|
|
966
|
-
"method": "putString",
|
|
967
|
-
"instanceId": 268282727,
|
|
968
|
-
"stackTrace": [
|
|
969
|
-
"android.app.SharedPreferencesImpl$EditorImpl.putString(Native Method)",
|
|
970
|
-
"androidx.security.crypto.EncryptedSharedPreferences$Editor.putEncryptedObject(EncryptedSharedPreferences.java:389)",
|
|
971
|
-
...
|
|
972
|
-
],
|
|
973
|
-
"inputParameters": [
|
|
974
|
-
{
|
|
975
|
-
"declaredType": "java.lang.String",
|
|
976
|
-
"value": "AQMRC7OWD6/h1iJseuzJVrClpwKE8swB8gOrGnsdaN4="
|
|
977
|
-
},
|
|
978
|
-
{
|
|
979
|
-
"declaredType": "java.lang.String",
|
|
980
|
-
"value": "AX4R5MZu+J1p0U3hvKyuEnJDQopI+wupiSi8CAG8dzq0PU76NbbebjhqMtqCD7fFUy2SmmQuQVDlDrrj30d3GQes+PlD8HmRFszVTge039GQ"
|
|
981
|
-
}
|
|
982
|
-
],
|
|
983
|
-
"returnValue": [
|
|
984
|
-
{
|
|
985
|
-
"declaredType": "android.content.SharedPreferences$Editor",
|
|
986
|
-
"value": "<instance: android.content.SharedPreferences$Editor, $className: android.app.SharedPreferencesImpl$EditorImpl>",
|
|
987
|
-
"runtimeType": "android.app.SharedPreferencesImpl$EditorImpl",
|
|
988
|
-
"instanceId": "268282727",
|
|
989
|
-
"instanceToString": "android.app.SharedPreferencesImpl$EditorImpl@ffdab67"
|
|
990
|
-
}
|
|
991
|
-
]
|
|
992
|
-
}
|
|
993
|
-
```
|
|
815
|
+
See more in [docs/usage.md](docs/usage.md) and see a full example in [docs/examples/example.md](docs/examples/example.md).
|
frooky-0.1.1/README.md
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Frooky
|
|
2
|
+
|
|
3
|
+
```txt
|
|
4
|
+
___ ____
|
|
5
|
+
/ __\ / _ | _ _ _ _ _ _
|
|
6
|
+
/ _\ | (_) | / _ \ / _ \ | / / | | | |
|
|
7
|
+
/ / / / | | | (_) | (_) || < | |_| |
|
|
8
|
+
\/ /_/ |_| \___/ \___/ |_|\_\ \__, |
|
|
9
|
+
|___/
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
`frooky` is a [Frida](https://www.frida.re/)-based dynamic analysis tool for Android and iOS apps based on JSON hook files.
|
|
13
|
+
|
|
14
|
+
[](https://pypi.python.org/pypi/frooky)
|
|
15
|
+
|
|
16
|
+
- Hook Java/Kotlin methods and native C/C++ functions
|
|
17
|
+
- Simple JSON hook file format
|
|
18
|
+
- Support for method overloads and stack trace capturing
|
|
19
|
+
- Argument capturing with various data types
|
|
20
|
+
- Filtering hooks by argument values or stack trace patterns
|
|
21
|
+
- Output events in JSON Lines format for easy processing
|
|
22
|
+
|
|
23
|
+
See more in [docs/usage.md](docs/usage.md).
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
Simply install via pip and you'll get the `frooky` CLI tool:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip3 install frooky
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
Create a hook file (e.g., `hooks.json`) as described in [docs/usage.md](docs/usage.md), then run `frooky` with the desired options:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# Attach by app name
|
|
39
|
+
frooky -U -n "My App" --platform android hooks.json
|
|
40
|
+
|
|
41
|
+
# Spawn and add multiple hook files (hooks are merged)
|
|
42
|
+
frooky -U -f com.example.app --platform android storage.json crypto.json
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
See `frooky -h` for more options.
|
|
46
|
+
|
|
47
|
+
## Example
|
|
48
|
+
|
|
49
|
+
We'll use the OWASP MAS [MASTG-DEMO-0072](https://mas.owasp.org/MASTG/demos/android/MASVS-CRYPTO/MASTG-DEMO-0072/MASTG-DEMO-0072/) app to demonstrate hooking a cryptographic key generation method.
|
|
50
|
+
|
|
51
|
+
First you need to create a hook file, e.g., `crypto.json`:
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"category": "CRYPTO",
|
|
56
|
+
"hooks": [
|
|
57
|
+
{
|
|
58
|
+
"class": "android.security.keystore.KeyGenParameterSpec$Builder",
|
|
59
|
+
"method": "$init",
|
|
60
|
+
"maxFrames": 10
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Then run `frooky` with the hook file against your target app:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
frooky -U -n "MASTestApp" --platform android crypto.json
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Output (pretty-printed for readability):
|
|
73
|
+
|
|
74
|
+
> Events are written to the output file in JSON Lines format (one JSON object per line, known as NDJSON). You can easily pretty-print it e.g. using `jq . output.json`.
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{
|
|
78
|
+
"id": "14535033-08ea-4063-897c-eacd4a885d8b",
|
|
79
|
+
"type": "hook",
|
|
80
|
+
"category": "CRYPTO",
|
|
81
|
+
"time": "2026-01-14T16:02:21.782Z",
|
|
82
|
+
"class": "android.security.keystore.KeyGenParameterSpec$Builder",
|
|
83
|
+
"method": "$init",
|
|
84
|
+
"instanceId": 35486102,
|
|
85
|
+
"stackTrace": [
|
|
86
|
+
"android.security.keystore.KeyGenParameterSpec$Builder.<init>(Native Method)",
|
|
87
|
+
"org.owasp.mastestapp.MastgTest.generateKey(MastgTest.kt:97)",
|
|
88
|
+
"org.owasp.mastestapp.MastgTest.mastgTest(MastgTest.kt:41)",
|
|
89
|
+
"org.owasp.mastestapp.MainActivityKt.MainScreen$lambda$12$lambda$11(MainActivity.kt:101)",
|
|
90
|
+
"org.owasp.mastestapp.MainActivityKt.$r8$lambda$Pm6AsbKBmypP53K-UABM21E_Xxk(Unknown Source:0)",
|
|
91
|
+
"org.owasp.mastestapp.MainActivityKt$$ExternalSyntheticLambda3.run(D8$$SyntheticClass:0)",
|
|
92
|
+
"java.lang.Thread.run(Thread.java:1012)"
|
|
93
|
+
],
|
|
94
|
+
"inputParameters": [
|
|
95
|
+
{
|
|
96
|
+
"declaredType": "java.lang.String",
|
|
97
|
+
"value": "MultiPurposeKey"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"declaredType": "int",
|
|
101
|
+
"value": 15
|
|
102
|
+
}
|
|
103
|
+
],
|
|
104
|
+
"returnValue": [
|
|
105
|
+
{
|
|
106
|
+
"declaredType": "void",
|
|
107
|
+
"value": "void"
|
|
108
|
+
}
|
|
109
|
+
]
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
See more in [docs/usage.md](docs/usage.md) and see a full example in [docs/examples/example.md](docs/examples/example.md).
|
|
@@ -33,6 +33,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
33
33
|
)
|
|
34
34
|
parser.add_argument("hooks", nargs="+", help="Path(s) to your input hook JSON file(s)")
|
|
35
35
|
parser.add_argument("-o", "--output", default="output.json", help="Output JSON file")
|
|
36
|
+
parser.add_argument(
|
|
37
|
+
"--keep-artifacts",
|
|
38
|
+
action="store_true",
|
|
39
|
+
help="Keep temporary artifacts (tmp/, node_modules/, package.json, package-lock.json)",
|
|
40
|
+
)
|
|
36
41
|
|
|
37
42
|
return parser
|
|
38
43
|
|
|
@@ -64,6 +69,7 @@ def main() -> int:
|
|
|
64
69
|
attach_identifier=args.attach_identifier,
|
|
65
70
|
attach_pid=args.attach_pid,
|
|
66
71
|
spawn=args.spawn,
|
|
72
|
+
keep_artifacts=args.keep_artifacts,
|
|
67
73
|
)
|
|
68
74
|
|
|
69
75
|
runner = FrookyRunner(options)
|
|
@@ -6,6 +6,7 @@ import subprocess
|
|
|
6
6
|
import os
|
|
7
7
|
import sys
|
|
8
8
|
import json
|
|
9
|
+
import shutil
|
|
9
10
|
from dataclasses import dataclass
|
|
10
11
|
from importlib import resources
|
|
11
12
|
from pathlib import Path
|
|
@@ -34,6 +35,7 @@ class RunnerOptions:
|
|
|
34
35
|
attach_identifier: Optional[str] = None
|
|
35
36
|
attach_pid: Optional[int] = None
|
|
36
37
|
spawn: Optional[str] = None
|
|
38
|
+
keep_artifacts: bool = False
|
|
37
39
|
|
|
38
40
|
|
|
39
41
|
class FrookyRunner:
|
|
@@ -328,6 +330,25 @@ class FrookyRunner:
|
|
|
328
330
|
else:
|
|
329
331
|
raise RuntimeError("No target specified")
|
|
330
332
|
|
|
333
|
+
def _cleanup_artifacts(self) -> None:
|
|
334
|
+
"""Remove temporary artifacts created during execution."""
|
|
335
|
+
artifacts = [
|
|
336
|
+
Path("tmp"),
|
|
337
|
+
Path("node_modules"),
|
|
338
|
+
Path("package.json"),
|
|
339
|
+
Path("package-lock.json"),
|
|
340
|
+
]
|
|
341
|
+
|
|
342
|
+
for artifact in artifacts:
|
|
343
|
+
try:
|
|
344
|
+
if artifact.is_dir():
|
|
345
|
+
shutil.rmtree(artifact)
|
|
346
|
+
elif artifact.is_file():
|
|
347
|
+
artifact.unlink()
|
|
348
|
+
except Exception:
|
|
349
|
+
# Silently ignore cleanup errors
|
|
350
|
+
pass
|
|
351
|
+
|
|
331
352
|
def run(self) -> int:
|
|
332
353
|
"""Run the Frooky hooks."""
|
|
333
354
|
try:
|
|
@@ -393,5 +414,9 @@ class FrookyRunner:
|
|
|
393
414
|
self.session.detach()
|
|
394
415
|
except Exception:
|
|
395
416
|
pass
|
|
417
|
+
|
|
418
|
+
# Clean up artifacts unless --keep-artifacts was specified
|
|
419
|
+
if not self.options.keep_artifacts:
|
|
420
|
+
self._cleanup_artifacts()
|
|
396
421
|
|
|
397
422
|
return 0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: frooky
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: Frida-powered hook runner based on JSON hook files.
|
|
5
5
|
Author-email: Carlos Holguera <holguera.cybersec@gmail.com>, Stefan Bernhardsgrütter <stefan.bernhardsgruetter@redguard.ch>
|
|
6
6
|
License: GNU GENERAL PUBLIC LICENSE
|
|
@@ -711,283 +711,105 @@ Dynamic: license-file
|
|
|
711
711
|
|___/
|
|
712
712
|
```
|
|
713
713
|
|
|
714
|
-
|
|
714
|
+
`frooky` is a [Frida](https://www.frida.re/)-based dynamic analysis tool for Android and iOS apps based on JSON hook files.
|
|
715
|
+
|
|
716
|
+
[](https://pypi.python.org/pypi/frooky)
|
|
717
|
+
|
|
718
|
+
- Hook Java/Kotlin methods and native C/C++ functions
|
|
719
|
+
- Simple JSON hook file format
|
|
720
|
+
- Support for method overloads and stack trace capturing
|
|
721
|
+
- Argument capturing with various data types
|
|
722
|
+
- Filtering hooks by argument values or stack trace patterns
|
|
723
|
+
- Output events in JSON Lines format for easy processing
|
|
724
|
+
|
|
725
|
+
See more in [docs/usage.md](docs/usage.md).
|
|
715
726
|
|
|
716
727
|
## Installation
|
|
717
728
|
|
|
729
|
+
Simply install via pip and you'll get the `frooky` CLI tool:
|
|
730
|
+
|
|
718
731
|
```bash
|
|
719
|
-
|
|
732
|
+
pip3 install frooky
|
|
720
733
|
```
|
|
721
734
|
|
|
722
735
|
## Usage
|
|
723
736
|
|
|
737
|
+
Create a hook file (e.g., `hooks.json`) as described in [docs/usage.md](docs/usage.md), then run `frooky` with the desired options:
|
|
738
|
+
|
|
724
739
|
```bash
|
|
725
740
|
# Attach by app name
|
|
726
|
-
frooky -U -n "My App" hooks.json
|
|
741
|
+
frooky -U -n "My App" --platform android hooks.json
|
|
727
742
|
|
|
728
743
|
# Spawn and add multiple hook files (hooks are merged)
|
|
729
|
-
frooky -U -f com.example.app
|
|
744
|
+
frooky -U -f com.example.app --platform android storage.json crypto.json
|
|
730
745
|
```
|
|
731
746
|
|
|
732
747
|
See `frooky -h` for more options.
|
|
733
748
|
|
|
734
|
-
##
|
|
749
|
+
## Example
|
|
735
750
|
|
|
736
|
-
|
|
751
|
+
We'll use the OWASP MAS [MASTG-DEMO-0072](https://mas.owasp.org/MASTG/demos/android/MASVS-CRYPTO/MASTG-DEMO-0072/MASTG-DEMO-0072/) app to demonstrate hooking a cryptographic key generation method.
|
|
737
752
|
|
|
738
|
-
|
|
753
|
+
First you need to create a hook file, e.g., `crypto.json`:
|
|
739
754
|
|
|
740
755
|
```json
|
|
741
756
|
{
|
|
742
|
-
"category": "
|
|
757
|
+
"category": "CRYPTO",
|
|
743
758
|
"hooks": [
|
|
744
759
|
{
|
|
745
|
-
"class": "
|
|
746
|
-
"
|
|
760
|
+
"class": "android.security.keystore.KeyGenParameterSpec$Builder",
|
|
761
|
+
"method": "$init",
|
|
762
|
+
"maxFrames": 10
|
|
747
763
|
}
|
|
748
764
|
]
|
|
749
765
|
}
|
|
750
766
|
```
|
|
751
767
|
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
#### Simple Method Hook
|
|
755
|
-
|
|
756
|
-
```json
|
|
757
|
-
{
|
|
758
|
-
"class": "java.io.File",
|
|
759
|
-
"method": "exists"
|
|
760
|
-
}
|
|
761
|
-
```
|
|
762
|
-
|
|
763
|
-
#### Multiple Methods
|
|
764
|
-
|
|
765
|
-
```json
|
|
766
|
-
{
|
|
767
|
-
"class": "java.io.FileOutputStream",
|
|
768
|
-
"methods": ["write", "close", "flush"]
|
|
769
|
-
}
|
|
770
|
-
```
|
|
771
|
-
|
|
772
|
-
#### Method Overloads
|
|
773
|
-
|
|
774
|
-
Specify exact method signatures using `overloads`:
|
|
775
|
-
|
|
776
|
-
```json
|
|
777
|
-
{
|
|
778
|
-
"class": "java.io.FileOutputStream",
|
|
779
|
-
"method": "write",
|
|
780
|
-
"overloads": [
|
|
781
|
-
{ "args": ["[B"] },
|
|
782
|
-
{ "args": ["[B", "int", "int"] },
|
|
783
|
-
{ "args": ["int"] }
|
|
784
|
-
]
|
|
785
|
-
}
|
|
786
|
-
```
|
|
787
|
-
|
|
788
|
-
#### Stack Traces
|
|
789
|
-
|
|
790
|
-
Control stack trace depth with `maxFrames`:
|
|
791
|
-
|
|
792
|
-
```json
|
|
793
|
-
{
|
|
794
|
-
"class": "javax.crypto.Cipher",
|
|
795
|
-
"method": "doFinal",
|
|
796
|
-
"maxFrames": 10
|
|
797
|
-
}
|
|
798
|
-
```
|
|
799
|
-
|
|
800
|
-
### Native Hooks
|
|
801
|
-
|
|
802
|
-
Native hooks intercept C/C++ functions. Set `native: true` and specify the symbol.
|
|
803
|
-
|
|
804
|
-
#### Basic Native Hook
|
|
805
|
-
|
|
806
|
-
```json
|
|
807
|
-
{
|
|
808
|
-
"native": true,
|
|
809
|
-
"symbol": "open",
|
|
810
|
-
"module": "libc.so"
|
|
811
|
-
}
|
|
812
|
-
```
|
|
813
|
-
|
|
814
|
-
#### Argument Descriptors
|
|
815
|
-
|
|
816
|
-
Define how arguments should be captured:
|
|
817
|
-
|
|
818
|
-
```json
|
|
819
|
-
{
|
|
820
|
-
"native": true,
|
|
821
|
-
"symbol": "write",
|
|
822
|
-
"module": "libc.so",
|
|
823
|
-
"args": [
|
|
824
|
-
{ "name": "fd", "type": "int32" },
|
|
825
|
-
{ "name": "buf", "type": "bytes", "length": 256 },
|
|
826
|
-
{ "name": "count", "type": "int32" }
|
|
827
|
-
]
|
|
828
|
-
}
|
|
829
|
-
```
|
|
830
|
-
|
|
831
|
-
#### Dynamic Length from Another Argument
|
|
832
|
-
|
|
833
|
-
Use `lengthInArg` to read length from another argument:
|
|
834
|
-
|
|
835
|
-
```json
|
|
836
|
-
{
|
|
837
|
-
"native": true,
|
|
838
|
-
"symbol": "send",
|
|
839
|
-
"module": "libc.so",
|
|
840
|
-
"args": [
|
|
841
|
-
{ "name": "sockfd", "type": "int32" },
|
|
842
|
-
{ "name": "buf", "type": "bytes", "lengthInArg": 2 },
|
|
843
|
-
{ "name": "len", "type": "int32" },
|
|
844
|
-
{ "name": "flags", "type": "int32" }
|
|
845
|
-
]
|
|
846
|
-
}
|
|
847
|
-
```
|
|
848
|
-
|
|
849
|
-
#### Capture Return Values
|
|
850
|
-
|
|
851
|
-
Set `returnValue: true` on the last argument:
|
|
852
|
-
|
|
853
|
-
```json
|
|
854
|
-
{
|
|
855
|
-
"native": true,
|
|
856
|
-
"symbol": "read",
|
|
857
|
-
"module": "libc.so",
|
|
858
|
-
"args": [
|
|
859
|
-
{ "name": "fd", "type": "int32" },
|
|
860
|
-
{ "name": "buf", "type": "bytes", "lengthInArg": 2 },
|
|
861
|
-
{ "name": "count", "type": "int32" },
|
|
862
|
-
{ "name": "result", "type": "int32", "returnValue": true }
|
|
863
|
-
]
|
|
864
|
-
}
|
|
865
|
-
```
|
|
866
|
-
|
|
867
|
-
#### Outbound Parameters
|
|
868
|
-
|
|
869
|
-
Use `direction: "out"` for output parameters that should be read after the function returns:
|
|
768
|
+
Then run `frooky` with the hook file against your target app:
|
|
870
769
|
|
|
871
|
-
```
|
|
872
|
-
|
|
873
|
-
"native": true,
|
|
874
|
-
"symbol": "CCCrypt",
|
|
875
|
-
"module": "libcommonCrypto.dylib",
|
|
876
|
-
"args": [
|
|
877
|
-
{ "name": "op", "type": "int32" },
|
|
878
|
-
{ "name": "alg", "type": "int32" },
|
|
879
|
-
{ "name": "dataOut", "type": "bytes", "length": 256, "direction": "out" },
|
|
880
|
-
{ "name": "dataOutMoved", "type": "pointer", "direction": "out" }
|
|
881
|
-
]
|
|
882
|
-
}
|
|
770
|
+
```bash
|
|
771
|
+
frooky -U -n "MASTestApp" --platform android crypto.json
|
|
883
772
|
```
|
|
884
773
|
|
|
885
|
-
|
|
774
|
+
Output (pretty-printed for readability):
|
|
886
775
|
|
|
887
|
-
|
|
776
|
+
> Events are written to the output file in JSON Lines format (one JSON object per line, known as NDJSON). You can easily pretty-print it e.g. using `jq . output.json`.
|
|
888
777
|
|
|
889
778
|
```json
|
|
890
779
|
{
|
|
891
|
-
"
|
|
892
|
-
"
|
|
893
|
-
"
|
|
894
|
-
"
|
|
895
|
-
|
|
780
|
+
"id": "14535033-08ea-4063-897c-eacd4a885d8b",
|
|
781
|
+
"type": "hook",
|
|
782
|
+
"category": "CRYPTO",
|
|
783
|
+
"time": "2026-01-14T16:02:21.782Z",
|
|
784
|
+
"class": "android.security.keystore.KeyGenParameterSpec$Builder",
|
|
785
|
+
"method": "$init",
|
|
786
|
+
"instanceId": 35486102,
|
|
787
|
+
"stackTrace": [
|
|
788
|
+
"android.security.keystore.KeyGenParameterSpec$Builder.<init>(Native Method)",
|
|
789
|
+
"org.owasp.mastestapp.MastgTest.generateKey(MastgTest.kt:97)",
|
|
790
|
+
"org.owasp.mastestapp.MastgTest.mastgTest(MastgTest.kt:41)",
|
|
791
|
+
"org.owasp.mastestapp.MainActivityKt.MainScreen$lambda$12$lambda$11(MainActivity.kt:101)",
|
|
792
|
+
"org.owasp.mastestapp.MainActivityKt.$r8$lambda$Pm6AsbKBmypP53K-UABM21E_Xxk(Unknown Source:0)",
|
|
793
|
+
"org.owasp.mastestapp.MainActivityKt$$ExternalSyntheticLambda3.run(D8$$SyntheticClass:0)",
|
|
794
|
+
"java.lang.Thread.run(Thread.java:1012)"
|
|
795
|
+
],
|
|
796
|
+
"inputParameters": [
|
|
797
|
+
{
|
|
798
|
+
"declaredType": "java.lang.String",
|
|
799
|
+
"value": "MultiPurposeKey"
|
|
800
|
+
},
|
|
801
|
+
{
|
|
802
|
+
"declaredType": "int",
|
|
803
|
+
"value": 15
|
|
804
|
+
}
|
|
805
|
+
],
|
|
806
|
+
"returnValue": [
|
|
807
|
+
{
|
|
808
|
+
"declaredType": "void",
|
|
809
|
+
"value": "void"
|
|
810
|
+
}
|
|
896
811
|
]
|
|
897
812
|
}
|
|
898
813
|
```
|
|
899
814
|
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
Only capture events when the call stack contains specific patterns:
|
|
903
|
-
|
|
904
|
-
```json
|
|
905
|
-
{
|
|
906
|
-
"native": true,
|
|
907
|
-
"symbol": "SSL_write",
|
|
908
|
-
"module": "libssl.so",
|
|
909
|
-
"filterEventsByStacktrace": ["com.example.network", "okhttp3"]
|
|
910
|
-
}
|
|
911
|
-
```
|
|
912
|
-
|
|
913
|
-
#### Debug Mode
|
|
914
|
-
|
|
915
|
-
Enable verbose logging for troubleshooting:
|
|
916
|
-
|
|
917
|
-
```json
|
|
918
|
-
{
|
|
919
|
-
"native": true,
|
|
920
|
-
"symbol": "problematic_function",
|
|
921
|
-
"module": "libfoo.so",
|
|
922
|
-
"debug": true
|
|
923
|
-
}
|
|
924
|
-
```
|
|
925
|
-
|
|
926
|
-
### Argument Types
|
|
927
|
-
|
|
928
|
-
| Type | Description |
|
|
929
|
-
|------|-------------|
|
|
930
|
-
| `string` | Null-terminated C string |
|
|
931
|
-
| `int32` | 32-bit signed integer |
|
|
932
|
-
| `uint32` | 32-bit unsigned integer |
|
|
933
|
-
| `int64` | 64-bit signed integer |
|
|
934
|
-
| `pointer` | Memory address |
|
|
935
|
-
| `bytes` | Raw bytes (requires `length` or `lengthInArg`) |
|
|
936
|
-
| `bool` | Boolean value |
|
|
937
|
-
| `double` | 64-bit floating point |
|
|
938
|
-
| `CFData` | iOS CFData object |
|
|
939
|
-
| `CFDictionary` | iOS CFDictionary object |
|
|
940
|
-
|
|
941
|
-
### iOS Objective-C Hooks
|
|
942
|
-
|
|
943
|
-
Hook Objective-C methods using `objClass` and `symbol`:
|
|
944
|
-
|
|
945
|
-
```json
|
|
946
|
-
{
|
|
947
|
-
"native": true,
|
|
948
|
-
"objClass": "NSURLSession",
|
|
949
|
-
"symbol": "dataTaskWithRequest:completionHandler:"
|
|
950
|
-
}
|
|
951
|
-
```
|
|
952
|
-
|
|
953
|
-
## Output Format
|
|
954
|
-
|
|
955
|
-
Events are written to the output file in JSON Lines format (one JSON object per line, know as NDJSON). You can easily pretty-print it e.g. using `jq . output.json`.
|
|
956
|
-
|
|
957
|
-
Example event (pretty-printed for clarity):
|
|
958
|
-
|
|
959
|
-
```json
|
|
960
|
-
{
|
|
961
|
-
"id": "0117229c-b034-4676-ba33-075fc27922ba",
|
|
962
|
-
"type": "hook",
|
|
963
|
-
"category": "STORAGE",
|
|
964
|
-
"time": "2026-01-18T16:17:25.470Z",
|
|
965
|
-
"class": "android.app.SharedPreferencesImpl$EditorImpl",
|
|
966
|
-
"method": "putString",
|
|
967
|
-
"instanceId": 268282727,
|
|
968
|
-
"stackTrace": [
|
|
969
|
-
"android.app.SharedPreferencesImpl$EditorImpl.putString(Native Method)",
|
|
970
|
-
"androidx.security.crypto.EncryptedSharedPreferences$Editor.putEncryptedObject(EncryptedSharedPreferences.java:389)",
|
|
971
|
-
...
|
|
972
|
-
],
|
|
973
|
-
"inputParameters": [
|
|
974
|
-
{
|
|
975
|
-
"declaredType": "java.lang.String",
|
|
976
|
-
"value": "AQMRC7OWD6/h1iJseuzJVrClpwKE8swB8gOrGnsdaN4="
|
|
977
|
-
},
|
|
978
|
-
{
|
|
979
|
-
"declaredType": "java.lang.String",
|
|
980
|
-
"value": "AX4R5MZu+J1p0U3hvKyuEnJDQopI+wupiSi8CAG8dzq0PU76NbbebjhqMtqCD7fFUy2SmmQuQVDlDrrj30d3GQes+PlD8HmRFszVTge039GQ"
|
|
981
|
-
}
|
|
982
|
-
],
|
|
983
|
-
"returnValue": [
|
|
984
|
-
{
|
|
985
|
-
"declaredType": "android.content.SharedPreferences$Editor",
|
|
986
|
-
"value": "<instance: android.content.SharedPreferences$Editor, $className: android.app.SharedPreferencesImpl$EditorImpl>",
|
|
987
|
-
"runtimeType": "android.app.SharedPreferencesImpl$EditorImpl",
|
|
988
|
-
"instanceId": "268282727",
|
|
989
|
-
"instanceToString": "android.app.SharedPreferencesImpl$EditorImpl@ffdab67"
|
|
990
|
-
}
|
|
991
|
-
]
|
|
992
|
-
}
|
|
993
|
-
```
|
|
815
|
+
See more in [docs/usage.md](docs/usage.md) and see a full example in [docs/examples/example.md](docs/examples/example.md).
|
frooky-0.1.0/README.md
DELETED
|
@@ -1,291 +0,0 @@
|
|
|
1
|
-
# Frooky
|
|
2
|
-
|
|
3
|
-
```txt
|
|
4
|
-
___ ____
|
|
5
|
-
/ __\ / _ | _ _ _ _ _ _
|
|
6
|
-
/ _\ | (_) | / _ \ / _ \ | / / | | | |
|
|
7
|
-
/ / / / | | | (_) | (_) || < | |_| |
|
|
8
|
-
\/ /_/ |_| \___/ \___/ |_|\_\ \__, |
|
|
9
|
-
|___/
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
A Frida-based dynamic analysis tool for Android and iOS applications.
|
|
13
|
-
|
|
14
|
-
## Installation
|
|
15
|
-
|
|
16
|
-
```bash
|
|
17
|
-
pip install frooky
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
## Usage
|
|
21
|
-
|
|
22
|
-
```bash
|
|
23
|
-
# Attach by app name
|
|
24
|
-
frooky -U -n "My App" hooks.json
|
|
25
|
-
|
|
26
|
-
# Spawn and add multiple hook files (hooks are merged)
|
|
27
|
-
frooky -U -f com.example.app hooks.json hooks2.json more_hooks.json
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
See `frooky -h` for more options.
|
|
31
|
-
|
|
32
|
-
## Hook Files
|
|
33
|
-
|
|
34
|
-
Hook files use JSON format. When multiple hook files are provided, their `hooks` arrays are merged together.
|
|
35
|
-
|
|
36
|
-
### Basic Structure
|
|
37
|
-
|
|
38
|
-
```json
|
|
39
|
-
{
|
|
40
|
-
"category": "STORAGE",
|
|
41
|
-
"hooks": [
|
|
42
|
-
{
|
|
43
|
-
"class": "com.example.MyClass",
|
|
44
|
-
"methods": ["method1", "method2"]
|
|
45
|
-
}
|
|
46
|
-
]
|
|
47
|
-
}
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
### Java/Kotlin Hooks
|
|
51
|
-
|
|
52
|
-
#### Simple Method Hook
|
|
53
|
-
|
|
54
|
-
```json
|
|
55
|
-
{
|
|
56
|
-
"class": "java.io.File",
|
|
57
|
-
"method": "exists"
|
|
58
|
-
}
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
#### Multiple Methods
|
|
62
|
-
|
|
63
|
-
```json
|
|
64
|
-
{
|
|
65
|
-
"class": "java.io.FileOutputStream",
|
|
66
|
-
"methods": ["write", "close", "flush"]
|
|
67
|
-
}
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
#### Method Overloads
|
|
71
|
-
|
|
72
|
-
Specify exact method signatures using `overloads`:
|
|
73
|
-
|
|
74
|
-
```json
|
|
75
|
-
{
|
|
76
|
-
"class": "java.io.FileOutputStream",
|
|
77
|
-
"method": "write",
|
|
78
|
-
"overloads": [
|
|
79
|
-
{ "args": ["[B"] },
|
|
80
|
-
{ "args": ["[B", "int", "int"] },
|
|
81
|
-
{ "args": ["int"] }
|
|
82
|
-
]
|
|
83
|
-
}
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
#### Stack Traces
|
|
87
|
-
|
|
88
|
-
Control stack trace depth with `maxFrames`:
|
|
89
|
-
|
|
90
|
-
```json
|
|
91
|
-
{
|
|
92
|
-
"class": "javax.crypto.Cipher",
|
|
93
|
-
"method": "doFinal",
|
|
94
|
-
"maxFrames": 10
|
|
95
|
-
}
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
### Native Hooks
|
|
99
|
-
|
|
100
|
-
Native hooks intercept C/C++ functions. Set `native: true` and specify the symbol.
|
|
101
|
-
|
|
102
|
-
#### Basic Native Hook
|
|
103
|
-
|
|
104
|
-
```json
|
|
105
|
-
{
|
|
106
|
-
"native": true,
|
|
107
|
-
"symbol": "open",
|
|
108
|
-
"module": "libc.so"
|
|
109
|
-
}
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
#### Argument Descriptors
|
|
113
|
-
|
|
114
|
-
Define how arguments should be captured:
|
|
115
|
-
|
|
116
|
-
```json
|
|
117
|
-
{
|
|
118
|
-
"native": true,
|
|
119
|
-
"symbol": "write",
|
|
120
|
-
"module": "libc.so",
|
|
121
|
-
"args": [
|
|
122
|
-
{ "name": "fd", "type": "int32" },
|
|
123
|
-
{ "name": "buf", "type": "bytes", "length": 256 },
|
|
124
|
-
{ "name": "count", "type": "int32" }
|
|
125
|
-
]
|
|
126
|
-
}
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
#### Dynamic Length from Another Argument
|
|
130
|
-
|
|
131
|
-
Use `lengthInArg` to read length from another argument:
|
|
132
|
-
|
|
133
|
-
```json
|
|
134
|
-
{
|
|
135
|
-
"native": true,
|
|
136
|
-
"symbol": "send",
|
|
137
|
-
"module": "libc.so",
|
|
138
|
-
"args": [
|
|
139
|
-
{ "name": "sockfd", "type": "int32" },
|
|
140
|
-
{ "name": "buf", "type": "bytes", "lengthInArg": 2 },
|
|
141
|
-
{ "name": "len", "type": "int32" },
|
|
142
|
-
{ "name": "flags", "type": "int32" }
|
|
143
|
-
]
|
|
144
|
-
}
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
#### Capture Return Values
|
|
148
|
-
|
|
149
|
-
Set `returnValue: true` on the last argument:
|
|
150
|
-
|
|
151
|
-
```json
|
|
152
|
-
{
|
|
153
|
-
"native": true,
|
|
154
|
-
"symbol": "read",
|
|
155
|
-
"module": "libc.so",
|
|
156
|
-
"args": [
|
|
157
|
-
{ "name": "fd", "type": "int32" },
|
|
158
|
-
{ "name": "buf", "type": "bytes", "lengthInArg": 2 },
|
|
159
|
-
{ "name": "count", "type": "int32" },
|
|
160
|
-
{ "name": "result", "type": "int32", "returnValue": true }
|
|
161
|
-
]
|
|
162
|
-
}
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
#### Outbound Parameters
|
|
166
|
-
|
|
167
|
-
Use `direction: "out"` for output parameters that should be read after the function returns:
|
|
168
|
-
|
|
169
|
-
```json
|
|
170
|
-
{
|
|
171
|
-
"native": true,
|
|
172
|
-
"symbol": "CCCrypt",
|
|
173
|
-
"module": "libcommonCrypto.dylib",
|
|
174
|
-
"args": [
|
|
175
|
-
{ "name": "op", "type": "int32" },
|
|
176
|
-
{ "name": "alg", "type": "int32" },
|
|
177
|
-
{ "name": "dataOut", "type": "bytes", "length": 256, "direction": "out" },
|
|
178
|
-
{ "name": "dataOutMoved", "type": "pointer", "direction": "out" }
|
|
179
|
-
]
|
|
180
|
-
}
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
#### Filter by Value
|
|
184
|
-
|
|
185
|
-
Only capture events when arguments match specific values:
|
|
186
|
-
|
|
187
|
-
```json
|
|
188
|
-
{
|
|
189
|
-
"native": true,
|
|
190
|
-
"symbol": "open",
|
|
191
|
-
"module": "libc.so",
|
|
192
|
-
"args": [
|
|
193
|
-
{ "name": "pathname", "type": "string", "filter": ["/data/", "/sdcard/"] }
|
|
194
|
-
]
|
|
195
|
-
}
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
#### Filter by Stack Trace
|
|
199
|
-
|
|
200
|
-
Only capture events when the call stack contains specific patterns:
|
|
201
|
-
|
|
202
|
-
```json
|
|
203
|
-
{
|
|
204
|
-
"native": true,
|
|
205
|
-
"symbol": "SSL_write",
|
|
206
|
-
"module": "libssl.so",
|
|
207
|
-
"filterEventsByStacktrace": ["com.example.network", "okhttp3"]
|
|
208
|
-
}
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
#### Debug Mode
|
|
212
|
-
|
|
213
|
-
Enable verbose logging for troubleshooting:
|
|
214
|
-
|
|
215
|
-
```json
|
|
216
|
-
{
|
|
217
|
-
"native": true,
|
|
218
|
-
"symbol": "problematic_function",
|
|
219
|
-
"module": "libfoo.so",
|
|
220
|
-
"debug": true
|
|
221
|
-
}
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
### Argument Types
|
|
225
|
-
|
|
226
|
-
| Type | Description |
|
|
227
|
-
|------|-------------|
|
|
228
|
-
| `string` | Null-terminated C string |
|
|
229
|
-
| `int32` | 32-bit signed integer |
|
|
230
|
-
| `uint32` | 32-bit unsigned integer |
|
|
231
|
-
| `int64` | 64-bit signed integer |
|
|
232
|
-
| `pointer` | Memory address |
|
|
233
|
-
| `bytes` | Raw bytes (requires `length` or `lengthInArg`) |
|
|
234
|
-
| `bool` | Boolean value |
|
|
235
|
-
| `double` | 64-bit floating point |
|
|
236
|
-
| `CFData` | iOS CFData object |
|
|
237
|
-
| `CFDictionary` | iOS CFDictionary object |
|
|
238
|
-
|
|
239
|
-
### iOS Objective-C Hooks
|
|
240
|
-
|
|
241
|
-
Hook Objective-C methods using `objClass` and `symbol`:
|
|
242
|
-
|
|
243
|
-
```json
|
|
244
|
-
{
|
|
245
|
-
"native": true,
|
|
246
|
-
"objClass": "NSURLSession",
|
|
247
|
-
"symbol": "dataTaskWithRequest:completionHandler:"
|
|
248
|
-
}
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
## Output Format
|
|
252
|
-
|
|
253
|
-
Events are written to the output file in JSON Lines format (one JSON object per line, know as NDJSON). You can easily pretty-print it e.g. using `jq . output.json`.
|
|
254
|
-
|
|
255
|
-
Example event (pretty-printed for clarity):
|
|
256
|
-
|
|
257
|
-
```json
|
|
258
|
-
{
|
|
259
|
-
"id": "0117229c-b034-4676-ba33-075fc27922ba",
|
|
260
|
-
"type": "hook",
|
|
261
|
-
"category": "STORAGE",
|
|
262
|
-
"time": "2026-01-18T16:17:25.470Z",
|
|
263
|
-
"class": "android.app.SharedPreferencesImpl$EditorImpl",
|
|
264
|
-
"method": "putString",
|
|
265
|
-
"instanceId": 268282727,
|
|
266
|
-
"stackTrace": [
|
|
267
|
-
"android.app.SharedPreferencesImpl$EditorImpl.putString(Native Method)",
|
|
268
|
-
"androidx.security.crypto.EncryptedSharedPreferences$Editor.putEncryptedObject(EncryptedSharedPreferences.java:389)",
|
|
269
|
-
...
|
|
270
|
-
],
|
|
271
|
-
"inputParameters": [
|
|
272
|
-
{
|
|
273
|
-
"declaredType": "java.lang.String",
|
|
274
|
-
"value": "AQMRC7OWD6/h1iJseuzJVrClpwKE8swB8gOrGnsdaN4="
|
|
275
|
-
},
|
|
276
|
-
{
|
|
277
|
-
"declaredType": "java.lang.String",
|
|
278
|
-
"value": "AX4R5MZu+J1p0U3hvKyuEnJDQopI+wupiSi8CAG8dzq0PU76NbbebjhqMtqCD7fFUy2SmmQuQVDlDrrj30d3GQes+PlD8HmRFszVTge039GQ"
|
|
279
|
-
}
|
|
280
|
-
],
|
|
281
|
-
"returnValue": [
|
|
282
|
-
{
|
|
283
|
-
"declaredType": "android.content.SharedPreferences$Editor",
|
|
284
|
-
"value": "<instance: android.content.SharedPreferences$Editor, $className: android.app.SharedPreferencesImpl$EditorImpl>",
|
|
285
|
-
"runtimeType": "android.app.SharedPreferencesImpl$EditorImpl",
|
|
286
|
-
"instanceId": "268282727",
|
|
287
|
-
"instanceToString": "android.app.SharedPreferencesImpl$EditorImpl@ffdab67"
|
|
288
|
-
}
|
|
289
|
-
]
|
|
290
|
-
}
|
|
291
|
-
```
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|