├── 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 | Forward -------------------------------------------------------------------------------- /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 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Resources/Icons/Banner_Orange.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Resources/Icons/Banner_Red.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Resources/Icons/Default.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | 17 | default 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /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 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /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 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 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 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /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 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Resources/Icons/Country.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /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 | 2 | 3 | 16 | 17 | phrase 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /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 | 2 | 3 | 24 | 25 | person 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /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 | 2 | 3 | 24 | 25 | person 26 | 27 | 28 | 29 | 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 | 2 | 3 | 16 | 17 | social-media-profile 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Resources/Icons/Geolocation.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | 17 | geolocation 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /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 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /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 | 2 | 3 | 4 | hardware-computer-electonic-HDD-sata-drive-storage-data 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Resources/Icons/Archive.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /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 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Resources/Icons/Bank_Account.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Finance 1 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /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 | 2 | 3 | 25 | 26 | ip-address 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Resources/Icons/Family.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /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 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Resources/Icons/Port.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | 17 | port 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Resources/Icons/Mobile_Device.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | 17 | phone-number 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /Resources/Icons/Sentiment.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | 17 | sentiment 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /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 | 2 | 3 | 16 | 17 | organization 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Resources/Icons/Spreadsheet.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Resources/Icons/Software.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | 17 | software 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Resources/Icons/Domain.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 25 | 26 | domain 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /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 | 2 | 3 | 20 | 21 | website 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Resources/Icons/OnionWebsite.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | 21 | website 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Resources/Icons/City.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /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 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /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 | 2 | 3 | 26 | 27 | ip-address-2 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Resources/Icons/Date.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Resources/Icons/Email.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /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 | 2 | 8 | 11 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Resources/Icons/Document.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 25 | 26 | document 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Resources/Icons/Address.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | 26 | 27 | 28 | 29 | 31 | 51 | 58 | 62 | 65 | 66 | 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 | 3 | 4 | 5 | 6 | 7 | 8 | 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 | 2 | 3 | 25 | 26 | hash 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Resources/Icons/MAC_Address.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Resources/Icons/VehicleRegistration.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /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 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /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 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /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 | 2 | 3 | 4 | company-Building-bank-Business-office 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /Resources/Icons/AutonomousSystem.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /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 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Resources/Icons/Camera.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Resources/Icons/Phone_Number.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /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 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Resources/Icons/Portfolio.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | --------------------------------------------------------------------------------