解决 redress 分析 gomobile so 失败的问题

查看 41|回复 3
作者:circle2   
前言
最近想分析一个 Android App,发现它的关键逻辑在 libgojni.so 中,推测是 go 相关的工具做的,查询资料后,发现了 gomobile。gomobile 是一个可以将 go 程序打包为 android、iOS app/lib 的工具,它有两种模式
[ol]
  • 直接构建 App
  • 构建一个 lib,供现有的 App 使用
    [/ol]
    第二种方式复用现有的go程序还是挺方便的,它会打包出一个 aar 文件,解压出来确实可以看到 libgojni.so,测试对比了一下,确实差不多。
    找到生成的方式后,就是找现成的分析工具,redress 是一个比较好的分析工具,可以查看 struct, interface 等信息,查看 gomobile 生成的
    libgojni.so 中的 struct 信息会报错。在使用 IDA 做了一些分析后,感觉有 struct 信息还是会对分析有帮助的,而且分析的过程中也了解了一些 go 的知识(不得不说,go 还是挺好上手的),
    所以又转向了 redress,准备解决其报错。目前已经解决报错的问题,可以正常分析了。
    下面分享一下解决报错的方式,下面的错误是我使用 ./redress types struct ./libgojni.so 依次出现并解决的错误
    no goversion found
    搜索错误信息,最终定位到相应的位置
    gore/goversion.go
    94-     }
    95-     notfound := false
    96-     for !notfound {
    97-             version := matchGoVersionString(data)
    98-             if version == "" {
    99:                     return nil, ErrNoGoVersionFound
    100-            }
    101-            ver := ResolveGoVersion(version)
    102-            // Go before 1.4 does not have the version string so if we have found
    103-            // a version string below 1.4beta1 it is a false positive.
    104-            if ver == nil || GoVersionCompare(ver.Name, "go1.4beta1")
    输出了一下 version ,发现实际是找到了的,只是在 ResolveGoVersion 的时候没有匹配到,它是在一堆 go version 信息中找的(gore/goversion_gen.go),样本中使用的是 1.20.14,它里面没有这么新的版本。
    不过它是生成的,使用的是 gore/gen.go,执行下面命令即可,要先切换到gore目录下,因为它会将部分产物放在当前目录
    cd gore
    go run gen.go
    failed to parse the module data: section does not exist
    报错位置在这里,parseModuleData 出错
    gore/type.go
    62-
    63-     md, err := parseModuledata(fileInfo, f)
    64-     if err != nil {
    65:             return nil, fmt.Errorf("failed to parse the module data: %w", err)
    66-     }
    67-
    parseModuledata -> findModuledata,出问题的是 getPCLNTABData,这个 f 是根据分析的二进制文件不同,走的逻辑不同,这里走的是 gore/elf.go 中的
    func findModuledata(f fileHandler) ([]byte, error) {
        _, secData, err := f.getSectionData(f.moduledataSection())
        if err != nil {
            return nil, err
        }
        tabAddr, _, err := f.getPCLNTABData()
        if err != nil {
            return nil, err
        }
        // ....
    }
    gore/elf.go 中,可以看到是要找 .gopclntab 或者是 .data.rel.ro.gopclntab section,gomobile生成的文件中确实没有这个段,但是在使用 IDA 分析的时候,我还见过 pclntab 这个字眼
    func (e *elfFile) getPCLNTABData() (uint64, []byte, error) {
        start, data, err := e.getSectionData(".gopclntab")
        if err == ErrSectionDoesNotExist {
            // Try PIE location
            return e.getSectionData(".data.rel.ro.gopclntab")
        }
        return start, data, err
    }
    在 redress 中搜索 pclntab,其中120magic这个 F1, FF, FF, FF 就非常符合在 IDA 中看到的数据,而这些 magic 数据只在 gore/pe.go 中使用
    gore/pclntab.go
    25:// pclntab12magic is the magic bytes used for binaries compiled with Go
    27:var pclntab12magic = []byte{0xfb, 0xff, 0xff, 0xff, 0x0, 0x0}
    29:// pclntab116magic is the magic bytes used for binaries compiled with
    31:var pclntab116magic = []byte{0xfa, 0xff, 0xff, 0xff, 0x0, 0x0}
    33:// pclntab118magic is the magic bytes used for binaries compiled with
    35:var pclntab118magic = []byte{0xf0, 0xff, 0xff, 0xff, 0x0, 0x0}
    37:// pclntab120magic is the magic bytes used for binaries compiled with
    39:var pclntab120magic = []byte{0xf1, 0xff, 0xff, 0xff, 0x0, 0x0}
    70:     for _, magic := range [][]byte{pclntab120magic, pclntab118magic, pclntab116magic, pclntab12magic} {
    所以解决办法是,模仿 pe,给 elf 写这样一处逻辑即可,新增了 searchFileForPCLNTabElf 这样一个函数
    // gore/elf.go
    func (e *elfFile) getPCLNTABData() (uint64, []byte, error) {
        start, data, err := e.getSectionData(".gopclntab")
        if err == ErrSectionDoesNotExist {
            // Try PIE location
            addr, pclndat, err := searchFileForPCLNTabElf(e.file)
            return uint64(addr), pclndat, err
            // return e.getSectionData(".data.rel.ro.gopclntab")
        }
        return start, data, err
    }
    // gore/pclntab.go
    func searchFileForPCLNTabElf(f *elf.File) (uint32, []byte, error) {
        for _, v := range []string{".data.rel.ro"} {
            sec := f.Section(v)
            if sec == nil {
                continue
            }
            secData, err := sec.Data()
            if err != nil {
                continue
            }
            tab, err := searchSectionForTab(secData)
            if err == ErrNoPCLNTab {
                continue
            }
            // TODO: Switch to returning a uint64 instead.
            addr := uint32(sec.Addr) + uint32(len(secData) - len(tab))
            return addr, tab, err
        }
        return 0, []byte{}, ErrNoPCLNTab
    }
    failed to parse the module data: could not find moduledata.
    218-func findModuledata(f fileHandler) ([]byte, error) {
    219-    _, secData, err := f.getSectionData(f.moduledataSection())
    220-    if err != nil {
    221-            return nil, err
    222-    }
    223-    tabAddr, _, err := f.getPCLNTABData()
    224-    if err != nil {
    225-            return nil, err
    226-    }
    227-
    228-    // Search for moduledata
    229-    buf := new(bytes.Buffer)
    230-    err = binary.Write(buf, binary.LittleEndian, &tabAddr)
    231-    if err != nil {
    232-            return nil, err
    233-    }
    234-    off := bytes.Index(secData, buf.Bytes()[:intSize32])
    235-    if off == -1 {
    236:            return nil, errors.New("could not find moduledata")
    secData 是 .noptrdata 段的数据,在 IDA 确实是有 tabAddr 的数据的,不过在输出之后,我发现这里面相应的位置是 0,通过 rizin 搜索,也是可以找到 tabAddr 对应的数据。在查找资料后,确认出现问题的是重定位
    查看重定位的数据,发现确实有在相应位置塞 tabAddr 的信息,那么在 getSectionData 里,修改获取的数据即可
    // gore/elf.go
    func relaSectionData(e *elfFile, targetSec []byte, targetSecBase uint64) {
        relaSection := e.file.Section(".rela.dyn")
        data, err := relaSection.Data()
        if err != nil {
            fmt.Printf("Error reading section data: %+v\n", err)
            return
        }
        fmt.Printf("rela section data 0x%X -- 0x%X\n", targetSecBase, targetSecBase + uint64(len(targetSec)))
        entrySize := 24
        for i := 0; i  targetSecBase + uint64(len(targetSec)) {
                continue
            }
            secOffset := offset - targetSecBase
            // fmt.Printf("rela offset: 0x%x, info: 0x%x, addend: 0x%x\n", offset, info, addend)
            // fmt.Printf("secOffset: %d\n", secOffset)
            binary.LittleEndian.PutUint64(targetSec[secOffset:], addend)
        }
    }
    func (e *elfFile) getSectionData(name string) (uint64, []byte, error) {
        section := e.file.Section(name)
        if section == nil {
            return 0, nil, ErrSectionDoesNotExist
        }
        data, err := section.Data()
        // 使用relaSectionData方法重定向 .noptrdata 中的数据
        fmt.Println("get section data: " + name)
        if name == e.moduledataSection() {
            relaSectionData(e, data, section.Addr)
        }
        return section.Addr, data, err
    }
    bytes.Reader.Seek: negative position.
    出现了这样的报错:failed to parse type at offset 0x2fc00: failed to parse resolved type for 0x67f4e0: bytes.Reader.Seek: negative position.
    问题出在这块代码中,遍历typeLink中的偏移,去解析相应位置的type,types 会传入 newTypeParser,后续在 parseType 时,会读取它的数据。
    通过中在 parseType 的分析,发现是在分析 child 时取到的数据不正确,也就是从 types 中某个偏移取数据的时候,拿到的是错误的(这部分就不详细展开了,里面也不复杂)
        types, err := md.Types().Data()
        if err != nil {
            return nil, fmt.Errorf("failed to get types data section: %w", err)
        }
        typeLink, err := md.TypeLink()
        if err != nil {
            return nil, fmt.Errorf("failed to get type link data: %w", err)
        }
        fmt.Printf("md info %+v\n", md)
        // New parser
        parser := newTypeParser(types, md.Types().Address, fileInfo)
        for _, off := range typeLink {
            typ, err := parser.parseType(uint64(off) + parser.base)
            if err != nil || typ == nil {
                return nil, fmt.Errorf("failed to parse type at offset 0x%x: %w", uint64(off) + parser.base, err)
            }
        }
        return parser.parsedTypes(), nil
    看下返回 types 的 Data 方法,会用 getSectionDataFromOffset 方法获取 sectionData,然后再取出需要的部分
    func (m ModuleDataSection) Data() ([]byte, error) {
        // If we don't have any data, return an empty slice.
        if m.Length == 0 {
            return []byte{}, nil
        }
        base, data, err := m.fh.getSectionDataFromOffset(m.Address)
        if err != nil {
            return nil, fmt.Errorf("getting module data section failed: %w", err)
        }
        start := m.Address - base
        if uint64(len(data))
    猜测还是有重定向的部分,在该方法中增加 relaSectionData 修改数据,再次运行,发现这个错误已经绕过了
    func (e *elfFile) getSectionDataFromOffset(off uint64) (uint64, []byte, error) {
        for _, section := range e.file.Sections {
            if section.Offset == 0 {
                // Only exist in memory
                continue
            }
            if section.Addr
    no gopclntab section found
    这部分就相对简单了,和前面的错误是一样的,只不过前面是 getPCLNTABData,其实都是
    func (e *elfFile) getPCLNTab() (*gosym.Table, error) {
        pclnSection := e.file.Section(".gopclntab")
        if pclnSection == nil {
            // No section found. Check if the PIE section exist instead.
            pclnSection = e.file.Section(".data.rel.ro.gopclntab")
        }
        if pclnSection == nil {
            // 通过search获取pclntab
            _, pclndat, err := searchFileForPCLNTabElf(e.file)
            if err != nil {
                return nil, err
            }
            pcln := gosym.NewLineTable(pclndat, e.file.Section(".text").Addr)
            return gosym.NewTable(make([]byte, 0), pcln)
            // 注释之前的返回
            // return nil, fmt.Errorf("no gopclntab section found")
        }
        pclndat, err := pclnSection.Data()
        if err != nil {
            return nil, fmt.Errorf("could not get the data for the pclntab: %w", err)
        }
        pcln := gosym.NewLineTable(pclndat, e.file.Section(".text").Addr)
        return gosym.NewTable(make([]byte, 0), pcln)
    }
    总结
    目前已经解决分析的样本中的 struct, interface 等信息了。总的来说,改动的其实并不复杂
    [ol]
  • 更新 redress 中列出的 go 的版本
  • 通过搜索 magic 数据来定位 gopclntab 部分,通过 gomobile 生成的确实是没有这个字段
  • 根据重定向段,修改获取的 section 的数据(这个有可能是我所分析的样本中特殊的部分)
    [/ol]

    的是, 数据

  • 光影由心   

    感谢分享!
    小草草   

    感谢楼主的分享
    chgf   

    感谢大神分享,来学习学习,研究一下
    您需要登录后才可以回帖 登录 | 立即注册

    返回顶部