Skip to content

Instantly share code, notes, and snippets.

@leonjza
Last active December 1, 2018 10:10
Show Gist options
  • Save leonjza/341b850f131e7078508ce2cb7ec23cdc to your computer and use it in GitHub Desktop.
Save leonjza/341b850f131e7078508ce2cb7ec23cdc to your computer and use it in GitHub Desktop.
BSides CPT 2017 - RFCat Challenge Server and Client Sources

BSidesCPT 2017 RFcat Challenge Sources

blog entry

https://leonjza.github.io/blog/2017/12/13/building-the-bsidescpt17-rf-challenge/

Three files are of interest. challenge_server.py, rfcast.py and xor.py.

challenge files

rfcast.py

This script implements a 'broadcast'-like chat system whereby everyone on the same channel, using the same modulation with the same baud rate and sync word can send and receive messages. This is also the base code needed to talk with the challenge server. This script is configured to work on 868Mhz, using FSK with a sync word of 0x1985 and 9600 baud. Obviously, those that decide to change these are free to do so!

This script should be modified to make use of the badges MAC address as identification, instead of asking the user for their handle.

challege-server.py

This script is intended to run on the Pi with the YardStick connected. To uses a slightly different frequency of 868195500hz, 0xd0cb as syncword, ASK/OOK modulation with a baud rate of 4800. It is based on exactly the same code as the chat client, with added game logic to process messages from badges to determine if the progress for a specific source should be advanced.

Those with access to the console of the server can enter state to see the status of all of the players that have connected to the game server.

The challenge server starts one thread to periodically save players progress my simply serializing the ChallengePlayers object and saving it to disk at gamestate.pkl. Restarting the server will load and deserialize this saved object.

xor.py

This script contains a simple XOR method to encode the final payload sent to the box to unlock. As all messages are broadcast to everyone that is listening, the encryption is used to partially thaw off a simple replay attack without first getting the key and the payload sample needed.

This script should be shared, or at the very least the key and format of the final payload.

expected problems (or are they? :P)

  • I hope to see people sniff for the challenge server (think ill add a periodic, random broadcast of quotes etc to help with this)
  • That object serialisation is dodge, hope someone pwns it.
  • The attempts to de-dupe messages that are being broadcast may go wonky and and start repeatedly sending messages. Wups. ^C and restart.

clues

  • A basic file with the full (translated?) chat client should exist on the badge. Ideally, just running it should already give you some form of joy when you chat to others.
  • The drawing with the Flux capacitor design should include the challenge servers frequency, modulation, syncword and baud rate. I like the idea of Doc Brown writing these down to help him in the past ;)
  • The challenge server will send out a hint every 30 seconds for the hint needed to get the flag right for the final payload. The message sent by the challenge server is hint:LwlVBRFIQwscAQFOAAYCCwkDG1cPSE8dEwYGRRcaTREGGhpPAggLBA1PDwESSVQLSAUHAwACUwAbTggCGBYBEgcQUjIEVRxJLgQeEgoGGg4eQFlIEk8fBwccAAkACg0aUx4AHApC which is a base64 encoded, XOR'd with fourth dimensionally! (which is the same key for the final payload) string that is: If we could somehow... harness this lightning; channel it into the Flux Capacitor, it just might work.
  • The xor key should maybe be tweeted or something. I dunno.

challenge flow

The intended way to solve this challenge is as follows:

  • Discover the chat client and familiarize yourself with it.
  • Discover the clues on the Flux capacitor design drawing and configure your chat client to use the specific frequency, modulation, baud and sync word. (or sniff it and skip all that crap)
  • Once 'connected', the game server should announce it has registered your presence. A response message of Welcome <player name>! The temporal displacement occurred at exactly 1:20 AM and zero seconds! I just cant remember how fast...
  • Sending the string 88 miles an hour or eighty-eight miles an hour should advance you to stage 2.
  • Discover that the hint that is broadcasted is XOR'd with the key fourth dimensionally!.
  • Discover that the unlock flag is sent from your client in the format of unlock:<base64flag>, where the <base64flag> is the base64 encoded, xored with fourth dimensionally! value of <your mac address>:1.21 gigawatts.
  • Profit.
# rfcat powered challenge server
# 2017 - @leonjza
#
# BSidesCPT 2017
import base64
import binascii
import datetime
import os
import pickle
import threading
import time
from itertools import izip, cycle
import rflib
# information about our comms
FREQUENCY = 868195500
SYNCWORD = 0xd0cb
MODULATION = rflib.MOD_ASK_OOK
BAUDRATE = 4800
MAX_PACKET_LEN = 150
# setup the radio
d = rflib.RfCat()
d.setFreq(FREQUENCY)
d.setMdmSyncWord(SYNCWORD)
d.setMdmModulation(MODULATION)
d.makePktFLEN(MAX_PACKET_LEN)
d.setMdmDRate(BAUDRATE)
game_process_states = [
(0, 'New user connected'),
(1, '88 Miles a hour'),
(2, '1.21 Gigawatts'), # winner winner, no chicken dinner
]
gamestate_file = 'gamestate.pkl'
def xor(payload):
"""
The xor method for the final unlock payload!
:param payload:
:return:
"""
key = 'fourth dimensionally!'
return ''.join(chr(ord(c) ^ ord(k)) for c, k in izip(payload, cycle(key)))
class ChallengePlayers(object):
"""
Affectively the 'state' of the game. A thread is
created to save this object periodically.
"""
player_status_model = {
'last_update': datetime.datetime.now(),
'progress': 0
}
def __init__(self):
self.player_statuses = {}
def update_player(self, name, progress=0):
# never seen this player before? add them!
if name not in self.player_statuses:
self.player_statuses[name] = self.player_status_model
return
# update the status
self.player_statuses[name]['progress'] = progress
def get_player(self, name):
if name in self.player_statuses:
return self.player_statuses[name]
return None
def __str__(self):
players = []
for player, player_state in self.player_statuses.iteritems():
players.append('<Player:{0}, LastUpdate:{1}, Progress:{2}>'.format(player, player_state['last_update'],
player_state['progress']))
return '\n'.join(players)
class GameLogic(object):
"""
This class defines the logic for a 'move'
that has been made.
"""
def __init__(self, source, message_data, current_state):
self.source = source
self.message = message_data
self.player_state = current_state
def process_move(self):
"""
Process the message received from a source as a move.
The return tuple indicates if an update is needed,
what the new progress value should be and what message
should be broadcast.
Methods processing the actual progress updates may still
perform further validation to ensure that the games logic
is followed.
:return:
"""
# first, newly connected players simply get a entry
# in the games state. They should not have an existing
# state and therefore are just recorded.
if not self.player_state:
return self._new_player_connected()
if 'status' in self.message.lower():
return self._get_player_status()
if any(x in self.message.lower() for x in ['88 miles an hour', 'eighty-eight miles an hour']):
return self._update_stage_one()
if 'unlock' in self.message.lower():
return self._update_stage_two()
# if we had no idea what to do, default to nothing
print('GameLogic can\'t do anything with this message: {0}'.format(self.message))
return False, None, None
def _new_player_connected(self):
"""
Process a new connection.
:return:
"""
return True, 0, 'Welcome {0}! The temporal displacement occurred at ' \
'exactly 1:20 AM and zero seconds! I just cant remember ' \
'how fast we went...'.format(self.source)
def _get_player_status(self):
"""
Return a players status.
:return:
"""
if not self.player_state:
print('Somehow, we got a player status request for an unknown player. Damn hackers!')
return False, None, 'Damn, I\'m late for school!'
return False, None, '{0} is at stage: {1}'.format(self.source, self.player_state['progress'])
def _update_stage_one(self):
"""
Set a players progress to 1 after the first
flag has been correctly sent.
:return:
"""
if not self.player_state:
print('Somehow, we got a stage one flag an unknown player. Damn hackers!')
return False, None, 'Damn, I\'m late for school!'
return True, 1, '{0} has progressed to stage 1!'.format(self.source)
def _update_stage_two(self):
"""
Process a stage two flag.
:return:
"""
if not self.player_state:
print('Somehow, we got a stage two flag an unknown player. Damn hackers!')
return False, None, 'Damn, I\'m late for school!'
# ensure the player has reached stage 1 at least
if self.player_state['progress'] < 1:
print('{0} tried to unlock without reaching progress level 1'.format(self.source))
return False, None, '{0}, slow down!!'.format(self.source)
try:
# get the base64 part and decode and run the xor
_, payload_data = self.message.split(':')
payload = xor(base64.b64decode(payload_data))
# get the user and flag
payload_source, payload_flag = payload.split(':')
# validate the user in the message as well as the user
# in the encrypted flag.
if self.source != payload_source:
print('Received an unlock, but the source and payload source did not match')
return False, None, '{0}, you\'re not thinking fourth dimensionally'.format(self.source)
# check that the flag is correct
if payload_flag.lower() == '1.21 gigawatts':
return True, 2, '{0} has progressed to stage 2. The box should unlock!'.format(self.source)
#
# Something to unlock that box should go here!
#
except Exception as e:
print('Invalid unlock payload from {0}'.format(self.source))
print('Error was: {0}'.format(e))
return False, None, '{0}, if my calculations are correct, when ' \
'this baby hits eighty-eight miles per hour, ' \
'you\'re gonna see some serious shit.'.format(self.source)
# if the incorrect flag was given, nope the heck out
print('Invalid flag received: {0}'.format(payload_flag))
return False, None, 'All right. This is an oldie, but, uh... well, it\'s an oldie where I come from.'
class RadioState(object):
"""
A globally unique radio 'state'.
This class is shared between the two threads used to
send and receive messages.
"""
send_state = 1
receive_state = 0
def __init__(self, username):
# default to receive state
self.state = self.receive_state
self.name = username
self.state_change_needed = False
self.new_state = None
self.message_queue = []
def get_state(self):
return self.state
def set_receive_state(self):
self.state = self.receive_state
def is_receive_state(self):
return self.state == self.receive_state
def set_send_state(self):
self.state = self.send_state
def is_send_state(self):
return self.state == self.send_state
def want_state_change(self):
return self.state_change_needed
def change_state_to(self, new_state):
self.state_change_needed = True
self.new_state = new_state
def change_state(self):
self.state = self.new_state
self.state_change_needed = False
self.new_state = None
def queue_new_message(self, message_data):
# Lets chunk up the message and send it into parts
# that will successfully move across the rf frames.
# 50 characters seem to be 'it'.
parts = [message_data[i:i + 50] for i in range(0, len(message_data), 50)]
for part in parts:
self.message_queue.append(binascii.hexlify(
self.name + ': ' + part).ljust(MAX_PACKET_LEN, '0'))
def get_messages_from_queue(self):
# count the pending messages
message_count = len(self.message_queue)
# if we have messages to send, pop messages off
# of the queue up to where we have counted. This
# should hopefully help in case more messages
# arrive as we are running *this* logic.
if message_count > 0:
return [self.message_queue.pop(0) for _ in xrange(message_count)]
return None
def __repr__(self):
return '<State:{0} StateChangeNeeded:{1} NewState:{2}>'.format(self.state, self.state_change_needed,
self.new_state)
class ChallengeStateSaveThread(threading.Thread):
"""
A helper thread used to save the game state
periodically. You know, incase shit does
down or something.
"""
def __init__(self, challenge_player_state, *args, **kwargs):
super(ChallengeStateSaveThread, self).__init__(*args, **kwargs)
self.state = challenge_player_state
self.current_thread = None
def run(self):
"""
Every 3 seconds, dump the player state object to file.
:return:
"""
self.current_thread = threading.currentThread()
while True:
# check if a stop is needed for this thread
if self.should_stop():
break
# wait
time.sleep(1)
# save the game state
with open(gamestate_file, 'wb') as output:
pickle.dump(self.state, output, pickle.HIGHEST_PROTOCOL)
def should_stop(self):
return hasattr(self.current_thread, 'stop') and getattr(self.current_thread, 'stop', True)
class HintBroadcastThread(threading.Thread):
"""
This thread handles hint broadcasting at certain intervals.
"""
def __init__(self, radio_state, *args, **kwargs):
super(HintBroadcastThread, self).__init__(*args, **kwargs)
self.state = radio_state
self.current_thread = None
def run(self):
self.current_thread = threading.currentThread()
while True:
if self.should_stop():
break
# get the pending messages to send
message_hint = 'hint:' + base64.encodestring(xor('If we could somehow... harness this lightning; ' +
'channel it into the Flux Capacitor, it just might work.'))
self.state.queue_new_message(message_hint.strip())
# get the pending messages to send
messages_to_send = self.state.get_messages_from_queue()
# if there are no messages to send, gtfo
if not messages_to_send:
time.sleep(0.5)
continue
# indicate that we need to change state
self.state.change_state_to(state.send_state)
# poll the state to see if we can send the message
while not self.state.is_send_state():
time.sleep(0.5)
# send the messages
for message_data in messages_to_send:
for _ in range(3):
d.RFxmit(data=message_data, repeat=1)
# change back to the receiving state
self.reverse_state_to_receive()
# wait 30 seconds before broadcasting again
print('sleeping for 30, then sending hints again')
# sleep for 30 seconds, but every second check if we
# should stop this thread.
for _ in range(30):
if self.should_stop():
break
time.sleep(1)
def reverse_state_to_receive(self):
self.state.change_state_to(state.receive_state)
self.state.change_state()
def should_stop(self):
return hasattr(self.current_thread, 'stop') and getattr(self.current_thread, 'stop', True)
class ListenThread(threading.Thread):
"""
The listening thread.
If the global radio state is configured to listen,
this thread will listen for a new packet for 1 second.
If a request to flip the state is waiting the run() method
will call to flip this and let the radio send stuff.
"""
def __init__(self, radio_state, challenge_player_state, *args, **kwargs):
super(ListenThread, self).__init__(*args, **kwargs)
self.state = radio_state
self.player_state = challenge_player_state
# get a handle on the current thread. We use this to
# get the signal to quit of needed.
self.current_thread = None
self.received_messages = []
def run(self):
self.current_thread = threading.currentThread()
while True:
# check if a stop is needed for this thread
if self.should_stop():
break
# process packet
if self.state.is_receive_state():
self.listen_for_packet()
else:
# wait half a second to check state again
time.sleep(0.5)
# check if a state change is needed
self.check_for_state_change()
def should_stop(self):
return hasattr(self.current_thread, 'stop') and getattr(self.current_thread, 'stop', True)
def listen_for_packet(self):
"""
Listens for a new packet for 1 second.
If a message is received, it is simply printed to the
screen.
:return:
"""
try:
pkt, _ = d.RFrecv(timeout=1000)
decoded_pkt = binascii.unhexlify(pkt)
# skip if we have already received this message
if decoded_pkt in self.received_messages:
return
self.received_messages.append(decoded_pkt)
self.trim_messages(total=2)
# handle the incoming message
self.handle_incoming_message(incoming=decoded_pkt)
except (rflib.ChipconUsbTimeoutException, TypeError):
pass
def check_for_state_change(self):
if self.state.want_state_change():
self.state.change_state()
def trim_messages(self, total):
self.received_messages = self.received_messages[-total:]
def handle_incoming_message(self, incoming):
"""
Handle an incoming, decoded frame with the games logic
class.
:param incoming:
:return:
"""
message_tuple = incoming.split(':')
source = message_tuple[0].strip()
message_data = ':'.join(message_tuple[1:]).strip()
print('\nFrom: {0} - Message: {1}'.format(source, message_data))
# process the incoming message, taking the games logic into account
game_logic = GameLogic(source=source,
message_data=message_data,
current_state=self.player_state.get_player(source))
should_update, new_progress, broadcast_message = game_logic.process_move()
if should_update:
self.player_state.update_player(source, progress=new_progress)
if broadcast_message:
print('Sending response: {0}'.format(broadcast_message))
state.queue_new_message(message_data=broadcast_message)
class SendThread(threading.Thread):
"""
The sending thread.
If the global radio state is configured to send,
this thread will pick up the message to send and
handle the state change to stop the radion from
listening and instead send frames.
"""
def __init__(self, radio_state, challenge_player_state, *args, **kwargs):
super(SendThread, self).__init__(*args, **kwargs)
self.state = radio_state
self.player_state = challenge_player_state
# get a handle on the current thread. We use this to
# get the signal to quit of needed.
self.current_thread = None
self.received_messages = []
def run(self):
# get a handle on the current thread. We use this to
# get the signal to quit of needed.
self.current_thread = threading.currentThread()
while True:
# check if a stop is needed for this thread
if self.should_stop():
break
# get the pending messages to send
messages_to_send = self.state.get_messages_from_queue()
# if there are no messages to send, gtfo
if not messages_to_send:
time.sleep(0.5)
continue
# indicate that we need to change state
self.state.change_state_to(state.send_state)
# poll the state to see if we can send the message
while not self.state.is_send_state():
time.sleep(0.5)
# send the messages
for message_data in messages_to_send:
for _ in range(3):
d.RFxmit(data=message_data, repeat=1)
# change back to the receiving state
self.reverse_state_to_receive()
def should_stop(self):
return hasattr(self.current_thread, 'stop') and getattr(self.current_thread, 'stop', True)
def reverse_state_to_receive(self):
self.state.change_state_to(state.receive_state)
self.state.change_state()
if __name__ == '__main__':
# try and load a saved player state file
if os.path.exists(gamestate_file):
print('Loading a saved game state from: {0}'.format(gamestate_file))
with open(gamestate_file, 'rb') as state_file:
gamestate = pickle.load(state_file)
else:
gamestate = ChallengePlayers()
# prepare the radio state
state = RadioState(username='Pu')
# prep and start threads.
listen_thread = ListenThread(radio_state=state, challenge_player_state=gamestate, name='listen-thread')
send_thread = SendThread(radio_state=state, challenge_player_state=gamestate, name='send-thread')
state_save_thread = ChallengeStateSaveThread(challenge_player_state=gamestate, name='game-state-save-thread')
hint_broadcast_thread = HintBroadcastThread(radio_state=state, name='hint-broadcast-thread')
listen_thread.start()
send_thread.start()
state_save_thread.start()
hint_broadcast_thread.start()
# broadcast that a new user has joined!
state.queue_new_message(message_data='joined the network')
print('%help for help.')
while True:
message = raw_input('game server> ')
message = message.strip()
if message == '%help':
print('Help Menu:\n'
'\n'
'%state: print the radios current state\n'
'%help: this menu, but good luck getting some\n'
'%exit: go back to 1985\n')
continue
if message == '%state':
print('Radio State:')
print(state)
print('\n')
print('Player States:')
print(gamestate)
print('\n')
print('Message queue')
for m in state.message_queue:
print('Message: {0}'.format(m))
continue
if message == '':
continue
if message == '%exit':
break
# queue a new message send!
state.queue_new_message(message_data=message)
# stop the threads
listen_thread.stop = True
send_thread.stop = True
state_save_thread.stop = True
hint_broadcast_thread.stop = True
# silly rfcat powered broadcast chat system
# 2017 - @leonjza
#
# BSidesCPT 2017
import binascii
import threading
import time
import rflib
# information about our comms
FREQUENCY = 868000000
SYNCWORD = 0x1985
MODULATION = rflib.MOD_2FSK
BAUDRATE = 9600
MAX_PACKET_LEN = 150
# setup the radio
d = rflib.RfCat()
d.setFreq(FREQUENCY)
d.setMdmSyncWord(SYNCWORD)
d.setMdmModulation(MODULATION)
d.makePktFLEN(MAX_PACKET_LEN)
d.setMdmDRate(BAUDRATE)
class RadioState(object):
"""
A globally unique radio 'state'.
This class is shared between the two threads used to
send and receive messages.
"""
send_state = 1
receive_state = 0
def __init__(self, username):
# default to receive state
self.state = self.receive_state
self.name = username
self.state_change_needed = False
self.new_state = None
self.message_queue = []
def get_state(self):
return self.state
def set_receive_state(self):
self.state = self.receive_state
def is_receive_state(self):
return self.state == self.receive_state
def set_send_state(self):
self.state = self.send_state
def is_send_state(self):
return self.state == self.send_state
def want_state_change(self):
return self.state_change_needed
def change_state_to(self, new_state):
self.state_change_needed = True
self.new_state = new_state
def change_state(self):
self.state = self.new_state
self.state_change_needed = False
self.new_state = None
def queue_new_message(self, message_data):
# Lets chunk up the message and send it into parts
# that will successfully move across the rf frames.
# 50 characters seem to be 'it'.
parts = [message_data[i:i + 50] for i in range(0, len(message_data), 50)]
for part in parts:
self.message_queue.append(binascii.hexlify(
self.name + ': ' + part).ljust(MAX_PACKET_LEN, '0'))
def get_messages_from_queue(self):
# count the pending messages
message_count = len(self.message_queue)
# if we have messages to send, pop messages off
# of the queue up to where we have counted. This
# should hopefully help in case more messages
# arrive as we are running *this* logic.
if message_count > 0:
return [self.message_queue.pop(0) for _ in xrange(message_count)]
return None
def __repr__(self):
return '<State:{0} StateChangeNeeded:{1} NewState:{2}>'.format(self.state, self.state_change_needed,
self.new_state)
class ListenThread(threading.Thread):
"""
The listening thread.
If the global radio state is configured to listen,
this thread will listen for a new packet for 1 second.
If a request to flip the state is waiting the run() method
will call to flip this and let the radio send stuff.
"""
def __init__(self, radio_state, *args, **kwargs):
super(ListenThread, self).__init__(*args, **kwargs)
self.state = radio_state
# get a handle on the current thread. We use this to
# get the signal to quit of needed.
self.current_thread = None
self.received_messages = []
def run(self):
self.current_thread = threading.currentThread()
while True:
# check if a stop is needed for this thread
if self.should_stop():
break
# process packet
if self.state.is_receive_state():
self.listen_for_packet()
else:
# wait half a second to check state again
time.sleep(0.5)
# check if a state change is needed
self.check_for_state_change()
def should_stop(self):
return hasattr(self.current_thread, 'stop') and getattr(self.current_thread, 'stop', True)
def listen_for_packet(self):
"""
Listens for a new packet for 1 second.
If a message is received, it is simply printed to the
screen.
:return:
"""
try:
pkt, _ = d.RFrecv(timeout=1000)
decoded_pkt = binascii.unhexlify(pkt).strip()
# skip if we have already received this message
if decoded_pkt in self.received_messages:
return
# add the new packet, and make sure our list is not longer
# than 10
self.received_messages.append(decoded_pkt)
self.trim_messages(total=2)
# print the message we got!
# @dalenunns make some badge lights flash here! :D
print('{}'.format(decoded_pkt))
except (rflib.ChipconUsbTimeoutException, TypeError):
pass
def check_for_state_change(self):
if self.state.want_state_change():
self.state.change_state()
def trim_messages(self, total):
self.received_messages = self.received_messages[-total:]
class SendThread(threading.Thread):
"""
The sending thread.
If the global radio state is configured to send,
this thread will pick up the message to send and
handle the state change to stop the radion from
listening and instead send frames.
"""
def __init__(self, radio_state, *args, **kwargs):
super(SendThread, self).__init__(*args, **kwargs)
self.state = radio_state
# get a handle on the current thread. We use this to
# get the signal to quit of needed.
self.current_thread = None
self.received_messages = []
def run(self):
# get a handle on the current thread. We use this to
# get the signal to quit of needed.
self.current_thread = threading.currentThread()
while True:
# check if a stop is needed for this thread
if self.should_stop():
break
# get the pending messages to send
messages_to_send = self.state.get_messages_from_queue()
# if there are no messages to send, gtfo
if not messages_to_send:
time.sleep(0.5)
continue
# indicate that we need to change state
self.state.change_state_to(state.send_state)
# poll the state to see if we can send the message
while not self.state.is_send_state():
time.sleep(0.5)
# send the messages
for message_data in messages_to_send:
for _ in range(3):
d.RFxmit(data=message_data, repeat=1)
# change back to the receiving state
self.reverse_state_to_receive()
def should_stop(self):
return hasattr(self.current_thread, 'stop') and getattr(self.current_thread, 'stop', True)
def reverse_state_to_receive(self):
self.state.change_state_to(state.receive_state)
self.state.change_state()
if __name__ == '__main__':
while True:
handle = raw_input('> hello! what is your name?\n')
if handle.strip() != '':
break
print('\nRFCast by @leonjza - BSidesCPT 2017\n')
print('Welcome, {0}! Type %help for a help menu.'.format(handle))
print('Note: This is a broadcast chat system with *no* encryption. Anyone on this frequency '
'can listen to and send broadcast messages.\n')
state = RadioState(username=handle)
# prep and start 2 threads. one to listen, and one to send
listen_thread = ListenThread(radio_state=state, name='listen-thread')
send_thread = SendThread(radio_state=state, name='send-thread')
listen_thread.start()
send_thread.start()
# broadcast that a new user has joined!
state.queue_new_message(message_data='Joined the network')
while True:
message = raw_input('[{}] message> '.format(handle))
message = message.strip()
if message == '%help':
print('Help Menu:\n'
'\n'
'%state: print the radios current state\n'
'%help: this menu, but good luck getting some\n'
'%exit: go back to 1985\n')
continue
if message == '%state':
print(state)
continue
if message == '':
continue
if message == '%exit':
break
# queue a new message send!
state.queue_new_message(message_data=message)
# stop the threads
listen_thread.stop = True
send_thread.stop = True
import base64
import sys
from itertools import cycle, izip
key = 'fourth dimensionally!'
def xor(string, key):
return ''.join(chr(ord(c) ^ ord(k)) for c, k in izip(string, cycle(key)))
if __name__ == '__main__':
if len(sys.argv) < 2:
print('Usage: {0} <string>'.format(sys.argv[0]))
sys.exit(1)
f = base64.encodestring(xor(sys.argv[1], key)).strip()
print(f)
g = xor(base64.b64decode(f), key)
print(g)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment