前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >共识异常注入兼容方案

共识异常注入兼容方案

原创
作者头像
KunkkaWu
发布2024-01-04 10:08:36
3140
发布2024-01-04 10:08:36
举报
文章被收录于专栏:算法协议算法协议

1. 背景介绍

目前consensus-attack库,基于TBFT共识算法的异常注入功能已实现。但是需要依赖于对consensus-tbft代码库的引入。通过gomonkey对原consensus-tbft代码库中的函数实现打桩替换。使得chainmaker-go在运行时,原本的共识逻辑被篡改,从而实现共识算法的异常注入。

1.1 兼容问题

由于目前gomonkey提供的打桩函数替换功能,需要提供原始函数。因此consensus-attack在对gomonkey使用的时候,需要引入consensus-tbft代码库。

目前consensus-tbft代码库:300版本和23x版本差异比较大。如果需要支持对这两个版本的共识算法实现异常注入,那么就需要引入不同版本的consensus-tbft代码库。但是不同版本的代码库对common等库的依赖有冲突。

因此,如何解决:

  1. 共识异常注入兼容两个不同版本 ?
  2. 共识异常注入支持多种不同的共识算法?

2. pclntab & moduledata

pclntab 全名是 Program Counter Line Table,可直译为 程序计数器行数映射表, 在 Go 中也叫Runtime Symbol Table, 所以我把它里面包含的信息叫做 RTSI(Runtime Symbol Information)

Moduledata 在 Go 二进制文件中也是一个更高层次的数据结构,它包含很多其他结构的索引信息,可以看作是 Go 二进制文件中 RTSI(Runtime Symbol Information) 和 RTTI(Runtime Type Information) 的 地图

moduledata中的ftab []functab中保存了所有函数的地址。因此,可以通过遍历函数地址,获取到我们所需要的目标函数。

因此,可以使用moduledata来根据函数名称获取函数的地址,就可以避免对原始公式算法包的引入。

3. 方案设计

3.1 Golang版本兼容设计

由于不同版本的Golang,在对moduledata的实现和使用上是有差异的。因此针对不同版本的Golang在实现对moduledata解析所提供的功能方法也是有所差异的。因此,通过定义接口来约束moduledata解析器。

3.2 Moduledata解析器接口定义

代码语言:go
复制
// ModuledataParser 通过moduledata获取运行时函数信息
type ModuledataParser interface {
    // GetFuncUintPtrByName 通过函数名获取函数起始地址
    GetFuncUintPtrByName(string) uintptr
    // GetVarUintPtrByName 通过变量名称获取变量地址
    GetVarUintPtrByName(string) uintptr
    // SupportVersions 获取当前支持的golang版本
    SupportVersions() []string
}

3.3 不同版本实现Moduledata解析器

文件 parser118.go 针对go1.18版本的moduledata解析器实现:

代码语言:go
复制
var (
    // parser118 支持的版本列表
    parser118SupportVersion = []string{Version118, Version119}
)

// parser118 实现了ModuledataParser接口
type parser118 struct {
    supportVersions []string
}

func NewParser118() ModuledataParser {
    return &parser118{
        supportVersions: parser118SupportVersion,
    }
}

// GetFuncUintPtrByName 通过函数名称获取函数起始地址
func (h *parser118) GetFuncUintPtrByName(name string) uintptr {
    for dataP := &firstmoduledata; dataP != nil; dataP = dataP.next {
        for i := 0; i < len(dataP.ftab); i++ {
            fp := runtime.FuncForPC(uintptr(dataP.ftab[i].entryoff) + dataP.text)
            if name == fp.Name() {
                //file, line := fp.FileLine(uintptr(dataP.filetab[i]))
                //fmt.Printf("name: %s, entry: %x, file:%s, line:%d\n", fp.Name(), fp.Entry(), file, line)
                entry := fp.Entry()
                return entry
            }
        }
    }
    return 0
}

// GetVarUintPtrByName 通过变量名称获取变量的地址
func (h *parser118) GetVarUintPtrByName(name string) uintptr {
    return 0
}

// SupportVersions 支持的版本列表
func (h *parser118) SupportVersions() []string {
    return h.supportVersions
}

3.4 版本控制设计

实现的每个版本moduledata解析器,都需要注册到Version控制器中,版本控制器提供方法根据当前Golang的版本,选择出适合的moduledata解析器。

代码语言:go
复制
// versionCtrl 版本控制器
type versionCtrl struct {
    parsers []func() ModuledataParser
}

// ChooseHackerByGoVersion 根据当前环境go版本,选择对应的hacker
func (v *versionCtrl) ChooseParserByGoVersion() ModuledataParser {
    versionParts := strings.Split(runtime.Version(), ".")
    major, _ := strconv.Atoi(versionParts[0][2:])
    minor, _ := strconv.Atoi(versionParts[1])
    if major < 1 || (major == 1 && minor < 18) {
        // 版本小于1.18
        return v.getParserByVersion(Version116)
    }
    return v.getParserByVersion(Version118)
}

func (v *versionCtrl) getParserByVersion(version string) ModuledataParser {
    for _, hack := range v.parsers {
        hacker := hack()
        versions := hacker.SupportVersions()
        for _, ver := range versions {
            if ver == version {
                return hacker
            }
        }
    }
    return nil
}

3.5 数据注入设计

通用的异常注入框架,不再需要对共识算法和版本的代码引入。但是需要实现通用的接口,来实现注入的数据源。

任意一个共识算法的注入攻击,都必须先实现接口提供攻击的对象等信息。

代码语言:go
复制
const (
    // InjectTypeFunc 注入类型为函数或者方法
    InjectTypeFunc = InjectType("func")
    // InjectTypeVar 注入类型为 变量
    InjectTypeVar = InjectType("var")
)

// InjectType 注入类型
type InjectType string

type Injection interface {
    // InjectReplacerMap 攻击函数映射: 目标函数名 => 新函数
    // 例如:main.(*person).say => {Type:"func", Replacer:attackSay()}
    InjectReplacerMap() map[string]InjectReplacer
    // ConsensusType 共识类型
    ConsensusType() consensus.Type
    // ConsensusVersion 共识版本
    ConsensusVersion() consensus.Version
}

// InjectReplacer 替换的新结构
// Type:func 时: Replacer : attackSay()
// Type: var 时:Replacer:var
type InjectReplacer struct {
    Type     InjectType
    Replacer interface{}
}

对于任意一个共识算法的注入,需要实现接口Injection。 其中InjectReplacerMap() 方法约束了,攻击目标名称和替换后新的对象。

  • InjectReplacerMaptypefunc的时候, Replacer为替换的函数。
  • InjectReplacerMaptypevar的时候, Replacer为替换的变量。

3.6 注入攻击设计

共识异常注入consensusHacker 需要实现Hacker接口。接口中约束了四个方法。

代码语言:go
复制
// hacker 注入攻击
type hacker interface {
    // Attack 攻击指定的函数
    Attack(targetName string)
    // Recover 撤销指定函数的攻击
    Recover(targetName string)
    // AttackingList 正在被攻击的函数列表
    AttackingList() []string
    // RecoverAll 撤销所有攻击
    RecoverAll()
}

consensusHacker的实现示例:

代码语言:go
复制
func NewConsensusHacker(injection consensus.Injection, logger *logger.CMLogger) *consensusHacker {
    ch := &consensusHacker{
        logger: logger,
    }
    // 检查注入的共识和版本是否合法
    ty := injection.ConsensusType()
    ver := injection.ConsensusVersion()
    if !consensus.CheckTypeAndVersionValid(ty, ver) {
        ch.logger.Errorf("check type [%s] and version [%s] failed! ", ty, ver)
        return nil
    }
    ch.ConsensusInjection = injection
    // 通过版本控制器,获取合适的解析器
    versionCtrl := parser.NewVersionCtrl()
    ch.Parser = versionCtrl.ChooseParserByGoVersion()
    ch.logger.Infof("New Consensus Hacker For [%s : %s]", ty, ver)
    return ch
}

// consensusHacker 共识算法入侵
type consensusHacker struct {
    ConsensusInjection consensus.Injection
    Parser             parser.ModuledataParser
    monkeyPatches      map[string]*monkey.Patches
    logger             *logger.CMLogger
}

// Attack 攻击目标函数
func (c *consensusHacker) Attack(targetName string) {
    funcMap := c.ConsensusInjection.InjectReplacerMap()
    if f, exist := funcMap[targetName]; exist {
        var uintPtr uintptr
        switch f.Type {
        case consensus.InjectTypeFunc:
            uintPtr = c.Parser.GetFuncUintPtrByName(targetName)
            // TODO 对 f.Replacer 类型检查

        case consensus.InjectTypeVar:
            uintPtr = c.Parser.GetVarUintPtrByName(targetName)
            // TODO 对 f.Replacer 类型检查
        }
        c.monkeyPatches[targetName] = monkey.ApplyUintPtr(uintPtr, f)
    }
}

// Recover 恢复目标函数
func (c *consensusHacker) Recover(targetName string) {
    if patches, exist := c.monkeyPatches[targetName]; exist {
        patches.Reset()
        delete(c.monkeyPatches, targetName)
    }
}

// RecoverAll 恢复所有正在攻击的函数
func (c *consensusHacker) RecoverAll() {
    for _, patches := range c.monkeyPatches {
        patches.Reset()
    }
    c.monkeyPatches = make(map[string]*monkey.Patches)
}

// AttackingList 获取正在被攻击的函数名称列表
func (c *consensusHacker) AttackingList() []string {
    var names []string
    for targetName, _ := range c.monkeyPatches {
        names = append(names, targetName)
    }
    return names
}

3.7 gomonkey改造

当前调研和使用的gomonkey如果要打桩注入,target目标,需要传入函数或者方法。因此,要实现上述通过函数名称实现对目标函数打桩注入的话,需要基于gomonkey底层提供的replace方法,实现一个ApplyUintPtr(target uintptr, double interface{}) *Patches 方法。

通过moduledata解析器,对函数名称解析处 uintptr地址,再通过新实现的ApplyUintPtr()方法,完成对目标函数的打桩注入。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 背景介绍
    • 1.1 兼容问题
    • 2. pclntab & moduledata
    • 3. 方案设计
      • 3.1 Golang版本兼容设计
        • 3.2 Moduledata解析器接口定义
          • 3.3 不同版本实现Moduledata解析器
            • 3.4 版本控制设计
              • 3.5 数据注入设计
                • 3.6 注入攻击设计
                  • 3.7 gomonkey改造
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
                  http://www.vxiaotou.com