首页
论坛
课程
招聘
[原创]GO恶意样本分析
2020-12-2 11:32 2185

[原创]GO恶意样本分析

2020-12-2 11:32
2185

GO二进制分析与整理

  Go 语言是一个比较新的强类型静态语言,2009 年由 Google 发布,在 2012 年才发布首个稳定版。Go 语言靠 Goroutine 和 channel、wait group、select、context 以及 sync 等辅助机制实现的 CSP 并发模型,以简洁高效编写高并发程序为亮点,很快就在全球范围内吸引大量开发者使用其编写各种程序。现在 Go 已成为云原生领域的首选开发语言。

 

  由于 Go 语言上手快,编码效率高,程序运行效率也高,而且很方便跨平台交叉编译,也吸引了恶意软件开发者的注意。渐渐地,安全厂商开始捕获到越来越多的 Go 语言编写的恶意软件 ,这些恶意软件横跨 Windows、Linux 和 Mac 三大平台,并且多个APT组织开始使用go语言构建他们的武器库,并且用于实际的攻击活动中,因此可以预见在不久的将来,会有越来愈多的go语言的恶意程序出现。

 

  在本文中,整理了作为一个样本分析人员该关注的go语言的一些基础知识,有错误的地方,烦请指正。

go version

1
2
remnux@remnux:~/go$ go version
go version go1.10.4 linux/amd64

go build

  在go语言的编译过程中,会全静态链接构建二进制文件,把标准库函数和第三方package 全部做了静态编译,在加上go语言还包含runtime代码和gc代码,即使做strip操作,(go build -ldflags="-s -w")生成的二进制文件的体积依然很大,在使用IDA打开go二进制文件时,会发现有几千个函数。
使用如下的代码编译一个简单的go二进制程序。

1
2
3
4
5
6
//helloworld.go
package main
import "fmt"
func main() {
    fmt.Printf("Καλημέρα κόσμε; or こんにちは 世界\n")
}

go build 产生的二进制文件的函数如下图

 

Alt text

 

  当我们使用go build -ldflags="-s"去除符号时,函数如下图,可以看到很多函数都已经识别不了。

 

Alt text

go语言的数据类型

  在go语言中,interface是一个非常重要的抽象数据类型,用与描述go语言中的其他具体的数据类型,如int,string。根据有无方法绑定,可以分为eface和iface。
在go语言中,基础的数据类型,如果没有绑定Method的情况下,在使用时,会将其转化为eface,具体的转化函数runtime_convT2E*

 

Alt text

 

eface的结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
type eface struct {
    _type *_type            //_type指针
    data  unsafe.Pointer    //数据
}
 
type _type struct {
    size       uintptr
    ptrdata    uintptr // size of memory prefix holding all pointers
    hash       uint32
    tflag      tflag
    align      uint8
    fieldAlign uint8
    kind       uint8
    // function for comparing objects of this type
    // (ptr to object A, ptr to object B) -> ==?
    equal func(unsafe.Pointer, unsafe.Pointer) bool
    // gcdata stores the GC type data for the garbage collector.
    // If the KindGCProg bit is set in kind, gcdata is a GC program.
    // Otherwise it is a ptrmask bitmap. See mbitmap.go for details.
    gcdata    *byte
    str       nameOff
    ptrToThis typeOff
}

runtime_convT2Estring
Alt text

 

  当一个类型绑定了Method的时候,并且存在data数据时,使用iface 进行表示。在没有data 的情况下,会利用runtime_itablink数组中保存保存的itab指针进行访问。
使用如下的代码进行验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main
 
import (
    "fmt"
)
 
type MyInterface interface {
    Print()
}
 
type MyStruct struct{
        bookid int
}
func (ms MyStruct) Print() {}
 
func main() {
    x := 1
    var y interface{} = x
    var s MyStruct
    s.bookid = 0x1000
    var t MyInterface = s
    fmt.Println(y, t)
}

当执行var t MyInterface = 这句代码时,可以看到在汇编代码中调用runtime_convT2I,生成iface。

 

Alt text

 

Alt text

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type iface struct {
    tab  *itab
    data unsafe.Pointer
}
//
layout of Itab known to compilers
// allocated in non-garbage-collected memory
// Needs to be in sync with
// ../cmd/compile/internal/gc/reflect.go:/^func.dumptypestructs.
type itab struct {
    inter  *interfacetype
    _type  *_type
    link   *itab
    bad    int32
    inhash int32      // has this itab been added to hash?
    fun    [1]uintptr // variable sized
}

go语言程序启动过程

  go语言的程序的具体启动流程的分析可以学习《Go语言程序初始化过程》,在分析恶意go语言的恶意样本时需要关注go语言的启动过程的点是在main.main()执行之前,可以人为实现的函数main.init(),init主要实现一些包级别的初始化的操作。所以一般在分析go恶意程序的时候,也需要关注一下mian.init()函数。

  • 是否在init()函数中有anti的动作。
  • 通过初始化的包可以对样本的动作有一定的预判。
    如下图为apt28 go zebrocy样本中的main.init(),可以看到初始化了aes,image_png ,http等包。
    Alt text

栈管理

  go语言在1.3版本之后,开始使用连续栈,其基本的思路是,在函数入口出进行栈溢出检查,当栈顶的地址小于stackguard时,则会调用runtime.morestack分配新的栈,并将原来栈的数据拷贝到新的栈然后继续执行。
Alt text

 

  这在逆向分析过程中,尤其是在动态分析时,可能的一种场景是,当我们不关注函数具体细节时,就会直接步过函数,然后在函数调用前后观察参数和返回值的内存数据,如果分配新的栈之后,关注的数据的内存地址也会发生改变。
使用如下的代码来进行验证(直接复制了《解密Go协程的栈内存管理》代码)

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
package main
 
func main() {
     var x [10]int
     println(&x)
     a(x)
     println(&x)
}
 
//go:noinline
func a(x [10]int) {
     println(`func a`)
     var y [100]int
     b(y)
}
 
//go:noinline
func b(x [100]int) {
     println(`func b`)
     var y [1000]int
     c(y)
}
 
//go:noinline
func c(x [1000]int) {
     println(`func c`)
}

当我们在分析是应该关注一个函数在调用前后rsp(esp)是否发生变化来判断是否被分配了新的栈。如果分配了新的栈,需要重新定位内存数据。如下图为调用main_a函数前后rsp的变化情况。
Alt text
Alt text

go语言的传参和返回值

  go语言的函数参数和返回值都是通过栈来传递的,并且是支持多值返回的,我们来看下go语言的参数传递的实现以及在逆向go语言二进制程序时应该要注意的问题。
我们使用如下的代码进行分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main
 
import "fmt"
 
func test(i, j int) (int, int) {
    a:=i+ j
    b:=i- j
    return a,b
}
 
func main() {
    a,b:= test(2,1)
    fmt.Println(a, b)
}

在build时禁止优化

1
go build -gcflags '-N -l'

Alt text

 

  此时的栈的数据分布如下图所示。所以不管时多值返回返回还是单值返回,调用函数都会为返回值预留栈空间以保存返回值。
Alt text

 

  在函数内部,IDA会统一将参数和返回值都识别成函数参数。在一般情况下,会在函数快结束位置对返回值进行赋值操作,以此来鉴别参数和返回值。
Alt text

分析工具

IDAGolangHelper
go_parser

学习到了什么

  • go语言的参数传递和返回值通过栈来进行传递的。
  • go语言的类型的底层实现。
  • go语言程序的启动过程。

深入解析go

 

Go二进制文件逆向分析从基础到进阶——综述

 

Go Interface 源码剖析

 

五分钟理解golang的init函数

 

解密Go协程的栈内存管理


看雪社区年底排行榜,查查你的排名?

收藏
点赞2
打赏
分享
最新回复 (4)
雪    币: 4665
活跃值: 活跃值 (2230)
能力值: (RANK:442 )
在线值:
发帖
回帖
粉丝
顾何 活跃值 8 2020-12-2 14:33
2
0
感谢分享,golang恶意样本的相关资料比较少,欢迎后面继续在此方向添砖添瓦,让更多人了解如何分析golang样本。期待下次关于golang恶意样本的完整分析
雪    币: 18
活跃值: 活跃值 (130)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wx_0xC05StackOver 活跃值 2020-12-2 15:26
3
0
文章写得很好 +1
雪    币: 56
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_gwigygyz 活跃值 2020-12-5 23:09
4
0
拜谢大佬分享
雪    币: 2242
活跃值: 活跃值 (1320)
能力值: ( LV4,RANK:55 )
在线值:
发帖
回帖
粉丝
独钓者OW 活跃值 1 2020-12-7 09:25
5
0
感谢分享,期待后续的分析
游客
登录 | 注册 方可回帖
返回