ProGuard를 통한 안드로이드 애플리케이션의 난독화

2023. 4. 11. 19:52모바일프로그래밍/안드로이드

728x90
저번 문서1에 따라서 코드를 난독화하거나, 최적화를 수행하려고 한다면, 문제가 있을 수 있을 것이다. 안드로이드 에서 제공하는 Ant build 환경이 많이 바뀌었고, proguard 를 지원하고 있기 때문이다. (도대체 얼만큼 많은 안드로이드 버전이 1~2 년 새에 바꼈는 지도 알 수 없을 정도로 많이 바뀌고 있다)

안드로이드 에서 코드 난독화를 하기 위한 방법을 저번 문서2의 예제를 가지고 수행해 보기로 한다.

 1. 시작하기 전에

우선, Ant 빌드 환경을 구성하기 전에 앞선 문서에서는 직접 build.xml 파일을 커스터마이징해서 쓰는 방법을 제시 하였는 데, 이렇게 build.xml 파일을 커스터마이징 해서 사용하지 않고서 중간에 내가 커스터마이징 할 타킷을 추가 할 수 있는 방법을 소개 한 적이 있다.

 

해당 문서3를 참조하여 다시 custom_rules.xml 만들어 보기로 한다.

그리고 샘플은 저번 문서에 사용되었던 obfuscation.zip 파일을 사용하도록 한다.

ANT를 이용한 안드로이드 애플리케이션 자동빌드 환경 구축4

를 따라서 만들면 되는데, 샘플 소스 상에 build.xml 파일만 제거해 주고, 진행 하면 될 듯하다.

 

2. Build.xml 파일 만들기

그럼 build.xml 파일을 한번 보도록 하자. 주석을 제거하고 나면 남아 있는 것들이 몇 개 없음을 알 수 있다. 물론 주석을 잘 읽어 보면 프로퍼티 파일들이 무슨 일을 해야 하는 지 자세히 나와 있으니 한번 읽어 보는 것도 나쁠 건 없지…

<?xml version="1.0" encoding="UTF-8"?>
<project name="Obfuscation" default="help">
<property file="local.properties" />

<property file="ant.properties" />
<property environment="env" />

<condition property="sdk.dir" value="${env.ANDROID_HOME}">
	<isset property="env.ANDROID_HOME" />
</condition>

<loadproperties srcFile="project.properties" />

<fail
	message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
	unless="sdk.dir"/>

<import file="custom_rules.xml" optional="true" />
<import file="${sdk.dir}/tools/ant/build.xml" />
</project>
 

빨간색으로 표시 한 것들만 잘 이용하면 될 듯하다.

그럼 우선 ant.properties 파일을 보면

key.store=keystore
key.alias=www.androidengineer.com
key.store.password=password
key.alias.password=password

version_code=199
version_name=1.76
 

서명을 위한 keystore에 대한 내용을 새로 들어 온 것을 알 수 있다.

 

두번 째, project.proeprties 파일 내용이다.

# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must be checked in Version Control Systems.
#
# To customize properties used by the Ant build system edit
# "ant.properties", and override values to adapt the script to your
# project structure.
#
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
# proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt

# Project target.
target=android-19

proguard.config==${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
 

초록 색으로 표시된 부분을 떼어 내서 아래에 다시 표시 하였다. 나의 경우에는 애플리케이션 root에 너무 많은 파일들이 나오는 것을 좋아하지 않으므로 proguard-project.txt 파일을 proguard 폴더 내부로 옮겼다. 그런 다음 이 proguard-project.txt 파일이 문젠데…

 

세번째로, proguard-project.txt은 어떻게 만들어야 할 지 감이 안와서, 먼저 만들어 놓은 사람의 것을 참조 하는 것이 가장 좋을 것 같아서 다음 사이트에 있는 proguard-project.txt5파일을 참고 하였다.

 

일단 무엇을 해야 할 지 모르는 상태 이므로 다음과 같이 과감히 모두 삭제를 하고 config.txt 파일의 내용을 참조 하도록 하였다.

# ProGuard configuration file for MAXS Transport XMPP
# Mostly Smack specific ProGuard instructions, so that ProGuard is
# able to strip the parts of Smack that aren't used by Transport XMPP
# -keep class org.projectmaxs.shared.global.util.Log
# -keep class org.projectmaxs.shared.maintransport.TransportInformation
##########################################
# Custom proguard settings

-dontnote android.os.SystemProperties
-dontnote sun.security.pkcs11.SunPKCS11

# See http://stackoverflow.com/questions/5701126
-optimizations !code/allocation/variable
-include config.txt
 

진짜 프로젝트에 적용하기 위해서는 뭔가를 고려 해야 되겠다는 생각이 많이 들었다.

Config.txt 파일은 과감하게 무슨 내용인 지 생략하기로 한다. 정확히 뭔지는 모르겠으나, 별로 쓸데 없는 것들을 빼는 부분인가? 하고 무작정 생각해본다.

 

2. Custom_rules.xml

이 파일에 대해서 많은 설명을 필요가 없을 것으로 보인다.

<project name="custom_android_rules">
	<import file="global_custom_rules.xml" />
	<!-- Zap debug-obfuscation-check from SDK's build.xml so that it
	can't set proguard.enabled to false !-->

	<target name="-debug-obfuscation-check"/>
	
	<target name="-set-manifest-values">
		<replaceregexp file="AndroidManifest.xml" encoding="UTF-8">
			<regexp pattern="android:versionName=".*""/>
			<substitution expression="android:versionName="${version_name}""/>
		</replaceregexp>
		
		<replaceregexp file="AndroidManifest.xml">
			<regexp pattern="android:versionCode=".*""/>
			<substitution expression="android:versionCode="${version_code}""/>
		</replaceregexp>
	</target>

	<target name="-pre-build" depends="-set-manifest-values">
		<condition property="proguard.enabled" value="true" else="false">
			<isset property="proguard.config" />
		</condition>
		
		<if condition="${proguard.enabled}">
			<then>
				<echo level="info">Proguard.config is enabled</echo>
				<!-- Secondary dx input (jar files) is empty since all the
					jar files will be in the obfuscated jar -->
				<path id="out.dex.jar.input.ref" />
			</then>
			<else>
				<echo level="info">Proguard.config is disabled</echo>
			</else>
		</if>
	</target>
    	<!-- Obfuscate target
	This is only active in release builds when proguard.config is defined
	in default.properties.

	To replace Proguard with a different obfuscation engine:
	Override the following targets in your build.xml, before the call to <setup>
	-release-obfuscation-check
	Check whether obfuscation should happen, and put the result in a property.
	-debug-obfuscation-check
	Obfuscation should not happen. Set the same property to false.
	-obfuscate
	check if the property set in -debug/release-obfuscation-check is set to true.
	If true:
	Perform obfuscation
	Set property out.dex.input.absolute.dir to be the output of the obfuscation
	-->
	<target name="-obfuscate">
		<if condition="${proguard.enabled}">
			<then>
				<property name="obfuscate.absolute.dir" location="${out.absolute.dir}/proguard" />
				<property name="preobfuscate.jar.file" value="${obfuscate.absolute.dir}/original.jar" />
				<property name="obfuscated.jar.file" value="${obfuscate.absolute.dir}/obfuscated.jar" />
				<!-- input for dex will be proguard's output -->
				<property name="out.dex.input.absolute.dir" value="${obfuscated.jar.file}" />

				<!-- Add Proguard Tasks -->
				<property name="proguard.jar" location="${android.tools.dir}/proguard/lib/proguard.jar" />
				
				<taskdef name="proguard" classname="proguard.ant.ProGuardTask" classpath="${proguard.jar}" />

				<!-- Set the android classpath Path object into a single property. It'll be
				all the jar files separated by a platform path-separator.
				Each path must be quoted if it contains spaces.
				-->
				<pathconvert property="project.target.classpath.value" refid="project.target.class.path">
					<firstmatchmapper>
						<regexpmapper from='^([^ ]*)( .*)$$' to='"\1\2"'/>
						<identitymapper/>
					</firstmatchmapper>
				</pathconvert>

				<!-- Build a path object with all the jar files that must be obfuscated.
				This include the project compiled source code and any 3rd party jar
				files. -->
				<path id="project.all.classes.path">
					<pathelement location="${preobfuscate.jar.file}" />
					<path refid="project.all.jars.path" />
				</path>

				<!-- Set the project jar files Path object into a single property. It'll be
				all the jar files separated by a platform path-separator.
				Each path must be quoted if it contains spaces.
				-->
				<pathconvert property="project.all.classes.value" refid="project.all.classes.path">
					<firstmatchmapper>
						<regexpmapper from='^([^ ]*)( .*)$$' to='"\1\2"'/>
						<identitymapper/>
					</firstmatchmapper>
				</pathconvert>

				<path id="proguard.configpath">
					<pathelement path="${proguard.config}"/>
				</path>

				<!-- The following code is submited to upstream under
				https://android-review.googlesource.com/#/c/79060/
				-->
				<union id="project.all.proguard.input">
					<path refid="proguard.configpath"/>
					<path refid="project.all.jars.path"/>
					<fileset dir="${out.classes.absolute.dir}" includes="**/*.class"/>
				</union>

				<uptodate property="obfuscated.jar.file.uptodate" targetfile="${obfuscated.jar.file}">
					<srcresources refid="project.all.proguard.input"/>
				</uptodate>

				<if condition="${obfuscated.jar.file.uptodate}">
					<then>
						<echo level="info">ProGuard output up-to-date. Skipping ProGuard invocation...</echo>
					</then>
					<else>
					<!-- Turn the path property ${proguard.config} from an A:B:C property
					into a series of includes: -include A -include B -include C
					suitable for processing by the ProGuard task. Note - this does
					not include the leading '-include "' or the closing '"'; those
					are added under the <proguard> call below.
					-->
					<pathconvert pathsep='" -include "' property="proguard.configcmd" refid="proguard.configpath"/>

					<mkdir dir="${obfuscate.absolute.dir}" />
					<delete file="${preobfuscate.jar.file}"/>
					<delete file="${obfuscated.jar.file}"/>
					<jar basedir="${out.classes.absolute.dir}"
					destfile="${preobfuscate.jar.file}" />
					<proguard>
					-include "${proguard.configcmd}"
					-include "${out.absolute.dir}/proguard.txt"
					-injars ${project.all.classes.value}
					-outjars "${obfuscated.jar.file}"
					-libraryjars ${project.target.classpath.value}
					-dump "${obfuscate.absolute.dir}/dump.txt"
					-printseeds "${obfuscate.absolute.dir}/seeds.txt"
					-printusage "${obfuscate.absolute.dir}/usage.txt"
					-printmapping "${obfuscate.absolute.dir}/mapping.txt"
					-target "${java.source}"
					-printusage "${obfuscate.absolute.dir}/unused.txt"
					-optimizationpasses 2
					# -overloadaggressively
					-dontusemixedcaseclassnames
					-dontskipnonpubliclibraryclasses
					-dontpreverify
					-verbose
					</proguard>
					</else>
				</if>
			</then>
		</if>
	</target>
    	<target name="rename-release-with-version-number">
		<xmlproperty file="AndroidManifest.xml"
			prefix="themanifest"
			collapseAttributes="true"/>

		<!-- see ${sdk.dir}/tools/ant/build.xml -set-release-mode -->
		<property name="out.packaged.file"
		location="${out.absolute.dir}/${ant.project.name}_sample_${themanifest.manifest.android:versionCode}-release-unsigned.apk" />
		<property name="out.final.file"
		location="${out.absolute.dir}/${ant.project.name}_sample_${themanifest.manifest.android:versionCode}.apk" />
	</target>

	<target name="-set-release-mode"
		depends="rename-release-with-version-number,android_rules.-set-release-mode">
		<echo message="target: ${build.target}"></echo>
		<!--
		<copy todir="${depoly.dir}">
		<fileset dir="${out.absolute.dir}">
		<include name="${ant.project.name}_sample_${themanifest.manifest.android:versionCode}.apk"/>
		</fileset>
		</copy>
		-->
	</target>
</project>
 
위의 내용 중에서 주의 깊게 봐야 할 부분이 <target name="-obfuscate"> 이다.

그리고 내용 중에 아규먼트값들이 proguard로 전달 되는 부분이 중요 할 듯 보인다.

<proguard>
	-include "${proguard.configcmd}"
	-include "${out.absolute.dir}/proguard.txt"
	-injars ${project.all.classes.value}
	-outjars "${obfuscated.jar.file}"
	-libraryjars ${project.target.classpath.value}
	-dump "${obfuscate.absolute.dir}/dump.txt"
	-printseeds "${obfuscate.absolute.dir}/seeds.txt"
	-printusage "${obfuscate.absolute.dir}/usage.txt"
	-printmapping "${obfuscate.absolute.dir}/mapping.txt"
	-target "${java.source}"
	-printusage "${obfuscate.absolute.dir}/unused.txt"
	-optimizationpasses 2
	# -overloadaggressively
	-dontusemixedcaseclassnames
	-dontskipnonpubliclibraryclasses
	-dontpreverify
	-verbose
</proguard>
 

3. 디렉토리 구조

이렇게 해서 한판 완성하면 디렉토리 구조는 다음과 같이 된다.

4. 컴파일 및 빌드

그럼 컴파일 및 빌드를 해보도록 하자. 우선

Ant clean 하고 나서 릴리즈로 빌드하여 준다.

이 프로세스가 끝나고 나면 릴리즈 최종본에 대해서 애플리케이션이 만들어 진 것이다.

 

5. 릴리즈 용 디렉토리

아래와 같이 릴리즈 디렉토리가 생성되었다. 해당 디렉토리 내에 proguard 디렉토리에 보면 난독화 전와 후의 파일이 존재하는 것을 볼 수가 있다.

난독화 이전과 이후의 파일 차이가 확실히 크다는 것을 눈으로 확인해 볼 수 있다.

6. 재 검증

물론 디렉토리 내의 jar 파일을 가지고도 검증은 확실히 되었으나, 혹시나 하는 맘이 있어서 dex2jar(https://sourceforge.net/projects/dex2jar/)

파일을 사용해서 classes.dex 파일을 풀어 보았다.

d2j-dex2jar.bat -d -o myjar.zipclasses.dex

7. Proguard 최신 버전 적용

이제 최신버전의 proguard를 적용 시킬 때가 된 것 같다. 사이트에 들어가보면 , 5.3 버전하고 5.2 버전이 있는 데, 베타 버전을 적용하기는 좀 그러니까 5.2를 다운 받아서 사용 하도록 한다.

 

위의 버전을 사용하기 위해서 Custom_rules.xml 파일에서 내가 원하는 디렉토리의 jar 파일을 라이브러리로 사용하도록 만들어 주고 여기에 최신 버전의 proguard를 다운 받아서 사용 하도록 한다.

<!-- Add Proguard Tasks -->
<!--<property name="proguard.jar" location="${android.tools.dir}/proguard/lib/proguard.jar" />-->
<property name="proguard.jar" location="./proguard/lib/proguard.jar" />
<taskdef name="proguard" classname="proguard.ant.ProGuardTask" classpath="${proguard.jar}" />
 

원래 안드로이드 기본 디렉토리를 참조 하던 것을 내 애플리케이션 디렉토리로 주었다.

그럼 현재 쓰고 있는 버전은 다음과 같이 출력 디버깅 정보에 나오는 데, 4.7 이다.

 

해당 디렉토리에 다운 받아서 넣고 다시 빌드 하면 5.2.1로 바뀐 것을 확인 할 수 있을 것이다.

 

 

8. 마치면서..

 

Proguard 사용법에 대해서 공부 해보았다. 그럼 이 프로세스를 진행하면서 별 문제가 없을 것인가에 대해서 내 자신에게 물어보았다.

나의 대답은 잘 모르겠다 이다. 실제로 여러가지 의존도를 가지고 있으며, 라이브러리를 사용하면서, jni까지 사용하기 있을 경우에 얼만큼 많이 커스터마이징을 시켜야 할지에 대해서는 실제 프로젝트에 적용시켜 보면서, 삽질을 해보지 않고서는 모르겠다는 것이다.

 

이 custorm_rules.xml파일을 참고한 사이트에서 내용을 봐도, 많은 삽질이 동반되었을 것 같은 불길한 예감이 드는 코맨트가 많이 있었다는 것이다.

 

과연 난독화를 해야 할 것인가에 대한 문제로 접근 한다면, 사실 부정적이다. 아는 사람은 알 것이고, 모르는 사람은 모를 것이기 때문이다. 그 격차는 더욱 더 벌어지고 있는 것이 지금의 추세이고, 대신에 앞선 문서에서도 언급 했듯이, 코드에 대한 성능 튜닝이 이 툴로 어느 정도 이루어 질 수 있다고 본다면 좋을 것이다.

 

위에서 사용한 파일은 다시 묶어서 첨부 한다. 원 저자에게 물어봐야 되나?

한글 문서 중에서 proguard 설정에 대해서 내 나름 괜찮다고 생각한 사이트가 있다.

 

참고 사이트6

 

Android Proguard 설정법 libs 라이브러리 포함

프로가드가 많이 바뀌었다..요즘적용을 안하다보니... 최신기준으로 수정 작성함 기본 프로젝트 생성을 하면 proguard-project.txt = 프로가드 세팅 파일project.properties = 프로젝트 세팅파일 project.propert

leejiheg.tistory.com

 

 

이상.

 

 


 

728x90