在某一次投稿时候,审核的大佬发现我分析的偏了(其实就是菜),分析了很多的库函数,还犯了些很有意思的错误,既然分析偏了,那就把go的加载过程好好研究一番,弄明白了,本篇文章只是站在样本分析的角度去看go的加载,更加的详细的直接看底部的引用链接。
当我们将样本投入exeinfo
或者die
查壳发现是go的样本,然后在投入ida
分析的时候,我们发现ida
没有识别函数符号,都是sub_xxxxxx
这样的函数,此时我们是没有办法分析的,我们需要加载go的符号表
https://github.com/sibears/IDAGolangHelper
git clone
之后,将GO_Utils
和go_entry.py
移动到ida
的python
内
然后重启ida
,File——>Script Command
,载入插件
然后点击run
看以上图片 ,第一步第二步是检测样本go版本,检测结果会输出到OUTPUT Windows
基本就识别出来了,function names
对应的函数名也会相应的显示出来
类似的项目还有
当我们开始之前,我们先看一个图
因为golang最关键的就是runtime
,有点类似java
的虚拟机,不过runtime
不是虚拟机,把它理解成标准库更贴切,这张图目的就是让我们更好的了解,当样本被执行的时候,究竟都发生了什么,有一个整体分析的思路
我们这里通过一个小的样本来说明,当我们直接把样本投入到IDA
进行调试
直接停在了_rt0_amd64_windows
,那为什么会停在这个函数?
这里就不得不提一下程序的执行过程中的编译过程
和链接过程
编译过程是把预处理完的文件进行一系列词法分析,语法分析,语义分析,优化后生成相应的汇编代码文件。
链接过程是把源代码模块独立的编译,然后按照需要将他们“组装”起来,这其中包括地址和空间分配,符号,重定位等等
go
也不例外,go
需要使用链接器将单个的文件进行链接组装,我们来简单看一下代码
https://tip.golang.org/src/cmd/link/internal/ld/lib.go
我们综合代码分析出,如果用户没有指定用户地址,那么在x84_64
下生成的exe
程序默认入口地址就是_rt0_amd64_windows
,这时候你分析样本时候,样本在ida
一打开,如果是PE
的就会自动跳转到_rt0_amd64_windows
,如果是ELF
就会自动跳转到_rt0_amd64_linux
然后我们F8
继续调试,程序进行一系列的调用
go bootstarp的流程就是首先,检查cpu
是否符合,配置好样本运行相关的参数,然后,进行tls初始化(具体看第三个链接),然后调用runtime.args
、runtime.osinit
、runtime.schedinit
配置好样本加载时候的各种变量以及调度器,总结如下
然后执行到main
这,这块基本就执行攻击者自己实现的恶意代码了,就是以main_xxxx
开头的基本都是攻击者自己实现的,我们分析的时候主要分析这块就可以
那么,我们了解完以上,如果攻击者自己实现了一个包或者模块时候,会在main_main
之前启动,比如说init_xxx()
,xxxx_init
,xxx_initx
等等,常用来运行前的注册,比如说decoder,parser的注册,运行时只需计算一次的模块,比如sync.once
或者全局数据库连接句柄的初始化等等
我们怎么判断是不是,init()
函数具有以下特点
https://jiayu0x.com/2020/09/28/go-binary-reverse-engineering-tips-and-example/
https://www.cnblogs.com/qcrao-2018/p/11124360.html
https://www.cnblogs.com/flhs/p/12657348.html
https://jiayu0x.com/2020/09/28/go-binary-reverse-engineering-tips-and-example/
https://blog.iceinto.com/posts/go/start/
https://zhuanlan.zhihu.com/p/111370792
https:
/
/
github.com
/
strazzere
/
golang_loader_assist
https:
/
/
github.com
/
0xjiayu
/
go_parser
https:
/
/
github.com
/
strazzere
/
golang_loader_assist
https:
/
/
github.com
/
0xjiayu
/
go_parser
.text:
0000000000465CC0
; [
00000005
BYTES: COLLAPSED FUNCTION _rt0_amd64_windows. PRESS CTRL
-
NUMPAD
+
TO EXPAN
.text:
0000000000465CC0
; [
00000005
BYTES: COLLAPSED FUNCTION _rt0_amd64_windows. PRESS CTRL
-
NUMPAD
+
TO EXPAN
/
/
linux build
if
*
flagEntrySymbol
=
=
"" {
switch ctxt.BuildMode {
case BuildModeCShared, BuildModeCArchive:
*
flagEntrySymbol
=
fmt.Sprintf(
"_rt0_%s_%s_lib"
, buildcfg.GOARCH, buildcfg.GOOS)
case BuildModeExe, BuildModePIE:
*
flagEntrySymbol
=
fmt.Sprintf(
"_rt0_%s_%s"
, buildcfg.GOARCH, buildcfg.GOOS)
case BuildModeShared, BuildModePlugin:
/
/
No
*
flagEntrySymbol
for
-
buildmode
=
shared
and
plugin
default:
Errorf(nil,
"unknown *flagEntrySymbol for buildmode %v"
, ctxt.BuildMode)
}
}
/
/
linux build
if
*
flagEntrySymbol
=
=
"" {
switch ctxt.BuildMode {
case BuildModeCShared, BuildModeCArchive:
*
flagEntrySymbol
=
fmt.Sprintf(
"_rt0_%s_%s_lib"
, buildcfg.GOARCH, buildcfg.GOOS)
case BuildModeExe, BuildModePIE:
*
flagEntrySymbol
=
fmt.Sprintf(
"_rt0_%s_%s"
, buildcfg.GOARCH, buildcfg.GOOS)
case BuildModeShared, BuildModePlugin:
/
/
No
*
flagEntrySymbol
for
-
buildmode
=
shared
and
plugin
default:
Errorf(nil,
"unknown *flagEntrySymbol for buildmode %v"
, ctxt.BuildMode)
}
}
/
/
windows build
if
(INITENTRY
=
=
nil) {
INITENTRY
=
mal(strlen(goarch)
+
strlen(goos)
+
20
);
if
(!flag_shared) {
sprint(INITENTRY,
"_rt0_%s_%s"
, goarch, goos);
}
else
{
sprint(INITENTRY,
"_rt0_%s_%s_lib"
, goarch, goos);
}
}
lookup(INITENTRY,
0
)
-
>
type
=
SXREF;
/
/
windows build
if
(INITENTRY
=
=
nil) {
INITENTRY
=
mal(strlen(goarch)
+
strlen(goos)
+
20
);
if
(!flag_shared) {
sprint(INITENTRY,
"_rt0_%s_%s"
, goarch, goos);
}
else
{
sprint(INITENTRY,
"_rt0_%s_%s_lib"
, goarch, goos);
}
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2021-6-25 10:34
被HexChristmas编辑
,原因: