过卡巴佬的【go二进制逆向反射符号&病毒分析】

admin 2026-03-27 01:19:18 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文深入探讨了Go二进制文件的逆向分析与病毒分析技术。首先介绍了如何通过IDAPro和Go官方命令查询Go语言版本,接着详细解析了pclntab(程序计数器线性表)的结构,包括pcHeader和moduledata结构体,并指导如何通过magic值定位关键数据。随后,文章提供了一份IDAPro的Python脚本,用于自动修复并重命名代码中的函数,以还原其原始符号信息。最后,以一个Go编写的恶意样本为例,分析了其具备的心跳包、进程注入、文件传输、凭证窃取等功能,并给出了相关的IOC(指标)。 综合评分: 85 文章分类: 二进制安全,逆向分析,恶意软件,安全工具,实战经验


cover_image

过卡巴佬的【go二进制逆向反射符号&病毒分析】

Kei sec

2026年3月20日 00:17 上海

编者荐语:

推荐一下某佬(逆向过卡巴dump内存)的go二进制逆向反射符号文章

以下文章来源于ThreatSafe Lab ,作者cooper

ThreatSafe Lab .

聚焦恶意情报与病毒分析

查询版本

Go 二进制文件中,还会把构建本文件的 Go 语言版本号打包进去。查看方式有以下 2 种:

  1. 通过 IDAPro 中的 strings 检索:

  1. 通过 Go 语言官方命令 $ go version [FILE] ,该命令不指定文件则打印本地安装的 Go 语言版本,指定文件则打印目标文件对应的 Go 语言版本:

Pclntab

  1. 查询对应版本的pcHeader,以及获取 moduledata 结构 源代码 https://github.com/golang/go/blob/go1.23.1/src/runtime/symtab.go
   // pcHeader holds data used by the pclntab lookups.
   type pcHeader struct {
    magic          uint32// 0xFFFFFFF1
    pad1, pad2     uint8   // 0,0
    minLC          uint8   // min instruction size
    ptrSize        uint8   // size of a ptr in bytes
    nfunc          int     // number of functions in the module
    nfiles         uint    // number of entries in the file tab
    textStart      uintptr// base for function entry PC offsets in this module, equal to moduledata.text
    funcnameOffset uintptr// offset to the funcnametab variable from pcHeader
    cuOffset       uintptr// offset to the cutab variable from pcHeader
    filetabOffset  uintptr// offset to the filetab variable from pcHeader
    pctabOffset    uintptr// offset to the pctab variable from pcHeader
    pclnOffset     uintptr// offset to the pclntab variable from pcHeader
   }
   // moduledata records information about the layout of the executable
   // image. It is written by the linker. Any changes here must be
   // matched changes to the code in cmd/link/internal/ld/symtab.go:symtab.
   // moduledata is stored in statically allocated non-pointer memory;
   // none of the pointers here are visible to the garbage collector.
   type moduledata struct {
    sys.NotInHeap // Only in static data

    pcHeader     *pcHeader
    funcnametab  []byte
    cutab        []uint32
    filetab      []byte
    pctab        []byte
    pclntable    []byte
    ftab         []functab
    findfunctab  uintptr
    minpc, maxpc uintptr

    text, etext           uintptr
    noptrdata, enoptrdata uintptr
    data, edata           uintptr
    bss, ebss             uintptr
    noptrbss, enoptrbss   uintptr
    covctrs, ecovctrs     uintptr
    end, gcdata, gcbss    uintptr
    types, etypes         uintptr
    rodata                uintptr
    gofunc                uintptr// go.func.*

    textsectmap []textsect
    typelinks   []int32// offsets from types
    itablinks   []*itab

    ptab []ptabEntry

    pluginpath string
    pkghashes  []modulehash

   // This slice records the initializing tasks that need to be
   // done to start up the program. It is built by the linker.
    inittasks []*initTask

    modulename   string
    modulehashes []modulehash

    hasmain uint8// 1 if module contains the main function, 0 otherwise
    bad     bool// module failed to load and should be ignored

    gcdatamask, gcbssmask bitvector

    typemap map[typeOff]*_type // offset to *_rtype in previous module

    next *moduledata
   }
  1. 通过magic          uint32  // 0xFFFFFFF1  定位到pcHeader 之后交叉引用找到moduledata

  1. 通过pclnOffset  (偏移与pcHeader) 定位 0x11F2DE0

  1. pcln对应的结构体如下
   //src\runtime\symtab.go
   type functab struct {
       entryoff uint32 // relative to runtime.text
       funcoff  uint32
   }

   type funcInfo struct {
       *_func
       datap *moduledata
   }

   //src\runtime\runtime2.go
   type _func struct {
       entry   uintptr // start pc
       nameoff int32   // function name

       args        int32  // in/out args size
       deferreturn uint32 // offset of start of a deferreturn call instruction from entry, if any.

       pcsp      uint32
       pcfile    uint32
       pcln      uint32
       npcdata   uint32
       cuOffset  uint32  // runtime.cutab offset of this function's CU
       funcID    funcID  // set for certain special runtime functions
       _         [2]byte // pad
       nfuncdata uint8   // must be last
   }

对应着一个 funcInfo 结构体, 里面包含一个 _func 类型,该类型中有我们想要的信息。 意思就是: func 的 _func = pclntable + funcOff 通过上图的信息计算:

0x11F2DE0+ 0x0D5B0 = 0x1200390 对应_func

  1. 函数结构名称偏移与对应

IDA 代码修复patch代码

import idc
from idc import *
import ida_nalt

moduledata_addr = addr
pcHeader_addr = idc.get_qword(moduledata_addr)

if idc.get_wide_dword(pcHeader_addr) != 0x0FFFFFFF0:
    print(idc.get_wide_dword(pcHeader_addr))
    print("错误,并不是一个正确的go文件")

funcnametable_addr = idc.get_qword(moduledata_addr + 8)
filetab_addr = idc.get_qword(moduledata_addr + 8 + ((8*3) * 2))
pclntable_addr = idc.get_qword(moduledata_addr + 8 + ((8*3) * 4))
pclntable_size = idc.get_qword(moduledata_addr + 8 + ((8*3) * 4) + (8 * 4))
set_name(moduledata_addr, "firstmoduledata")
set_name(funcnametable_addr, "funcnametable")
set_name(filetab_addr, "filetab")
set_name(pclntable_addr, "pclntable")

print(pclntable_size)

def readString(addr):
    ea = addr

    res = ''
    cur_ea_db = get_db_byte(ea)
    while  cur_ea_db != 0and cur_ea_db != 0xff:
        res += chr(cur_ea_db)
        ea += 1
        cur_ea_db = get_db_byte(ea)
    return res

def relaxName(name):
    # 将函数名称改成ida 支持的字符串
    #print(name)
    if type(name) != str:
        name = name.decode()
&nbsp; &nbsp; name = name.replace('.',&nbsp;'_').replace("<-",&nbsp;'_chan_left_').replace('*',&nbsp;'_ptr_').replace('-',&nbsp;'_').replace(';','').replace('"',&nbsp;'').replace('\\',&nbsp;'')
&nbsp; &nbsp; name = name.replace('(',&nbsp;'').replace(')',&nbsp;'').replace('/',&nbsp;'_').replace(' ',&nbsp;'_').replace(',',&nbsp;'comma').replace('{','').replace('}',&nbsp;'').replace('[',&nbsp;'').replace(']',&nbsp;'')
&nbsp; &nbsp;&nbsp;return&nbsp;name

cur_addr =&nbsp;0
for&nbsp;i&nbsp;in&nbsp;range(pclntable_size):

&nbsp; &nbsp;&nbsp;# 获取函数信息表
&nbsp; &nbsp; cur_addr = pclntable_addr + (i *&nbsp;8)

&nbsp; &nbsp;&nbsp;# 获取函数入口偏移
&nbsp; &nbsp; funcentryOff = get_wide_dword(cur_addr)
&nbsp; &nbsp; funcoff = get_wide_dword(cur_addr +&nbsp;4)

&nbsp; &nbsp; funcInfo_addr = pclntable_addr + funcoff

&nbsp; &nbsp; funcentry_addr = get_wide_dword(funcInfo_addr)
&nbsp; &nbsp; funnameoff = get_wide_dword(funcInfo_addr +&nbsp;4)
&nbsp; &nbsp; funname_addr = funcnametable_addr + funnameoff
&nbsp; &nbsp; funname = readString(funname_addr)

&nbsp; &nbsp;&nbsp;# 真实函数地址
&nbsp; &nbsp; truefuncname = relaxName(funname)
&nbsp; &nbsp; truefuncentry = ida_nalt.get_imagebase() +&nbsp;0x1000&nbsp;+ funcentryOff

&nbsp; &nbsp; print(hex(truefuncentry), hex(funcoff), hex(funcInfo_addr),hex(funcentry_addr), hex(funnameoff),hex(funname_addr) ,funname)
&nbsp; &nbsp;&nbsp;# 改名
&nbsp; &nbsp; set_name(truefuncentry, truefuncname)

样本分析

最新的ida 9.0以上已经支持解析go反射符号,以该go木马为例

分析其功能如下

  • 心跳包

  • cmd_spaw_inject

  • 文件上传下载

  • 凭证窃取&进程kill

  • cmd&powershell

  • Assembly&端口扫描

IOC

  • C&C: 149.104.104.244
  • sha1:f1c494f8e6f6c4461bd2d5142137b5effe2b757a

好久没发文章了,等我最近有空挤一篇“

加解密签名对抗

”文章


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:Kei sec 《过卡巴佬的【go二进制逆向反射符号&病毒分析】》

脑筋急转弯两题 网络安全文章

脑筋急转弯两题

文章总结: 本文分享了两道脑筋急转弯类的题目及解答。第一题通过分析整数解,得出xy的值可能为-6、4、-10或0。第二题在分析二次函数恒大于0的条件时,还需考虑
评论:0   参与:  0