frooky 0.1.0__tar.gz → 0.1.2__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.
Files changed (30) hide show
  1. frooky-0.1.2/.github/workflows/publish.yml +35 -0
  2. frooky-0.1.2/.github/workflows/sync-labels.yml +117 -0
  3. frooky-0.1.2/.gitignore +44 -0
  4. {frooky-0.1.0 → frooky-0.1.2}/PKG-INFO +64 -242
  5. frooky-0.1.2/README.md +113 -0
  6. frooky-0.1.2/docs/examples/example.md +189 -0
  7. frooky-0.1.2/docs/examples/hooks.json +12 -0
  8. frooky-0.1.2/docs/examples/hooks2.json +12 -0
  9. frooky-0.1.2/docs/examples/output.json +7 -0
  10. frooky-0.1.0/README.md → frooky-0.1.2/docs/usage.md +33 -32
  11. frooky-0.1.2/frooky/__init__.py +8 -0
  12. frooky-0.1.2/frooky/_version.py +34 -0
  13. {frooky-0.1.0 → frooky-0.1.2}/frooky/cli.py +13 -0
  14. {frooky-0.1.0 → frooky-0.1.2}/frooky/frida_runner.py +25 -0
  15. {frooky-0.1.0 → frooky-0.1.2}/frooky.egg-info/PKG-INFO +64 -242
  16. {frooky-0.1.0 → frooky-0.1.2}/frooky.egg-info/SOURCES.txt +9 -0
  17. {frooky-0.1.0 → frooky-0.1.2}/pyproject.toml +5 -2
  18. frooky-0.1.0/frooky/__init__.py +0 -4
  19. {frooky-0.1.0 → frooky-0.1.2}/LICENSE +0 -0
  20. {frooky-0.1.0 → frooky-0.1.2}/frooky/__main__.py +0 -0
  21. {frooky-0.1.0 → frooky-0.1.2}/frooky/android/android_decoder.js +0 -0
  22. {frooky-0.1.0 → frooky-0.1.2}/frooky/android/base_script.js +0 -0
  23. {frooky-0.1.0 → frooky-0.1.2}/frooky/android/native_decoder.js +0 -0
  24. {frooky-0.1.0 → frooky-0.1.2}/frooky/ios/base_script.js +0 -0
  25. {frooky-0.1.0 → frooky-0.1.2}/frooky/resources.py +0 -0
  26. {frooky-0.1.0 → frooky-0.1.2}/frooky.egg-info/dependency_links.txt +0 -0
  27. {frooky-0.1.0 → frooky-0.1.2}/frooky.egg-info/entry_points.txt +0 -0
  28. {frooky-0.1.0 → frooky-0.1.2}/frooky.egg-info/requires.txt +0 -0
  29. {frooky-0.1.0 → frooky-0.1.2}/frooky.egg-info/top_level.txt +0 -0
  30. {frooky-0.1.0 → frooky-0.1.2}/setup.cfg +0 -0
@@ -0,0 +1,35 @@
1
+ name: publish
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ jobs:
9
+ build:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ with:
14
+ fetch-depth: 0 # Fetch all history and tags for setuptools-scm
15
+ - uses: actions/setup-python@v5
16
+ with:
17
+ python-version: "3.x"
18
+ - run: python -m pip install --upgrade build
19
+ - run: python -m build
20
+ - uses: actions/upload-artifact@v4
21
+ with:
22
+ name: dist
23
+ path: dist/*
24
+
25
+ publish:
26
+ needs: build
27
+ runs-on: ubuntu-latest
28
+ permissions:
29
+ id-token: write
30
+ steps:
31
+ - uses: actions/download-artifact@v4
32
+ with:
33
+ name: dist
34
+ path: dist
35
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,117 @@
1
+ name: Sync Labels from Issues to PRs
2
+
3
+ on:
4
+ pull_request:
5
+ types: [opened, edited, reopened, synchronize]
6
+
7
+ jobs:
8
+ sync-labels:
9
+ runs-on: ubuntu-latest
10
+ permissions:
11
+ pull-requests: write
12
+ issues: read
13
+
14
+ steps:
15
+ - name: Sync labels from linked issues
16
+ uses: actions/github-script@v7
17
+ with:
18
+ script: |
19
+ const prNumber = context.payload.pull_request.number;
20
+ const prBody = context.payload.pull_request.body || '';
21
+
22
+ const issueNumbers = new Set();
23
+
24
+ // Pattern 1: Simple format - fixes #123, closes#456
25
+ const simpleRegex = /(?:close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved)\s*#(\d+)/gi;
26
+ const simpleMatches = [...prBody.matchAll(simpleRegex)];
27
+ simpleMatches.forEach(match => issueNumbers.add(parseInt(match[1])));
28
+
29
+ // Pattern 2: Owner/repo format - fixes owner/repo#123
30
+ const ownerRepoRegex = /(?:close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved)\s+([a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+)#(\d+)/gi;
31
+ const ownerRepoMatches = [...prBody.matchAll(ownerRepoRegex)];
32
+ ownerRepoMatches.forEach(match => {
33
+ const repoPath = match[1];
34
+ const issueNum = parseInt(match[2]);
35
+ // Only add if it's the same repository
36
+ if (repoPath === `${context.repo.owner}/${context.repo.repo}`) {
37
+ issueNumbers.add(issueNum);
38
+ } else {
39
+ console.log(`Skipping cross-repository issue: ${repoPath}#${issueNum}`);
40
+ }
41
+ });
42
+
43
+ // Pattern 3: Full URL format - fixes https://github.com/owner/repo#123 or /issues/123
44
+ const urlRegex = /(?:close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved)\s+https?:\/\/github\.com\/([a-zA-Z0-9._-]+)\/([a-zA-Z0-9._-]+)(?:\/issues\/|#)(\d+)/gi;
45
+ const urlMatches = [...prBody.matchAll(urlRegex)];
46
+ urlMatches.forEach(match => {
47
+ const owner = match[1];
48
+ const repo = match[2];
49
+ const issueNum = parseInt(match[3]);
50
+ // Only add if it's the same repository
51
+ if (owner === context.repo.owner && repo === context.repo.repo) {
52
+ issueNumbers.add(issueNum);
53
+ } else {
54
+ console.log(`Skipping cross-repository issue: ${owner}/${repo}#${issueNum}`);
55
+ }
56
+ });
57
+
58
+ const issueNumbersArray = Array.from(issueNumbers);
59
+
60
+ if (issueNumbersArray.length === 0) {
61
+ console.log('No linked issues found in PR description');
62
+ return;
63
+ }
64
+
65
+ console.log(`Found linked issues: ${issueNumbersArray.join(', ')}`);
66
+
67
+ // Collect all labels from linked issues
68
+ const allLabels = new Set();
69
+
70
+ for (const issueNumber of issueNumbersArray) {
71
+ try {
72
+ const { data: issue } = await github.rest.issues.get({
73
+ owner: context.repo.owner,
74
+ repo: context.repo.repo,
75
+ issue_number: issueNumber
76
+ });
77
+
78
+ console.log(`Issue #${issueNumber} has labels: ${issue.labels.map(l => l.name).join(', ')}`);
79
+
80
+ issue.labels.forEach(label => {
81
+ allLabels.add(label.name);
82
+ });
83
+ } catch (error) {
84
+ console.log(`Error fetching issue #${issueNumber}: ${error.message}`);
85
+ }
86
+ }
87
+
88
+ if (allLabels.size === 0) {
89
+ console.log('No labels found on linked issues');
90
+ return;
91
+ }
92
+
93
+ console.log(`Syncing labels to PR: ${Array.from(allLabels).join(', ')}`);
94
+
95
+ // Get current PR labels
96
+ const { data: currentPR } = await github.rest.pulls.get({
97
+ owner: context.repo.owner,
98
+ repo: context.repo.repo,
99
+ pull_number: prNumber
100
+ });
101
+
102
+ const currentLabels = new Set(currentPR.labels.map(l => l.name));
103
+
104
+ // Add new labels from issues
105
+ const labelsToAdd = Array.from(allLabels).filter(label => !currentLabels.has(label));
106
+
107
+ if (labelsToAdd.length > 0) {
108
+ await github.rest.issues.addLabels({
109
+ owner: context.repo.owner,
110
+ repo: context.repo.repo,
111
+ issue_number: prNumber,
112
+ labels: labelsToAdd
113
+ });
114
+ console.log(`Added labels: ${labelsToAdd.join(', ')}`);
115
+ } else {
116
+ console.log('All labels already present on PR');
117
+ }
@@ -0,0 +1,44 @@
1
+ # Frooky local debug artifacts
2
+ tmp/
3
+ output.json
4
+ *.jsonl
5
+
6
+ # node
7
+ node_modules/
8
+ package.json
9
+ package-lock.json
10
+
11
+
12
+ # Python bytecode
13
+ __pycache__/
14
+ *.py[cod]
15
+ *$py.class
16
+
17
+ # Virtual environments
18
+ .venv/
19
+ venv/
20
+ env/
21
+ .env/
22
+
23
+ # Packaging/build outputs
24
+ build/
25
+ dist/
26
+ *.egg-info/
27
+ .eggs/
28
+ pip-wheel-metadata/
29
+
30
+ # Generated version file (created by setuptools-scm during build)
31
+ frooky/_version.py
32
+
33
+ # Test / type-check / lint caches
34
+ .pytest_cache/
35
+ .mypy_cache/
36
+ .ruff_cache/
37
+ .coverage
38
+ coverage.xml
39
+ htmlcov/
40
+
41
+ # IDE / OS
42
+ .DS_Store
43
+ .vscode/
44
+ .idea/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: frooky
3
- Version: 0.1.0
3
+ Version: 0.1.2
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
- A Frida-based dynamic analysis tool for Android and iOS applications.
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
+ [![PyPi](https://badge.fury.io/py/frooky.svg)](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
- pip install frooky
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 hooks.json hooks2.json more_hooks.json
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
- ## Hook Files
749
+ ## Example
735
750
 
736
- Hook files use JSON format. When multiple hook files are provided, their `hooks` arrays are merged together.
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
- ### Basic Structure
753
+ First you need to create a hook file, e.g., `crypto.json`:
739
754
 
740
755
  ```json
741
756
  {
742
- "category": "STORAGE",
757
+ "category": "CRYPTO",
743
758
  "hooks": [
744
759
  {
745
- "class": "com.example.MyClass",
746
- "methods": ["method1", "method2"]
760
+ "class": "android.security.keystore.KeyGenParameterSpec$Builder",
761
+ "method": "$init",
762
+ "maxFrames": 10
747
763
  }
748
764
  ]
749
765
  }
750
766
  ```
751
767
 
752
- ### Java/Kotlin Hooks
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
- ```json
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
- #### Filter by Value
774
+ Output (pretty-printed for readability):
886
775
 
887
- Only capture events when arguments match specific values:
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
- "native": true,
892
- "symbol": "open",
893
- "module": "libc.so",
894
- "args": [
895
- { "name": "pathname", "type": "string", "filter": ["/data/", "/sdcard/"] }
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
- #### Filter by Stack Trace
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.2/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
+ [![PyPi](https://badge.fury.io/py/frooky.svg)](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).