Naver Tech Concert Day-1

07. Obfuscation 101: 난독화, 프로가드, R8, 트랜스포머 API

  • 발표자 : 김용욱 (카카오뱅크)
  • 동영상 : https://tv.naver.com/v/4655623/list/272653
  • 슬라이드 : https://www.slideshare.net/NaverEngineering/16obfuscation-101-r8-api
  • 세션설명 : 안드로이드의 앱들은 현재 프로 가드를 통해서 보호되고 있고 향후로는 구글이 작성한 R8으로 대체할 예정에 있다. 난독화 도구들을 써봤지만 막연히 쓰는 경우가 많은데 난독화 도구가 어떤 일을 하고 있고 기본적인 메커니즘이 어떻게 구현되어있는지 프로가드, R8은 무엇인지, 그리고 안드로이드 빌드 과정에 어떻게 통합되는지를 살펴보는 시간을 갖는다.

안드로이드 코드의 특징

  • 이식에 좋은 바이트 코드 사용
  • 동적 컴파일에 기반한 성능 향상
  • 의존성도 동적으로 로드
  • 암호화되지 않은 클래스와 리소스로 구성
  • 공간 효율적인 데이터 포맷

바이트 코드에 대한 간략한 이해

자바 Bytecode

  • 스택 기반의 VM을 사용 (검증이 쉽지만 레지스터 기반의 실제 기기와 차이로 성능상 단점)
  • 32비트 스택 하나의 요소가 대부분의 타입 커버
  • 256개의 연산

자바는 상수의 종류를 구반하지 않음.

1+2 연산시 1과 2의 스택을 쌓은 후 두 스택을 꺼내 연산 후 3을 스택에 쌓음.

istore의 i는 int형을 의미.

기본적으로 ARM이나 x86 기계어보다는 단순함.

달빅 Bytecode

  • 레지스터 기반의 VM 사용 (실제 하드웨어와 매핑에 이점으로 메모리 덜 쓰나, 상대적으로 검증이 어려운 단점)
  • 32비트 레지스터 하나의 요소가 대부분의 타입 커버
  • 64K 개의 레지스터 하지만 주로 앞의 256개, 가끔은 16개만 사용.

const 명령을 통해 레지스터 v1, v2에 상수를 넣음.

add-int를 통해 v2와 v3의 합을 v0에 저장.

자바와 달빅 bytecode 차이점

desugar는 자바 8을 지원하기 위함. (람다 등을 제거하여 자바 7 컴파일러가 돌 수 있도록)

.class 파일을 dex(dx, d8)을 통해 .dex 파일로 변환.

  • DX - 안드로이드 스튜디오 3.0 전 (D8보다 시간이 오래 걸리고 용량도 큼)
  • D8 - 안드로이드 스튜디오 3.0 이후 (써드파티 툴에서 새로운 DEX를 지원하지 못하는 문제 있을 수도)
  • gradle.properties에서 android.enableD8 = true | false

DX/D8 비교는 안드로이드 스튜디오의 APK Analyzer로 비교 가능.

Jack & Jill을 통해 .java를 .dex로 한번에 빌드하는 것을 구글에서 시도했지만, 기술을 폐지하기로 함.

(써드파티 생태계에서 달빅 바이트코드를 제어하지 않기에)

Transform API

구글이 써드파티를 위해 제공한 API

.class -> Transformer -> .class 형태를 가짐.

프로가드 등의 도구들도 Transform API에 의존적이나 여전히 그러지 못한 도구들이 존재함.

Transform 을 상속받아 transform을 구현하여 입력받은 클래스 파일을 변조 후 출력하면 됨.

ProGuard, Realm Transformer, Desugar 등도 표준화된 API를 따름.

Transformer를 만들 때는 ASM, BCEL 같은 Low Level를 쓰거나 (주로 Google), AspectJ 같은 High Level을 쓰기도 함 (Jake Wharton). 또는 그 중간인 Javassist를 사용하기도 함 (Realm).

동적 컴파일에 기반한 성능 향상

인터프리터 vs JIT(코드 일부를 컴파일해 개선) vs AOT(미리 빌드)

  1. 아무 정보 없을 때 인터프리터로 해석. (느림)
  2. 일정 횟수 이상 수행된 메서드만 컴파일해 JIT 코드 캐쉬에 저장. (프로파일링 정보에 기반해 업데이트)
  3. 주기적으로 dex2aot 데몬이 코드를 빌드해서 oat 파일을 생성.
  4. 다른 앱에 의해 사용되면 전체 빌드, 아니면 프로파일에 기반해 빌드.
  5. JIT 컴파일과 AOT 컴파일이 있다면 JIT 컴파일 사용.(동적 최적화)

의존성도 동적으로 로드

자바는 모든 것이 동적으로 로딩.

메서드 명, 클래스 명, 필드 명이 공개되며, 안드로이드 앱의 경우 Activity, Fragment, Service 등을 외부로 공개해야 함.

난독화 도구에 exlcue/include 항목을 설정해야 함.

암호화되지 않은 클래스와 리소스로 구성

  1. 클래스와 리소스는 암호화되어 있지 않음.
  2. 기본 클래스 로더가 암호화를 지원하지 않아 한계가 존재. 기본 클래스 로더가 호출되기 전에 암호화가 풀린 클래스를 가로챌 수 있음.
  3. 클래스 암호화는 선호되는 보호 방법이 아님.

공간 효율적인 데이터 포맷

  1. LEB-128 사용해 가변 바이트(1-5바이트)로 32비트 잘 저장 7비트 단위로 나누어 그룹화 하여 앞에 첫문자인지 0/1로 구분
  2. 부호가 있는 수를 위한 SLEB128, 부호 없는 수를 위한 ULEB128과 -1만 지원하는 ULEB128p1이 있음. ULEB128p1일 경우 -1이면 0.
  3. 여러 클래스에서 상수 공유 (.class가 아닌 .dex 지원으로 가능)
  4. 상대 주소 사용 (특정 주소(1024) 이후엔 상대적인 주소(1)로 사용)
  5. 메서드 갯수까지 효율적으로. (64K)

난독화와 앱 보호에 대한 간략한 이론

  1. 클래스/리소스에 암호화를 적용
    • 클래스를 암호화하고 커스텀 로더 사용
    • Packer와 Protector로 나누어짐.
    • 해독을 위해 클래스를 전달해야 하며, 결국 커스텀 로더가 공격의 취약점이 됨.
    • 많은 패커와 프로텍터는 ODEX 복사나 심지어 Dex2Jar 툴에 의해 쉽게 풀림.
  2. 리소스 사이에 클래스를 매복
    • 이미지 파일로 dex 파일을 숨기는 방법
    • 수상하게 큰 파일을 찾거나 파일 포맷 검증으로 혐의가 좁혀짐.
  3. 네이티브 코드로 핵심 코드를 숨김
    • 성능상의 이점도 있지만 그래도 기계어 코드도 해석이 가능하긴 함.
    • VM 밖에선 온갖 하드웨어 이슈를 경험할 수 있음.
    • ABI 세트 맞추기 어려움.
  4. 서버에 핵심 코드를 숨김
    • 리얼 타임 응답성이 떨어짐. 로컬 DB 필요할 수도.
    • 서버 다운시 사용 불가. 로컬 캐쉬 이용해서 풀백 구현도 어려움.
  5. 템퍼 감지를 추가
    • 디버거, ptrace 를 발견하는 코드 삽입
    • 에뮬레이터 감지
    • 해당 부분 우회시 대부분 앱 진입이 가능함.
  6. 클래스, 필드, 메서드 명 변경
    • ProGuard부터 대부분의 도구가 지원
    • 짧고 간격한 이름으로 클래스, 필드, 메서드 명 변경하지만 외부에서 import 되거나 export 되는 명칭은 변경 불가
    • 위 처리로 공간 복잡도도 줄이고 실행시간에도 긍정적인 영향 줌.
    • 명칭만 바뀌는 것이라 결국 해결 가능.
    • 도구마다 고유의 네이밍 패턴이 있음. (APKiD에서 검출)
  7. 리플렉션 호출 추가
    • 메서드 호출 단계를 추가
    • ProGuard는 지원 못하지만, DexGuard나 Arxan 등 유료도구에서 지원
    • 리플렉션이 반복적인 패턴을 가지기 때문에 기계적으로 풀 수 있음. (ex. Dex-Oracle에서 DexGuard 패턴 해제)
    • 많은 단말에서 리플렉션 크래시 보고됨.
  8. 노이즈 추가
    • 메서드 외부에 아무런 영향이 없는 코드 추가
    • 클래스 상태 변경, I/O, 반환 값 등이 없음.
    • 디버거나 디스컴파일러가 깨지는 코드를 추가하기도 하지만, 그만큼 쉽게 고쳐짐.
  9. 제어 흐름
    • 반복문이나 분기문을 불필요하게 추가
    • If 문 대신 try-catch 블록을 사용하기도 함.
    • switch 문을 중첩적인 레이블로 변경하기도 함.
    • 브랜치를 jsr 명령 (goto)로 변환하기도 함.
    • 일반적으로 속도를 저하시키는 요인.

ProGuard와 R8의 역할

프로가드는?

  1. 클래스, 필드, 메서드 명 변경

  2. 사용하지 않은 클래스, 필드, 메서드 제거

.class->ProGuard->.class

위는 아래와 같음.

.class->Shrinker->.class->Name Obfuscator->.class

안드로이드 빌드에 통합된 프로가드

.java->javac->.class

​ ->Desugar Transform->.class

​ ->ProGuard Transform->ProGuard->.class

​ .dex<-Dex<-

통합되지 않은 난독화 도구들의 흐름

.java->javac->.class

​ ->Desugar Transform->.class

​ ->Dex->.dex->Dex2jar->.class

​ .dex<-Dex<-.class<-Obfuscator<-

R8

.java->javac->.class

​ ->Desugar Transform->.class

​ ->R8->.dex

R8 = D8 + Shrinker + Name Obfuscator

(ProGuard 버리려는 계획?)

'IT > 행사' 카테고리의 다른 글

[행사] Naver Tech Concert Day-2 01  (0) 2019.01.14
[행사] Microsoft Azure Everywhere  (0) 2019.01.11
[행사] Naver Tech Concert Day-1 05  (0) 2019.01.07
[행사] Naver Tech Concert Day-1 04  (0) 2019.01.04
[행사] Naver Tech Concert Day-1 03  (0) 2019.01.04

+ Recent posts