├── Icon.ico
├── Repository_Images
├── Img2.png
├── DemoPic.png
├── Flower.png
├── 00c7df89af924aec9e715fc8b50deb37.png
├── 02abfae83f674165a695f32f6d562dfc.png
├── 049b4b328ed84ce7bb427747e3d8f8ad.png
├── 1f35b7bdb28348dd82f02ea10cf71415.png
├── 3bf235bd9772c779536386c606216a09.png
├── 3e2c7bb4fd16465e9e87602155380c7b.png
├── 41016d9ceb004d099b776afde68591d3.png
├── 433188a5960d4b0e8c1c9999a1cd9a24.png
├── 45ece2d83a334458a8196a8c9f929c2d.png
├── 4c8ad7709af944f390d962e1bc0fc3d7.png
├── 4e9e523190564c528386a5083c62896d.png
├── 53842098dbb14d77869b734193887d14.png
├── 5de3194b6929419995c119296df51f28.png
├── 650f91220a29451586bc89f715deca73.png
├── 70783014ba204063acc63b9af46380ed.png
├── 734206fc76f64df29cc1062e8424ef39.png
├── 76baa56d21aae018a3e3bad8f408f0be.png
├── 771a0d11c2114f368eaef3cf817ff256.png
├── 83985e5f0b514ecf914992afa3712f85.png
├── 85d0c0bf3f564ceba28a2cbad47bf613.png
├── 8a403de4c8b2406c9ba507b9d4777752.png
├── 8ae9ace07e544634a0776346cbe214c5.png
├── 8d87c8637a164ed284c8eeafe767490d.png
├── 9c93785c10a54b7d99d19eb5d0e0164b.png
├── 9d3803402e9d42c5b7e15df04494b7bc.png
├── 9e99036ea3c34eaa8843da16b74be099.png
├── b1fc1054f28c4bb6a269d57c1c6a7763.png
├── b95b96e0354b437e9189e272f1838895.png
├── bf101bc5cf064aaab8a15e185bfdb433.png
├── c29393324ff04b32ba10902f4b46cfef.png
├── cd8c7a40d0094fcfa179811d51c7d746.png
├── d541ca18113b4a0793aa048a60322904.png
├── f195df7ab8144ebeae5132ae63bb0eee.png
└── f8892a407c293443f50061d2d14404c0.png
├── Resources
└── Icons
│ ├── Merge.png
│ ├── Split.png
│ ├── UpArrow.png
│ ├── DownArrow.png
│ ├── DrawLink.png
│ ├── Uploaded.png
│ ├── Uploading.png
│ ├── Add_Canvas.png
│ ├── ColorPicker.png
│ ├── Resolution.png
│ ├── SelectLeaf.png
│ ├── SelectRoot.png
│ ├── ShortestPath.png
│ ├── Uploading2.png
│ ├── Import_Browser.png
│ ├── RearrangeGraph.png
│ ├── SelectIsolated.png
│ ├── Generate_Report.png
│ ├── SelectNonIsolated.png
│ ├── Right-Arrow.svg
│ ├── Banner_Cyan.svg
│ ├── Banner_Orange.svg
│ ├── Banner_Red.svg
│ ├── Default.svg
│ ├── Image.svg
│ ├── CVE.svg
│ ├── Computer.svg
│ ├── Flight_Number.svg
│ ├── Country.svg
│ ├── Phrase.svg
│ ├── Person.svg
│ ├── PEP.svg
│ ├── ID_Number.svg
│ ├── Geolocation.svg
│ ├── Terrorists.svg
│ ├── HardDisk.svg
│ ├── Archive.svg
│ ├── Cult.svg
│ ├── Bank_Account.svg
│ ├── IP_Address.svg
│ ├── Family.svg
│ ├── Bitcoin.svg
│ ├── Port.svg
│ ├── Mobile_Device.svg
│ ├── Sentiment.svg
│ ├── Organization.svg
│ ├── Spreadsheet.svg
│ ├── Software.svg
│ ├── Domain.svg
│ ├── Website.svg
│ ├── OnionWebsite.svg
│ ├── City.svg
│ ├── Video.svg
│ ├── IPv6_Address.svg
│ ├── Date.svg
│ ├── Email.svg
│ ├── Network.svg
│ ├── Document.svg
│ ├── WebInfrastructure.svg
│ ├── Address.svg
│ ├── Transaction.svg
│ ├── Hash.svg
│ ├── MAC_Address.svg
│ ├── VehicleRegistration.svg
│ ├── GeoArea.svg
│ ├── Currency.svg
│ ├── Company.svg
│ ├── AutonomousSystem.svg
│ ├── GroupNode.svg
│ ├── Camera.svg
│ ├── Phone_Number.svg
│ ├── Social_Media_Group.svg
│ ├── Social_Media_Account.svg
│ ├── Portfolio.svg
│ └── Passport.svg
├── .gitignore
├── Core
├── Entities
│ ├── Meta.xml
│ ├── Social Media.xml
│ ├── Software.xml
│ ├── Devices.xml
│ ├── Vehicles.xml
│ ├── Identifiers.xml
│ ├── Financials.xml
│ ├── Groups.xml
│ ├── Places.xml
│ ├── Materials.xml
│ ├── Individuals.xml
│ └── Infrastructure.xml
├── Resolutions
│ └── Core
│ │ ├── ExtractDateCreated.py
│ │ ├── ToPhrase.py
│ │ ├── EmailToPhrase.py
│ │ ├── HostnameToDomain.py
│ │ ├── EmailToDomain.py
│ │ ├── IPToHostname.py
│ │ ├── WordCounter.py
│ │ ├── NPMJSSearch.py
│ │ ├── ImageToGeoLocation.py
│ │ ├── WebsiteFromPhrase.py
│ │ ├── TikTokVideoPublishDetails.py
│ │ ├── DomainFromPhrase.py
│ │ ├── ImageToDevice.py
│ │ ├── HostnameToIP.py
│ │ ├── FileHasher.py
│ │ ├── DecodeRedirectUrlParameter.py
│ │ ├── ExtractDOCXMeta.py
│ │ ├── PhraseSimilarity.py
│ │ ├── IPWhois.py
│ │ ├── DecodePhrase.py
│ │ ├── IPToASN.py
│ │ ├── RegexMatch.py
│ │ ├── ASNToCIDR.py
│ │ ├── GetWebsiteText.py
│ │ ├── DeleteColumn.py
│ │ ├── ContainsPhrase.py
│ │ ├── ReplacePhrase.py
│ │ ├── ExtractPDFMeta.py
│ │ └── PhoneNumbersExtractor.py
└── GlobalVariables.py
├── SECURITY.md
├── requirements.txt
├── UpdaterUtil.py
├── CONTRIBUTING
├── PatchMagicWin.py
└── Installer
└── LinuxInstaller.sh
/Icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Icon.ico
--------------------------------------------------------------------------------
/Repository_Images/Img2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Repository_Images/Img2.png
--------------------------------------------------------------------------------
/Resources/Icons/Merge.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Resources/Icons/Merge.png
--------------------------------------------------------------------------------
/Resources/Icons/Split.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Resources/Icons/Split.png
--------------------------------------------------------------------------------
/Resources/Icons/UpArrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Resources/Icons/UpArrow.png
--------------------------------------------------------------------------------
/Repository_Images/DemoPic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Repository_Images/DemoPic.png
--------------------------------------------------------------------------------
/Repository_Images/Flower.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Repository_Images/Flower.png
--------------------------------------------------------------------------------
/Resources/Icons/DownArrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Resources/Icons/DownArrow.png
--------------------------------------------------------------------------------
/Resources/Icons/DrawLink.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Resources/Icons/DrawLink.png
--------------------------------------------------------------------------------
/Resources/Icons/Uploaded.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Resources/Icons/Uploaded.png
--------------------------------------------------------------------------------
/Resources/Icons/Uploading.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Resources/Icons/Uploading.png
--------------------------------------------------------------------------------
/Resources/Icons/Add_Canvas.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Resources/Icons/Add_Canvas.png
--------------------------------------------------------------------------------
/Resources/Icons/ColorPicker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Resources/Icons/ColorPicker.png
--------------------------------------------------------------------------------
/Resources/Icons/Resolution.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Resources/Icons/Resolution.png
--------------------------------------------------------------------------------
/Resources/Icons/SelectLeaf.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Resources/Icons/SelectLeaf.png
--------------------------------------------------------------------------------
/Resources/Icons/SelectRoot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Resources/Icons/SelectRoot.png
--------------------------------------------------------------------------------
/Resources/Icons/ShortestPath.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Resources/Icons/ShortestPath.png
--------------------------------------------------------------------------------
/Resources/Icons/Uploading2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Resources/Icons/Uploading2.png
--------------------------------------------------------------------------------
/Resources/Icons/Import_Browser.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Resources/Icons/Import_Browser.png
--------------------------------------------------------------------------------
/Resources/Icons/RearrangeGraph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Resources/Icons/RearrangeGraph.png
--------------------------------------------------------------------------------
/Resources/Icons/SelectIsolated.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Resources/Icons/SelectIsolated.png
--------------------------------------------------------------------------------
/Resources/Icons/Generate_Report.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Resources/Icons/Generate_Report.png
--------------------------------------------------------------------------------
/Resources/Icons/SelectNonIsolated.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Resources/Icons/SelectNonIsolated.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /magic/COPYING.file
2 | /magic/COPYING.libgnurx
3 | /magic/file.exe
4 | /magic/libgnurx-0.dll
5 | /magic/libmagic-1.dll
6 | /magic/magic.mgc
7 |
--------------------------------------------------------------------------------
/Repository_Images/00c7df89af924aec9e715fc8b50deb37.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Repository_Images/00c7df89af924aec9e715fc8b50deb37.png
--------------------------------------------------------------------------------
/Repository_Images/02abfae83f674165a695f32f6d562dfc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Repository_Images/02abfae83f674165a695f32f6d562dfc.png
--------------------------------------------------------------------------------
/Repository_Images/049b4b328ed84ce7bb427747e3d8f8ad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Repository_Images/049b4b328ed84ce7bb427747e3d8f8ad.png
--------------------------------------------------------------------------------
/Repository_Images/1f35b7bdb28348dd82f02ea10cf71415.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Repository_Images/1f35b7bdb28348dd82f02ea10cf71415.png
--------------------------------------------------------------------------------
/Repository_Images/3bf235bd9772c779536386c606216a09.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Repository_Images/3bf235bd9772c779536386c606216a09.png
--------------------------------------------------------------------------------
/Repository_Images/3e2c7bb4fd16465e9e87602155380c7b.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Repository_Images/3e2c7bb4fd16465e9e87602155380c7b.png
--------------------------------------------------------------------------------
/Repository_Images/41016d9ceb004d099b776afde68591d3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Repository_Images/41016d9ceb004d099b776afde68591d3.png
--------------------------------------------------------------------------------
/Repository_Images/433188a5960d4b0e8c1c9999a1cd9a24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Repository_Images/433188a5960d4b0e8c1c9999a1cd9a24.png
--------------------------------------------------------------------------------
/Repository_Images/45ece2d83a334458a8196a8c9f929c2d.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Repository_Images/45ece2d83a334458a8196a8c9f929c2d.png
--------------------------------------------------------------------------------
/Repository_Images/4c8ad7709af944f390d962e1bc0fc3d7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Repository_Images/4c8ad7709af944f390d962e1bc0fc3d7.png
--------------------------------------------------------------------------------
/Repository_Images/4e9e523190564c528386a5083c62896d.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Repository_Images/4e9e523190564c528386a5083c62896d.png
--------------------------------------------------------------------------------
/Repository_Images/53842098dbb14d77869b734193887d14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Repository_Images/53842098dbb14d77869b734193887d14.png
--------------------------------------------------------------------------------
/Repository_Images/5de3194b6929419995c119296df51f28.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Repository_Images/5de3194b6929419995c119296df51f28.png
--------------------------------------------------------------------------------
/Repository_Images/650f91220a29451586bc89f715deca73.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Repository_Images/650f91220a29451586bc89f715deca73.png
--------------------------------------------------------------------------------
/Repository_Images/70783014ba204063acc63b9af46380ed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Repository_Images/70783014ba204063acc63b9af46380ed.png
--------------------------------------------------------------------------------
/Repository_Images/734206fc76f64df29cc1062e8424ef39.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Repository_Images/734206fc76f64df29cc1062e8424ef39.png
--------------------------------------------------------------------------------
/Repository_Images/76baa56d21aae018a3e3bad8f408f0be.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Repository_Images/76baa56d21aae018a3e3bad8f408f0be.png
--------------------------------------------------------------------------------
/Repository_Images/771a0d11c2114f368eaef3cf817ff256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Repository_Images/771a0d11c2114f368eaef3cf817ff256.png
--------------------------------------------------------------------------------
/Repository_Images/83985e5f0b514ecf914992afa3712f85.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Repository_Images/83985e5f0b514ecf914992afa3712f85.png
--------------------------------------------------------------------------------
/Repository_Images/85d0c0bf3f564ceba28a2cbad47bf613.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Repository_Images/85d0c0bf3f564ceba28a2cbad47bf613.png
--------------------------------------------------------------------------------
/Repository_Images/8a403de4c8b2406c9ba507b9d4777752.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Repository_Images/8a403de4c8b2406c9ba507b9d4777752.png
--------------------------------------------------------------------------------
/Repository_Images/8ae9ace07e544634a0776346cbe214c5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Repository_Images/8ae9ace07e544634a0776346cbe214c5.png
--------------------------------------------------------------------------------
/Repository_Images/8d87c8637a164ed284c8eeafe767490d.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Repository_Images/8d87c8637a164ed284c8eeafe767490d.png
--------------------------------------------------------------------------------
/Repository_Images/9c93785c10a54b7d99d19eb5d0e0164b.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Repository_Images/9c93785c10a54b7d99d19eb5d0e0164b.png
--------------------------------------------------------------------------------
/Repository_Images/9d3803402e9d42c5b7e15df04494b7bc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Repository_Images/9d3803402e9d42c5b7e15df04494b7bc.png
--------------------------------------------------------------------------------
/Repository_Images/9e99036ea3c34eaa8843da16b74be099.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Repository_Images/9e99036ea3c34eaa8843da16b74be099.png
--------------------------------------------------------------------------------
/Repository_Images/b1fc1054f28c4bb6a269d57c1c6a7763.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Repository_Images/b1fc1054f28c4bb6a269d57c1c6a7763.png
--------------------------------------------------------------------------------
/Repository_Images/b95b96e0354b437e9189e272f1838895.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Repository_Images/b95b96e0354b437e9189e272f1838895.png
--------------------------------------------------------------------------------
/Repository_Images/bf101bc5cf064aaab8a15e185bfdb433.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Repository_Images/bf101bc5cf064aaab8a15e185bfdb433.png
--------------------------------------------------------------------------------
/Repository_Images/c29393324ff04b32ba10902f4b46cfef.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Repository_Images/c29393324ff04b32ba10902f4b46cfef.png
--------------------------------------------------------------------------------
/Repository_Images/cd8c7a40d0094fcfa179811d51c7d746.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Repository_Images/cd8c7a40d0094fcfa179811d51c7d746.png
--------------------------------------------------------------------------------
/Repository_Images/d541ca18113b4a0793aa048a60322904.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Repository_Images/d541ca18113b4a0793aa048a60322904.png
--------------------------------------------------------------------------------
/Repository_Images/f195df7ab8144ebeae5132ae63bb0eee.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Repository_Images/f195df7ab8144ebeae5132ae63bb0eee.png
--------------------------------------------------------------------------------
/Repository_Images/f8892a407c293443f50061d2d14404c0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AccentuSoft/LinkScope_Client/HEAD/Repository_Images/f8892a407c293443f50061d2d14404c0.png
--------------------------------------------------------------------------------
/Core/Entities/Meta.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Group Name
5 | Child UIDs
6 |
7 |
8 | GroupNode.svg
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Resources/Icons/Right-Arrow.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Core/Entities/Social Media.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Group Name
5 |
6 |
7 | Social_Media_Group.svg
8 |
9 |
10 |
11 |
12 | User Name
13 |
14 |
15 | Social_Media_Account.svg
16 |
17 |
18 |
--------------------------------------------------------------------------------
/Resources/Icons/Banner_Cyan.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/Resources/Icons/Banner_Orange.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/Resources/Icons/Banner_Red.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/Resources/Icons/Default.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Core/Resolutions/Core/ExtractDateCreated.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 |
4 | class ExtractDateCreated:
5 | name = "Extract Date Created"
6 | category = "Date Operations"
7 | description = "Extract the date in the Date Created field of the selected entities."
8 | originTypes = {'*'}
9 | resultTypes = {'Date'}
10 | parameters = {}
11 |
12 | def resolution(self, entityJsonList, parameters):
13 |
14 | return_result = []
15 |
16 | for entity in entityJsonList:
17 | uid = entity['uid']
18 | return_result.append([{'Date': str(entity['Date Created']),
19 | 'Entity Type': 'Date'},
20 | {uid: {'Resolution': 'Extract Dates', 'Notes': ''}}])
21 | return return_result
22 |
--------------------------------------------------------------------------------
/Resources/Icons/Image.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Core/Resolutions/Core/ToPhrase.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 |
4 | class ToPhrase:
5 | name = "Convert To Phrase"
6 | category = "String Operations"
7 | description = "Convert the primary field of selected entities to a Phrase entity."
8 | originTypes = {'*'}
9 | resultTypes = {'Phrase'}
10 |
11 | parameters = {}
12 |
13 | def resolution(self, entityJsonList, parameters):
14 |
15 | returnResults = []
16 |
17 | for entity in entityJsonList:
18 | primaryField = entity[list(entity)[1]]
19 | returnResults.append([{'Phrase': primaryField,
20 | 'Entity Type': 'Phrase'},
21 | {entity['uid']: {'Resolution': 'To Phrase',
22 | 'Notes': ''}}])
23 |
24 | return returnResults
25 |
--------------------------------------------------------------------------------
/Resources/Icons/CVE.svg:
--------------------------------------------------------------------------------
1 |
2 |
11 |
--------------------------------------------------------------------------------
/Core/Resolutions/Core/EmailToPhrase.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 |
4 | class EmailToPhrase:
5 | name = "Email Username To Phrase"
6 | category = "String Operations"
7 | description = "Get the username associated with the given email address."
8 | originTypes = {'Email Address'}
9 | resultTypes = {'Phrase'}
10 |
11 | parameters = {}
12 |
13 | def resolution(self, entityJsonList, parameters):
14 |
15 | returnResults = []
16 |
17 | for entity in entityJsonList:
18 | primaryField = entity['Email Address']
19 | returnResults.append([{'Phrase': primaryField.split('@')[0],
20 | 'Entity Type': 'Phrase'},
21 | {entity['uid']: {'Resolution': 'Email Username To Phrase',
22 | 'Notes': ''}}])
23 |
24 | return returnResults
25 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Supported Platforms and Versions
4 |
5 | Currently, only the latest version of the software is supported.
6 |
7 | The software is targeted towards Linux (Debian / Ubuntu) and Windows (version 11).
8 | Other platforms are not officially supported.
9 |
10 | ## Reporting a Vulnerability
11 |
12 | If you believe that you have found a vulnerability, please send an email to support@accentusoft.com.
13 | Make sure to include a detailed explanation and a reference to the vulnerable part of the project.
14 |
15 | ### Note: Vulnerabilities relating to the execution of malicious code due to the user opening malicious project files or running malicious modules are considered to be out of scope. Vulnerabilities relating to third-party modules are also out of scope.
16 |
17 | If you are unsure if what you have found is a vulnerability, send an email regardless. Better be safe than sorry.
18 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | PySide6
2 |
3 |
4 | # For python-magic, the following packages may be required:
5 | ## Linux:
6 | ### sudo apt-get install libmagic1
7 | python-magic
8 |
9 | # The graphviz package is required.
10 | ## Linux: sudo apt-get install graphviz
11 | ## Windows: https://www.graphviz.org/download/
12 |
13 | pydot
14 | networkx
15 | pygit2
16 |
17 | requests
18 | exif
19 | email-validator
20 | pypdf
21 | docx2python
22 | jellyfish
23 | python-dateutil
24 | pyyaml
25 | tldextract
26 | ipwhois
27 | pycountry
28 | defusedxml
29 | cryptography
30 | msgpack
31 | folium
32 | pillow
33 | lz4
34 | reportlab
35 | pandas
36 | svglib
37 | lxml
38 | py7zr
39 | semver
40 | # For opening spreadsheets
41 | odfpy
42 | openpyxl
43 | xlrd # For old Excel formats
44 | # Speeds up parsing of webpages by bs4
45 | faust-cchardet
46 | bs4
47 | # Redundant, but doesn't hurt.
48 | beautifulsoup4
49 |
50 | # Need to run 'playwright install'.
51 | playwright
52 |
--------------------------------------------------------------------------------
/Resources/Icons/Computer.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/UpdaterUtil.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import sys
3 | import time
4 | from pathlib import Path
5 |
6 | import py7zr
7 |
8 |
9 | def main():
10 | if len(sys.argv) != 3:
11 | sys.exit(3)
12 |
13 | tempPath = Path(sys.argv[1])
14 | softwarePath = Path(sys.argv[2])
15 |
16 | time.sleep(10) # Wait for a few seconds for the main application to close.
17 |
18 | input("\nPlease make sure that all LinkScope Client processes are closed, "
19 | "and then press Enter to begin the update.\n")
20 | print("Updating...\n")
21 |
22 | with py7zr.SevenZipFile(tempPath, 'r') as archive:
23 | archive.extractall(path=softwarePath.parent)
24 |
25 | tempPath.unlink(missing_ok=True)
26 |
27 | input("Update complete. Press Enter to exit.")
28 |
29 |
30 | if __name__ == '__main__':
31 | try:
32 | main()
33 | except Exception as e:
34 | input(f"Update failed. Press Enter to exit. Reason: {repr(e)}")
35 |
--------------------------------------------------------------------------------
/Resources/Icons/Flight_Number.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Resources/Icons/Country.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Core/Entities/Software.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Application Name
5 |
6 |
7 | Software.svg
8 |
9 |
10 |
11 |
12 | Hash Value
13 | Hash Algorithm
14 |
15 |
16 | Hash.svg
17 |
18 |
19 |
20 |
21 | CVE
22 | CVSS Score
23 |
24 |
25 | CVE.svg
26 |
27 |
28 |
--------------------------------------------------------------------------------
/Resources/Icons/Phrase.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/CONTRIBUTING:
--------------------------------------------------------------------------------
1 | By contributing to this software, the contributor hereby grants to AccentuSoft and to recipients of software distributed by AccentuSoft a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer all or parts of this software, where such license applies only to those patent claims licensable by the contributor that are necessarily infringed by the contributor's contribution(s) alone or by combination of the contributor's contribution(s) with the all or parts of this software to which such contribution(s) was submitted.
2 |
3 | In non-legalese, this means that any contributions made to this software can be used by AccentuSoft and any other user for any purpose without concern that the original author will revoke permission to use their contribution, or take anyone to court. Make sure that all your contributions consist of things that you want to contribute to the project. In all cases, the project's license still applies.
4 |
--------------------------------------------------------------------------------
/Resources/Icons/Person.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Core/Resolutions/Core/HostnameToDomain.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 |
4 | class HostnameToDomain:
5 | name = "Get Top Level Domain"
6 | category = "Network Infrastructure"
7 | description = "Get the top level domain of any given website or subdomain."
8 | originTypes = {'Domain', 'Website'}
9 | resultTypes = {'Domain'}
10 | parameters = {}
11 |
12 | def resolution(self, entityJsonList, parameters):
13 | from tldextract import extract
14 |
15 | return_result = []
16 | for entity in entityJsonList:
17 | uid = entity['uid']
18 | primary_field = entity[list(entity)[1]].strip()
19 | domain = extract(primary_field).fqdn
20 | if domain == primary_field:
21 | continue
22 | return_result.append([{'Domain Name': domain,
23 | 'Entity Type': 'Domain'},
24 | {uid: {'Resolution': 'Domain to Hostname', 'Notes': ''}}])
25 | return return_result
26 |
--------------------------------------------------------------------------------
/Resources/Icons/PEP.svg:
--------------------------------------------------------------------------------
1 |
30 |
--------------------------------------------------------------------------------
/Core/Resolutions/Core/EmailToDomain.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 |
4 | class EmailToDomain:
5 | name = "Email To Domain"
6 | category = "Network Infrastructure"
7 | description = "Get the Domain that an email address belongs to."
8 | originTypes = {'Email Address'}
9 | resultTypes = {'Domain'}
10 |
11 | parameters = {}
12 |
13 | def resolution(self, entityJsonList, parameters):
14 | import contextlib
15 |
16 | returnResults = []
17 |
18 | for entity in entityJsonList:
19 | primaryField = entity['Email Address']
20 | # There is no provider that I am aware of that allows '@' signs in the user part of the email.
21 | with contextlib.suppress(Exception):
22 | returnResults.append([{'Domain Name': primaryField.split('@')[1].strip(),
23 | 'Entity Type': 'Domain'},
24 | {entity['uid']: {'Resolution': 'Email To Domain',
25 | 'Notes': ''}}])
26 | return returnResults
27 |
--------------------------------------------------------------------------------
/Resources/Icons/ID_Number.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Resources/Icons/Geolocation.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Core/Entities/Devices.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Device UID
5 | Brand
6 |
7 |
8 | Mobile_Device.svg
9 |
10 |
11 |
12 |
13 | Hostname
14 | Brand
15 |
16 |
17 | Computer.svg
18 |
19 |
20 |
21 |
22 | Device UID
23 | Brand
24 |
25 |
26 | Camera.svg
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/Core/Resolutions/Core/IPToHostname.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 |
4 | class IPToHostname:
5 | name = "IP To Hostname"
6 | category = "Network Infrastructure"
7 | description = "Gets the Fully Qualified Domain Name associated with the given IPv4 / IPv6 address."
8 | originTypes = {'IPv6 Address', 'IP Address'}
9 | resultTypes = {'Domain'}
10 |
11 | parameters = {}
12 |
13 | def resolution(self, entityJsonList, parameters):
14 | import socket
15 |
16 | returnResults = []
17 |
18 | for entity in entityJsonList:
19 | primaryField = entity[list(entity)[1]]
20 | try:
21 | fqdn = socket.getfqdn(primaryField)
22 | except Exception:
23 | continue
24 |
25 | if fqdn != primaryField:
26 | returnResults.append([{'Domain Name': fqdn,
27 | 'Entity Type': 'Domain'},
28 | {entity['uid']: {'Resolution': 'Fully Qualified Domain Name',
29 | 'Notes': ''}}])
30 |
31 | return returnResults
32 |
--------------------------------------------------------------------------------
/Resources/Icons/Terrorists.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Core/Entities/Vehicles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Vessel Registration
5 | Vessel Name
6 | Vessel CallSign
7 | Vessel Flag
8 |
9 |
10 | Vessel.svg
11 |
12 |
13 |
14 |
15 | Aircraft Serial Number
16 | Aircraft Registration
17 | Aircraft Name
18 | Aircraft Model
19 |
20 |
21 | Aircraft.svg
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Resources/Icons/HardDisk.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Resources/Icons/Archive.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/PatchMagicWin.py:
--------------------------------------------------------------------------------
1 | # This file patches the magic library on Windows so that it works properly after being compiled.
2 |
3 | import re
4 | from pathlib import Path
5 |
6 | magicLibPath = Path.cwd() / "buildEnv" / "Lib" / "site-packages" / "magic" / "loader.py"
7 |
8 | with open(magicLibPath, 'r') as magicLibFile:
9 | magicLines = magicLibFile.readlines()
10 |
11 | # Find out what level of indentation is used, in case it changes in the future.
12 | lineIndent = next(
13 | (
14 | len(re.split(r'\S', line)[0])
15 | for line in magicLines
16 | if line.startswith(' ')
17 | ),
18 | None,
19 | )
20 | magicLines.insert(0, 'from pathlib import Path\n')
21 | magicLines.insert(0, 'import os\n')
22 |
23 | funcIndex = magicLines.index(" elif sys.platform in ('win32', 'cygwin'):\n")
24 |
25 | # This patch is only done on Windows, so we can safely 'hardcode' the path to the dll, since we're the ones
26 | # providing it.
27 | magicLines.insert(funcIndex + 1, ' ' * lineIndent * 2 + "yield find_library(str(Path(os.environ['ProgramFiles']) / 'LinkScope' / 'magic' / 'libmagic-1.dll'))\n")
28 |
29 | with open(magicLibPath, 'w') as magicLibFile:
30 | magicLibFile.writelines(magicLines)
31 |
--------------------------------------------------------------------------------
/Resources/Icons/Cult.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Resources/Icons/Bank_Account.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Core/Entities/Identifiers.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ID Number
5 |
6 |
7 | ID_Number.svg
8 |
9 |
10 |
11 |
12 | Passport Number
13 |
14 |
15 | Passport.svg
16 |
17 |
18 |
19 |
20 | Registration Number
21 |
22 |
23 | VehicleRegistration.svg
24 |
25 |
26 |
27 |
28 | Flight Number
29 | Airline
30 |
31 |
32 | Flight_Number.svg
33 |
34 |
35 |
--------------------------------------------------------------------------------
/Resources/Icons/IP_Address.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Resources/Icons/Family.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Core/Resolutions/Core/WordCounter.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 |
4 | class WordCounter:
5 | name = "Get Number Of Words"
6 | category = "String Operations"
7 | description = "Count the number of words in a given phrase."
8 | originTypes = {'Phrase'}
9 | resultTypes = {'Phrase'}
10 | parameters = {'Primary field or Notes': {'description': 'Choose Either Primary field or Notes',
11 | 'type': 'SingleChoice',
12 | 'value': {'Notes', 'Primary Field'},
13 | 'default': 'Notes'}}
14 |
15 | def resolution(self, entityJsonList, parameters):
16 | import re
17 |
18 | return_result = []
19 | selection = parameters['Primary field or Notes']
20 | for entity in entityJsonList:
21 | uid = entity['uid']
22 | if selection == "Notes":
23 | total = len(re.findall(r'\w+', entity['Notes'].strip()))
24 | else:
25 | total = len(re.findall(r'\w+', entity["Phrase"].strip()))
26 | return_result.append([{'Phrase': f"{total} words",
27 | 'Entity Type': 'Phrase'},
28 | {uid: {'Resolution': 'Notes Word Count', 'Notes': ''}}])
29 |
30 | return return_result
31 |
--------------------------------------------------------------------------------
/Resources/Icons/Bitcoin.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/Resources/Icons/Port.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Resources/Icons/Mobile_Device.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Resources/Icons/Sentiment.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Core/Resolutions/Core/NPMJSSearch.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 |
4 | class NPMJSSearch:
5 | name = "Find NPM organization"
6 | category = "Online Identity"
7 | description = "Find a collective's npmjs organization page."
8 | originTypes = {'Phrase', 'Company', 'Organization'}
9 | resultTypes = {'Website'}
10 |
11 | parameters = {}
12 |
13 | def resolution(self, entityJsonList, parameters):
14 | import requests
15 |
16 | headers = {'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:108.0) Gecko/20100101 Firefox/108.0'}
17 | url_base = 'https://www.npmjs.com/org/'
18 |
19 | returnResults = []
20 |
21 | for entity in entityJsonList:
22 | primaryField = entity[list(entity)[1]].lower()
23 |
24 | string_checks = {''.join(primaryField.split(' '))}
25 | string_checks.add('_'.join(primaryField.split(' ')))
26 | string_checks.add('-'.join(primaryField.split(' ')))
27 |
28 | for check in string_checks:
29 | check_url = url_base + check
30 | request = requests.head(check_url, headers=headers)
31 | if request.status_code == 200:
32 | returnResults.append([{'URL': check_url,
33 | 'Entity Type': 'Website'},
34 | {entity['uid']: {'Resolution': 'NPMJS Org',
35 | 'Notes': ''}}])
36 |
37 | return returnResults
38 |
--------------------------------------------------------------------------------
/Core/Resolutions/Core/ImageToGeoLocation.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 |
4 | class ImageToGeoLocation:
5 | name = "Geolocation From Image"
6 | category = "File Operations"
7 | description = "Find information about where an image was taken."
8 | originTypes = {"Image"}
9 | resultTypes = {'GeoCoordinates'}
10 | parameters = {}
11 |
12 | def resolution(self, entityJsonList, parameters):
13 | from pathlib import Path
14 | from exif import Image
15 |
16 | return_result = []
17 | for entity in entityJsonList:
18 | uid = entity['uid']
19 | image_path = Path(parameters['Project Files Directory']) / entity['File Path']
20 | if not image_path.exists() or not image_path.is_file():
21 | continue
22 | with open(image_path, 'rb') as image_file:
23 | my_image = Image(image_file)
24 | if my_image.has_exif is False:
25 | continue
26 | return_result.extend([{'Label': f"Location of {str(entity[list(entity)[1]].strip())}",
27 | 'Latitude': my_image.gps_latitude,
28 | 'Longitude': my_image.gps_longitude,
29 | 'Entity Type': 'GeoCoordinates'},
30 | {uid: {'Resolution': 'GeoCoordinates', 'Notes': ''}}]
31 | for tag in my_image.list_all() if tag == "gps_latitude")
32 |
33 | return return_result
34 |
--------------------------------------------------------------------------------
/Core/Resolutions/Core/WebsiteFromPhrase.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 |
4 | class WebsiteFromPhrase:
5 | name = "Website From Phrase"
6 | category = "String Operations"
7 | description = "Extract website-like objects from a phrase."
8 | originTypes = {'Phrase'}
9 | resultTypes = {'Domain'}
10 |
11 | parameters = {}
12 |
13 | def resolution(self, entityJsonList, parameters):
14 | import contextlib
15 | import re
16 | import tldextract
17 |
18 | websiteRegex = re.compile(r"""^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$""")
19 | wordChar = re.compile(r'\w')
20 |
21 | returnResults = []
22 |
23 | for entity in entityJsonList:
24 | primaryField = entity['Phrase']
25 | for entityChunk in primaryField.split():
26 | while wordChar.match(entityChunk[-1]) is None:
27 | entityChunk = entityChunk[:-1]
28 | if websiteRegex.match(entityChunk):
29 | with contextlib.suppress(Exception):
30 | tldObject = tldextract.extract(entityChunk)
31 | if tldObject.suffix != '':
32 | returnResults.append([{'URL': entityChunk,
33 | 'Entity Type': 'Website'},
34 | {entity['uid']: {'Resolution': 'Phrase To Website',
35 | 'Notes': ''}}])
36 | return returnResults
37 |
--------------------------------------------------------------------------------
/Resources/Icons/Organization.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Resources/Icons/Spreadsheet.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/Resources/Icons/Software.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Resources/Icons/Domain.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Core/Resolutions/Core/TikTokVideoPublishDetails.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 |
4 | class TikTokVideoPublishDetails:
5 | # A string that is treated as the name of this resolution.
6 | name = "TikTok Video Publishing Details"
7 |
8 | category = "Website Information"
9 |
10 | # A string that describes this resolution.
11 | description = "Extracts information from public TikTok Videos."
12 |
13 | originTypes = {'Website'}
14 |
15 | resultTypes = {'Social Media Handle', 'Date'}
16 |
17 | parameters = {}
18 |
19 | def resolution(self, entityJsonList, parameters):
20 | from datetime import datetime
21 |
22 | returnResults = []
23 |
24 | for entity in entityJsonList:
25 | uid = entity['uid']
26 | url = entity['URL'].lower()
27 |
28 | try:
29 | splitURL = url.split('/')
30 | username = splitURL[3]
31 | videoID = int(splitURL[5].split('?')[0])
32 | except (IndexError, ValueError):
33 | continue
34 |
35 | binString = "{0:b}".format(videoID)
36 | if len(binString) == 63:
37 | binString = f'0{binString}'
38 | binString = int(binString[:32], 2)
39 |
40 | UTCTimestamp = f'{datetime.utcfromtimestamp(binString).isoformat()}+00:00'
41 |
42 | returnResults.extend(([{'Date': UTCTimestamp, 'Entity Type': 'Date'},
43 | {uid: {'Resolution': 'Video Publish Date', 'Notes': ''}}],
44 | [{'User Name': username, 'Entity Type': 'Social Media Handle'},
45 | {uid: {'Resolution': 'Published By', 'Notes': ''}}]))
46 |
47 | return returnResults
48 |
--------------------------------------------------------------------------------
/Core/Entities/Financials.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Account Number
5 | Bank Name
6 | Branch Number
7 |
8 |
9 | Bank_Account.svg
10 |
11 |
12 |
13 |
14 | Amount
15 | Currency Type
16 |
17 |
18 | Currency.svg
19 |
20 |
21 |
22 |
23 | Ticker ID
24 |
25 |
26 | Share.svg
27 |
28 |
29 |
30 |
31 | Label
32 |
33 |
34 | Portfolio.svg
35 |
36 |
37 |
38 |
39 | Wallet Address
40 | Currency Name
41 |
42 |
43 | CryptoWallet.svg
44 |
45 |
46 |
--------------------------------------------------------------------------------
/Core/Resolutions/Core/DomainFromPhrase.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 |
4 | class DomainFromPhrase:
5 | name = "Domain From Phrase"
6 | category = "String Operations"
7 | description = "Extract domain-like objects from a phrase."
8 | originTypes = {'Phrase'}
9 | resultTypes = {'Domain'}
10 |
11 | parameters = {}
12 |
13 | def resolution(self, entityJsonList, parameters):
14 | import re
15 | import contextlib
16 | import tldextract
17 |
18 | domainRegex = re.compile(
19 | r'^(([a-zA-Z]{1})|([a-zA-Z]{1}[a-zA-Z]{1})|'
20 | r'([a-zA-Z]{1}[0-9]{1})|([0-9]{1}[a-zA-Z]{1})|'
21 | r'([a-zA-Z0-9][-_.a-zA-Z0-9]{0,61}[a-zA-Z0-9]))\.'
22 | r'([a-zA-Z]{2,13}|[a-zA-Z0-9-]{2,30}.[a-zA-Z]{2,3})$'
23 | )
24 | wordChar = re.compile(r'\w')
25 |
26 | returnResults = []
27 |
28 | for entity in entityJsonList:
29 | primaryField = entity['Phrase'].lower()
30 | for entityChunk in primaryField.split():
31 | while wordChar.match(entityChunk[-1]) is None:
32 | entityChunk = entityChunk[:-1]
33 | if domainRegex.match(entityChunk):
34 | with contextlib.suppress(Exception):
35 | tldObject = tldextract.extract(entityChunk)
36 | if tldObject.suffix != '':
37 | returnResults.append([{'Domain Name': entityChunk,
38 | 'Entity Type': 'Domain'},
39 | {entity['uid']: {'Resolution': 'Phrase To Domain',
40 | 'Notes': ''}}])
41 | return returnResults
42 |
--------------------------------------------------------------------------------
/Core/Entities/Groups.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Company Name
5 | Registration Number
6 |
7 |
8 | Company.svg
9 |
10 |
11 |
12 |
13 | Organization Name
14 | Registration Number
15 |
16 |
17 | Organization.svg
18 |
19 |
20 |
21 |
22 | Cult Name
23 | Ideology
24 |
25 |
26 | Cult.svg
27 |
28 |
29 |
30 |
31 | Group Name
32 | Ideology
33 |
34 |
35 | Terrorists.svg
36 |
37 |
38 |
39 |
40 | Family Surname
41 |
42 |
43 | Family.svg
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/Resources/Icons/Website.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Resources/Icons/OnionWebsite.svg:
--------------------------------------------------------------------------------
1 |
33 |
--------------------------------------------------------------------------------
/Resources/Icons/City.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Core/Resolutions/Core/ImageToDevice.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 |
4 | class ImageToDevice:
5 | name = "Device From Image"
6 | category = "File Operations"
7 | description = "Find information about on what device an image was taken with."
8 | originTypes = {"Image"}
9 | resultTypes = {'Phrase'}
10 | parameters = {}
11 |
12 | def resolution(self, entityJsonList, parameters):
13 | from pathlib import Path
14 | from exif import Image
15 |
16 | return_result = []
17 | for entity in entityJsonList:
18 | uid = entity['uid']
19 | index_of_child = len(return_result)
20 | image_path = Path(parameters['Project Files Directory']) / entity['File Path']
21 | if not image_path.exists() or not image_path.is_file():
22 | continue
23 | with open(image_path, 'rb') as image_file:
24 | my_image = Image(image_file)
25 | if my_image.has_exif is False:
26 | continue
27 | for tag in my_image.list_all():
28 | if tag == "make":
29 | return_result.append([{'Phrase': my_image.make,
30 | 'Entity Type': 'Phrase'},
31 | {uid: {'Resolution': 'ExifMetadata Device Manufacturer',
32 | 'Notes': ''}}])
33 | elif tag == "model":
34 | return_result.append([{'Phrase': my_image.model,
35 | 'Entity Type': 'Phrase'},
36 | {index_of_child: {'Resolution': 'ExifMetadata Device Model',
37 | 'Notes': ''}}])
38 | return return_result
39 |
--------------------------------------------------------------------------------
/Core/Entities/Places.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Label
5 | Latitude
6 | Longitude
7 |
8 |
9 | Geolocation.svg
10 |
11 |
12 |
13 |
14 | Street Address
15 | Locality
16 | Postal Code
17 | Country
18 |
19 |
20 | Address.svg
21 |
22 |
23 |
24 |
25 | Country Name
26 |
27 |
28 | Country.svg
29 |
30 |
31 |
32 |
33 | City Name
34 |
35 |
36 | City.svg
37 |
38 |
39 |
40 |
41 | Label
42 | Latitude
43 | Longitude
44 | Radius
45 |
46 |
47 | GeoArea.svg
48 |
49 |
50 |
--------------------------------------------------------------------------------
/Resources/Icons/Video.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Core/Resolutions/Core/HostnameToIP.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 |
4 | class HostnameToIP:
5 | name = "Hostname To IP"
6 | category = "Network Infrastructure"
7 | description = "Gets the IP associated with the given hostname."
8 | originTypes = {'Domain', 'Website'}
9 | resultTypes = {'IP Address', 'IPv6 Address'}
10 |
11 | parameters = {}
12 |
13 | def resolution(self, entityJsonList, parameters):
14 | import socket
15 |
16 | returnResults = []
17 |
18 | for entity in entityJsonList:
19 | primaryField = entity[list(entity)[1]]
20 | urlFeatures = primaryField.find('://')
21 | if urlFeatures != -1:
22 | primaryField = primaryField[urlFeatures + 3:]
23 | primaryField = primaryField.split('/')[0] # Remove trailing slashes
24 | try:
25 | entityResults = socket.getaddrinfo(primaryField, 443, proto=socket.IPPROTO_TCP) + \
26 | socket.getaddrinfo(primaryField, 80, proto=socket.IPPROTO_TCP)
27 | except Exception:
28 | continue
29 |
30 | for result in entityResults:
31 | if result[0].value == 2:
32 | returnResults.append([{'IP Address': result[4][0],
33 | 'Entity Type': 'IP Address'},
34 | {entity['uid']: {'Resolution': 'Hostname To IP',
35 | 'Notes': ''}}])
36 | elif result[0].value == 10:
37 | returnResults.append([{'IPv6 Address': result[4][0],
38 | 'Entity Type': 'IPv6 Address'},
39 | {entity['uid']: {'Resolution': 'Hostname To IP',
40 | 'Notes': ''}}])
41 |
42 | return returnResults
43 |
--------------------------------------------------------------------------------
/Core/Entities/Materials.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Document Name
5 | File Path
6 |
7 |
8 | Document.svg
9 |
10 |
11 |
12 |
13 | Spreadsheet Name
14 | File Path
15 |
16 |
17 | Spreadsheet.svg
18 |
19 |
20 |
21 |
22 | Image Name
23 | File Path
24 |
25 |
26 | Image.svg
27 |
28 |
29 |
38 |
39 |
40 | Archive Name
41 | File Path
42 |
43 |
44 | Archive.svg
45 |
46 |
47 |
48 |
49 | Disk Name
50 | File Path
51 |
52 |
53 | HardDisk.svg
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/Resources/Icons/IPv6_Address.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Resources/Icons/Date.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Resources/Icons/Email.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Core/Resolutions/Core/FileHasher.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 |
4 | class FileHasher:
5 | name = "Get File Hash"
6 | category = "File Operations"
7 | description = "Get the Hash of a file."
8 | originTypes = {"Image", "Document", "Spreadsheet", "Video", "Archive", "Disk"}
9 | resultTypes = {'Hash'}
10 | parameters = {'Hashing Algorithm': {'description': 'Choose the type of hash(es) that you want to be returned:',
11 | 'type': 'MultiChoice',
12 | 'value': {'SHA1', 'SHA256', 'MD5'}
13 | }}
14 |
15 | def resolution(self, entityJsonList, parameters):
16 | import hashlib
17 | from pathlib import Path
18 |
19 | return_result = []
20 | hashing_algorithms = parameters['Hashing Algorithm']
21 | for entity in entityJsonList:
22 | uid = entity['uid']
23 | file_path = Path(parameters['Project Files Directory']) / entity['File Path']
24 | if not file_path.is_file():
25 | continue
26 | block_size = 65536 # The size of each read from the file
27 | for hashing_algorithm in hashing_algorithms:
28 | if hashing_algorithm == "SHA1":
29 | file_hash = hashlib.sha1() # nosec
30 | elif hashing_algorithm == "SHA256":
31 | file_hash = hashlib.sha256() # nosec
32 | else:
33 | file_hash = hashlib.md5() # nosec
34 | with open(file_path, 'rb') as f:
35 | fb = f.read(block_size)
36 | while len(fb) > 0:
37 | file_hash.update(fb)
38 | fb = f.read(block_size)
39 | resulting_hash = file_hash.hexdigest()
40 | return_result.append([{'Hash Value': resulting_hash,
41 | 'Hash Algorithm': hashing_algorithm,
42 | 'Entity Type': 'Hash'},
43 | {uid: {'Resolution': f'{hashing_algorithm} Hash', 'Notes': ''}}])
44 | return return_result
45 |
--------------------------------------------------------------------------------
/Resources/Icons/Network.svg:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/Resources/Icons/Document.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Core/Resolutions/Core/DecodeRedirectUrlParameter.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 |
4 | class DecodeRedirectUrlParameter:
5 | name = "Decode Redirect URL"
6 | category = "Website Information"
7 | description = "Search the URL's parameters to see where you'll be redirected."
8 | originTypes = {'Website'}
9 | resultTypes = {'Website'}
10 |
11 | parameters = {}
12 |
13 | def resolution(self, entityJsonList, parameters):
14 | import contextlib
15 | from urllib.parse import urlparse
16 | from urllib.parse import parse_qs
17 | from urllib.parse import unquote
18 | from base64 import b64decode
19 |
20 | def get_redirect_value(parameter_arg: str) -> str:
21 | with contextlib.suppress(Exception):
22 | clean_val = unquote(parameter_arg)
23 | if urlparse(clean_val).scheme:
24 | return clean_val
25 | clean_val = unquote(b64decode(parameter_arg).decode('UTF-8'))
26 | if urlparse(clean_val).scheme:
27 | return clean_val
28 | return ''
29 |
30 | returnResults = []
31 |
32 | for entity in entityJsonList:
33 | primaryField = entity['URL'].strip()
34 | parsed_url = urlparse(primaryField, allow_fragments=False)
35 |
36 | parsed_url_params = parse_qs(parsed_url.query)
37 | for param, param_value in parsed_url_params.items():
38 | param_potential_url_value = get_redirect_value(', '.join(param_value))
39 | if param_potential_url_value:
40 | parsed_url_params_copy = dict(parsed_url_params)
41 | parsed_url_params_copy.pop(param)
42 | new_entity = {'URL': param_potential_url_value,
43 | 'Entity Type': 'Website'}
44 | for param_copy, param_value_copy in parsed_url_params_copy.items():
45 | new_entity[param_copy] = ', '.join(param_value_copy)
46 | returnResults.append([new_entity,
47 | {entity['uid']: {'Resolution': 'Redirect To',
48 | 'Notes': ''}}])
49 | break
50 |
51 | return returnResults
52 |
--------------------------------------------------------------------------------
/Resources/Icons/WebInfrastructure.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Resources/Icons/Address.svg:
--------------------------------------------------------------------------------
1 |
2 |
67 |
--------------------------------------------------------------------------------
/Core/Entities/Individuals.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Full Name
5 | Gender
6 | Occupation
7 | Date of Birth
8 | Nationality
9 |
10 |
11 | Person.svg
12 |
13 |
14 |
15 |
16 | Full Name
17 | Gender
18 | Occupation
19 | Date of Birth
20 | Nationality
21 |
22 |
23 | PEP.svg
24 |
25 |
26 |
27 |
28 | Phrase
29 |
30 |
31 | Phrase.svg
32 |
33 |
34 |
35 |
36 | Sentiment
37 |
38 |
39 | Sentiment.svg
40 |
41 |
42 |
43 |
44 | Email Address
45 |
46 |
47 | Email.svg
48 |
49 |
50 |
51 |
52 | Phone Number
53 |
54 |
55 | Phone_Number.svg
56 |
57 |
58 |
59 |
60 | Date
61 |
62 |
63 | Date.svg
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/Core/Resolutions/Core/ExtractDOCXMeta.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 |
4 | class ExtractDOCXMeta:
5 | # A string that is treated as the name of this resolution.
6 | name = "Get DOCX Metadata"
7 | category = "File Operations"
8 |
9 | # A string that describes this resolution.
10 | description = "Returns a set of nodes that contain all the metadata info of a docx file."
11 |
12 | originTypes = {'Document', 'Archive'}
13 |
14 | resultTypes = {'Phrase', 'Date'}
15 |
16 | parameters = {}
17 |
18 | def resolution(self, entityJsonList, parameters):
19 | from docx2python import docx2python
20 | import magic
21 | from pathlib import Path
22 |
23 | returnResults = []
24 |
25 | for entity in entityJsonList:
26 | uid = entity['uid']
27 | filePath = Path(parameters['Project Files Directory']) / entity['File Path']
28 |
29 | if not filePath.exists() or not filePath.is_file():
30 | continue
31 |
32 | if magic.from_file(str(filePath), mime=True) != \
33 | 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
34 | continue # We only care about docx files
35 |
36 | metadata = docx2python(filePath)
37 |
38 | data = metadata.properties
39 | defaultDateProperties = ['created', 'modified']
40 |
41 | modifiedDate = str(data.get('modified'))
42 | modifiedDate = modifiedDate.replace('Z', '')
43 | returnResults.append([{'Date': modifiedDate,
44 | 'Entity Type': 'Date'},
45 | {uid: {'Resolution': 'modified', 'Notes': ''}}])
46 | createdDate = str(data.get('created'))
47 | createdDate = createdDate.replace('Z', '')
48 | returnResults.append([{'Date': createdDate,
49 | 'Entity Type': 'Date'},
50 | {uid: {'Resolution': 'created', 'Notes': ''}}])
51 |
52 | returnResults.extend([{'Phrase': f'{metadataKey}: {str(data.get(metadataKey))}', 'Entity Type': 'Phrase'},
53 | {uid: {'Resolution': metadataKey, 'Notes': ''}}] for metadataKey in
54 | [dataKey for dataKey in data if dataKey not in defaultDateProperties])
55 |
56 | return returnResults
57 |
--------------------------------------------------------------------------------
/Resources/Icons/Transaction.svg:
--------------------------------------------------------------------------------
1 |
2 |
9 |
--------------------------------------------------------------------------------
/Core/GlobalVariables.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import random
4 |
5 | non_string_fields = ('Icon', 'Child UIDs')
6 | hidden_fields = ('uid', 'Date Last Edited', 'Child UIDs', 'Canvas Banner', 'Entity Type')
7 | hidden_fields_dockbars = ('uid', 'Child UIDs', 'Canvas Banner', 'Icon')
8 | meta_fields = ('Child UIDs',)
9 | avoid_parsing_fields = ('uid', 'Date Last Edited', 'Child UIDs', 'Icon', 'Canvas Banner')
10 |
11 | # Closer to the top means more recent.
12 | user_agents = {'Chrome': {'Windows': ('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
13 | '(KHTML, like Gecko) Chrome/101.0.4951.15 Safari/537.36',),
14 | 'Linux': ('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) '
15 | 'Chrome/101.0.4951.15 Safari/537.36',)
16 | },
17 | 'Firefox': {'Windows': ('Mozilla/5.0 (Windows NT 10.0; rv:100.0) Gecko/20100101 Firefox/98.0',),
18 | 'Linux': ('Mozilla/5.0 (X11; Linux x86_64; rv:98.0) Gecko/20100101 Firefox/98.0',)
19 | },
20 | 'Webkit': {'Windows': ('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/605.1.15 '
21 | '(KHTML, like Gecko) Version/15.4 Safari/605.1.15',),
22 | 'Linux': ('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 '
23 | '(KHTML, like Gecko) Version/15.4 Safari/605.1.15',)
24 | }
25 | }
26 |
27 |
28 | def getLatestUserAgent(browser: str = None, platform: str = None) -> str:
29 | if browser is None or browser not in user_agents:
30 | browser = random.choice(list(user_agents.keys()))
31 | browserPlatforms = user_agents[browser]
32 | if platform is None or platform not in browserPlatforms:
33 | platform = random.choice(list(browserPlatforms.keys()))
34 | return user_agents[browser][platform][0]
35 |
36 |
37 | def getUserAgents(browser: str = None, platform: str = None, amount: int = 1) -> tuple:
38 | if browser is None or browser not in user_agents:
39 | browser = random.choice(list(user_agents.keys()))
40 | browserPlatforms = user_agents[browser]
41 | if platform is None or platform not in browserPlatforms:
42 | platform = random.choice(list(browserPlatforms.keys()))
43 | return user_agents[browser][platform][:amount]
44 |
--------------------------------------------------------------------------------
/Core/Resolutions/Core/PhraseSimilarity.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | class PhraseSimilarity:
4 | name = "String Similarity Check"
5 | category = "String Operations"
6 | description = "Find the ratio of similarity between two strings."
7 | originTypes = {'*'}
8 | resultTypes = {'Phrase'}
9 | parameters = {'Primary field or Notes': {'description': 'Choose Either Primary field or Notes',
10 | 'type': 'SingleChoice',
11 | 'value': {'Notes', 'Primary Field'}},
12 | 'Algorithm': {'description': 'Select the Algorithm to use',
13 | 'type': 'SingleChoice',
14 | 'value': {'levenshtein distance', 'damerau levenshtein distance',
15 | 'jaro distance', 'jaro winkler similarity', 'hamming distance'}}
16 | }
17 |
18 | def resolution(self, entityJsonList, parameters):
19 | import jellyfish
20 | from itertools import combinations
21 |
22 | return_result = []
23 | entity_fields = []
24 | selection = parameters['Primary field or Notes']
25 | algorithm = parameters['Algorithm'].replace(" ", "_")
26 | for entity in entityJsonList:
27 | if selection == 'Primary Field':
28 | entity_fields.append((entity['uid'], entity[list(entity)[1]].strip()))
29 | elif selection == 'Notes':
30 | entity_fields.append((entity['uid'], entity.get('Notes')))
31 |
32 | if len(entity_fields) < 2:
33 | return "Please Select 2 or more entities for comparison"
34 | for combination in combinations(entity_fields, 2):
35 | stringA = combination[0][1]
36 | uidA = combination[0][0]
37 | stringB = combination[1][1]
38 | uidB = combination[1][0]
39 | similarity = getattr(jellyfish, algorithm)(stringA, stringB)
40 | return_result.append([{'Phrase': f'{algorithm} between "{stringA}" and "{stringB}": {similarity}',
41 | 'Entity Type': 'Phrase'},
42 | {uidA: {'Resolution': f'String Similarity for "{stringA}" and "{stringB}"',
43 | 'Notes': ''},
44 | uidB: {'Resolution': f'String Similarity for "{stringA}" and "{stringB}"',
45 | 'Notes': ''}}])
46 |
47 | return return_result
48 |
--------------------------------------------------------------------------------
/Core/Resolutions/Core/IPWhois.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 |
4 | class IPWhois:
5 | name = "IPv4 WhoIs Information"
6 | category = "Network Infrastructure"
7 | description = "Extract information from the Whois record that corresponds to a given IP Address."
8 | originTypes = {'IP Address'}
9 | resultTypes = {'Country', 'Autonomous System', 'Email Address'}
10 | parameters = {}
11 |
12 | def resolution(self, entityJsonList, parameters):
13 | from ipwhois import IPWhois
14 | from ipaddress import ip_address
15 | import pycountry
16 |
17 | return_result = []
18 |
19 | for entity in entityJsonList:
20 | uid = entity['uid']
21 | primary_field = entity[list(entity)[1]].strip()
22 | try:
23 | ip_address(primary_field)
24 | except ValueError:
25 | return "The Entity Provided isn't a valid IP Address"
26 | IPobject = IPWhois(primary_field)
27 | response = IPobject.lookup_whois()
28 | return_result.append([{'AS Number': 'AS' + str(response['asn']),
29 | 'ASN Cidr': str(response['asn_cidr']),
30 | 'Entity Type': 'Autonomous System'},
31 | {uid: {'Resolution': 'IPWhois', 'Notes': ''}}])
32 | for net in response['nets']:
33 | if net['country'] is not None:
34 | if country := pycountry.countries.get(alpha_2=net['country']):
35 | country_name = country.name
36 | else:
37 | # May not always be an actual Country.
38 | country_name = net['country']
39 | return_result.append([{'Country Name': country_name,
40 | 'Entity Type': 'Country'},
41 | {uid: {'Resolution': 'IPWhois', 'Notes': ''}}])
42 | if net['name'] is not None:
43 | return_result.append([{'Company Name': net['name'],
44 | 'Entity Type': 'Company'},
45 | {uid: {'Resolution': 'IPWhois', 'Notes': ''}}])
46 | if net['emails'] is not None:
47 | return_result.extend([{'Email Address': email, 'Entity Type': 'Email Address'},
48 | {uid: {'Resolution': 'IPWhois', 'Notes': ''}}] for email in net['emails'])
49 | return return_result
50 |
--------------------------------------------------------------------------------
/Resources/Icons/Hash.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Resources/Icons/MAC_Address.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Resources/Icons/VehicleRegistration.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Core/Resolutions/Core/DecodePhrase.py:
--------------------------------------------------------------------------------
1 | class DecodePhrase:
2 | # A string that is treated as the name of this resolution.
3 | name = "Decode Phrase"
4 |
5 | category = "String Operations"
6 |
7 | # A string that describes this resolution.
8 | description = "Decode encoded phrases."
9 |
10 | # A set of entities that this resolution can be ran on.
11 | originTypes = {'Phrase'}
12 |
13 | # A set of entities that could be the result of this resolution.
14 | resultTypes = {'Phrase'}
15 |
16 | # A dictionary of properties for this resolution. The key is the property name,
17 | # the value is the property attributes. The type of input expected from the user is determined by the
18 | # variable type of the 'value' parameter.
19 | parameters = {'Encoding Type': {'description': 'Select the type of encoding that was applied to the data.',
20 | 'type': 'SingleChoice',
21 | 'value': {'Binary', 'Hexadecimal', 'Base64'}
22 | }}
23 |
24 | def resolution(self, entityJsonList, parameters):
25 | import base64
26 | returnResult = []
27 |
28 | for entity in entityJsonList:
29 | uid = entity['uid']
30 |
31 | if parameters['Encoded Type'] == 'Hexadecimal':
32 | text = entity[list(entity)[1]].strip()
33 | data = str(bytearray.fromhex(text).decode())
34 | returnResult.append([{'Phrase': data,
35 | 'Entity Type': 'Phrase'},
36 | {uid: {'Resolution': 'Hexadecimal Phrase', 'Notes': ''}}])
37 |
38 | elif parameters['Encoded Type'] == 'Base64':
39 | text = entity[list(entity)[1]].strip()
40 | data = base64.b64decode(text).decode()
41 | returnResult.append([{'Phrase': str(data),
42 | 'Entity Type': 'Phrase'},
43 | {uid: {'Resolution': 'Base 64 Decoded Phrase', 'Notes': ''}}])
44 |
45 | elif parameters['Encoded Type'] == 'Binary':
46 | text = entity[list(entity)[1]].replace(' ', '')
47 | if len(text) % 8 != 0:
48 | return "Malformed format not in Octaves"
49 |
50 | ascii_string = ''.join(chr(int(text[binaryIndex: binaryIndex + 8], 2))
51 | for binaryIndex in range(0, len(text), 8))
52 |
53 | returnResult.append([{'Phrase': ascii_string, 'Entity Type': 'Phrase'},
54 | {uid: {'Resolution': 'Binary Decoded Phrase', 'Notes': ''}}])
55 |
56 | return returnResult
57 |
--------------------------------------------------------------------------------
/Core/Resolutions/Core/IPToASN.py:
--------------------------------------------------------------------------------
1 | class IPToASN:
2 | # A string that is treated as the name of this resolution.
3 | name = "Get ASN From IP"
4 |
5 | category = "Network Infrastructure"
6 |
7 | # A string that describes this resolution.
8 | description = "Get the Autonomous System Number that the selected IP Addresses belong to."
9 |
10 | # A set of entities that this resolution can be ran on.
11 | originTypes = {'IP Address'}
12 |
13 | # A set of entities that could be the result of this resolution.
14 | resultTypes = {'Autonomous System'}
15 |
16 | # A dictionary of properties for this resolution. The key is the property name,
17 | # the value is the property attributes. The type of input expected from the user is determined by the
18 | # variable type of the 'value' parameter.
19 | parameters = {}
20 |
21 | def resolution(self, entityJsonList, parameters):
22 | from ipwhois.net import Net
23 | from ipwhois.asn import IPASN
24 | import pycountry
25 |
26 | returnResult = []
27 |
28 | for entity in entityJsonList:
29 | if entity['Entity Type'] == 'IP Address':
30 | uid = entity['uid']
31 | IP_add = entity[list(entity)[1]]
32 | net = Net(IP_add)
33 | obj = IPASN(net)
34 | results = obj.lookup()
35 | index_of_child = len(returnResult)
36 | countryCode = results['asn_country_code']
37 | country = pycountry.countries.get(alpha_2=countryCode).name
38 | returnResult.extend(([{'AS Number': "AS" + results['asn'],
39 | 'ASN Cidr': results['asn_cidr'],
40 | 'Date Created': results['asn_date'],
41 | 'Entity Type': 'Autonomous System'},
42 | {uid: {'Resolution': 'Autonomous System of IP', 'Notes': ''}}],
43 | [{'Organization Name': results['asn_registry'],
44 | 'Entity Type': 'Organization'},
45 | {index_of_child: {'Resolution': 'ASN Registry', 'Notes': ''}}],
46 | [{'Country Name': country,
47 | 'Entity Type': 'Country'},
48 | {index_of_child: {'Resolution': 'Country of Registry for ASN', 'Notes': ''}}],
49 | [{'Phrase': results['asn_description'],
50 | 'Entity Type': 'Phrase'},
51 | {index_of_child: {'Resolution': 'ASN Description', 'Notes': ''}}]))
52 |
53 | return returnResult
54 |
--------------------------------------------------------------------------------
/Resources/Icons/GeoArea.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Core/Resolutions/Core/RegexMatch.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 |
4 | class RegexMatch:
5 | # A string that is treated as the name of this resolution.
6 | name = "Get Regex Match"
7 |
8 | category = "String Operations"
9 |
10 | # A string that describes this resolution.
11 | description = "Extract text from Phrases and Websites with Regular Expressions."
12 |
13 | originTypes = {'Phrase', 'Website'}
14 |
15 | resultTypes = {'Phrase'}
16 |
17 | parameters = {'Regex Match': {'description': "Please enter the Regex expression to extract strings with.",
18 | 'type': 'String',
19 | 'value': ''},
20 | 'Max Results': {'description': 'Please enter the Maximum number of Results to return.',
21 | 'type': 'String',
22 | 'value': '',
23 | 'default': '5'},
24 | 'Re Flags': {'description': 'Select the Regex Flags to be used.',
25 | 'type': 'MultiChoice',
26 | 'value': {'re.I', 're.M', 're.S'}
27 | }
28 | }
29 |
30 | def resolution(self, entityJsonList, parameters):
31 | import re
32 | import requests
33 |
34 | headers = {
35 | 'User-Agent': 'user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:94.0) Gecko/20100101 Firefox/94.0',
36 | }
37 |
38 | returnResults = []
39 | try:
40 | maxResults = int(parameters['Max Results'])
41 | except ValueError:
42 | return "Invalid integer provided in 'Max Results' parameter"
43 | if maxResults <= 0:
44 | return []
45 | search_param = parameters['Regex Match']
46 | flags = parameters['Re Flags']
47 |
48 | flagsToUse = 0
49 | for flag in flags:
50 | flagsToUse |= flag
51 |
52 | for entity in entityJsonList:
53 | uid = entity['uid']
54 | if entity['Entity Type'] == 'Phrase':
55 | text = str(entity['Notes']) + "\n" + str(entity['Phrase'])
56 | else: # Website entity
57 | r = requests.get(entity["URL"], headers=headers)
58 | text = r.text
59 |
60 | search_re = re.findall(search_param, text, flags=flagsToUse)
61 |
62 | returnResults.extend([{'Phrase': f'Regex Match: {regexMatch}',
63 | 'Entity Type': 'Phrase',
64 | 'Notes': ''},
65 | {uid: {'Resolution': 'Regex String Match', 'Notes': ''}}]
66 | for regexMatch in search_re[:maxResults])
67 | return returnResults
68 |
--------------------------------------------------------------------------------
/Resources/Icons/Currency.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Core/Resolutions/Core/ASNToCIDR.py:
--------------------------------------------------------------------------------
1 | class ASNToCIDR:
2 | # A string that is treated as the name of this resolution.
3 | name = "Get CIDR from ASN"
4 | category = "Network Infrastructure"
5 |
6 | # A string that describes this resolution.
7 | description = "Get network information for an Autonomous System Number."
8 |
9 | # A set of entities that this resolution can be ran on.
10 | originTypes = {'Autonomous System'}
11 |
12 | # A set of entities that could be the result of this resolution.
13 | resultTypes = {'Network', 'Company', 'Organization', 'Phrase'}
14 |
15 | # A dictionary of properties for this resolution. The key is the property name,
16 | # the value is the property attributes. The type of input expected from the user is determined by the
17 | # variable type of the 'value' parameter.
18 | parameters = {}
19 |
20 | def resolution(self, entityJsonList, parameters):
21 | from ipwhois.net import Net
22 | from ipwhois.asn import ASNOrigin
23 |
24 | returnResult = []
25 |
26 | for entity in entityJsonList:
27 | if entity['Entity Type'] == 'Autonomous System':
28 | try:
29 | ipWithPrefix = entity['ASN Cidr']
30 | uid = entity['uid']
31 | split_string = ipWithPrefix.split("/", 1)
32 | ipWithOutPrefix = split_string[0]
33 | ASN = entity["AS Number"]
34 | except Exception:
35 | continue
36 | try:
37 | net = Net(ipWithOutPrefix)
38 | except Exception:
39 | net = Net('1.1.1.1')
40 | obj = ASNOrigin(net)
41 | results = obj.lookup(asn=ASN)
42 |
43 | for network in results['nets']:
44 | cidrWithPrefix = network['cidr']
45 | split_string = cidrWithPrefix.split("/", 1)
46 | cidrWithOutPrefix = split_string[0]
47 | prefix = split_string[1]
48 | index_of_child = len(returnResult)
49 | returnResult.extend(([{'IP Address': cidrWithOutPrefix, 'Range': prefix, 'Entity Type': 'Network'},
50 | {uid: {'Resolution': 'ASN to CIDR', 'Notes': ''}}],
51 | [{'Phrase': network['description'], 'Entity Type': 'Phrase'},
52 | {index_of_child: {'Resolution': 'CIDR Description', 'Notes': ''}}],
53 | [{'Organization Name': network['source'], 'Entity Type': 'Organization'},
54 | {index_of_child: {'Resolution': 'ASN Registry', 'Notes': ''}}],
55 | [{'Company Name': network['maintainer'], 'Entity Type': 'Company'},
56 | {index_of_child: {'Resolution': 'Company Name', 'Notes': ''}}]))
57 |
58 | return returnResult
59 |
--------------------------------------------------------------------------------
/Core/Resolutions/Core/GetWebsiteText.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 |
4 | class GetWebsiteText:
5 | # A string that is treated as the name of this resolution.
6 | name = "Get Website Text"
7 |
8 | category = "Website Information"
9 |
10 | # A string that describes this resolution.
11 | description = "Returns the text content of the selected websites."
12 |
13 | originTypes = {'Website'}
14 |
15 | resultTypes = {'Phrase'}
16 |
17 | parameters = {}
18 |
19 | def resolution(self, entityJsonList, parameters):
20 | from bs4 import BeautifulSoup
21 | from bs4.element import Comment
22 | from playwright.sync_api import sync_playwright, TimeoutError, Error
23 | from urllib.parse import urlparse
24 | from pathlib import Path
25 |
26 | returnResults = []
27 | playwrightPath = Path(parameters['Playwright Chromium'])
28 |
29 | def tag_visible(element):
30 | if element.parent.name in ['style', 'script', 'head', 'title', 'meta', '[document]']:
31 | return False
32 | return not isinstance(element, Comment)
33 |
34 | def text_from_html(body):
35 | soup = BeautifulSoup(body, 'lxml')
36 | texts = soup.findAll(text=True)
37 | visible_texts = filter(tag_visible, texts)
38 | return u" ".join(t.strip() for t in visible_texts if t.strip() != '')
39 |
40 | with sync_playwright() as p:
41 | browser = p.chromium.launch(executable_path=playwrightPath)
42 | context = browser.new_context(
43 | viewport={'width': 1920, 'height': 1080},
44 | user_agent='Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) '
45 | 'Chrome/101.0.4951.64 Safari/537.36'
46 | )
47 | page = context.new_page()
48 |
49 | for site in entityJsonList:
50 | uid = site['uid']
51 | url = site['URL']
52 | parsedURL = urlparse(url)
53 | if not all([parsedURL.scheme, parsedURL.netloc]):
54 | continue
55 |
56 | # Try to load the page a few times, in case of timeouts.
57 | # I don't think making parts of this async actually helps in this case.
58 | for _ in range(3):
59 | try:
60 | page.goto(url, wait_until="networkidle", timeout=10000)
61 | textContent = text_from_html(page.content())
62 | returnResults.append([{'Phrase': f'Website Body of: {url}',
63 | 'Notes': textContent,
64 | 'Entity Type': 'Phrase'},
65 | {uid: {'Resolution': 'Website Body', 'Notes': ''}}])
66 | break
67 | except TimeoutError:
68 | pass
69 | except Error:
70 | break
71 |
72 | return returnResults
73 |
--------------------------------------------------------------------------------
/Resources/Icons/Company.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Resources/Icons/AutonomousSystem.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Core/Entities/Infrastructure.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | OS Name
5 |
6 |
7 | OperatingSystem.svg
8 |
9 |
10 |
11 |
12 | IP Address
13 | Range
14 |
15 |
16 | Network.svg
17 |
18 |
19 |
20 |
21 | URL
22 |
23 |
24 | Website.svg
25 |
26 |
27 |
28 |
29 | Onion URL
30 |
31 |
32 | OnionWebsite.svg
33 |
34 |
35 |
36 |
37 | Domain Name
38 |
39 |
40 | Domain.svg
41 |
42 |
43 |
44 |
45 | IP Address
46 |
47 |
48 | IP_Address.svg
49 |
50 |
51 |
52 |
53 | IPv6 Address
54 |
55 |
56 | IPv6_Address.svg
57 |
58 |
59 |
60 |
61 | AS Number
62 | ASN Cidr
63 |
64 |
65 | AutonomousSystem.svg
66 |
67 |
68 |
69 |
70 | Port
71 |
72 |
73 | Port.svg
74 |
75 |
76 |
77 |
78 | MAC Address
79 |
80 |
81 | MAC_Address.svg
82 |
83 |
84 |
85 |
86 | Infrastructure
87 |
88 |
89 | WebInfrastructure.svg
90 |
91 |
92 |
93 |
94 | Subdomain
95 | Authority
96 |
97 |
98 | Web_Certificate.svg
99 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/Resources/Icons/GroupNode.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Resources/Icons/Camera.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Resources/Icons/Phone_Number.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Core/Resolutions/Core/DeleteColumn.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 |
4 | class DeleteColumn:
5 | # A string that is treated as the name of this resolution.
6 | name = "Rename or Delete Column"
7 |
8 | category = "Spreadsheet Operations"
9 |
10 | # A string that describes this resolution.
11 | description = "Deletes the column with the specified index from a Spreadsheet document."
12 |
13 | originTypes = {'Spreadsheet'}
14 |
15 | resultTypes = {'Spreadsheet'}
16 |
17 | parameters = {'Working Sheet': {'description': 'The name or index of the Sheet to read in the Spreadsheet '
18 | 'file. By default, the first Sheet is used.',
19 | 'type': 'String',
20 | 'value': '0',
21 | 'default': '0'},
22 | 'Column Name to Rename': {'description': 'Please enter the name of the column that you wish to '
23 | 'rename or delete.',
24 | 'type': 'String',
25 | 'value': ''},
26 | 'New Column Name': {'description': 'Please enter the new name for the column.\nEnter the same name '
27 | 'to delete the column instead.',
28 | 'type': 'String',
29 | 'value': ''}
30 | }
31 |
32 | def resolution(self, entityJsonList, parameters):
33 | from pathlib import Path
34 | import pandas as pd
35 | import contextlib
36 |
37 | workingSheet = parameters['Working Sheet']
38 | with contextlib.suppress(ValueError):
39 | workingSheet = int(workingSheet)
40 |
41 | renameColumn = parameters['Column Name to Rename']
42 | targetColumn = parameters['New Column Name']
43 |
44 | returnResults = []
45 |
46 | for entity in entityJsonList:
47 | uid = entity['uid']
48 | filePath = Path(parameters['Project Files Directory']) / entity['File Path']
49 | if not filePath.exists() or not filePath.is_file():
50 | continue
51 |
52 | try:
53 | csvDF = pd.read_excel(filePath, sheet_name=workingSheet)
54 | except ValueError:
55 | continue
56 |
57 | if renameColumn == targetColumn:
58 | csvDF.drop(renameColumn, inplace=True)
59 | else:
60 | csvDF.rename(columns={renameColumn: targetColumn}, inplace=True)
61 |
62 | count = 0
63 | while True:
64 | newFileName = f"{filePath.name.split(filePath.suffix, 1)[0]}-c{count}{filePath.suffix}"
65 | newFilePath = filePath.parent / newFileName
66 | if not newFilePath.exists():
67 | break
68 | csvDF.to_excel(newFilePath, index=False)
69 |
70 | returnResults.append([{'Spreadsheet Name': newFileName,
71 | 'File Path': newFileName,
72 | 'Entity Type': 'Spreadsheet'},
73 | {uid: {'Resolution': 'Rename/Delete Column',
74 | 'Notes': ''}}])
75 |
76 | return returnResults
77 |
--------------------------------------------------------------------------------
/Core/Resolutions/Core/ContainsPhrase.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 |
4 | class ContainsPhrase:
5 | # A string that is treated as the name of this resolution.
6 | name = "Contains Phrase"
7 |
8 | category = "String Operations"
9 |
10 | # A string that describes this resolution.
11 | description = "Checks if a Phrase exists in the Notes of a Phrase entity or the body of a Website."
12 |
13 | originTypes = {'Phrase', 'Website'}
14 |
15 | resultTypes = {'Phrase'}
16 |
17 | parameters = {'Phrase to Search for': {'description': 'Please enter the Phrase to be searched for. Note that the '
18 | 'search is performed in the "Notes" field of a Phrase or the '
19 | 'body of the selected Website entities, not their primary '
20 | 'fields.',
21 | 'type': 'String',
22 | 'value': ''},
23 | 'Case Sensitive': {'description': 'Do you want the phrase to be case sensitive?',
24 | 'type': 'SingleChoice',
25 | 'value': {'Yes', 'No'},
26 | 'default': 'Yes'
27 | }
28 | }
29 |
30 | def resolution(self, entityJsonList, parameters):
31 | import requests
32 | import re
33 |
34 | headers = {
35 | 'User-Agent': 'user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:94.0) Gecko/20100101 Firefox/94.0',
36 | }
37 |
38 | returnResults = []
39 |
40 | for entity in entityJsonList:
41 | offsets = []
42 | counter = 0
43 | searchPhrase = parameters['Phrase to Search for']
44 | uid = entity['uid']
45 | if entity['Entity Type'] == 'Phrase':
46 | text = str(entity.get('Notes'))
47 | primaryField = entity['Phrase']
48 | else: # Website entity
49 | request = requests.get(entity["URL"], headers=headers)
50 | primaryField = entity['URL']
51 | text = request.text
52 |
53 | if parameters['Case Sensitive'] == 'No':
54 | iterator = re.finditer(rf"{searchPhrase}", text, re.IGNORECASE)
55 | for match in iterator:
56 | offsets.append(match.start())
57 | counter += 1
58 | elif parameters['Case Sensitive'] == 'Yes':
59 | iterator = re.finditer(rf"{searchPhrase}", text)
60 | for match in iterator:
61 | offsets.append(match.start())
62 | counter += 1
63 |
64 | if counter > 0:
65 | returnResults.append([{'Phrase': f'{primaryField} Contains Phrase: "{searchPhrase}" {counter} times',
66 | 'Entity Type': 'Phrase',
67 | 'Notes': f'"{searchPhrase}" was found {counter} time(s)\n'
68 | f'Offsets: Matches at character indices: '
69 | f'{(", ".join(map(str, offsets)))}'},
70 | {uid: {'Resolution': f'Contains Phrase {searchPhrase}',
71 | 'Notes': ''}}])
72 |
73 | return returnResults
74 |
--------------------------------------------------------------------------------
/Resources/Icons/Social_Media_Group.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Core/Resolutions/Core/ReplacePhrase.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 |
4 | class ReplacePhrase:
5 | name = "Replace String in Phrase"
6 | category = "String Operations"
7 | description = "Replace a character, or sequence of characters in a Phrase with another character or sequence."
8 | originTypes = {'Phrase'}
9 | resultTypes = {'Phrase'}
10 |
11 | parameters = {'Sequence to Remove': {'description': 'Specify the character or sequence of characters to replace.',
12 | 'type': 'String',
13 | 'value': ''
14 | },
15 | 'Sequence to Insert': {'description': 'Specify the character or sequence of characters to replace '
16 | 'the old character or sequence with. Enter the same character '
17 | 'or sequence to delete the character or sequence instead.',
18 | 'type': 'String',
19 | 'value': ''
20 | },
21 | 'Match Type': {'description': 'Specify whether the matching of characters to replace '
22 | 'should be plain (as in, match characters as they were typed), '
23 | 'case insensitive, or regex.',
24 | 'type': 'SingleChoice',
25 | 'value': {'Plain', 'Case Insensitive', 'Regex'},
26 | 'default': 'Plain'
27 | },
28 | 'Match Count': {'description': 'Specify the number of times to replace the specified character or '
29 | 'sequence with the new sequence. Zero is unlimited times.',
30 | 'type': 'String',
31 | 'value': '0',
32 | 'default': '0'
33 | }
34 | }
35 |
36 | def resolution(self, entityJsonList, parameters):
37 | import re
38 |
39 | returnResults = []
40 |
41 | remove = parameters['Sequence to Remove']
42 | insert = parameters['Sequence to Insert']
43 | if insert == remove:
44 | insert = ''
45 | matchType = parameters['Match Type']
46 |
47 | try:
48 | matchCount = int(parameters['Match Count'])
49 | if matchCount < 0:
50 | return []
51 | except ValueError:
52 | return "Invalid Match Count specified."
53 |
54 | for entity in entityJsonList:
55 | primaryField = entity['Phrase']
56 |
57 | if matchType == 'Plain':
58 | if matchCount == 0:
59 | matchCount = -1
60 | primaryField = primaryField.replace(remove, insert, matchCount)
61 | elif matchType == 'Case Insensitive':
62 | pattern = re.compile(re.escape(remove), re.IGNORECASE)
63 | primaryField = pattern.sub(insert, primaryField, matchCount)
64 | else:
65 | pattern = re.compile(remove)
66 | primaryField = pattern.sub(insert, primaryField, matchCount)
67 |
68 | returnResults.append([{'Phrase': primaryField,
69 | 'Entity Type': 'Phrase'},
70 | {entity['uid']: {'Resolution': 'Replace characters',
71 | 'Notes': ''}}])
72 |
73 | return returnResults
74 |
--------------------------------------------------------------------------------
/Core/Resolutions/Core/ExtractPDFMeta.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 |
4 | class ExtractPDFMeta:
5 | # A string that is treated as the name of this resolution.
6 | name = "Get PDF Metadata"
7 | category = "File Operations"
8 |
9 | # A string that describes this resolution.
10 | description = "Returns a set of nodes that contain notable metadata info of pdf files."
11 |
12 | originTypes = {'Document'}
13 |
14 | resultTypes = {'Phrase'}
15 |
16 | parameters = {}
17 |
18 | def resolution(self, entityJsonList, parameters):
19 | from pypdf import PdfReader
20 | from datetime import datetime, timedelta
21 | import magic
22 | from pathlib import Path
23 |
24 | returnResults = []
25 |
26 | for entity in entityJsonList:
27 | uid = entity['uid']
28 | filePath = Path(parameters['Project Files Directory']) / entity['File Path']
29 |
30 | if not (filePath.is_file()):
31 | continue
32 |
33 | if magic.from_file(str(filePath), mime=True) != 'application/pdf':
34 | continue
35 |
36 | with open(filePath, 'rb') as f:
37 | pdf = PdfReader(f)
38 | info = pdf.metadata
39 | number_of_pages = len(pdf.pages)
40 |
41 | for metadataKey in info:
42 | attrValue = metadataKey[1:] if metadataKey.startswith('/') else metadataKey
43 | if 'Date' in metadataKey:
44 | try:
45 | strDate = info[metadataKey].split(':', 1)[1]
46 | if strDate.endswith('Z'):
47 | dateString = datetime.strptime(strDate, "%Y%m%d%H%M%SZ").isoformat()
48 | elif '+' in strDate:
49 | datePart1, datePart2 = strDate.split('+', 1)
50 | date1 = datetime.strptime(datePart1, "%Y%m%d%H%M%S")
51 | date2 = timedelta(hours=int(datePart2.split("'")[0]), minutes=int(datePart2.split("'")[1]))
52 | dateString = (date1 + date2).isoformat()
53 | elif '-' in strDate:
54 | datePart1, datePart2 = strDate.split('-', 1)
55 | date1 = datetime.strptime(datePart1, "%Y%m%d%H%M%S")
56 | date2 = timedelta(hours=int(datePart2.split("'")[0]), minutes=int(datePart2.split("'")[1]))
57 | dateString = (date1 - date2).isoformat()
58 | else:
59 | raise ValueError('Cannot parse Date format.')
60 |
61 | returnResults.append([{'Date': dateString,
62 | 'Entity Type': 'Date'},
63 | {uid: {'Resolution': attrValue, 'Notes': ''}}])
64 | except Exception:
65 | # Reset strDate to default value
66 | strDate = info[metadataKey]
67 | returnResults.append([{'Date': strDate,
68 | 'Entity Type': 'Date'},
69 | {uid: {'Resolution': attrValue, 'Notes': ''}}])
70 | else:
71 | # Clean some misshapen strings
72 | value = str(info[metadataKey])
73 | value = value.removeprefix('/')
74 | returnResults.append([{'Phrase': f'{attrValue}: {value}',
75 | 'Entity Type': 'Phrase'},
76 | {uid: {'Resolution': attrValue, 'Notes': ''}}])
77 |
78 | returnResults.append([{'Phrase': f'Number of Pages: {number_of_pages}', 'Entity Type': 'Phrase'},
79 | {uid: {'Resolution': 'Number of Pages', 'Notes': ''}}])
80 |
81 | return returnResults
82 |
--------------------------------------------------------------------------------
/Resources/Icons/Social_Media_Account.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Resources/Icons/Portfolio.svg:
--------------------------------------------------------------------------------
1 |
2 |
12 |
--------------------------------------------------------------------------------
/Core/Resolutions/Core/PhoneNumbersExtractor.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | """
4 | This extracts only phone numbers that are marked as a 'tel:' hyperlink.
5 | Extraction of other phone numbers is imprecise, as random numbers may be interpreted as valid phone numbers.
6 | """
7 |
8 |
9 | class PhoneNumbersExtractor:
10 | # A string that is treated as the name of this resolution.
11 | name = "Extract Phone Numbers"
12 |
13 | category = "Website Information"
14 |
15 | # A string that describes this resolution.
16 | description = "Returns the Phone Numbers present on a website or index page of a domain."
17 |
18 | originTypes = {'Domain', 'Website'}
19 |
20 | resultTypes = {'Phone Number'}
21 |
22 | parameters = {}
23 |
24 | def resolution(self, entityJsonList, parameters):
25 | from playwright.sync_api import sync_playwright, TimeoutError, Error
26 | from bs4 import BeautifulSoup
27 | from pathlib import Path
28 | import re
29 |
30 | playwrightPath = Path(parameters['Playwright Chromium'])
31 |
32 | cleanTagsRegex = re.compile(r'<.*?>')
33 | phoneNumCharsExclusion = re.compile(r'[^ -+()\[\]\d]')
34 |
35 | returnResults = []
36 |
37 | def extractTels(currentUID: str, site: str):
38 | page = context.new_page()
39 | pageResolved = False
40 | for _ in range(3):
41 | try:
42 | page.goto(site, wait_until="networkidle", timeout=10000)
43 | pageResolved = True
44 | break
45 | except TimeoutError:
46 | pass
47 | except Error:
48 | break
49 | if not pageResolved:
50 | # Last chance for this to work; some pages have issues with the "networkidle" trigger.
51 | try:
52 | page.goto(site, wait_until="load", timeout=10000)
53 | except Error:
54 | return
55 |
56 | soupContents = BeautifulSoup(page.content(), 'lxml')
57 |
58 | linksInAHref = soupContents.find_all('a')
59 | for tag in linksInAHref:
60 | newLink = tag.get('href', None)
61 | if newLink is not None and newLink.startswith('tel:'):
62 | returnResults.append([{'Phone Number': newLink[4:],
63 | 'Entity Type': 'Phone Number'},
64 | {currentUID: {'Resolution': 'Phone Number Found',
65 | 'Notes': ''}}])
66 |
67 | textTags = soupContents.find_all('p')
68 | for tag in textTags:
69 | tagContents = tag.text
70 | cleanTagContentsList = re.sub(cleanTagsRegex, '', tagContents).split('\n')
71 | for cleanTag in cleanTagContentsList:
72 | cleanTag = cleanTag.strip()
73 | if not phoneNumCharsExclusion.findall(cleanTag) and len(re.findall(r'\d', cleanTag)) >= 3:
74 | returnResults.append([{'Phone Number': cleanTag,
75 | 'Entity Type': 'Phone Number'},
76 | {currentUID: {'Resolution': 'Phone Number Found',
77 | 'Notes': ''}}])
78 |
79 | with sync_playwright() as p:
80 | browser = p.chromium.launch(executable_path=playwrightPath)
81 | context = browser.new_context(
82 | viewport={'width': 1920, 'height': 1080},
83 | user_agent='Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) '
84 | 'Chrome/101.0.4951.54 Safari/537.36',
85 | )
86 | for entity in entityJsonList:
87 | uid = entity['uid']
88 | url = entity.get('URL') if entity.get('Entity Type', '') == 'Website' else \
89 | entity.get('Domain Name', None)
90 | if url is None:
91 | continue
92 | if not url.startswith('http://') and not url.startswith('https://'):
93 | url = f'http://{url}'
94 | extractTels(uid, url)
95 | browser.close()
96 |
97 | return returnResults
98 |
--------------------------------------------------------------------------------
/Installer/LinuxInstaller.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Spec for .desktop files:
4 | # https://specifications.freedesktop.org/menu-spec/latest/apcs02.html
5 | # https://specifications.freedesktop.org/menu-spec/latest/index.html
6 |
7 | read -r -d '' HELPTEXT << EOF
8 | == Installation script for LinkScope Client for Ubuntu 20.04 or greater ==
9 |
10 | Usage:
11 | ------
12 | bash $0 install
13 | - or -
14 | bash $0 uninstall
15 |
16 | Instructions:
17 | -------------
18 | Run the script as a user with sudo permissions, with either 'install' or 'uninstall' (no quotes) as the one and only parameter.
19 | The script will then install or uninstall the software for all users on the computer.
20 |
21 | Function:
22 | ---------
23 | The script will update the package repositories, and attempt to install the following packages:
24 | p7zip-full curl libopengl0 graphviz libmagic1
25 |
26 | Then, the script will download the latest version of the software from AccentuSoft's repository, add it to /usr/local/sbin, and optionally create a Desktop shortcut for the software.
27 | EOF
28 |
29 | read -r -d '' DESKTOP_ENTRY << EOF
30 | [Desktop Entry]
31 | Name=LinkScope Client
32 | StartupWMClass=LinkScope Client
33 | Comment=Start LinkScope Client
34 | GenericName=Investigation Software
35 | Terminal=false
36 | Exec=/usr/local/sbin/LinkScope/LinkScope
37 | Icon=/usr/local/sbin/LinkScope/Icon.ico
38 | Type=Application
39 | Categories=Application;Office;DataVisualization;
40 | MimeType=application/linkscope
41 | Keywords=LinkScope;Investigation;Graph;Knowledge
42 | EOF
43 |
44 |
45 | # Make sure we're not running as root.
46 | if [ "$EUID" -eq 0 ]; then
47 | echo "Please run the installation script as a user with sudo permissions, not as root."
48 | exit
49 | fi
50 |
51 | if [ "$#" -ne 1 ]; then
52 | echo "$HELPTEXT"
53 | exit
54 | fi
55 |
56 | echo "Sudo permission check"
57 | sudo echo "Sudo permissions available"
58 |
59 | if [ "$?" -ne 0 ]; then
60 | echo "Sudo permissions not available, aborting."
61 | exit
62 | fi
63 |
64 | if [ "$1" == "install" ]; then
65 | echo "Installing Software"
66 | echo "Updating Packages"
67 | sudo apt update
68 | echo "Installing New Packages"
69 | sudo apt install p7zip-full curl libopengl0 graphviz libmagic1 -y
70 | echo "Downloading latest version of LinkScope client..."
71 | linuxURL=$(curl -sL https://github.com/AccentuSoft/LinkScope_Client/releases/latest | grep 'Ubuntu-x64.7z' -m 1 | cut -d '"' -f 2 | tr -d ' ')
72 | curl -L https://github.com${linuxURL} -o /tmp/LinkScope.7z
73 | sudo 7z x /tmp/LinkScope.7z -o/usr/local/sbin/ && rm /tmp/LinkScope.7z
74 | if [ $? -ne 0 ]; then
75 | echo "Something went wrong during the download or extraction."
76 | echo "Please check that /tmp/LinkScope.7z exists, and that it is an archive containing the latest version of the LinkScope Client software."
77 | exit
78 | fi
79 | sudo echo "$DESKTOP_ENTRY" > /tmp/LinkScope.desktop
80 | sudo mv /tmp/LinkScope.desktop /usr/share/applications/LinkScope.desktop
81 | sudo chmod +x /usr/share/applications/LinkScope.desktop
82 | read -p "Create a Desktop shortcut? WARNING: This will refresh the desktop! [y/N]" -n 1 -r
83 | # https://askubuntu.com/a/1014261 -- Making Desktop launchers with .desktop files
84 | # https://stackoverflow.com/a/62240478 -- Making .desktop files work.
85 | # Not refreshing the desktop would mean that the desktop icon starts working after a restart.
86 | # Manually creating a desktop icon through the gui is simpler; after creating a symlink, the user can right-click
87 | # the .desktop file and select 'Allow Launching'. This however requires user interaction.
88 | if [[ $REPLY =~ ^[Yy]$ ]]; then
89 | ln -s /usr/share/applications/LinkScope.desktop ${HOME}/Desktop/LinkScope.desktop
90 | dbus-launch gio set ${HOME}/Desktop/LinkScope.desktop "metadata::trusted" true
91 | dbus-send --type=method_call --print-reply --dest=org.gnome.Shell /org/gnome/Shell org.gnome.Shell.Eval string:'global.reexec_self()'
92 | fi
93 | echo "Done."
94 | exit
95 | fi
96 |
97 | if [ "$1" == "uninstall" ]; then
98 | echo "Removing Software"
99 | sudo rm -rf /usr/local/sbin/LinkScope
100 | sudo rm /usr/share/applications/LinkScope.desktop
101 | rm ${HOME}/Desktop/LinkScope.desktop
102 |
103 | echo "Done."
104 | echo "Note that the system packages used by the software are not removed, in the case that they are necessary for other software to function."
105 | exit
106 | fi
107 |
108 | # If the argument passed in is not 'install' or 'uninstall', print the help message.
109 | echo "$HELPTEXT"
110 |
--------------------------------------------------------------------------------
/Resources/Icons/Passport.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------