目前consensus-attack
库,基于TBFT共识算法的异常注入功能已实现。但是需要依赖于对consensus-tbft
代码库的引入。通过gomonkey
对原consensus-tbft
代码库中的函数实现打桩替换。使得chainmaker-go
在运行时,原本的共识逻辑被篡改,从而实现共识算法的异常注入。
由于目前gomonkey
提供的打桩函数替换功能,需要提供原始函数。因此consensus-attack
在对gomonkey
使用的时候,需要引入consensus-tbft
代码库。
目前consensus-tbft
代码库:300版本和23x版本差异比较大。如果需要支持对这两个版本的共识算法实现异常注入,那么就需要引入不同版本的consensus-tbft
代码库。但是不同版本的代码库对common等库的依赖有冲突。
因此,如何解决:
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来根据函数名称获取函数的地址,就可以避免对原始公式算法包的引入。
由于不同版本的Golang,在对moduledata的实现和使用上是有差异的。因此针对不同版本的Golang在实现对moduledata解析所提供的功能方法也是有所差异的。因此,通过定义接口来约束moduledata解析器。
// ModuledataParser 通过moduledata获取运行时函数信息
type ModuledataParser interface {
// GetFuncUintPtrByName 通过函数名获取函数起始地址
GetFuncUintPtrByName(string) uintptr
// GetVarUintPtrByName 通过变量名称获取变量地址
GetVarUintPtrByName(string) uintptr
// SupportVersions 获取当前支持的golang版本
SupportVersions() []string
}
文件 parser118.go
针对go1.18版本的moduledata解析器实现:
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
}
实现的每个版本moduledata解析器,都需要注册到Version控制器中,版本控制器提供方法根据当前Golang的版本,选择出适合的moduledata解析器。
// 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
}
通用的异常注入框架,不再需要对共识算法和版本的代码引入。但是需要实现通用的接口,来实现注入的数据源。
任意一个共识算法的注入攻击,都必须先实现接口提供攻击的对象等信息。
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()
方法约束了,攻击目标名称和替换后新的对象。
InjectReplacerMap
中type
为func
的时候, Replacer
为替换的函数。InjectReplacerMap
中type
为var
的时候, Replacer
为替换的变量。共识异常注入consensusHacker
需要实现Hacker
接口。接口中约束了四个方法。
// hacker 注入攻击
type hacker interface {
// Attack 攻击指定的函数
Attack(targetName string)
// Recover 撤销指定函数的攻击
Recover(targetName string)
// AttackingList 正在被攻击的函数列表
AttackingList() []string
// RecoverAll 撤销所有攻击
RecoverAll()
}
consensusHacker
的实现示例:
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
}
当前调研和使用的gomonkey如果要打桩注入,target目标,需要传入函数或者方法。因此,要实现上述通过函数名称实现对目标函数打桩注入的话,需要基于gomonkey底层提供的replace
方法,实现一个ApplyUintPtr(target uintptr, double interface{}) *Patches
方法。
通过moduledata解析器,对函数名称解析处 uintptr地址,再通过新实现的ApplyUintPtr()
方法,完成对目标函数的打桩注入。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。