前言
最近想分析一个 Android App,发现它的关键逻辑在 libgojni.so
中,推测是 go 相关的工具做的,查询资料后,发现了 gomobile
。gomobile 是一个可以将 go 程序打包为 android、iOS app/lib 的工具,它有两种模式
- 直接构建 App
- 构建一个 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
看下返回 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)) < 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 等信息了。总的来说,改动的其实并不复杂
- 更新
redress
中列出的 go 的版本 - 通过搜索 magic 数据来定位
gopclntab
部分,通过 gomobile 生成的确实是没有这个字段 - 根据重定向段,修改获取的 section 的数据(这个有可能是我所分析的样本中特殊的部分)