avast / avast/apkparser

resources.arsc parser does not support sparse ResTable_type entries

Open
#9 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
Go
Stars
154
Forks
22
PR merge metrics
No merged PRs in 30d

Description

## Summary

`ResourceTable.GetResourceEntry` fails on APKs whose `resources.arsc` contains sparse `ResTable_type` chunks (`FLAG_SPARSE = 0x01`). The parser currently treats the entry index table as a dense `uint32[]` for every type chunk, so sparse entries are interpreted as huge invalid offsets.

## Observed behavior

For one APK in a private test corpus, resolving resource id `0x7f110001` fails with:

```text
Invalid entry 0x0001 offset: 262238!
```

This happens through calls like:

```go
rt, err := apkparser.ParseResourceTable(reader)
entry, err := rt.GetResourceEntry(0x7f110001)
```

I cannot attach the APK, but the failing resource table chunk has this shape:

```text
resource id: 0x7f110001
package id: 0x7f
type chunk id: 0x11
entry id: 0x0001
ResTable_type flags: 0x01
entryCount: 2
entriesStart: 92
headerSize: 84
```

The sparse index table bytes decode as pairs:

```text
(idx=1, offset=0) -> actual entry offset = entriesStart + 0*4 = 92
(idx=2, offset=4) -> actual entry offset = entriesStart + 4*4 = 108
```

But the current code reads the second pair as a dense `uint32` offset for entry `1`:

```text
bytes: 02 00 04 00
uint32 little-endian: 0x00040002 = 262146
computed offset: entriesStart + 262146 = 262238
```

That produces the invalid offset error above.

## Likely cause

In `parseType`, the second byte after `Id` is read as `Res0` and discarded:

```go
vals := struct {
Id uint8
Res0 uint8
Res1 uint16

EntryCount uint32
EntriesStart uint32
}{...}
```

For `ResTable_type`, this byte is the type chunk flags. When `flags & 0x01 != 0`, the entry index table is sparse and should be read as sorted `(uint16 idx, uint16 offsetDiv4)` pairs instead of dense `uint32` offsets.

`getEntryConfigs` currently always does dense lookup:

```go
r.Seek(int64(thisType.indexesStart+entry*4), io.SeekStart)
var thisOffset uint32
binary.Read(r, binary.LittleEndian, &thisOffset)
offset := thisType.entriesStart + thisOffset
```

## Expected behavior

For sparse type chunks, `getEntryConfigs` should search the sparse index table for the requested entry id and calculate:

```text
offset = entriesStart + offsetDiv4*4
```

If the entry id is not present in the sparse table, it should behave like a missing entry/config rather than interpreting unrelated bytes as a dense offset.

## Notes

This affects normal resource references such as launcher icons stored as `android:icon="@7f..."`. Android and Google Play resolve the resource correctly, but `apkparser` currently fails before the resource value can be read.

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.