在Go中执行字节数组

7

我正在尝试在Go程序中执行shellcode,类似于其他语言的做法。

示例1 - C程序中的Shellcode

示例2 - http://www.debasish.in/2012/04/execute-shellcode-using-python.html

所有方法的技术大致相似 - 通过操作系统特定分配(mmap、virtualalloc等)将shellcode分配到可执行内存中,然后创建一个指向该位置的函数指针并执行代码。

这是我用Go进行同样操作的可怕示例。在传递给函数之前,对shellcode进行了操作,因此其[]byte格式已固定。尽管如此,由于mmap期望传递文件描述符,因此存在可怕的“写入临时文件”的部分。

func osxExec(shellcode []byte) {
    f, err := os.Create("data/shellcode.tmp")
    if err != nil {
        fmt.Println(err)
    }
    defer f.Close()
    _,_ = f.Write(shellcode)
    f.Sync()

    b, err := syscall.Mmap(int(f.Fd()), 0, len(shellcode), syscall.PROT_READ|syscall.PROT_WRITE|syscall.PROT_EXEC, syscall.MAP_SHARED)
    if err != nil {
        fmt.Println(err)
    }

    fmt.Printf("%p", b)
}

在代码的末尾,我得到了一个指针(切片?)指向我认为是可执行内存中的代码,但我不知道如何将此地址转换为函数指针以进行执行。我在一些IRC频道上提问,但有人建议这可能是不可能的。
非常感谢任何帮助。
干杯。

如果你要将它写入文件,那么最好直接调用syscall.Exec。 - ReyCharles
1
@ReyCharles,这意味着要编写一个格式正确的文件(比如Linux上的ELF,Windows上的PE等)。不确定Windows是否仍允许运行MS-DOS COM格式的文件(这些文件非常简单)。 - kostix
是的,抱歉,我没有考虑清楚 :) - ReyCharles
3个回答

6

首先,由于Go语言的内存是可执行的,因此您目前不需要使用 mmap。如果您确实需要使用mmap,您可以使用匿名内存而无需使用临时文件:

b, e := syscall.Mmap(0, 0, len(shellcode), syscall.PROT_READ|syscall.PROT_WRITE|syscall.PROT_EXEC, syscall.MAP_ANON)
copy(b, shellcode)

否则,您可以尝试直接使用 shellcode ,因为它已经由一个连续的数组支持。
至于将 shellcode 中的字节转换为函数,与C语言中的类比如下:
f := *(*func() int)(unsafe.Pointer(&d[0]))

这会创建一个名为f的函数值,可以像普通函数一样调用。

如果shellcode不是专门针对Go编写的,并且需要从C堆栈中调用它,则更容易直接使用cgo在C中完成。

/*
call(char *code) {
    int (*ret)() = (int(*)())code;
    ret();
}
*/
import "C"

func main() {
    ...
    // at your call site, you can send the shellcode directly to the C 
    // function by converting it to a pointer of the correct type.
    C.call((*C.char)(unsafe.Pointer(&shellcode[0])))

感谢您的回答。我将以上标记为正确,因为它提供了稍微简单一些的解决方案——不过我很感激您所写的内容也教会了我其他一些技巧,非常感谢。已点赞。 - Peleus
@JimB:感谢您的简短回答。但是如果没有C包,如何完成这种操作呢?我的意思是在禁用cgo的情况下。 - user2284570
@user2284570:我不确定是否可能。所有的Go程序都是多线程的,而且可能需要cgo使用的运行时钩子才能安全地进入非Go代码。 - JimB
@JimB: 如果unsafe包没有安装呢? - user2284570

4
有多种方法可以实现,但我自己没有测试过。
您可能需要看一下https://github.com/nelhage/gojit/blob/master/jit.go它基于cgo实现了一种解决方案(更安全但速度较慢),以及一种基于直接调用的解决方案。查看所有与构建相关的函数。

谢谢 - 这是一个非常棒的库,完美地发挥了作用。 - Peleus
@Peleus:如果cgo被禁用了呢? - user2284570

1
我想学习如何在Go中使用mmap PROT_EXEC,但遇到了相同的问题。
我花了很长时间才成功地将[]byte正确转换为函数。这并不像一些答案所述那样显而易见。
我已经找到了一个可行的解决方案,使用一个名为mmap-go的高级mmap库。在这个例子中,我们有一个可执行函数代码,它会将传递的参数加1。
code := []byte{
    0x48, 0xc7, 0x44, 0x24, 0x10, 0x00, 0x00, 0x00, 0x00,
    0x48, 0x8b, 0x44, 0x24, 0x08,
    0x48, 0xff, 0xc0,
    0x48, 0x89, 0x44, 0x24, 0x10,
    0xc3,
}

memory, err := mmap.MapRegion(nil, len(code), mmap.EXEC|mmap.RDWR, mmap.ANON, 0)
if err != nil {
    panic(err)
}

copy(memory, code)

memory_ptr := &memory
ptr := unsafe.Pointer(&memory_ptr)
f := *(*func(int) int)(ptr)

fmt.Println(f(10)) // Prints 11

我曾认为unsafe.Pointer(&memory[0])可以像其中一个答案所建议的那样解决我的问题。但是,通过这样做,您无法将地址转换为正确地址中的可调用函数。诀窍是再往前走一步,做到这一点:

memory_ptr := &memory
ptr := unsafe.Pointer(&memory_ptr)

替代

f := *(*func(int) int)(&memory[0])

这是一个很好的解决方案!但是,我注意到使用您的方法时,随机地(5次运行中的1次),程序无法正确运行f函数,而是抛出“异常0xc00000fd 0x0 0xdc1c003000 0x7ffbb0ab1fe7 PC=0x7ffbb0ab1fe7 runtime: unknown pc”的错误。您有什么想法这里发生了什么? - Sebastian Haas
似乎与ASLR有关。禁用它可以帮助解决问题。 - Sebastian Haas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接