001/* 002 * Copyright (C) 2015-2021 KeepSafe Software 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package com.getkeepsafe.dexcount; 017 018import org.apache.commons.io.FileUtils; 019 020import java.io.File; 021import java.io.IOException; 022import java.nio.charset.StandardCharsets; 023import java.util.Collections; 024import java.util.LinkedHashMap; 025import java.util.List; 026import java.util.Map; 027import java.util.regex.Matcher; 028import java.util.regex.Pattern; 029 030/** 031 * An object that can produce an unobfuscated class name from a Proguard 032 * mapping file. 033 */ 034public class Deobfuscator { 035 /** 036 * Proguard mapping files have the following syntax: 037 * 038 * ``` 039 * line : comment | class_mapping | member_mapping 040 * comment: '#' ... 041 * class_mapping: type_name ' -> ' obfuscated_name ':' 042 * member_mapping: ' ' type_name ' ' member_name ' -> ' obfuscated_name 043 * ``` 044 * 045 * Class mapping lines are easily distinguished because they're the only 046 * lines that start with an identifier character. We can just pluck them 047 * out of the file with a regex. 048 */ 049 private static final Pattern CLASS_LINE = Pattern.compile("^([a-zA-Z][^\\s]*) -> ([^:]+):$"); 050 051 public static final Deobfuscator EMPTY = new Deobfuscator(Collections.emptyMap()); 052 053 private final Map<String, String> mapping; 054 055 public Deobfuscator(Map<String, String> mapping) { 056 this.mapping = mapping; 057 } 058 059 public String deobfuscate(String name) { 060 return mapping.getOrDefault(name, name); 061 } 062 063 public static Deobfuscator create(File mappingFile) throws IOException { 064 if (mappingFile == null) { 065 return EMPTY; 066 } 067 068 if (!mappingFile.exists()) { 069 return EMPTY; 070 } 071 072 Map<String, String> mapping = new LinkedHashMap<>(); 073 074 List<String> lines = FileUtils.readLines(mappingFile, StandardCharsets.UTF_8); 075 for (String line : lines) { 076 Matcher matcher = CLASS_LINE.matcher(line); 077 if (!matcher.matches()) { 078 continue; 079 } 080 081 String cleartext = matcher.group(1); 082 String obfuscated = matcher.group(2); 083 mapping.put(obfuscated, cleartext); 084 } 085 086 return new Deobfuscator(mapping); 087 } 088}