Genel

ProGuard ile Java Springboot Code Obfuscation

Merhabalar herkese. Bu yazımda ProGuard ile Java projelerinde obfuscation işlemini nasıl yapacağımızı bunu maven projelerinde projelerine nasıl uygulayacağımızı anlatacağım. Unutmayın bilgi paylaştıkça çoğalır 🙂 .

Obfuscation Nedir?

Obfuscation kelime anlamı olarak şaşırtma/aldatma anlamına gelmektedir. Projelerimizde kaynak kodların tersine mühendislik ile okunmasının ve anlaşılmasının önüne geçmek için obfuscation işlemleri yapılmaktadır. Python projelerinde PyArmor kullanılmaktadır fakat bu farklı bir yazının konusudur :). Java projelerinde obfuscation literatürde kullanılan en bilinen yöntem ProGuard’dır. Obufscation işleminde aşağıda açıklanan proguard.conf dosyasındaki parametrelere göre kaynak kod obfuscate edilir. Bu da bize class isimlerinin değişkenlerin rastgele isimler almasına olanak sağlar. Bu sayede kod tersine mühendislik yapılamaz hale gelir.

Pom XML Ayarlanması

Aşağıda örnek bir pom.xml verilmiştir. Burada dikkat edilmesi gereken husus ProGuard Maven plugin’in Java 8 ile uyumlu olmasıdır. Kaynak kodun Java versiyonu önemli değildir fakat ProGuard Maven Plugin Java 8 library’lerine ihtiyaç duymaktadır.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.jayk.springboot.proguard</groupId>
    <artifactId>obfuscation-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>obfuscation-demo</name>
    <description>Obfuscation Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>com.github.wvengen</groupId>
                <artifactId>proguard-maven-plugin</artifactId>
                <version>2.5.1</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>proguard</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <proguardVersion>7.1.0</proguardVersion>
                    <injar>${project.build.finalName}.jar</injar>
                    <outjar>${project.build.finalName}.jar</outjar>
                    <obfuscate>true</obfuscate>
                    <proguardInclude>${project.basedir}/proguard.cfg</proguardInclude>
                    <libs>
                        <lib>/etc/java-8-openjdk/lib/rt.jar</lib>
                        <lib>/etc/java-8-openjdk/lib/jce.jar</lib>
                        <lib>/etc/java-8-openjdk/lib/jsse.jar</lib>
                    </libs>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>net.sf.proguard</groupId>
                        <artifactId>proguard-base</artifactId>
                        <version>6.0.3</version>
                    </dependency>
                </dependencies>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                        <configuration>
                            <mainClass>com.jayk.springboot.proguard.obfuscationdemo.ObfuscationDemoApplication
                            </mainClass>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

ProGuard.conf Ayarlanması

Bu aşamada aşağıda belirtilen konfig ile obfuscation işlemleri yapılmaktadır. Burada dikkat edilmesi gereken bazı hususlar vardır. Uygulamada her class obfuscate edilememektedir. Bu class’ları aşağıdaki konfig üzerinde belirmemiz gerekmektedir. Örneğin:

-keepnames class com.some.path.** { *; } ## İlgili classa ait her şeyi obfuscate işleminin dışında tutar.
-repackageclasses com.some.class.path ## İlgili class ve altındakileri tekrar paketler.
-useuniqueclassmemebernames ##Benzersiz class isimlendirmelerine izin verir bu sayede çakışmaların önüne geçilir.
–keepdirectories com.some.main.class.path ## Main class obfuscate edilmemelidir.
-keepclasseswithmembers public class * { public static void main(java.lang.String[]);} ## Main class’ın onbfuscate edilmemesi için gerekli olan komuttur.

-target 1.8 ##Specify the java version number
-dontshrink ##Default is enabled, here the shrink is turned off, that is, the unused classes/members are not deleted.
-dontoptimize ##Default is enabled, here to turn off bytecode level optimization
-useuniqueclassmembernames ## Take a unique strategy for confusing the naming of class members
-adaptclassstrings ## After confusing the class name, replace it with a place like Class.forName('className')
-dontnote
-ignorewarnings
-dontwarn
-keep public class * extends org.springframework.boot.web.support.SpringBootServletInitializer
-keepdirectories
-keepclasseswithmembers public class * { public static void main(java.lang.String[]);} ##Maintain the class of the main method and its method name
-keepclassmembers enum * { *; }  ##Reserving enumeration members and methods
-keepclassmembers class * {
     @org.springframework.beans.factory.annotation.Autowired *;
     @org.springframework.beans.factory.annotation.Qualifier *;
     @org.springframework.beans.factory.annotation.Value *;
     @org.springframework.beans.factory.annotation.Required *;
     @org.springframework.context.annotation.Bean *;
     @org.springframework.context.annotation.Primary *;
     @org.springframework.boot.context.properties.ConfigurationProperties *;
     @org.springframework.boot.context.properties.EnableConfigurationProperties *;
     @javax.inject.Inject *;
     @javax.annotation.PostConstruct *;
     @javax.annotation.PreDestroy *;
}
-keep @org.springframework.cache.annotation.EnableCaching class *
-keep @org.springframework.context.annotation.Configuration class *
-keep @org.springframework.boot.context.properties.ConfigurationProperties class *
-keep @org.springframework.boot.autoconfigure.SpringBootApplication class *
-allowaccessmodification
-keepattributes *Annotation*
-keepdirectories com.jayk.springboot.proguard.obfuscationdemo
-keepdirectories org.springframework.boot.autoconfigure
-keepclassmembers class * {
    *** get*();
    void set*(***);
}
-keepclassmembernames class * {
     java.lang.Class class$(java.lang.String);
     java.lang.Class class$(java.lang.String, boolean);
}
-keepclassmembers enum * {
     public static **[] values();
     public static ** valueOf(java.lang.String);
     public static ** fromValue(java.lang.String);
}
-keepnames class * implements java.io.Serializable
-keepclassmembernames public class com.test.blah.config.liquibase.AsyncSpringLiquibase
-keepclassmembers class * implements java.io.Serializable {
     static final long serialVersionUID;
     private static final java.io.ObjectStreamField[] serialPersistentFields;
     !static !transient <fields>;
     !private <fields>;
     !private <methods>;
     private void writeObject(java.io.ObjectOutputStream);
     private void readObject(java.io.ObjectInputStream);
     java.lang.Object writeReplace();
     java.lang.Object readResolve();
}
-keepclassmembers class * {
     @org.springframework.beans.factory.annotation.Autowired <fields>;
     @org.springframework.beans.factory.annotation.Autowired <methods>;
     @org.springframework.security.access.prepost.PreAuthorize <methods>;
}

Obfuscate Çıktısı

Uygulamayı compile ettikten sonra aşağıdaki gibi bir çıktı elde ederiz.

mvn clean package

Obfuscate edilmiş parça.

package BOOT-INF.classes.com.jayk.springboot.proguard.obfuscationdemo.a;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class a {
  @GetMapping({"greetings"})
  public String a(Model paramModel) {
    com.jayk.springboot.proguard.obfuscationdemo.b.a a1 = new com.jayk.springboot.proguard.obfuscationdemo.b.a();
    a1.setMessage("Hello World");
    paramModel.addAttribute("greetingDTO", a1);
    return "greeting";
  }
}

Obfuscate edilmemiş parça.

package com.jayk.springboot.proguard.obfuscationdemo.controller;

import com.jayk.springboot.proguard.obfuscationdemo.dto.GreetingDTO;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class GreetingController {
  @GetMapping({"greetings"})
  public String dashboard(Model model) {
    GreetingDTO greetingDTO = new GreetingDTO();
    greetingDTO.setMessage("Hello World");
    model.addAttribute("greetingDTO", greetingDTO);
    return "greeting";
  }
}

Yukarıda obufscation çıktısı verilmiştir. Görüldüğü üzere b.a gibi karıştırmalar ile uygulamanın tersine mühendisliği önlenmiştir. Ayrıca Proguard aşağıdaki gibi obfuscation yapılan classların ve değiştirdiği değişkenlerin çıktısını verir.

com.jayk.springboot.proguard.obfuscationdemo.ObfuscationDemoApplication -> com.jayk.springboot.proguard.obfuscationdemo.ObfuscationDemoApplication:
    void <init>() -> <init>
    void main(java.lang.String[]) -> main
com.jayk.springboot.proguard.obfuscationdemo.controller.GreetingController -> com.jayk.springboot.proguard.obfuscationdemo.a.a:
    void <init>() -> <init>
    java.lang.String dashboard(org.springframework.ui.Model) -> a
com.jayk.springboot.proguard.obfuscationdemo.dto.GreetingDTO -> com.jayk.springboot.proguard.obfuscationdemo.b.a:
    java.lang.String message -> message
    void <init>() -> <init>
    java.lang.String getMessage() -> getMessage
    void setMessage(java.lang.String) -> setMessage

Bir sonraki yazımda görüşmek üzere 🙂 Herkese iyi çalışmalar…