解决 redress 分析 gomobile so 失败的问题
# 前言最近想分析一个 Android App,发现它的关键逻辑在 `libgojni.so` 中,推测是 go 相关的工具做的,查询资料后,发现了 `gomobile`。(https://go.dev/wiki/Mobile) 是一个可以将 go 程序打包为 android、iOS app/lib 的工具,它有两种模式
1. 直接构建 App
2. 构建一个 lib,供现有的 App 使用
第二种方式复用现有的go程序还是挺方便的,它会打包出一个 aar 文件,解压出来确实可以看到 `libgojni.so`,测试对比了一下,确实差不多。
找到生成的方式后,就是找现成的分析工具,(https://github.com/goretk/redress) 是一个比较好的分析工具,可以查看 `struct`, `interface` 等信息,查看 `gomobile` 生成的
`libgojni.so` 中的 `struct` 信息会报错。在使用 IDA 做了一些分析后,感觉有 `struct` 信息还是会对分析有帮助的,而且分析的过程中也了解了一些 go 的知识(不得不说,go 还是挺好上手的),
所以又转向了 `redress`,准备解决其报错。目前已经解决报错的问题,可以正常分析了。
下面分享一下解决报错的方式,下面的错误是我使用 `./redress types struct ./libgojni.so` 依次出现并解决的错误
# no goversion found
搜索错误信息,最终定位到相应的位置
``` text
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目录下,因为它会将部分产物放在当前目录
``` bash
cd gore
go run gen.go
```
# failed to parse the module data: section does not exist
报错位置在这里,`parseModuleData` 出错
``` text
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` 中的
``` 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 这个字眼
``` go
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` 中使用
``` text
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` 这样一个函数
``` go
// 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.
``` text
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 里,修改获取的数据即可
``` go
// 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)
// info := binary.LittleEndian.Uint64(data)
addend := binary.LittleEndian.Uint64(data)
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, 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` 中某个偏移取数据的时候,拿到的是错误的(这部分就不详细展开了,里面也不复杂)
``` go
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,然后再取出需要的部分
``` go
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)
return buf, nil
}
```
猜测还是有重定向的部分,在该方法中增加 `relaSectionData` 修改数据,再次运行,发现这个错误已经绕过了
``` go
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,其实都是
``` go
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 的数据(这个有可能是我所分析的样本中特殊的部分) 感谢分享! 感谢楼主的分享
页:
[1]