Skip to content

Instantly share code, notes, and snippets.

@Intyre

Intyre/0.md Secret

Last active May 10, 2023 03:08
Show Gist options
  • Save Intyre/9fd3a3ee528b65411432ea934e9f925e to your computer and use it in GitHub Desktop.
Save Intyre/9fd3a3ee528b65411432ea934e9f925e to your computer and use it in GitHub Desktop.

BoltApp class names finder

Tries to find class names by checking fields in the .smali files

extract the apk to get the .smali files

$ apktool d -b -f -r -q -o out BoltApp.apk

searches all .smali files in the apktool out directory filters on the com/wahoofitness package prints found field names mapped to the class

$ ./wh-find-methods -i out -out-class
package main
import (
"bufio"
"encoding/json"
"flag"
"fmt"
"log"
"os"
"path/filepath"
"sort"
"strings"
)
const WahooPackageName = "com/wahoofitness"
type ClassInfo struct {
Name string
Rename string `json:",omitempty"`
Fields []ClassField
Methods []ClassMethod
}
type ClassField struct {
AccessFlags string `json:",omitempty"`
Name string
Rename string `json:",omitempty"`
Type string
Value string `json:",omitempty"`
}
type ClassMethod struct {
AccessFlags string `json:",omitempty"`
Name string
Rename string `json:",omitempty"`
Parameters string `json:",omitempty"`
ReturnType string
}
func main() {
var (
prettyJson bool
writeJson bool
writeClassMap bool
inputDir string
outputFile string
)
flag.BoolVar(&prettyJson, "p", false, "write pretty json. results in a bigger output file")
flag.BoolVar(&writeJson, "out-json", false, "write output to .json")
flag.BoolVar(&writeClassMap, "out-class", false, "write class map to .txt")
flag.StringVar(&inputDir, "i", "", "apktool output directory")
flag.StringVar(&outputFile, "o", "classdump", "output file to write")
flag.Parse()
if inputDir == "" {
flag.Usage()
os.Exit(1)
}
files, err := FindFiles(inputDir)
if err != nil {
log.Fatal(err)
}
classes := make(map[string]ClassInfo, len(files))
innerClasses := make([]string, 0)
for _, filename := range files {
if strings.Contains(filename, "$") {
innerClasses = append(innerClasses, filename)
continue
}
className := ClassFromFilename(filename)
classInfo := AnalyzeFile(filename)
classInfo.Name = className
classes[className] = classInfo
}
if writeJson {
const ext = ".json"
if !strings.HasSuffix(outputFile, ext) {
outputFile += ext
}
if err := WriteJson(outputFile, classes, prettyJson); err != nil {
log.Fatal(err)
}
}
if writeClassMap {
const ext = ".txt"
if !strings.HasSuffix(outputFile, ext) {
outputFile += ext
}
if err := WriteClassMap(outputFile, classes); err != nil {
log.Fatal(err)
}
}
fmt.Printf("Skipped %d inner class files\n", len(innerClasses))
fmt.Printf("Total %d files\n", len(files))
// innerMatch := 0
// for _, v := range innerClasses {
// className := ClassFromFilename(v)
// dollarIdx := strings.Index(className, "$")
// // fmt.Printf("%#v\n", className[:dollarIdx])
// if v, ok := classes[className[:dollarIdx-1]]; ok {
// _ = v
// innerMatch++
// if v.Rename != "" {
// fmt.Printf("%s -> %s%s\n", v.Name, v.Rename, className[dollarIdx:])
// // } else {
// // fmt.Printf("%s\n", v.Name)
// }
// }
// }
fmt.Println("Done")
}
func WriteClassMap(outputFile string, classes map[string]ClassInfo) error {
fmt.Printf("Writing %d classes to %s\n", len(classes), outputFile)
sorted := make([]string, 0, len(classes))
for k := range classes {
sorted = append(sorted, k)
}
sort.Strings(sorted)
for _, cName := range sorted {
v := classes[cName]
if v.Rename != "" {
fmt.Printf("%s -> %s\n", v.Name, v.Rename)
} else {
fmt.Printf("%s -> \n", v.Name)
}
}
return nil
}
func WriteJson(outputFile string, classes map[string]ClassInfo, pretty bool) error {
fmt.Printf("Writing %d classes to %s\n", len(classes), outputFile)
f, err := os.Create(outputFile)
if err != nil {
log.Fatal(err)
}
defer f.Close()
if pretty {
buf, err := json.MarshalIndent(classes, "", " ")
if err != nil {
log.Fatal(err)
}
f.Write(buf)
} else {
if err := json.NewEncoder(f).Encode(classes); err != nil {
log.Fatal(err)
}
}
return nil
}
func AnalyzeFile(filename string) ClassInfo {
f, err := os.Open(filename)
if err != nil {
log.Fatal(err)
}
defer f.Close()
s := bufio.NewScanner(f)
fields := make([]ClassField, 0)
methods := make([]ClassMethod, 0)
for s.Scan() {
line := s.Text()
if strings.HasPrefix(line, ".field") {
var classField ClassField
field := line[7:]
if strings.Contains(field, " = ") {
split := strings.Split(field, " = ")
classField.Value = split[1]
field = split[0]
}
split := strings.Split(field, " ")
ntIdx := len(split) - 1
if ntIdx > 0 {
classField.AccessFlags = strings.Join(split[:ntIdx], " ")
}
nt := strings.Split(split[ntIdx], ":")
classField.Name = nt[0]
classField.Type = nt[1]
fields = append(fields, classField)
} else if strings.HasPrefix(line, ".method") {
var classMethod ClassMethod
method := line[8:]
pStart := strings.Index(method, "(")
pEnd := strings.Index(method, ")")
classMethod.Parameters = method[pStart+1 : pEnd]
classMethod.ReturnType = method[pEnd+1:]
split := strings.Split(method[:pStart], " ")
nIdx := len(split) - 1
if nIdx > 0 {
classMethod.AccessFlags = strings.Join(split[:nIdx], " ")
}
classMethod.Name = split[nIdx]
methods = append(methods, classMethod)
}
}
ci := ClassInfo{Fields: fields, Methods: methods}
className := ClassFromFilename(filename)
ci.Name = className
ci.findClassname()
return ci
}
func (ci *ClassInfo) findClassname() {
pkgIdx := strings.LastIndex(ci.Name, "/")
if len(ci.Fields) > 0 {
nameFields := make([]ClassField, 0)
for _, field := range ci.Fields {
if field.AccessFlags == "private static final" && field.Type == "Ljava/lang/String;" {
if field.Value != "" {
nameFields = append(nameFields, field)
}
}
}
switch len(nameFields) {
case 0:
return
case 1:
name, ok := ValidClassname(nameFields[0].Value)
if ok {
if ci.Name[:pkgIdx+1]+name != ci.Name {
ci.Rename = ci.Name[:pkgIdx+1] + name
}
}
default:
name, ok := ValidClassField(nameFields)
if ok {
if ci.Name[:pkgIdx+1]+name != ci.Name {
ci.Rename = ci.Name[:pkgIdx+1] + name
}
}
}
}
}
func FindFiles(dir string) ([]string, error) {
files := []string{}
err := filepath.Walk(dir, func(path string, f os.FileInfo, err error) error {
if strings.Contains(path, WahooPackageName) && filepath.Ext(path) == ".smali" {
files = append(files, path)
}
return nil
})
return files, err
}
func ClassFromFilename(filename string) string {
pkgIdx := strings.Index(filename, WahooPackageName)
extIdx := strings.Index(filename, ".smali")
return filename[pkgIdx:extIdx]
}
func ValidClassField(fields []ClassField) (string, bool) {
names := make(map[string]struct{})
for _, field := range fields {
name, ok := ValidClassname(field.Value)
if !ok {
continue
}
if _, ok := names[name]; !ok {
names[name] = struct{}{}
break
}
}
if len(names) == 0 {
return "", false
} else if len(names) == 1 {
for k := range names {
return k, true
}
}
log.Fatalf("not expected: %#v\n", names)
return "", false
}
func ValidClassname(s string) (string, bool) {
if strings.HasPrefix(s, "\"") && strings.HasSuffix(s, "\"") {
s = s[1 : len(s)-1]
}
if strings.Contains(s, ".") {
return "", false
}
if strings.HasSuffix(s, "Manager") {
return s, true
}
if strings.HasPrefix(s, "Std") {
return s, true
}
if strings.Contains(s, "#") {
return "", false
} else if strings.Contains(s, "-") {
return "", false
} else if strings.HasPrefix(s, "BA") {
return s, true
} else if strings.HasPrefix(s, "B") {
return s, true
} else if s[0] < 'A' || s[0] > 'Z' {
return "", false
}
if strings.Title(s) == s {
return "", false
}
cCount := strings.Count(s, "_")
if cCount == 0 {
return s, true
} else if cCount == 1 {
return s, true
} else if cCount > 1 {
return "", false
}
log.Printf("what: %s\n", s)
return "", false
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment