async def send_result(self, result: Optional[Dict]): if result: try: success = access_dot_path(result, 'data.tcp.status') except: success = 'unknown-error' try: content_length = int( access_dot_path(result, 'data.tcp.result.response.content_length')) except: content_length = 0 if self.stats: if success == 'success': self.stats.count_good += 1 else: self.stats.count_error += 1 line = None line_out = None try: if self.success_only: if success == 'success': line = result else: line = result except Exception: pass if line: if self.body_not_empty: if content_length > 0: line_out = ujson_dumps(line) else: line_out = ujson_dumps(line) if line_out: await self.output_queue.put(line_out)
def test_access_dot_path(some_dict, path, expected): assert access_dot_path(some_dict, path) == expected
async def do(self, target: Target): """ сопрограмма, осуществляет подключение к Target, отправку и прием данных, формирует результата в виде dict """ def return_ip_from_deep(sess, response) -> str: try: ip_port = response.connection.transport.get_extra_info( 'peername') if is_ip(ip_port[0]): return ip_port[0] except BaseException: pass try: _tmp_conn_key = sess.connector._conns.items() for k, v in _tmp_conn_key: _h = v[0][0] ip_port = _h.transport.get_extra_info('peername') if is_ip(ip_port[0]): return ip_port[0] except BaseException: pass return '' def update_line(json_record, target): json_record['ip'] = target.ip # json_record['ip_v4_int'] = int(ip_address(target.ip)) # json_record['datetime'] = datetime.datetime.utcnow() # json_record['port'] = int(target.port) return json_record async with self.semaphore: result = None timeout = ClientTimeout(total=target.total_timeout) # region tmp disable trace_config = TraceConfig() trace_config.on_request_start.append(on_request_start) trace_config.on_request_end.append(on_request_end) # endregion resolver = AsyncResolver(nameservers=['8.8.8.8', '8.8.4.4']) # resolver = None # https://github.com/aio-libs/aiohttp/issues/2228 - closed if target.ssl_check: conn = TCPConnector( ssl=False, family=2, # need set current family (only IPv4) limit_per_host=0, resolver=resolver) session = ClientSession(timeout=timeout, connector=conn, response_class=WrappedResponseClass, trace_configs=[trace_config]) simple_zero_sleep = 0.250 else: simple_zero_sleep = 0.001 session = ClientSession( connector=TCPConnector( limit_per_host=0, family=2, # need set current family (only IPv4) resolver=resolver), timeout=timeout, trace_configs=[trace_config]) selected_proxy_connection = None try: selected_proxy_connection = next( self.app_config.proxy_connections) except: pass try: async with session.request( target.method, target.url, timeout=timeout, headers=target.headers, cookies=target.cookies, allow_redirects=target.allow_redirects, data=target.payload, proxy=selected_proxy_connection, trace_request_ctx=self.trace_request_ctx) as response: _default_record = create_template_struct(target) if target.ssl_check: cert = convert_bytes_to_cert(response.peer_cert) if not self.app_config.without_certraw: _default_record['data']['http']['result'][ 'response']['request']['tls_log'][ 'handshake_log']['server_certificates'][ 'certificate']['raw'] = b64encode( response.peer_cert).decode('utf-8') if cert: _default_record['data']['http']['result'][ 'response']['request']['tls_log'][ 'handshake_log']['server_certificates'][ 'certificate']['parsed'] = cert _default_record['data']['http']['status'] = "success" _default_record['data']['http']['result']['response'][ 'status_code'] = response.status # region _header = {} for key in response.headers: _header[key.lower().replace( '-', '_')] = response.headers.getall(key) _default_record['data']['http']['result']['response'][ 'headers'] = _header # endregion if target.method in [ 'GET', 'POST', 'PUT', 'DELETE', 'UPDATE' ]: buffer = b"" try: read_c = asyncio.wait_for( read_http_content(response, n=target.max_size), timeout=target.total_timeout) buffer = await read_c except Exception as e: pass else: if filter_bytes(buffer, target): _default_record['data']['http']['result'][ 'response']['content_length'] = len(buffer) _default_record['data']['http']['result'][ 'response']['body'] = '' try: _default_record['data']['http']['result'][ 'response']['body'] = buffer.decode() except Exception as e: pass if not self.app_config.without_base64: try: _base64_data = b64encode( buffer).decode('utf-8') _default_record['data']['http'][ 'result']['response'][ 'body_raw'] = _base64_data except Exception as e: pass if not self.app_config.without_hashs: try: hashs = { 'sha256': sha256, 'sha1': sha1, 'md5': md5 } for namehash, func in hashs.items(): hm = func() hm.update(buffer) _default_record['data']['http'][ 'result']['response'][ f'body_{namehash}'] = hm.hexdigest( ) except Exception as e: pass result = update_line(_default_record, target) else: # TODO: добавить статус success-not-contain для обозначения того, # что сервис найден, но не попал под фильтр? result = create_error_template( target, error_str='', status_string='success-not-contain') if result: if not result['ip']: result['ip'] = return_ip_from_deep( session, response) except Exception as exp: error_str = '' try: error_str = exp.strerror except: pass result = create_error_template(target, error_str, type(exp).__name__) await asyncio.sleep(simple_zero_sleep) try: await session.close() except: pass try: await conn.close() except: pass if result: if 'duration' in self.trace_request_ctx: request_duration = self.trace_request_ctx['duration'] result['data']['http']['duration'] = request_duration success = access_dot_path(result, "data.http.status") if self.stats: if success == "success": self.stats.count_good += 1 else: self.stats.count_error += 1 if not (self.app_config.status_code == CONST_ANY_STATUS): response_status = access_dot_path( result, 'data.http.result.response.status_code') if response_status: if self.app_config.status_code != response_status: error_str = f'status code: {response_status} is not equal to filter: {self.app_config.status_code}' result = create_error_template( target, error_str=error_str, status_string='success-not-need-status') self.stats.count_good -= 1 self.stats.count_error += 1 line = None try: if self.success_only: if success == "success": line = ujson_dumps(result) else: line = ujson_dumps(result) except Exception: pass if line: await self.output_queue.put(line) await asyncio.sleep(simple_zero_sleep) try: await session.close() except: pass try: await conn.close() except: pass
async def do(self, target: Target): """ сопрограмма, осуществляет подключение к Target, отправку и прием данных, формирует результата в виде dict """ async with self.semaphore: result = None certificate_dict = None cert_bytes_base64 = None if target.ssl_check: # если при запуске в настройках указано --use-ssl - то контекст ssl ssl_context = ssl_create_unverified_context() future_connection = asyncio.open_connection( target.ip, target.port, ssl=ssl_context, ssl_handshake_timeout=target.ssl_timeout) else: future_connection = asyncio.open_connection( target.ip, target.port) try: reader, writer = await asyncio.wait_for( future_connection, timeout=target.conn_timeout) if target.ssl_check: try: # noinspection PyProtectedMember _sub_ssl = writer._transport.get_extra_info( 'ssl_object') cert_bytes = _sub_ssl.getpeercert(binary_form=True) cert_bytes_base64 = b64encode(cert_bytes).decode( 'utf-8') certificate_dict = convert_bytes_to_cert(cert_bytes) except BaseException: pass except Exception as e: await asyncio.sleep(0.005) try: future_connection.close() del future_connection except Exception as e: pass result = create_error_template(target, str(e)) else: try: status_data = False if target.payload: # если указан payload - то он и отправляется в первую очередь writer.write(target.payload) await writer.drain() if target.mode == 'single': status_data, data_or_error_result = await single_read( reader, target) elif target.mode == 'multi': status_data, data_or_error_result = await asyncio.wait_for( multi_read(reader, target), timeout=target.read_timeout) if status_data: check_filter = filter_bytes(data_or_error_result, target) if check_filter: result = make_document_from_response( data_or_error_result, target) if target.ssl_check: if cert_bytes_base64: result['data']['tcp']['result']['response'][ 'request']['tls_log']['handshake_log'][ 'server_certificates'][ 'certificate'][ 'raw'] = cert_bytes_base64 if certificate_dict: result['data']['tcp']['result']['response'][ 'request']['tls_log']['handshake_log'][ 'server_certificates'][ 'certificate'][ 'parsed'] = certificate_dict else: # TODO: добавить статус success-not-contain для обозначения того, # что сервис найден, но не попал под фильтр pass await asyncio.sleep(0.005) else: result = data_or_error_result # get errors try: writer.close() except BaseException: pass except Exception as e: result = create_error_template(target, str(e)) try: future_connection.close() except Exception: pass await asyncio.sleep(0.005) try: writer.close() except Exception: pass if result: success = access_dot_path(result, "data.tcp.status") if self.stats: if success == "success": self.stats.count_good += 1 else: self.stats.count_error += 1 line = None try: if self.success_only: if success == "success": line = ujson_dumps(result) else: line = ujson_dumps(result) except Exception: pass if line: await self.output_queue.put(line)
async def do(self, target: Target): async with self.semaphore: result = None future_connection = asyncio.open_connection(target.ip, target.port) try: reader, writer = await asyncio.wait_for( future_connection, timeout=target.conn_timeout) except Exception as e: await asyncio.sleep(0.005) try: future_connection.close() del future_connection except Exception as e: pass result = create_error_template(target, str(e)) else: try: for i in range(15, 43): TLS_ClientHello[i] = (TLS_ClientHello[i] ^ randint(0, 4294967294)) & 0xFF writer.write(bytes(TLS_ClientHello)) await writer.drain() await asyncio.sleep(0.05) desc_text = '1 step: read 65535 bytes' status_data, answer = await single_read( reader, target, custom_max_size=65535, operation_description=desc_text) if status_data: if answer[0] != 0x16: message_error_text = "Not a C2. TLS ContentType state" raise AssertionError(message_error_text) if answer[5] != 0x02: message_error_text = f"Not a C2. Expected ServerHello (02) got ({answer[5]:02X})" raise AssertionError(message_error_text) for i in range(15, 267): TLS_HelloRequest[i] = ( TLS_HelloRequest[i] ^ randint(0, 4294967294)) & 0xFF packed_size = pack(">H", FULL_SIZE) for i, j in enumerate(range(276, 278)): TLS_HelloRequest[j] = packed_size[i] for i, j in enumerate(range(278, 278 + len(SECRET))): TLS_HelloRequest[j] = SECRET[i] for i in range(278 + len(SECRET), 278 + FULL_SIZE): TLS_HelloRequest[i] = ( TLS_HelloRequest[i] ^ randint(0, 4294967294)) & 0xFF writer.write(bytes(TLS_HelloRequest)) await writer.drain() await asyncio.sleep(0.05) desc_text = f'2 step: read 5 bytes, timeout={target.read_timeout}' status_data, answer = await single_read( reader, target, custom_max_size=5, operation_description=desc_text) if status_data: if answer[0] != 0x14: message_error_text = f"Not a C2. Expected ChangeCipherSpec (0x14) got ({answer[0]:02X})" raise AssertionError(message_error_text) length = unpack(">H", bytes(answer[3:]))[0] if length > 0x3ff9: message_error_text = f"Not a C2. ChangeCipherSpec too big" raise AssertionError(message_error_text) await asyncio.sleep(0.05) desc_text = f'3 step: read {length}+5 bytes, timeout={target.read_timeout}' status_data, _answer = await single_read( reader, target, custom_max_size=length + 5, operation_description=desc_text) await asyncio.sleep(0.05) if status_data: answer = _answer[-5:] if answer[0] != 0x16: message_error_text = f"Not a C2. Expected Handshake (0x16) got ({answer[0]:02X})" raise AssertionError(message_error_text) length = unpack(">H", bytes(answer[3:]))[0] if length > 0x3ff9: message_error_text = f"Not a C2. Handshake too big" raise AssertionError(message_error_text) desc_text = f'4 step: read {length} bytes, timeout={target.read_timeout}' status_data, answer = await single_read( reader, target, custom_max_size=length, operation_description=desc_text) if status_data: server_secret = answer[:len(SECRET)] key_stream = derive_key(server_secret) need_payload = pack_payload(key_stream, pack("<H", 0x200) + \ pack("<H", 0x03) + \ b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") writer.write(need_payload) await writer.drain() await asyncio.sleep(0.05) desc_text = f'5 step: read 5 bytes, timeout={target.read_timeout}' status_data, answer = await single_read( reader, target, custom_max_size=5, operation_description=desc_text) length = unpack(">H", bytes(answer[3:]))[0] desc_text = f'6 step: read {length} bytes, timeout={target.read_timeout}' status_data, answer = await single_read( reader, target, custom_max_size=length, operation_description=desc_text) c2_answer = key_stream.decrypt(answer) command_id = unpack("<H", c2_answer[:2])[0] module_id = unpack("<H", c2_answer[2:4])[0] if command_id not in COMMANDS: message_error_text = f"Not a C2. Invalid response command id" raise AssertionError( message_error_text) if module_id != 3: message_error_text = f"Not a C2. Invalid response module id" raise AssertionError( message_error_text) result = make_document_from_response( b'C2 found', target) await asyncio.sleep(0.005) else: result = answer else: result = _answer else: result = answer else: result = answer # get errors try: writer.close() except BaseException: pass except AssertionError as e: result = create_error_template(target, str(e)) try: future_connection.close() except Exception: pass await asyncio.sleep(0.005) try: writer.close() except Exception: pass except Exception as e: result = create_error_template(target, str(e)) try: future_connection.close() except Exception: pass await asyncio.sleep(0.005) try: writer.close() except Exception: pass if result: try: success = access_dot_path(result, 'data.tcp.status') except: success = 'unknown-error' try: content_length = int( access_dot_path( result, 'data.tcp.result.response.content_length')) except: content_length = 0 if self.stats: if success == 'success': self.stats.count_good += 1 else: self.stats.count_error += 1 line = None line_out = None try: if self.success_only: if success == 'success': line = result else: line = result except Exception: pass if line: if self.body_not_empty: if content_length > 0: line_out = ujson_dumps(line) else: line_out = ujson_dumps(line) if line_out: await self.output_queue.put(line_out)