首页 > 其他分享 >Go语言逆向初探

Go语言逆向初探

时间:2025-01-09 21:31:28浏览次数:3  
标签:逆向 get self 初探 offset Go baseAddress idc

前言

       前几日获得一个Go语言编写的程序外挂,分析该外挂过程中发现与C、C++编译出来的二进制文件有很大的不同,相比于传统语言编译出来的可执行文件Go程序在参数传递、栈空间管理和函数调用等方面都有自己的特点。

Go的二进制逆向在互联网上有一篇很全面的文章《Go二进制文件逆向分析从基础到进阶》,建议有兴趣的朋友读一下。珠玉在前为什么还要写这篇文章呢?这是因为Go最新版本1.16有所变化,以前的解析办法已经不适用,这里把自己在逆向Go外挂过程中的一些经验沉淀下来。

Go语言特点介绍

Go是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。Go的语法接近C语言,但对于变量的声明有所不同,与C++相比,Go并不包括如枚举、异常处理、继承、泛型、断言、虚函数等功能,但增加了 切片(Slice) 型、并发、管道、垃圾回收功能、接口等特性的语言级支持[1]。

在这里简单介绍一下在逆向Go程序过程中需要用到的一些特性知识。

1.     反射

反射是逆向最好的朋友,如果一门语言具备反射那么其编译出的可执行文件本身就约等于符号文件,这不是靠strip命令或者加壳压缩就能删除的。最好的证明就是UE4引擎编译出来的游戏程序,UE4采用C++开发,该语言实际不支持反射,但UE4实现了一套反射机制从而泄露了函数名、类等信息,这也是外挂作者最喜欢的东西。反射的实现通常依赖于所谓的Metadata即数据的数据,不同的语言可能有不同的称呼。

作为一篇逆向文章,本文不会介绍Go语言的反射语法或者作用,相反这里讲解的是怎么通过Metadata来读懂二进制文件。在Go语言程序中存在一个叫pcHeader 的结构,也就是所谓的Meatadata。pcHeader 结构参考symtab.go源代码如下图所示,值得注意的是Go 1.16之前的MagicNumber是0xffffffb,之后是0xfffffffa,并且nfunc和nfiles实际占用8个字节。

pcHeader的地址可以搜索FA FF FF FF 00 00 01 08或者FA FF FF FF 00 00 01 04获得,其中x64程序是0x08,32位程序是0x04。

pcHeader + pclnOffset即指向pclntab也就是Go用来描述函数信息的地方,该结构由funcAddress + funcMetaAddress两部分组成。

pcHeader + pclnOffset + funcMetaAddress指向Go的_func结构,具体见symtab.go文件的type pcHeader struct。通过_func中的entry也就是funcAddress和nameOff就可以把函数地址和函数名结合起来。

2.     Stack

Go 语言用的是 continue stack 栈管理机制 [2],并且 Go 语言函数中 callee 的栈空间由 caller 来维护,callee 的参数、返回值都由 caller 在栈中预留空间。详见 The Go low-level calling convention on x86-64[3]。

Go支持goroutine也就是协程,每个goroutine都有自己的栈,其初始栈空间很小并在使用过程中自动增长。这种机制使得Go编译出来的函数在起始处判断当前栈是否够用,如果不够用就分配足够大的新空间并将旧栈数据拷贝到新栈中。该特点表现到二进制中如下,可以利用该特点定位到Go语言函数,当然有部分函数也不会判断栈空间是否够用。

3.     字符串

       Go 二进制文件中的 string 数据不是传统的以 0x00 结尾的 C语言系字符串,而是采用StartAddress + Size的模式,例如下面程序传入的是Hello和Test的起始地址和长度。

Go逆向突破口

所谓的逆向突破口就是把程序中的所有函数标识并命名出来,知道了函数名那么距离解析出具体功能就只差一步之遥。下面是我编写IDA脚本用于半自动重命名函数,具体效果见下图,。

1

<br>

#!/usr/bin/env python

# -*- coding: UTF-8 -*-

'''

SimpleGoParser.py:

IDA Plugin for Golang Executable file parsing.

'''

import idautils, idc, idaapi

import sys

import string

def getstring(addr):

    out = ""

    while True:

        value = idc.get_wide_byte(addr)

        if value != 0:

            out += chr(value)

        else:

            break

        addr += 1

    return out

STRIP_CHARS = [ '(', ')', '[', ']', '{', '}', ' ', '"' ]

REPLACE_CHARS = ['.', '*', '-', ',', ';', ':', '/', '\xb7' ]

def cleanfuncname(funcName):

    for c in STRIP_CHARS:

        funcName = funcName.replace(c, '')

    for c in REPLACE_CHARS:

        funcName = funcName.replace(c, '_')

    return funcName

class Pclntab():

    

    # symtab.go file

    # type pcHeader struct {

    #     magic          uint32  // 0xFFFFFFFA

    #     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.

    #     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 varible from pcHeader

    #     pclnOffset     uintptr // offset to the pclntab variable from pcHeader

    # }

    def __init__(self):

        self.MAGIC = 0xFFFFFFFA

        self.offset = 0

        self.baseAddress = 0

        self.minLC = 0

        self.ptrSize = 0

        self.functionCounts = 0

        self.fileCounts = 0

        self.functionNameOffset = 0

        self.cuOffset = 0

        self.filetabOffset = 0

        self.pctabOffset = 0

        self.functabOffset = 0

    def parse(self, baseAddress):

        print("Pclntab Go")

        magic = idc.get_wide_dword(baseAddress)

        if magic != self.MAGIC:

            print("Pclntab address invalid");

            return False

        

        self.baseAddress = baseAddress

        self.offset = 4 + 2

        self.minLC = idc.get_wide_byte(baseAddress + self.offset)

        self.offset = self.offset + 1

        self.ptrSize = idc.get_wide_byte(baseAddress + self.offset)

        self.offset = self.offset + 1

        self.functionCounts = idc.get_wide_dword(baseAddress + self.offset)

        self.offset = self.offset + 8    #  静态文件中实际占8字节

        self.fileCounts = idc.get_wide_dword(baseAddress + self.offset)

        self.offset = self.offset + 8    #  静态文件中实际占8字节

        self.functionNameOffset = baseAddress + idc.get_qword(baseAddress + self.offset)

        self.offset = self.offset + 8

        self.cuOffset = baseAddress + idc.get_qword(baseAddress + self.offset)

        self.offset = self.offset + 8

        self.filetabOffset = baseAddress + idc.get_qword(baseAddress + self.offset)

        self.offset = self.offset + 8

        self.pctabOffset = baseAddress + idc.get_qword(baseAddress + self.offset)

        self.offset = self.offset + 8

        self.functabOffset = baseAddress + idc.get_qword(baseAddress + self.offset)

        print("Pclntab Base Info")

        print("Min Instuction Size %d Pointer Size %d Function Counts %d File Counts %d" % (self.minLC, self.ptrSize, self.functionCounts, self.fileCounts))

        print("Function Name Offset 0x%x CuOffset 0x%x File Table Offset 0x%x Pctab Offset 0x%x Function Table Offset 0x%x" % (self.functionNameOffset, self.cuOffset, self.filetabOffset, self.pctabOffset, self. functabOffset))

        return True

    def setfuncname(self):

        index = 0

        while index < self.functionCounts:

            funcAddr = idc.get_qword(self.functabOffset + 2 * index * self.ptrSize)

            funcMetaAddr = self.functabOffset + idc.get_qword(self.functabOffset + 2 * index * self.ptrSize + self.ptrSize)

            if funcAddr != idc.get_qword(funcMetaAddr):

                print("Traverse Function Failed")

            else:

                funcNameAddr = self.functionNameOffset + idc.get_wide_dword(funcMetaAddr + self.ptrSize)

                funcName = getstring(funcNameAddr)

                funcName = cleanfuncname(funcName)

                print("Function Address 0x%x Name %s" % (funcAddr, funcName))

                if idc.get_wide_dword(funcAddr) == 0x0c8b4865:

                   idc.create_insn(funcAddr)

                   idc.add_func(funcAddr)

                idc.set_name(funcAddr, funcName, SN_CHECK)

            

            index = index + 1

def main():

    pc = Pclntab()

    if pc.parse(0xE90E60) == True:

        pc.setfuncname()

if __name__ == '__main__':

   main()

   

未完待续

1.        当前脚本需要手动寻找pclntab所在地址,可以优化自动寻找;

2.        Go二进制中的数据结构解析;

3.        很多Go函数IDA不能很好识别,如下所示以字节码(65 48 8B 0C 25 28 00 00 00    mov     rcx, gs:28h)开始的都是函数,即使调用idc.add_func()定义为函数IDA也不能全部转换,不知道道友们否有更好的解决办法

标签:逆向,get,self,初探,offset,Go,baseAddress,idc
From: https://blog.csdn.net/2403_87755661/article/details/145042849

相关文章

  • android逆向—头条新闻app的token算法
    某刷新闻赚钱token算法逆向分析1.前言因为学校被当作高考考点,所以有了几天假期,正好可以用来逆逆前几天找到的一个刷新闻赚钱app。2.工具:Xposed、Charles、反射大师、VMOSPro3.抓包:通过抓包,发现手机验证码提交时有一个token。4.逆向token算法在jadx-gui里直接搜索t......
  • 基于python+Django+mysql校园教室图书馆座位预约网站系统设计与实现
     博主介绍:黄菊华老师《Vue.js入门与商城开发实战》《微信小程序商城开发》图书作者,CSDN博客专家,在线教育专家,CSDN钻石讲师;专注大学生毕业设计教育、辅导。所有项目都配有从入门到精通的基础知识视频课程,学习后应对毕业设计答辩,提供核心代码讲解,答辩指导。项目配有对应开发......
  • 软件系统安全逆向分析-混淆对抗
    1.概述在一般的软件中,我们逆向分析时候通常都不能直接看到软件的明文源代码,或多或少存在着混淆对抗的操作。下面,我会实践操作一个例子从无从下手到攻破目标。花指令对抗虚函数表RC42.实战-donntyousee题目载体为具有漏洞的小型软件,部分题目提供源代码,要求攻击者......
  • 漏洞发现-漏扫项目篇&Nuclei&Yakit&Goby&Afrog&Xray&Awvs&联动中转被动
    知识点1、综合类-Burp&Xray&Awvs&Goby2、特征类-Afrog&Yakit&Nuclei3、联动类-主动扫描&被动扫描&中转扫描章节点:漏洞发现-Web&框架组件&中间件&APP&小程序&系统扫描项目-综合漏扫&特征漏扫&被动漏扫&联动漏扫Poc开发-Ymal语法&联动导入&项目拓展扫描插件-Burpsuite插件&......
  • go语言RSA分段加密V2501
    go语言RSA分段加密V2501,GOLANG,RSA/ECB/PKCS1Padding。 加密解析://假设私钥长度为1024,1024/8-11=117。//如果明文的长度小于117,直接全加密,然后转base64。(data.Length<=maxBlockSize)//如果明文长度大于117,则每117分一段加密,publicKey.Size()刚好是(私钥长度/8)。privateKey.......
  • 2025-01-09:清除数字。用go语言,给定一个字符串 s ,你的任务是执行以下操作,直到字符串中
    2025-01-09:清除数字。用go语言,给定一个字符串s,你的任务是执行以下操作,直到字符串中不再有数字字符:删除第一个出现的数字字符,以及它左侧最近的非数字字符。最终,你需要返回经过操作后剩下的字符串。1<=s.length<=100。s只包含小写英文字母和数字字符。输入保证所......
  • Python+Django鹿幸公司员工在线餐饮管理系统的设计与实现(Pycharm Flask Django Vue m
    收藏关注不迷路,防止下次找不到!文章末尾有惊喜项目介绍Python+Django鹿幸公司员工在线餐饮管理系统的设计与实现(PycharmFlaskDjangoVuemysql)项目展示详细视频演示请联系我获取更详细的演示视频,相识就是缘分,欢迎合作!!!所用技术栈前端vue.js框......
  • Python+Django高校网上缴费综合务系统(Pycharm Flask Django Vue mysql)
    收藏关注不迷路,防止下次找不到!文章末尾有惊喜项目介绍Python+Django高校网上缴费综合务系统(PycharmFlaskDjangoVuemysql)项目展示详细视频演示请联系我获取更详细的演示视频,相识就是缘分,欢迎合作!!!所用技术栈前端vue.js框架支持:django数据库:mysql5.7数......
  • python+django/flask的社区汽车共享平台java+nodejs+php-计算机毕业设计
    目录技术栈和环境说明具体实现截图预期达到的目标系统设计详细视频演示技术路线解决的思路性能/安全/负载方面可行性分析论证python-flask核心代码部分展示python-django核心代码部分展示研究方法感恩大学老师和同学源码获取技术栈和环境说明本系统以Python开发语言......
  • python+django/flask的油田物料管理系统java+nodejs+php-计算机毕业设计
    目录技术栈和环境说明具体实现截图预期达到的目标系统设计详细视频演示技术路线解决的思路性能/安全/负载方面可行性分析论证python-flask核心代码部分展示python-django核心代码部分展示研究方法感恩大学老师和同学源码获取技术栈和环境说明本系统以Python开发语言......