首页
社区
课程
招聘
[原创]Go syscall免杀与原理浅探
发表于: 2024-7-16 12:11 3160

[原创]Go syscall免杀与原理浅探

2024-7-16 12:11
3160

一直以来用的C写马子,早就听说go的免杀效果很好(现在不行了),syscall能直接调,于是简单看一下syscall调用的思路,以及看看他的不足。

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
package lazydll
 
import (
    "fmt"
    "syscall"
    "time"
    "unsafe"
)
 
const (
    MB_OK = 0x00000000
)
 
func abort(funcname string, err syscall.Errno) {
    panic(funcname + " failed: " + err.Error())
}
 
var (
    user32, _     = syscall.LoadLibrary("user32.dll")
    messageBox, _ = syscall.GetProcAddress(user32, "MessageBoxW")
)
 
func IntPtr(n int) uintptr {
    return uintptr(n)
}
 
func StrPtr(s string) uintptr {
    return uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(s)))
}
 
func MessageBox(caption, text string, style uintptr) int {
    ret, _, callErr := syscall.SyscallN(messageBox, 4, 0, StrPtr(text), StrPtr(caption), style, 0, 0, 0, 0)
    if callErr != 0 {
        abort("Call MessageBox", callErr)
    }
    return int(ret)
}
 
func main() {
    defer syscall.FreeLibrary(user32)
 
    // 调用 MessageBox 函数
    num := MessageBox("Done Title", "This test is Done.", MB_OK)
    fmt.Printf("Return value before MessageBox invocation: %d\n", num)
 
    time.Sleep(3 * time.Second)
}

syscall调用dll,使用syscall.LoadLibrary或者syscall.NewLazyDll。
然后调用syscall.GetProcAddress或者[dllhandle.NewProc,获取dll中函数
跟进LoadLibrary,可以看到Syscall(procLoadLibraryW.Addr()......。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func LoadLibrary(libname string) (handle Handle, err error) {
    var _p0 *uint16
    _p0, err = UTF16PtrFromString(libname)
    if err != nil {
        return
    }
    return _LoadLibrary(_p0)
}
 
func _LoadLibrary(libname *uint16) (handle Handle, err error) {
    r0, _, e1 := Syscall(procLoadLibraryW.Addr(), 1, uintptr(unsafe.Pointer(libname)), 0, 0)
    handle = Handle(r0)
    if handle == 0 {
        err = errnoErr(e1)
    }
    return
}

procLoadLibraryW是内置的,使用NewLazyDLL获取的kernel32.dll库,然后NewProc构建一个LoadLibraryW的LazyProc实例

1
2
3
4
5
6
7
modkernel32 = NewLazyDLL(sysdll.Add("kernel32.dll"))
....
procLoadLibraryW = modkernel32.NewProc("LoadLibraryW")
....
func (d *LazyDLL) NewProc(name string) *LazyProc {
    return &LazyProc{l: d, Name: name}
}

LazyProc中存放函数的name、对应的dll,以及函数对应的addr

1
2
3
4
5
6
7
8
9
10
11
type LazyProc struct {
    mu   sync.Mutex
    Name string
    l    *LazyDLL
    proc *Proc
}
type Proc struct {
    Dll  *DLL
    Name string
    addr uintptr
}

而这里的addr最终就是由syscall函数使用的,里面存放dll函数的指针

1
2
3
4
5
6
7
8
9
10
r0, _, e1 := Syscall(procLoadLibraryW.Addr(), 1, uintptr(unsafe.Pointer(libname)), 0, 0)
....
func (p *LazyProc) Addr() uintptr {
    p.mustFind()
    return p.proc.Addr()
}
....
func (p *Proc) Addr() uintptr {
    return p.addr
}

那么这里的addr如何设置的?实际上是LazyProc.Find()中设置的,具体是proc, e := p.l.dll.FindProc(p.Name)这一条语句,调用了LazyDll中指定的dll句柄的findproc函数。
这里调用LazyProc.LazyDll.Load()加载了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
// Find searches DLL for procedure named p.Name. It returns
// an error if search fails. Find will not search procedure,
// if it is already found and loaded into memory.
func (p *LazyProc) Find() error {
    // Non-racy version of:
    // if p.proc == nil {
    if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&p.proc))) == nil {
        p.mu.Lock()
        defer p.mu.Unlock()
        if p.proc == nil {
            e := p.l.Load()
            if e != nil {
                return e
            }
            proc, e := p.l.dll.FindProc(p.Name)
            if e != nil {
                return e
            }
            // Non-racy version of:
            // p.proc = proc
            atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&p.proc)), unsafe.Pointer(proc))
        }
    }
    return nil
}

可以观察到直接调用了getprocaddress,这里是直接使用了kernel32中的函数

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
// A DLL implements access to a single DLL.
type DLL struct {
    Name   string
    Handle Handle
}
 
// FindProc searches DLL d for procedure named name and returns *Proc
// if found. It returns an error if search fails.
func (d *DLL) FindProc(name string) (proc *Proc, err error) {
    namep, err := BytePtrFromString(name)
    if err != nil {
        return nil, err
    }
    a, e := getprocaddress(uintptr(d.Handle), namep)
    if e != 0 {
        return nil, &DLLError{
            Err:     e,
            ObjName: name,
            Msg:     "Failed to find " + name + " procedure in " + d.Name + ": " + e.Error(),
        }
    }
    p := &Proc{
        Dll:  d,
        Name: name,
        addr: a,
    }
    return p, nil
}

核心是LoadDLL(),其中直接调用loadlibrary函数

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
// Load loads DLL file d.Name into memory. It returns an error if fails.
// Load will not try to load DLL, if it is already loaded into memory.
func (d *LazyDLL) Load() error {
    // Non-racy version of:
    // if d.dll == nil {
    if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&d.dll))) == nil {
        d.mu.Lock()
        defer d.mu.Unlock()
        if d.dll == nil {
            dll, e := LoadDLL(d.Name)
            if e != nil {
                return e
            }
            // Non-racy version of:
            // d.dll = dll
            atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&d.dll)), unsafe.Pointer(dll))
        }
    }
    return nil
}
 
func LoadDLL(name string) (*DLL, error) {
    namep, err := UTF16PtrFromString(name)
    if err != nil {
        return nil, err
    }
    var h uintptr
    var e Errno
    if sysdll.IsSystemDLL[name] {
        absoluteFilepathp, err := UTF16PtrFromString(systemDirectoryPrefix + name)
        if err != nil {
            return nil, err
        }
        h, e = loadsystemlibrary(namep, absoluteFilepathp)
    } else {
        h, e = loadlibrary(namep)
    }
    if e != 0 {
        return nil, &DLLError{
            Err:     e,
            ObjName: name,
            Msg:     "Failed to load " + name + ": " + e.Error(),
        }
    }
    d := &DLL{
        Name:   name,
        Handle: Handle(h),
    }
    return d, nil
}

上面说的这几个核心函数都没有直接定义,是使用cgo加上编译选项链接过去的,如下

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
//go:linkname syscall_loadlibrary syscall.loadlibrary
//go:nosplit
//go:cgo_unsafe_args
func syscall_loadlibrary(filename *uint16) (handle, err uintptr) {
    lockOSThread()
    defer unlockOSThread()
    c := &getg().m.syscall
    c.fn = getLoadLibrary()
    c.n = 1
    c.args = uintptr(noescape(unsafe.Pointer(&filename)))
    cgocall(asmstdcallAddr, unsafe.Pointer(c))
    KeepAlive(filename)
    handle = c.r1
    if handle == 0 {
        err = c.err
    }
    return
}
 
//go:linkname syscall_getprocaddress syscall.getprocaddress
//go:nosplit
//go:cgo_unsafe_args
func syscall_getprocaddress(handle uintptr, procname *byte) (outhandle, err uintptr) {
    lockOSThread()
    defer unlockOSThread()
    c := &getg().m.syscall
    c.fn = getGetProcAddress()
    c.n = 2
    c.args = uintptr(noescape(unsafe.Pointer(&handle)))
    cgocall(asmstdcallAddr, unsafe.Pointer(c))
    KeepAlive(procname)
    outhandle = c.r1
    if outhandle == 0 {
        err = c.err
    }
    return
}

其中调用的函数是getLoadLibrary,其中调用了运行时提供的_LoadLibraryW。

1
2
3
4
//go:nosplit
func getLoadLibrary() uintptr {
    return uintptr(unsafe.Pointer(_LoadLibraryW))
}

image.png
getprocaddress是一样的

1
2
3
4
//go:nosplit
func getGetProcAddress() uintptr {
    return uintptr(unsafe.Pointer(_GetProcAddress))
}

image.png
之后就交给运行时去做了,这里不细究。

总结

go的syscall是直接走的kernel32.dll加载的dll以及其中函数,相当于是C的win32api LoadLibrary、GetProcAddress的替代品,把R3接口包装了一下,不过用的是golang runtime。这种情况下,留给用户自定义高级的syscall技术的可操作性基本为0,除非二开syscall库。但是这个复杂度远超C等系统语言实现syscall。因此还是C/Rust香。


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2024-7-23 10:33 被天堂猪0ink编辑 ,原因:
收藏
免费 0
支持
分享
最新回复 (3)
雪    币: 49
活跃值: (2233)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
你可真刑啊!
2024-7-16 12:15
0
雪    币: 3011
活跃值: (953)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
3
flashgg 你可真刑啊!
学习交流
2024-7-16 13:47
0
雪    币: 14484
活跃值: (17463)
能力值: ( LV12,RANK:290 )
在线值:
发帖
回帖
粉丝
4
你可真刑啊!
2024-7-18 14:39
0
游客
登录 | 注册 方可回帖
返回
//