
안드로이드 가상 머신 및 디난독화 도구
Simplify는 앱을 가상으로 실행하여 동작을 이해한 후, 동일하게 동작하면서도 사람이 이해하기 쉬운 코드로 최적화를 시도합니다. 각 최적화 유형은 단순하고 일반적이어서 특정 난독화 유형과 관계없이 적용됩니다.
왼쪽 코드는 난독화된 앱의 디컴파일 결과이고, 오른쪽 코드는 난독화가 해제된 코드입니다.
이 프로젝트는 smalivm, simplify, 데모 앱의 세 부분으로 구성됩니다.
if 또는 switch 조건문이 있으면 두 분기를 모두 실행합니다.usage: java -jar simplify.jar <input> [options]
deobfuscates a dalvik executable
-et,--exclude-types <pattern> Exclude classes and methods which include REGEX, eg: "com/android", applied after include-types
-h,--help Display this message
-ie,--ignore-errors Ignore errors while executing and optimizing methods. This may lead to unexpected behavior.
--include-support Attempt to execute and optimize classes in Android support library packages, default: false
-it,--include-types <pattern> Limit execution to classes and methods which include REGEX, eg: ";->targetMethod\("
--max-address-visits <N> Give up executing a method after visiting the same address N times, limits loops, default: 10000
--max-call-depth <N> Do not call methods after reaching a call depth of N, limits recursion and long method chains, default: 50
--max-execution-time <N> Give up executing a method after N seconds, default: 300
--max-method-visits <N> Give up executing a method after executing N instructions in that method, default: 1000000
--max-passes <N> Do not run optimizers on a method more than N times, default: 100
-o,--output <file> Output simplified input to FILE
--output-api-level <LEVEL> Set output DEX API compatibility to LEVEL, default: 15
-q,--quiet Be quiet
--remove-weak Remove code even if there are weak side effects, default: true
-v,--verbose <LEVEL> Set verbosity to LEVEL, default: 0
빌드하려면 Java Development Kit 8 (JDK)가 설치되어 있어야 합니다.
이 프로젝트는 안드로이드 프레임워크의 서브모듈을 포함하고 있으므로 --recursive 옵션으로 클론하거나:
git clone --recursive https://github.com/CalebFenton/simplify.git
다음 명령어로 언제든지 서브모듈을 업데이트할 수 있습니다:
git submodule update --init --recursive
그런 다음, 모든 의존성을 포함하는 단일 jar 파일을 빌드하려면:
./gradlew fatjar
Simplify jar 파일은 simplify/build/libs/에 위치합니다. 제공된 난독화 예제 앱을 단순화하여 정상 작동을 테스트할 수 있습니다. 실행 방법은 다음과 같습니다 (simplify.jar 경로는 필요에 따라 변경하세요):
java -jar simplify/build/libs/simplify.jar -it "org/cf/obfuscated" -et "MainActivity" simplify/obfuscated-app.apk
무엇이 난독화 해제되는지 이해하려면 Obfuscated App의 README를 확인하세요.
Simplify가 실패하면 다음 권장 사항을 순서대로 시도해보세요:
-it 옵션을 사용하여 몇 개의 메서드나 클래스만 대상으로 지정하세요.--max-address-visits, --max-call-depth, --max-method-visits 값을 높여보세요.-v 또는 -v 2 옵션으로 실행하고 로그와 DEX 또는 APK의 해시를 함께 문제를 보고하세요.Windows에서 빌드할 때 다음과 유사한 오류로 빌드가 실패하는 경우:
Could not find tools.jar. Please check that C:\Program Files\Java\jre1.8.0_151 contains a valid JDK installation.
이는 Gradle이 올바른 JDK 경로를 찾지 못함을 의미합니다. JDK가 설치되어 있는지 확인하고, JAVA_HOME 환경 변수를 JDK 경로로 설정한 후, 빌드에 사용하는 명령 프롬프트를 닫고 다시 열어주세요.
망설이지 마세요. 저는 가상 실행과 난독화 해제가 매우 흥미로운 문제라고 생각합니다. 관심 있는 사람은 자동으로 멋진 사람이며, 오타 수정만 해도 기여는 환영입니다. 이슈에서 질문하고 풀 리퀘스트를 제출해 주세요.
APK나 DEX 링크와 사용 중인 전체 명령어를 포함해 주세요. 그래야 문제를 재현하고 수정하기가 훨씬 쉽습니다.
샘플을 공유할 수 없는 경우, 파일 해시(SHA1, SHA256 등)를 꼭 포함해 주세요.
명령어가 문자열, 숫자, 부울 등 상수로 변환 가능한 유형의 값을 배치하는 경우, 이 최적화는 해당 명령어를 상수로 대체합니다. 예를 들어:
const-string v0, "VGVsbCBtZSBvZiB5b3VyIGhvbWV3b3JsZCwgVXN1bC4="
invoke-static {v0}, Lmy/string/Decryptor;->decrypt(Ljava/lang/String;)Ljava/lang/String;
# Decrypts to: "Tell me of your homeworld, Usul."
move-result v0
이 예제에서 암호화된 문자열이 복호화되어 v0에 배치됩니다. 문자열은 "상수화 가능"하므로 move-result v0를 const-string으로 대체할 수 있습니다:
const-string v0, "VGVsbCBtZSBvZiB5b3VyIGhvbWV3b3JsZCwgVXN1bC4="
invoke-static {v0}, Lmy/string/Decryptor;->decrypt(Ljava/lang/String;)Ljava/lang/String;
const-string v0, "Tell me of your homeworld, Usul."
코드를 제거해도 앱의 동작을 절대 변경할 수 없다면 그 코드는 죽은 코드입니다. 가장 명확한 경우는 코드에 도달할 수 없는 경우입니다(예: if (false) { // dead }). 코드에 도달할 수 있더라도 메서드 외부의 상태에 영향을 미치지 않는다면(즉, 부작용이 없다면) 죽은 코드로 간주될 수 있습니다. 예를 들어, 코드가 메서드의 반환값에 영향을 주지 않거나, 클래스 변수를 변경하지 않거나, I/O를 수행하지 않는 경우입니다. 정적 분석에서는 이를 판단하기 어렵습니다. 다행히 smalivm은 영리할 필요가 없습니다. 그냥 할 수 있는 모든 것을 무식하게 실행하고, 확실하지 않으면 부작용이 있다고 가정합니다. 상수 전파의 예제를 고려해보세요:
const-string v0, "VGVsbCBtZSBvZiB5b3VyIGhvbWV3b3JsZCwgVXN1bC4="
invoke-static {v0}, Lmy/string/Decryptor;->decrypt(Ljava/lang/String;)Ljava/lang/String;
const-string v0, "Tell me of your homeworld, Usul."
이 코드에서 invoke-static은 더 이상 메서드의 반환값에 영향을 주지 않으며, 파일 시스템에 바이트를 쓰거나 네트워크 소켓에 쓰는 등의 이상한 동작을 하지 않는다고 가정합니다. 따라서 부작용이 없으므로 간단히 제거할 수 있습니다.
const-string v0, "VGVsbCBtZSBvZiB5b3VyIGhvbWV3b3JsZCwgVXN1bC4="
const-string v0, "Tell me of your homeworld, Usul."
마지막으로 첫 번째 const-string은 레지스터에 값을 할당하지만, 그 값은 사용되지 않습니다. 즉, 할당이 죽은 것입니다. 이것도 제거할 수 있습니다.
const-string v0, "Tell me of your homeworld, Usul."
만세!
자바의 정적 분석에서 가장 큰 도전 과제 중 하나는 리플렉션입니다. 신중한 데이터 흐름 분석 없이는 리플렉션 메서드의 인수를 알 수 없습니다. 이를 해결하는 똑똑하고 영리한 방법이 있지만, smalivm은 코드를 실행하여 해결합니다. 리플렉션 메서드 호출(예: 아래와 같은)을 발견하면:
invoke-virtual {v0, v1, v2}, Ljava/lang/reflect/Method;->invoke(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
v0, v1, v2의 값을 알 수 있습니다. 값이 확실하다면 Method.invoke() 호출을 실제 리플렉션이 아닌 메서드 호출로 대체할 수 있습니다. 이는 리플렉션 필드 및 클래스 조회에도 동일하게 적용됩니다.
특정 범주에 깔끔하게 들어맞지 않는 모든 것에는 피호홀 최적화가 적용됩니다. 여기에는 불필요한 check-cast 명령어 제거, Ljava/lang/String;-><init> 호출을 const-string으로 대체하는 작업 등이 포함됩니다.
.method public static test1()I
.locals 2
new-instance v0, Ljava/lang/Integer;
const/4 v1, 0x1
invoke-direct {v0, v1}, Ljava/lang/Integer;-><init>(I)V
invoke-virtual {v0}, Ljava/lang/Integer;->intValue()I
move-result v0
return v0
.end method
이것이 하는 일은 v0 = 1뿐입니다.
.method public static test1()I
.locals 2
new-instance v0, Ljava/lang/Integer;
const/4 v1, 0x1
invoke-direct {v0, v1}, Ljava/lang/Integer;-><init>(I)V
invoke-virtual {v0}, Ljava/lang/Integer;->intValue()I
const/4 v0, 0x1
return v0
.end method
move-result v0가 const/4 v0, 0x1로 대체되었습니다. 이는 intValue()I의 반환값이 하나뿐이고 반환 유형을 상수로 만들 수 있기 때문입니다. 인수 v0와 v1은 명확하며 변경되지 않습니다. 즉, intValue()I의 가능한 모든 실행 경로에 대해 값의 합의가 있습니다. 상수로 변환할 수 있는 다른 유형의 값:
const/4, const/16 등const-stringconst-class.method public static test1()I
.locals 2
const/4 v0, 0x1
return v0
.end method
const/4 v0, 0x1 위의 코드는 메서드 외부 상태에 영향을 미치지 않으므로(부작용 없음) 동작을 변경하지 않고 제거할 수 있습니다. 파일 시스템이나 네트워크에 무언가를 쓰는 메서드 호출이 있었다면 메서드 외부 상태에 영향을 미치므로 제거할 수 없습니다. 또는 test()I가 LinkedList와 같은 변경 가능한 인수를 받았다면, 해당 인수에 접근하는 명령어는 죽은 것으로 간주할 수 없습니다.
죽은 코드의 다른 예:
if (false) { dead_code(); }이 도구는 이중 라이선스로 제공됩니다: 폐쇄 소스 프로젝트에 적합한 상업용 라이선스와 오픈 소스 소프트웨어에서 사용할 수 있는 GPL 라이선스입니다.
필요에 따라 하나를 선택하고 해당 정책을 따라야 합니다. 각 라이선스 유형의 정책과 계약에 대한 자세한 내용은 LICENSE.COMMERCIAL 및 LICENSE.GPL 파일에서 확인할 수 있습니다.