【问题标题】:Executing Byte Array in Go在 Go 中执行字节数组
【发布时间】:2015-05-15 15:38:33
【问题描述】:

我正在尝试在 Go 程序中执行 shellcode,类似于使用其他语言执行此操作的方式。

示例 1 - Shellcode in C program

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

所有方法都有大体相似的技术——通过特定于操作系统的分配(mmap、virtualalloc 等)将 shellcode 分配给可执行内存,然后在执行前通过创建指向该位置的函数指针来执行代码。

这是我在 Go 中执行相同操作的可怕 hacky 示例。 shellcode 在传递给函数之前对其进行了操作,因此它的格式为 []byte 是固定的。说 mmap 需要传入一个文件描述符,这就是为什么存在可怕的“写入 tmp 文件”部分的原因。

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,这意味着编写格式正确的文件(如 Linux 上的 ELF、Windows 上的 PE 等)。不确定 Windows 是否仍然允许运行 MS-DOS COM 格式的文件(非常简单)。
  • 是的,抱歉,我没想到 :)

标签: go function-pointers shellcode


【解决方案1】:

首先,您根本不需要(当前)使用mmap,因为 go memory 是可执行的。如果您确实需要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])))

【讨论】:

  • 感谢您的回答。我将上述内容标记为正确,因为它允许一个稍微简单的解决方案 - 但是我很感激你写的内容也教会了我一些我很欣赏的其他技术。赞成。
  • @JimB :感谢您的简短回答。但是没有Cpackage 怎么办呢?我的意思是禁用 cgo?
  • @user2284570:我不确定这是否可能。所有 Go 程序都是多线程的,可能需要 cgo 使用的运行时钩子才能安全地输入非 Go 代码。
  • @JimB : 如果unsafepackage 没有安装怎么办?
【解决方案2】:

有多种方法可以做到这一点,虽然我自己没有测试过。

您可能想看看https://github.com/nelhage/gojit/blob/master/jit.go 它实现了基于cgo的解决方案(更安全,但速度较慢),以及基于直接调用的解决方案。查看所有与构建相关的函数。

【讨论】:

  • 谢谢 - 这是一个很棒的库,运行良好。
  • @Peleus : 如果 cgo 被禁用?
【解决方案3】:

我试图了解mmap PROT_EXECGo 中的工作方式,并遇到了同样的问题。

我很难将[]byte 正确地转换为函数。这并不像这里的一些答案所说的那么明显。

我想出了一个工作示例,说明如何使用称为mmap-go 的更高级别的mmap 库来完成此操作。在这个例子中,我们有一个函数的可执行代码,它使传递的参数加一。

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 运行时:未知 pc”知道这里发生了什么吗?
  • 似乎与 aslr 相关。禁用它会有所帮助
猜你喜欢
  • 2012-09-08
  • 2013-10-02
  • 2016-04-01
  • 1970-01-01
  • 1970-01-01
  • 2018-05-03
  • 1970-01-01
  • 2022-01-13
相关资源
最近更新 更多