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


[md]# 前言最近想分析一个 Android App,发现它的关键逻辑在 libgojni.so 中,推测是 go 相关的工具做的,查询资料后,发现了 gomobile。[gomobile](https://go.de ... 解决 redress 分析 gomobile so 失败的问题

前言

最近想分析一个 Android App,发现它的关键逻辑在 libgojni.so 中,推测是 go 相关的工具做的,查询资料后,发现了 gomobile。gomobile 是一个可以将 go 程序打包为 android、iOS app/lib 的工具,它有两种模式

  1. 直接构建 App
  2. 构建一个 lib,供现有的 App 使用

第二种方式复用现有的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") < 0 {

输出了一下 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 < len(data) ; i += entrySize {
        offset := binary.LittleEndian.Uint64(data[i : i+8])
        // info := binary.LittleEndian.Uint64(data[i+8 : i+16])
        addend := binary.LittleEndian.Uint64(data[i+16 : i+24])

        if offset < targetSecBase || offset + 8 > 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

看下返回 typesData 方法,会用 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)) < start+m.Length {
        return nil, fmt.Errorf("the length of module data section is to big: address 0x%x, base 0x%x, length 0x%x", m.Address, base, m.Length)
    }
    buf := make([]byte, m.Length)
    copy(buf, data[start:start+m.Length])
    return buf, nil
}

猜测还是有重定向的部分,在该方法中增加 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 <= off && off < (section.Addr+section.Size) {
            data, err := section.Data()
            fmt.Printf("section start: %x\n", section.Addr)
            relaSectionData(e, data, section.Addr)     // 这里增加
            return section.Addr, data, err
        }
    }
    return 0, nil, ErrSectionDoesNotExist
}

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 等信息了。总的来说,改动的其实并不复杂

  1. 更新 redress 中列出的 go 的版本
  2. 通过搜索 magic 数据来定位 gopclntab 部分,通过 gomobile 生成的确实是没有这个字段
  3. 根据重定向段,修改获取的 section 的数据(这个有可能是我所分析的样本中特殊的部分)

超简单通用云端更新软件,golang实现

抖音最新解封技术

获取更多资讯请加入交流群


    协助本站SEO优化一下,谢谢!
    关键词不能为空
评 论
此页面未开启评论