ProGuard源碼閱讀

ProGuard源碼下載地址
https://sourceforge.net/projects/proguard/

整個PG.jar的編譯腳本在buildscripts/functions.sh
這里不關心他們的編譯處理
核心處理類在core包下。

PG.core的包結構
proguard 用戶調用接口
proguard.ant ant邏輯相關
proguard.classfile class處理
proguard.evaluation 執行相關
proguard.gui gui界面
proguard.io IO流,處理類似jar和zip,classpath文件流
proguard.obfuscate 混淆相關
proguard.optimize 優化
proguard.preverify 預編譯相關
proguard.retrace 追蹤
proguard.shrink 壓縮相關
proguard.util 工具類

proguard.java的入口方法

public static void main(String[] args)
    {
        if (args.length == 0)
        {
            System.out.println(VERSION);
            System.out.println("Usage: java proguard.ProGuard [options ...]");
            System.exit(1);
        }
        
        // Create the default options.
        Configuration configuration = new Configuration();

        try
        {
            // Parse the options specified in the command line arguments.
            ConfigurationParser parser = new ConfigurationParser(args,
                                                                 System.getProperties());
            try
            {
//檢查configuration有沒有異常的字符,只有全對才能繼續
                parser.parse(configuration);
            }
            finally
            {
                parser.close();
            }

            // Execute ProGuard with these options.
            new ProGuard(configuration).execute();//真正的實現方法
        }
        catch (Exception ex)
        {
            if (configuration.verbose)
            {
                // Print a verbose stack trace.
                ex.printStackTrace();
            }
            else
            {
                // Print just the stack trace message.
                System.err.println("Error: "+ex.getMessage());
            }

            System.exit(1);
        }

        System.exit(0);
    }
}

main方法主要在做configuration的輸入檢查工作
當檢查結果無異常,新構造一個Proguard類并調用excute方法執行

excute方法這里著重看obfuscate方法

private void obfuscate() throws IOException
    {
        if (configuration.verbose)
        {
            System.out.println("Obfuscating...");

            // We'll apply a mapping, if requested.
            if (configuration.applyMapping != null)
            {
                System.out.println("Applying mapping [" + PrintWriterUtil.fileName(configuration.applyMapping) + "]");
            }

            // We'll print out the mapping, if requested.
            if (configuration.printMapping != null)
            {
                System.out.println("Printing mapping to [" + PrintWriterUtil.fileName(configuration.printMapping) + "]...");
            }
        }

        // Perform the actual obfuscation.
        new Obfuscator(configuration).execute(programClassPool,
                                              libraryClassPool);
    }

Obfuscator類是真正做混淆處理的類,比如這里的成員混淆者MemberObfuscator

NameFactory nameFactory = new SimpleNameFactory();
        if (configuration.obfuscationDictionary != null)
        {
            nameFactory =
                new DictionaryNameFactory(configuration.obfuscationDictionary,
                                          nameFactory);
        }

        WarningPrinter warningPrinter = new WarningPrinter(System.err, configuration.warn);

        // Maintain a map of names to avoid [descriptor - new name - old name].
        Map descriptorMap = new HashMap();

        // Do the class member names have to be globally unique?
        if (configuration.useUniqueClassMemberNames)
        {
            // Collect all member names in all classes.
            programClassPool.classesAccept(
                new AllMemberVisitor(
                new MemberNameCollector(configuration.overloadAggressively,
                                        descriptorMap)));

            // Assign new names to all members in all classes.
            programClassPool.classesAccept(
                new AllMemberVisitor(
                new MemberObfuscator(configuration.overloadAggressively,
                                     nameFactory,
                                     descriptorMap)));

其初始化完畢后,內部循環調用nameFactory.nextName方法進行混淆

String newName = newMemberName(member);

        // Assign a new one, if necessary.
        if (newName == null)
        {
            // Find an acceptable new name.
            nameFactory.reset();

            do
            {
                newName = nameFactory.nextName();
            }
            while (nameMap.containsKey(newName));

            // Remember not to use the new name again in this name space.
            nameMap.put(newName, name);

            // Assign the new name.
            setNewMemberName(member, newName);
        }

混淆規則就在諸如SimpleNameFactory類的工廠類里。

 public String nextName() {
        return name(index++);
    }

private String name(int index) {
        // Which cache do we need?
        List cachedNames = generateMixedCaseNames ?
                cachedMixedCaseNames :
                cachedLowerCaseNames;

        // Do we have the name in the cache?
        if (index < cachedNames.size()) {
            return (String) cachedNames.get(index);
        }

        // Create a new name and cache it.
        String name = newName(index);
        cachedNames.add(index, name);

        return name;
    }


    /**
     * Creates and returns the name at the given index.
     */
    private String newName(int index) {
        // If we're allowed to generate mixed-case names, we can use twice as
        // many characters.
        int totalCharacterCount = generateMixedCaseNames ?
                2 * CHARACTER_COUNT :
                CHARACTER_COUNT;

        int baseIndex = index / totalCharacterCount;
        System.out.println(baseIndex);
        int offset = index % totalCharacterCount;
        System.out.println(offset);

        char newChar = charAt(offset);

        String newName = baseIndex == 0 ?
                new String(new char[]{newChar}) :
                (name(baseIndex - 1) + newChar);
        return newName;

        //修改后的方法
//        String newStr = stringAt(offset);
//        String newStrName = baseIndex == 0 ? new String(newStr) : (name(baseIndex - 1) + newStr);
//        return newStrName;
    }


    /**
     * Returns the character with the given index, between 0 and the number of
     * acceptable characters.
     */
    private char charAt(int index) {
        return (char) ((index < CHARACTER_COUNT ?
//                'o' : 'O'));//修改
                'a' - 0 : 'A' - CHARACTER_COUNT) + index);
    }

    /**
     * 隨機產生五個字符串內容
     *
     * @param index
     * @return
     */
    private String stringAt(int index) {
//        return new String(new char[]{
//                '談', '笑', '風', '聲', '蛤'
//        });
        return new String(new char[]{
                (char) (CHARACTER_START + index),
                (char) (CHARACTER_START + 1 + index),
                (char) (CHARACTER_START + 2 + index),
                (char) (CHARACTER_START + 3 + index),
                (char) (CHARACTER_START + 4 + index)
        });
    }

混淆就是用簡單字符替換原有字符
這里值得注意的是,string因為是個不可變類,初始化后就放在字符串池里,完全可以復用,PG也是這樣做的。
在name方法里,generateMixedCaseNames是其構造方法傳來的,
用處是判斷:拿大小寫混合字符緩存池,還是純小寫字符緩存池。
說白了就是混淆用大小寫,還是純小寫。

通過index判斷,之前有沒有new過該string,有的話就直接從緩存池拿,否則就new,并且放入緩存池。

參考
http://leanote.com/s/599d6779ab6441379c001ec7

http://www.520monkey.com/archives/992

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。