[16]Obfuscation 101 : 난독화, 프로가드, R8, 트랜스포머 API

NAVER Engineering
NAVER EngineeringNAVER Engineering
[16]Obfuscation 101 : 난독화, 프로가드, R8, 트랜스포머 API
[16]Obfuscation 101 : 난독화, 프로가드, R8, 트랜스포머 API
Session 6 l 카카오뱅크 김용욱
Obfuscation 101
KakaoBank | Leonardo YongUk Kim
이 발표에서 다루지 않는 것.
1. ProGuard 사용법
2. 난독화 도구 벤치마크
3. 앱을 해킹하는 방법
4. ProGuard 유즈 케이스
5. 소스 코드에 기반한 분석
6. 복잡한 난독화 이론
7. 안드로이드 10 이름
8. 구글, 네이버, 라인, 카카오, 카뱅 입사
이 발표에서 다루는 것.
1. 안드로이드 코드의 특징
2. 바이트 코드에 대한 간략한 이해
3. Transform API
4. 난독화와 앱 보호에 대한 간략한 이론
5. ProGuard와 R8의 역할
안드로이드 코드의 특징
1. 이식에 좋은 바이트 코드를 사용.
2. 동적 컴파일에 기반한 성능 향상.
3. 의존성도 동적으로 로드.
4. 암호화되지 않은 클래스와 리소스로 구성.
5. 공간 효율적인 데이터 포맷.
안드로이드 코드의 특징
1. 이식에 좋은 바이트 코드를 사용.
2. 동적 컴파일에 기반한 성능 향상.
3. 의존성도 동적으로 로드.
4. 암호화되지 않은 클래스와 리소스로 구성.
5. 공간 효율적인 데이터 포맷.
이식에 좋은 바이트 코드
자바 Bytecode
1. 스택 기반의 VM을 사용. 스택에서 입력 받고 결과도 스택으로.
2. 32비트 스택 하나의 요소가 대부분의 타입을 커버.
3. char을 사용해도 기본적으로는 메모리에 이점이 없다.
4. 64비트가 필요한 자료형(double, long)을 사용할 경우 스택 두 요소.
5. 256개의 연산 (실제로는 예약어가 많음.)
6. 스택 기반은 검증하기 쉬움.
7. 레지스터 기반의 실제 기기와 차이가 있어 성능상 단점.
스택 기반 VM
스택
변수
Frame
상수 풀
상수의 종류를
구분하지 않는 것이
특징.
(안드로이드는 분리함.)
스택 기반 VM
1 + 2 = ?
1
스택에 1과 2를 넣는다.
istore_1 // 1을 스택에 넣는다.
int 상수 -1(m1)부터 5(0)까지는
별도의 istore 명령을 제공한다.
스택 기반 VM
1 + 2 = ?
1
2
스택에 1과 2를 넣는다.
istore_1 // 1을 스택에 넣는다.
istore_2 // 2를 스택에 넣는다.
int를 위한 연산
스택 기반 VM
1 + 2 = ? 1 2
스택에서 값을 빼고 합산한다.
istore_1 // 1을 스택에 넣는다.
istore_2 // 2를 스택에 넣는다.
iadd // 두 개의 수를 더해 스택에 넣는다.
사칙연산도 Java는 타입을 따진다.
(닷넷은 따지지 않음.)
스택 기반 VM
1 + 2 = ?
3
합산된 3을 스택에 넣는다.
istore_1 // 1을 스택에 넣는다.
istore_2 // 2를 스택에 넣는다.
iadd // 두 개의 수를 더해 스택에 넣는다.
스택 기반 VM
1 + 2 = ?
istore_1
명령을 나열해보자.
istore_2 iadd
스택 기반 VM
1 + 2 = ?
0x04
실제 opcodes로 바꾸어 보자.
0x05 0x60
낯설어도 x86, ARM 기계어보다 단순.
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html
istore_1 istore_2 iadd
이식에 좋은 바이트 코드
달빅 Bytecode
1. 레지스터 기반의 VM을 사용.
2. 64K 개의 레지스터 하지만 주로 앞의 256개, 가끔은 16개만 사용.
3. 32비트 레지스터 하나의 요소가 대부분의 타입을 커버.
4. char을 사용해도 기본적으로는 메모리에 이점이 없다.
5. 64비트가 필요한 자료형(double, long)을 사용할 경우 레지스터 2개.
6. 200여개의 연산.
7. 실제 하드웨어와 매핑에 이점. 성능 상의 이점. 메모리를 덜 씀.
8. 상대적으로 검증이 어려움.
달빅 bytecode
1+2 = ?
const/4 v2, 0x1 // 레지스터 v1에 4비트 상수 0x1을 넣습니다.
const/4 v3, 0x2 // 레지스터 v2에 4비트 상수 0x2를 넣습니다.
add-int v0, v2, v3 // 레지스터 v1과 v2의 합을 v0에 저장한다.
const/4 0x1, v2
0x12 0x12
http://pallergabor.uw.hu/androidblog/dalvik_opcodes.html
달빅 bytecode
1+2 = ?
const/4 v2, 0x1 // 레지스터 v1에 4비트 상수 0x1을 넣습니다.
const/4 v3, 0x2 // 레지스터 v2에 4비트 상수 0x2를 넣습니다.
add-int v0, v2, v3 // 레지스터 v1과 v2의 합을 v0에 저장한다.
add-int v0
0x90 0x00
v2 v3
0x02 0x03
달빅 bytecode
조금 더 작은 바이너리
const/4 v2, 0x1 // 레지스터 v1에 4비트 상수 0x1을 넣습니다.
const/4 v3, 0x2 // 레지스터 v2에 4비트 상수 0x2를 넣습니다.
add-int/2addr v2, v3 // 레지스터 v2에 v3를 더한다.
add-
int/2addr
v3, v2
0xB0 0x32
자바와 달빅 bytecode가 다른데?
javac desugar dex
.java .class .class
.dex
자바와 달빅 bytecode가 다른데?
javac desugar dex
.java .class .class
.dex
JAVA 8 기능을 사용하는 코드를 JAVA7 바이트 코드로 변형.
자바와 달빅 bytecode가 다른데?
javac desugar dex
.java .class .class
.dex
.class 파일을 dex(dx, d8)을 통해 .dex 파일로 변환
DX와 D8은 무엇인가요?
dex
.class
.dex
.class 파일을 dex(dx, d8)을 통해 .dex 파일로 변환
DX - 안드로이드 3.0 전.
D8 - 안드로이드 3.0 이후.
gradle.properties에서
android.enableD8 = true | false
D8은 써드파티 툴에서 문제가 될 수 있다.
- 새로운 DEX를 지원하지 못하는 문제.
DX와 D8은 무엇인가요?
DX와 D8은 무엇인가요?
D8가 진짜 좋나요?
https://developer.android.com/studio/build/apk-analyzer
한번에 달빅 바이너리로 빌드하면 안되요?
Jack & Jill .dex
구글은 Jack & Jill을 통해 한번에 빌드하는 것을 시도
.java
우리에겐 꿈과 희망이 없어.
렌더스크립트에 몰빵했던 투자했던 1인으로 구글의
신기술은 비판적으로 수용하게 됩니다…
구글은 결국 써드파티를 위한 API을 열었습니다.
javac desugar
3rd party
transformers
dex
바이트 코드를 써드파티도 조작할 수 있다.
Transform API
구글은 결국 써드파티를 위한 API을 열었습니다.
Transformer
.class
.class
안드로이드 빌드 툴에서 제공하는 클래스 파일
변조 표준.
프로가드 등의 도구들도 Transform API에 의존적.
다만 여전히 Transform API를 사용하지 못하거나
이해하지 못하는 도구들이 존재.
구글은 결국 써드파티를 위한 API을 열었습니다.
Transform을 상속받고 transform을 구현합니다.
입력으로 클래스 파일을 받아 출력으로 클래스를 내보내면 됩니다.
(실제로는 다른 파일에 접근 가능하지만 대부분은 클래스에)
구글은 결국 써드파티를 위한 API을 열었습니다.
ProGuard
.class
.class
Realm
Transformer
.class
.class
Desugar
.class
.class
구글 코드들 조차도 표준화된 API를 따릅니다.
구글도 Transform API를 사용한다.
1. FixStackFramesTransform – 스택을 검증하기 위해 바이트 코드 재계산.
2. DesugarTransform – Java 8 코드의 기능을 Java 7으로 변경. (람다!)
3. StripDebugSymbolTransform – 네이티브 라이브러리 디버그 심볼 제거.
4. ProGuardTransform – 프로가드 적용 트랜스포머.
5. R8Transform – D8에 난독화를 추가한 R8을 적용. (신기능)
6. JarMergingTransform – 여러 Jar를 합쳐줌.
7. DexMergerTransform - 여러 Dex를 합침.
...
https://android.googlesource.com/platform/tools/base/+/studio-master-
dev/build-system/gradle-
core/src/main/java/com/android/build/gradle/internal/transforms/
여러분도 Transformer를 만들고 싶다면
바이트 코드를 한땀 한땀 작성합시다.
여러분도 Transformer를 만들고 싶다면
ASM
BCEL
Javassist AspectJ
Low Level High Level
바이트 코드를 손으로 한땀 한땀 조작 도구를 사용합시다.
구글은 ASM을 좋아하는 것 같으며,
Realm은 Javassist를 선택했고,
Jake Wharton이 AspectJ를 쓴 걸 봤어요.
안드로이드 코드의 특징
1. 이식에 좋은 바이트 코드를 사용.
2. 동적 컴파일에 기반한 성능 향상.
3. 의존성도 동적으로 로드.
4. 암호화되지 않은 클래스와 리소스로 구성.
5. 공간 효율적인 데이터 포맷.
동적 컴파일에 기반한 성능 향상
인터프리터 vs JIT vs AOT
1. 아무런 정보가 없었을 때 인터프리터로 해석. (느림)
2. 일정 횟수 이상 수행된 메서드만 컴파일해 JIT 코드 캐쉬에 저장.
3. 메서드 수행 시 JIT 코드 캐쉬에 있다면 인터프리터 대신 캐쉬 사용.
4. 프로파일링 정보에 기반해서 JIT 코드 캐쉬를 업데이트.
5. 주기적으로 dex2aot 데몬이 코드를 빌드해서 oat 파일을 생성.
6. 다른 앱에 의해 사용되면 전체 빌드하고 아니면 프로파일에 기반해 빌드.
7. JIT 컴파일과 AOT 컴파일이 있다면 JIT 컴파일을 사용. (동적 최적화)
JIT + AOT
JIT 컴파일
AOT 컴파일
전체 컴파일
안드로이드 코드의 특징
1. 이식에 좋은 바이트 코드를 사용.
2. 동적 컴파일에 기반한 성능 향상.
3. 의존성도 동적으로 로드.
4. 암호화되지 않은 클래스와 리소스로 구성.
5. 공간 효율적인 데이터 포맷.
의존성도 동적으로 로드.
1. import / export 해야하는 부분은 숨길 수 없다.
2. 메서드 명, 클래스 명, 필드 명이 공개된다.
3. 안드로이드 앱의 경우 Activity, Fragment, Service 등을 외부로
공개해야 한다.
4. 난독화 도구에 exclude/include 항목을 설정해야 함.
안드로이드 코드의 특징
1. 이식에 좋은 바이트 코드를 사용.
2. 동적 컴파일에 기반한 성능 향상.
3. 의존성도 동적으로 로드.
4. 암호화되지 않은 클래스와 리소스로 구성.
5. 공간 효율적인 데이터 포맷.
암호화되지 않은 클래스와 리소스로 구성
1. 클래스와 리소스는 압축되지 않음.
2. 기본 클래스 로더가 지원하지 않아 한계가 존재.
기본 클래스 로더가 호출되기 전에 암호화가 풀린 클래스를
가로챌 수 있음.
3. 클래스 암호화는 선호되는 보호 방법이 아님.
안드로이드 코드의 특징
1. 이식에 좋은 바이트 코드를 사용.
2. 동적 컴파일에 기반한 성능 향상.
3. 의존성도 동적으로 로드.
4. 암호화되지 않은 클래스와 리소스로 구성.
5. 공간 효율적인 데이터 포맷.
공간 효율적인 데이터 포맷
1. LEB-128을 사용하여 가변 바이트(1-5바이트)로 32비트 잘 저장.
(최악의 경우에는 더 나빠질 수 있음.)
2. 부호가 있는 수를 위한 SLEB128, 부호없는 수를 위한
ULEB128과 -1만 지원하는 ULEB128p1이 있음.
3. 여러 클래스에서 상수를 공유. (Shared Constant Pool)
4. 상대 주소 사용.
5. 하지만 메서드 갯수까지 효율적으로 64K T.T
LEB-128 (Little Endian Base-128)
10011000011101100101
7비트 단위로 나누어 그룹화.
0100110 0001110 1100101
LEB-128 (Little Endian Base-128)
10011000011101100101
제일 앞 그룹은 0, 그 외는 앞에 1을 붙인다.
0100110 0001110 1100101
00100110 10001110 11100101
LEB-128 (Little Endian Base-128)
10011000011101100101
1. 가변 바이트를 사용해 작은 수는 더 적은 비트로 저장. (1바이트)
2. 최악의 경우 32비트 자료형을 5바이트로 저장할 수 있다.
3. SLEB128P는 전체 값이 -1이면 0이 된다. 음수 중 -1만 지원.
0100110 0001110 1100101
00100110 10001110 11100101
Shared Constant Pool
Shared Constant Pool
상대주소
fieldA 1024
fieldB 1 (1024 + 1)
안드로이드 코드를 어떻게 숨길까?
1. 클래스/리소스에 암호화를 적용한다.
2. 리소스 사이에 클래스를 매복시킨다.
3. 네이티브 코드로 핵심 코드를 숨긴다.
4. 서버에 핵심 코드를 숨긴다.
5. 템퍼 감지를 추가한다.
6. 클래스, 필드, 메서드 명을 변경한다.
7. 리플렉션 호출을 추가한다.
8. 노이즈 추가.
9. 제어 흐름.
안드로이드 코드를 어떻게 숨길까?
1. 클래스/리소스에 암호화를 적용한다.
2. 리소스 사이에 클래스를 매복시킨다.
3. 네이티브 코드로 핵심 코드를 숨긴다.
4. 서버에 핵심 코드를 숨긴다.
5. 템퍼 감지를 추가한다.
6. 클래스, 필드, 메서드 명을 변경한다.
7. 리플렉션 호출을 추가한다.
8. 노이즈 추가.
9. 제어 흐름.
클래스/리소스에 암호화를 적용한다.
1. 클래스를 암호화 하고 커스텀 로더를 사용하는 방법.
2. 암호화된 클래스와 로더를 가지는 패커(Packer)와 깨어진
클래스와 복구 코드를 가지는 프로텍터(Protector)로 나누어짐.
3. VM이 암호화된 클래스를 지원하지 않기 때문에 결국 해독을
하여 클래스를 전달해야 한다.
결국, 커스텀 로더가 공격의 취약점이 된다.
4. 많은 패커와 프로텍터는 ODEX 복사나 심지어 Dex2jar툴에 의해
쉽게 풀리기도 한다.
패커 (Packer)
클래스 로더 암호화된 코드를 해독.
암호화된 클래스 실제 코드가 암호화.
프로텍터 (Protector)
스텁 어플리케이션 깨어진 코드를 복구.
망가진 클래스 실제 코드가 깨어짐.
안드로이드 코드를 어떻게 숨길까?
1. 클래스/리소스에 암호화를 적용한다.
2. 리소스 사이에 클래스를 매복시킨다.
3. 네이티브 코드로 핵심 코드를 숨긴다.
4. 서버에 핵심 코드를 숨긴다.
5. 템퍼 감지를 추가한다.
6. 클래스, 필드, 메서드 명을 변경한다.
7. 리플렉션 호출을 추가한다.
8. 노이즈 추가.
9. 제어 흐름.
리소스 사이에 클래스를 매복시킨다.
1. 이미지 파일로 dex 파일을 숨기는 방법 등을 사용한다.
2. 수상하게 큰 파일을 찾거나 파일 포맷이 올바른지 검증하는
것만으로 혐의가 좁혀진다.
안드로이드 코드를 어떻게 숨길까?
1. 클래스/리소스에 암호화를 적용한다.
2. 리소스 사이에 클래스를 매복시킨다.
3. 네이티브 코드로 핵심 코드를 숨긴다.
4. 서버에 핵심 코드를 숨긴다.
5. 템퍼 감지를 추가한다.
6. 클래스, 필드, 메서드 명을 변경한다.
7. 리플렉션 호출을 추가한다.
8. 노이즈 추가.
9. 제어 흐름.
네이티브 코드로 핵심 코드를 숨긴다.
1. 성능 상의 이점이 있으며 기계어 코드가 자바 / 달빅의 바이트
코드보다 해석하기 어렵다는 장점이 있음.
하지만 인내가 있다면 결국 기계어 코드도 해석할 수 있다.
2. VM 밖은 위험하다. (온갖 하드웨어 이슈를 경험할 수 있다.)
.so 파일이 간혈적으로 설치되지 않는 문제 발생.
POSIX 함수가 목업이었던 B사.
OpenSSL이 목업이었던 L사.
메모리 할당량을 잘못 알려주었던 S사.
재현 불가능한 이슈도 동작한다.
3. ABI 세트를 맞추기 어렵다.
VM 밖은 위험하다.
강제로 .so 파일을 재설치하는 라이브러리 ReLinker
VM 밖은 위험하다.
VM 밖은 위험하다.
ABI 세트를 맞추기 어렵다.
안드로이드 코드를 어떻게 숨길까?
1. 클래스/리소스에 암호화를 적용한다.
2. 리소스 사이에 클래스를 매복시킨다.
3. 네이티브 코드로 핵심 코드를 숨긴다.
4. 서버에 핵심 코드를 숨긴다.
5. 템퍼 감지를 추가한다.
6. 클래스, 필드, 메서드 명을 변경한다.
7. 리플렉션 호출을 추가한다.
8. 노이즈 추가.
9. 제어 흐름.
서버에 핵심 코드를 숨긴다.
1. 리얼 타임 응답성이 떨어진다. 웹 소켓 등의 상시 연결을
하더라도 로컬만큼 쾌적하기 어렵다. 응답성을 올리기 위해
로컬 데이터베이스등이 필요할 수 있다.
2. 서버가 다운된 경우 어플리케이션을 사용할 수 없다. 로컬
캐쉬를 이용해서 폴백을 구현하기 쉽지 않다.
안드로이드 코드를 어떻게 숨길까?
1. 클래스/리소스에 암호화를 적용한다.
2. 리소스 사이에 클래스를 매복시킨다.
3. 네이티브 코드로 핵심 코드를 숨긴다.
4. 서버에 핵심 코드를 숨긴다.
5. 템퍼 감지를 추가한다.
6. 클래스, 필드, 메서드 명을 변경한다.
7. 리플렉션 호출을 추가한다.
8. 노이즈 추가.
9. 제어 흐름.
템퍼 감지를 추가한다.
1. 디버거, ptrace를 발견하는 코드를 삽입한다.
2. 에뮬레이터 등에 감지한다.
3. 해당 부분을 우회하면 대부분 앱에 진입할 수 있다.
안드로이드 코드를 어떻게 숨길까?
1. 클래스/리소스에 암호화를 적용한다.
2. 리소스 사이에 클래스를 매복시킨다.
3. 네이티브 코드로 핵심 코드를 숨긴다.
4. 서버에 핵심 코드를 숨긴다.
5. 템퍼 감지를 추가한다.
6. 클래스, 필드, 메서드 명을 변경한다.
7. 리플렉션 호출을 추가한다.
8. 노이즈 추가.
9. 제어 흐름.
클래스, 필드, 메서드 등의 이름을 변경한다.
1. 무료 도구인 ProGuard 부터 대부분의 도구가 지원.
2. 짧고 간결한 이름으로 클래스, 필드, 메서드의 이름을 변경. 단
외부에서 import되거나 export되는 명칭들은 변경될 수 없음.
3. 짧고 간결한 이름의 경우 공간 복잡도도 줄이고 실행 시간에도
긍정적인 영향을 줄 수 있음.
4. 명칭이 바뀌는 것이기 때문에 공격자의 인내로 해결 가능.
5. 도구마다 고유의 네이밍 패턴이 있음. (APKiD에서 검출)
Arxan이 가지는 네이밍 패턴
안드로이드 코드를 어떻게 숨길까?
1. 클래스/리소스에 암호화를 적용한다.
2. 리소스 사이에 클래스를 매복시킨다.
3. 네이티브 코드로 핵심 코드를 숨긴다.
4. 서버에 핵심 코드를 숨긴다.
5. 템퍼 감지를 추가한다.
6. 클래스, 필드, 메서드 명을 변경한다.
7. 리플렉션 호출을 추가한다.
8. 노이즈 추가.
9. 제어 흐름.
리플렉션 호출을 추가
1. 메서드 호출에 단계를 추가하여 해석하기 어렵게 만듬.
2. ProGuard는 지원하지 못하지만 DexGuard나 Arxan 등의 유료
도구에서 지원.
3. 리플렉션이 반복적인 패턴을 가지기 때문에 기계적으로 풀 수
있음. (예: Dex-Oracle에서 DexGuard 패턴 해제.)
4. 많은 단말에서 리플렉션 크래시가 보고됨. 리플렉션 실행이
랜덤하게 실패하는 것으로 보임. (혼탁한 안드로이드의 세계)
리플렉션 호출을 추가
안드로이드 코드를 어떻게 숨길까?
1. 클래스/리소스에 암호화를 적용한다.
2. 리소스 사이에 클래스를 매복시킨다.
3. 네이티브 코드로 핵심 코드를 숨긴다.
4. 서버에 핵심 코드를 숨긴다.
5. 템퍼 감지를 추가한다.
6. 클래스, 필드, 메서드 명을 변경한다.
7. 리플렉션 호출을 추가한다.
8. 노이즈 추가.
9. 제어 흐름.
노이즈 추가
1. 메서드 외부에 아무런 영향이 없는 코드를 추가.
2. 클래스 상태 변경, I/O, 반환 값 등이 없음.
3. 디버거나 디스컴파일러가 깨지는 코드를 추가하기도 한다.
하지만 몇 줄로 망가트린 문제는 그만큼 쉽게 고쳐진다.
노이즈 추가
1201
3801 0300
FFFF
V1에 0을 적재
다음줄 스킵
불량 코드
안드로이드 코드를 어떻게 숨길까?
1. 클래스/리소스에 암호화를 적용한다.
2. 리소스 사이에 클래스를 매복시킨다.
3. 네이티브 코드로 핵심 코드를 숨긴다.
4. 서버에 핵심 코드를 숨긴다.
5. 템퍼 감지를 추가한다.
6. 클래스, 필드, 메서드 명을 변경한다.
7. 리플렉션 호출을 추가한다.
8. 노이즈 추가.
9. 제어 흐름.
제어 흐름
1. 반복문이나 분기문을 불 필요하게 추가.
2. If문 대신 try-catch 블록을 사용하기도 함.
(Replacing if null instructiions with try-catch blocks)
3. Switch 문을 중첩적인 레이블로 변경하기도 함.
(Partially trapping switch statements)
4. 브랜치를 jsr 명령 (goto)로 변환하기도 함.
(Converting branches to jsr instructions)
일반적으로 속도를 저하시키는 요인.
프로가드는 무엇을 지원할까?
1. 클래스/리소스에 암호화를 적용한다.
2. 리소스 사이에 클래스를 매복시킨다.
3. 네이티브 코드로 핵심 코드를 숨긴다.
4. 서버에 핵심 코드를 숨긴다.
5. 템퍼 감지를 추가한다.
6. 클래스, 필드, 메서드 명을 변경한다.
7. 리플렉션 호출을 추가한다.
8. 노이즈 추가.
9. 제어 흐름.
프로가드는 무엇을 지원할까?
1. 클래스/리소스에 암호화를 적용한다.
2. 리소스 사이에 클래스를 매복시킨다.
3. 네이티브 코드로 핵심 코드를 숨긴다.
4. 서버에 핵심 코드를 숨긴다.
5. 템퍼 감지를 추가한다.
6. 클래스, 필드, 메서드 명을 변경한다.
7. 리플렉션 호출을 추가한다.
8. 노이즈 추가.
9. 제어 흐름.
10. 사용하지 않은 클래스, 필드, 메서드 제거.
프로가드는 무엇을 지원할까?
ProGuard
.class
.class
Shrinker
.class
.class
Name
Obfuscator
.class
안드로이드 빌드에 통합된 프로가드
ProGuard
Transform
javac
Desugar
Transform
ProGuard
.java .class .class
.class
Dex
.dex
제대로 통합되지 않은 툴이 많은 것을 감안하면 엄청난 장점.
통합되지 않은 난독화 도구들의 흐름
javac
Desugar
Transform
.java .class .class
Dex
.dex
Dex2jarObfuscator
.class
.class
.class
Dex
.dex
구글의 신 병기 R8
R8javac
Desugar
Transform
.java .class .class
.dex
R8 = D8 + Shrinker + Name Obfuscator
https://android.googlesource.com/platform/external/r8/
ProGuard를 버리겠다는 계획.
갑자기 괜히 렌더스크립트, Jack & Jill을 불러보고 싶다.
일관성
(Consistency)
가용성
(Availability)
분할내성
(Partial
tolerance)
데이터베이스에 CAP 정리가 있다.
성능
(Performance)
안정성
(Relability)
보안
(Security)
앱에서 아래 셋은 다 가질 수 있는 것인가?
(PRS?) 방금 지어냄.
감사합니다.
1 de 91

Recomendados

[15]Android Kotlin을 통한 개발 전략 por
[15]Android Kotlin을 통한 개발 전략[15]Android Kotlin을 통한 개발 전략
[15]Android Kotlin을 통한 개발 전략NAVER Engineering
589 visualizações38 slides
Spring one참석기 ksug por
Spring one참석기 ksugSpring one참석기 ksug
Spring one참석기 ksugSanghyuk Jung
1.5K visualizações50 slides
Java 기초 por
Java 기초Java 기초
Java 기초Hyosang Hong
87 visualizações28 slides
Spring batch와 함께 하는 TDD por
Spring batch와 함께 하는 TDDSpring batch와 함께 하는 TDD
Spring batch와 함께 하는 TDDSanghyuk Jung
4.9K visualizações29 slides
Java rmi 개발 가이드 por
Java rmi 개발 가이드Java rmi 개발 가이드
Java rmi 개발 가이드중선 곽
2.5K visualizações13 slides
메이븐 기본 이해 por
메이븐 기본 이해메이븐 기본 이해
메이븐 기본 이해중선 곽
36.3K visualizações28 slides

Mais conteúdo relacionado

Mais procurados

[네이버오픈소스세미나] Pinpoint를 이용해서 서버리스 플랫폼 Apache Openwhisk 트레이싱하기 - 오승현 por
[네이버오픈소스세미나] Pinpoint를 이용해서 서버리스 플랫폼 Apache Openwhisk 트레이싱하기 - 오승현[네이버오픈소스세미나] Pinpoint를 이용해서 서버리스 플랫폼 Apache Openwhisk 트레이싱하기 - 오승현
[네이버오픈소스세미나] Pinpoint를 이용해서 서버리스 플랫폼 Apache Openwhisk 트레이싱하기 - 오승현NAVER Engineering
7.4K visualizações51 slides
Java(2/4) por
Java(2/4)Java(2/4)
Java(2/4)handfoot
448 visualizações27 slides
[D2 CAMPUS] 안드로이드 오픈소스 스터디자료 - Http Request por
[D2 CAMPUS] 안드로이드 오픈소스 스터디자료 - Http Request[D2 CAMPUS] 안드로이드 오픈소스 스터디자료 - Http Request
[D2 CAMPUS] 안드로이드 오픈소스 스터디자료 - Http RequestNAVER D2
3.9K visualizações15 slides
Java(1/4) por
Java(1/4)Java(1/4)
Java(1/4)handfoot
928 visualizações23 slides
Java the good parts por
Java the good partsJava the good parts
Java the good partsSungchul Park
4.1K visualizações33 slides
클라우드 서비스운영 플랫폼 가루다 Open cloudengine_패스트캣_cto 송상욱 por
클라우드 서비스운영 플랫폼 가루다 Open cloudengine_패스트캣_cto 송상욱클라우드 서비스운영 플랫폼 가루다 Open cloudengine_패스트캣_cto 송상욱
클라우드 서비스운영 플랫폼 가루다 Open cloudengine_패스트캣_cto 송상욱uEngine Solutions
1K visualizações59 slides

Mais procurados(14)

[네이버오픈소스세미나] Pinpoint를 이용해서 서버리스 플랫폼 Apache Openwhisk 트레이싱하기 - 오승현 por NAVER Engineering
[네이버오픈소스세미나] Pinpoint를 이용해서 서버리스 플랫폼 Apache Openwhisk 트레이싱하기 - 오승현[네이버오픈소스세미나] Pinpoint를 이용해서 서버리스 플랫폼 Apache Openwhisk 트레이싱하기 - 오승현
[네이버오픈소스세미나] Pinpoint를 이용해서 서버리스 플랫폼 Apache Openwhisk 트레이싱하기 - 오승현
NAVER Engineering7.4K visualizações
Java(2/4) por handfoot
Java(2/4)Java(2/4)
Java(2/4)
handfoot448 visualizações
[D2 CAMPUS] 안드로이드 오픈소스 스터디자료 - Http Request por NAVER D2
[D2 CAMPUS] 안드로이드 오픈소스 스터디자료 - Http Request[D2 CAMPUS] 안드로이드 오픈소스 스터디자료 - Http Request
[D2 CAMPUS] 안드로이드 오픈소스 스터디자료 - Http Request
NAVER D23.9K visualizações
Java(1/4) por handfoot
Java(1/4)Java(1/4)
Java(1/4)
handfoot928 visualizações
Java the good parts por Sungchul Park
Java the good partsJava the good parts
Java the good parts
Sungchul Park4.1K visualizações
클라우드 서비스운영 플랫폼 가루다 Open cloudengine_패스트캣_cto 송상욱 por uEngine Solutions
클라우드 서비스운영 플랫폼 가루다 Open cloudengine_패스트캣_cto 송상욱클라우드 서비스운영 플랫폼 가루다 Open cloudengine_패스트캣_cto 송상욱
클라우드 서비스운영 플랫폼 가루다 Open cloudengine_패스트캣_cto 송상욱
uEngine Solutions1K visualizações
Tcp ip & io model por Nam Hyeonuk
Tcp ip & io modelTcp ip & io model
Tcp ip & io model
Nam Hyeonuk7.7K visualizações
[D2 CAMPUS] 안드로이드 오픈소스 스터디자료 - java OOM, Reference API por NAVER D2
[D2 CAMPUS] 안드로이드 오픈소스 스터디자료 - java OOM, Reference API[D2 CAMPUS] 안드로이드 오픈소스 스터디자료 - java OOM, Reference API
[D2 CAMPUS] 안드로이드 오픈소스 스터디자료 - java OOM, Reference API
NAVER D23.6K visualizações
자바8 나머지 공개 por Sungchul Park
자바8 나머지 공개자바8 나머지 공개
자바8 나머지 공개
Sungchul Park2.9K visualizações
Concurrent Programming (Java thread 다루기) por JungGeun Lee
Concurrent Programming (Java thread 다루기)Concurrent Programming (Java thread 다루기)
Concurrent Programming (Java thread 다루기)
JungGeun Lee1K visualizações
JVM Synchronization_Wh apm por 엑셈
JVM Synchronization_Wh apmJVM Synchronization_Wh apm
JVM Synchronization_Wh apm
엑셈304 visualizações
IBM JVM GC_Wh apm por 엑셈
IBM JVM GC_Wh apmIBM JVM GC_Wh apm
IBM JVM GC_Wh apm
엑셈588 visualizações
Hotspot JVM GC_Wh apm por 엑셈
Hotspot JVM GC_Wh apmHotspot JVM GC_Wh apm
Hotspot JVM GC_Wh apm
엑셈435 visualizações
함수형 프로그래밍 por QooJuice
함수형 프로그래밍함수형 프로그래밍
함수형 프로그래밍
QooJuice1.1K visualizações

Similar a [16]Obfuscation 101 : 난독화, 프로가드, R8, 트랜스포머 API

안드로이드 빌드: 설탕없는 세계 por
안드로이드 빌드: 설탕없는 세계안드로이드 빌드: 설탕없는 세계
안드로이드 빌드: 설탕없는 세계Leonardo YongUk Kim
39 visualizações76 slides
Architecture patterns with python (2) por
Architecture patterns with python (2)Architecture patterns with python (2)
Architecture patterns with python (2)동환 김
171 visualizações25 slides
[1B3]모바일 앱 크래시 네이버에서는 어떻게 수집하고 보여줄까요 por
[1B3]모바일 앱 크래시 네이버에서는 어떻게 수집하고 보여줄까요[1B3]모바일 앱 크래시 네이버에서는 어떻게 수집하고 보여줄까요
[1B3]모바일 앱 크래시 네이버에서는 어떻게 수집하고 보여줄까요NAVER D2
14.7K visualizações44 slides
윈도우 커널 익스플로잇 por
윈도우 커널 익스플로잇윈도우 커널 익스플로잇
윈도우 커널 익스플로잇Seungyong Lee
778 visualizações7 slides
[아꿈사] The C++ Programming Language 9장 소스 파일과 프로그램 por
[아꿈사] The C++ Programming Language 9장 소스 파일과 프로그램[아꿈사] The C++ Programming Language 9장 소스 파일과 프로그램
[아꿈사] The C++ Programming Language 9장 소스 파일과 프로그램해강
1.3K visualizações12 slides
스프링 스터디 1장 por
스프링 스터디 1장스프링 스터디 1장
스프링 스터디 1장Seongchan Kang
137 visualizações74 slides

Similar a [16]Obfuscation 101 : 난독화, 프로가드, R8, 트랜스포머 API(20)

안드로이드 빌드: 설탕없는 세계 por Leonardo YongUk Kim
안드로이드 빌드: 설탕없는 세계안드로이드 빌드: 설탕없는 세계
안드로이드 빌드: 설탕없는 세계
Leonardo YongUk Kim39 visualizações
Architecture patterns with python (2) por 동환 김
Architecture patterns with python (2)Architecture patterns with python (2)
Architecture patterns with python (2)
동환 김171 visualizações
[1B3]모바일 앱 크래시 네이버에서는 어떻게 수집하고 보여줄까요 por NAVER D2
[1B3]모바일 앱 크래시 네이버에서는 어떻게 수집하고 보여줄까요[1B3]모바일 앱 크래시 네이버에서는 어떻게 수집하고 보여줄까요
[1B3]모바일 앱 크래시 네이버에서는 어떻게 수집하고 보여줄까요
NAVER D214.7K visualizações
윈도우 커널 익스플로잇 por Seungyong Lee
윈도우 커널 익스플로잇윈도우 커널 익스플로잇
윈도우 커널 익스플로잇
Seungyong Lee778 visualizações
[아꿈사] The C++ Programming Language 9장 소스 파일과 프로그램 por 해강
[아꿈사] The C++ Programming Language 9장 소스 파일과 프로그램[아꿈사] The C++ Programming Language 9장 소스 파일과 프로그램
[아꿈사] The C++ Programming Language 9장 소스 파일과 프로그램
해강 1.3K visualizações
스프링 스터디 1장 por Seongchan Kang
스프링 스터디 1장스프링 스터디 1장
스프링 스터디 1장
Seongchan Kang137 visualizações
몽고디비교육1일차 por seung-hyun Park
몽고디비교육1일차몽고디비교육1일차
몽고디비교육1일차
seung-hyun Park2.4K visualizações
제프리 리처의 Windows via C/C++ : 8장 유저 모드에서의 스레드 동기화 por sung ki choi
제프리 리처의 Windows via C/C++ : 8장 유저 모드에서의 스레드 동기화제프리 리처의 Windows via C/C++ : 8장 유저 모드에서의 스레드 동기화
제프리 리처의 Windows via C/C++ : 8장 유저 모드에서의 스레드 동기화
sung ki choi8.1K visualizações
LDA : latent Dirichlet Allocation (Fairies NLP Series) - Korean Ver. por Adonis Han
LDA : latent Dirichlet Allocation (Fairies NLP Series) - Korean Ver.LDA : latent Dirichlet Allocation (Fairies NLP Series) - Korean Ver.
LDA : latent Dirichlet Allocation (Fairies NLP Series) - Korean Ver.
Adonis Han1.9K visualizações
Rust por Wonjun Hwang
RustRust
Rust
Wonjun Hwang38 visualizações
[1B6]Realm a database for android & ios por NAVER D2
[1B6]Realm a database for android & ios[1B6]Realm a database for android & ios
[1B6]Realm a database for android & ios
NAVER D26.4K visualizações
SOSCON 2017 - Backend.AI por Joongi Kim
SOSCON 2017 - Backend.AISOSCON 2017 - Backend.AI
SOSCON 2017 - Backend.AI
Joongi Kim327 visualizações
From Java code to Java heap_SYS4U I&C por sys4u
From Java code to Java heap_SYS4U I&CFrom Java code to Java heap_SYS4U I&C
From Java code to Java heap_SYS4U I&C
sys4u1.1K visualizações
Net debugging 3_전한별 por Han-Byul Jeon
Net debugging 3_전한별Net debugging 3_전한별
Net debugging 3_전한별
Han-Byul Jeon249 visualizações
리소스 중심의 서든어택2 실시간 메모리 프로파일링 시스템 개발기 por Wonha Ryu
리소스 중심의 서든어택2 실시간 메모리 프로파일링 시스템 개발기리소스 중심의 서든어택2 실시간 메모리 프로파일링 시스템 개발기
리소스 중심의 서든어택2 실시간 메모리 프로파일링 시스템 개발기
Wonha Ryu1.3K visualizações
Node week1 por 은석 김은석
Node week1Node week1
Node week1
은석 김은석452 visualizações
생체 광학 데이터 분석 AI 경진대회 3위 수상작 por DACON AI 데이콘
생체 광학 데이터 분석 AI 경진대회 3위 수상작생체 광학 데이터 분석 AI 경진대회 3위 수상작
생체 광학 데이터 분석 AI 경진대회 3위 수상작
DACON AI 데이콘562 visualizações
Upgrade VCL! 오래된 프로그램, 최신 버전으로 탈바꿈하기 por Devgear
Upgrade VCL! 오래된 프로그램, 최신 버전으로 탈바꿈하기Upgrade VCL! 오래된 프로그램, 최신 버전으로 탈바꿈하기
Upgrade VCL! 오래된 프로그램, 최신 버전으로 탈바꿈하기
Devgear1.2K visualizações
『이펙티브 디버깅』 - 디버깅 지옥에서 탈출하는 66가지 전략과 기법 por 복연 이
『이펙티브 디버깅』 - 디버깅 지옥에서 탈출하는 66가지 전략과 기법『이펙티브 디버깅』 - 디버깅 지옥에서 탈출하는 66가지 전략과 기법
『이펙티브 디버깅』 - 디버깅 지옥에서 탈출하는 66가지 전략과 기법
복연 이7.8K visualizações
프로그래밍 패러다임의 진화 및 Spring의 금융권 적용 por 중선 곽
프로그래밍 패러다임의 진화 및 Spring의 금융권 적용프로그래밍 패러다임의 진화 및 Spring의 금융권 적용
프로그래밍 패러다임의 진화 및 Spring의 금융권 적용
중선 곽3.7K visualizações

Mais de NAVER Engineering

React vac pattern por
React vac patternReact vac pattern
React vac patternNAVER Engineering
2K visualizações21 slides
디자인 시스템에 직방 ZUIX por
디자인 시스템에 직방 ZUIX디자인 시스템에 직방 ZUIX
디자인 시스템에 직방 ZUIXNAVER Engineering
1.4K visualizações42 slides
진화하는 디자인 시스템(걸음마 편) por
진화하는 디자인 시스템(걸음마 편)진화하는 디자인 시스템(걸음마 편)
진화하는 디자인 시스템(걸음마 편)NAVER Engineering
688 visualizações47 slides
서비스 운영을 위한 디자인시스템 프로젝트 por
서비스 운영을 위한 디자인시스템 프로젝트서비스 운영을 위한 디자인시스템 프로젝트
서비스 운영을 위한 디자인시스템 프로젝트NAVER Engineering
1K visualizações169 slides
BPL(Banksalad Product Language) 무야호 por
BPL(Banksalad Product Language) 무야호BPL(Banksalad Product Language) 무야호
BPL(Banksalad Product Language) 무야호NAVER Engineering
499 visualizações32 slides
이번 생에 디자인 시스템은 처음이라 por
이번 생에 디자인 시스템은 처음이라이번 생에 디자인 시스템은 처음이라
이번 생에 디자인 시스템은 처음이라NAVER Engineering
855 visualizações79 slides

Mais de NAVER Engineering(20)

React vac pattern por NAVER Engineering
React vac patternReact vac pattern
React vac pattern
NAVER Engineering2K visualizações
디자인 시스템에 직방 ZUIX por NAVER Engineering
디자인 시스템에 직방 ZUIX디자인 시스템에 직방 ZUIX
디자인 시스템에 직방 ZUIX
NAVER Engineering1.4K visualizações
진화하는 디자인 시스템(걸음마 편) por NAVER Engineering
진화하는 디자인 시스템(걸음마 편)진화하는 디자인 시스템(걸음마 편)
진화하는 디자인 시스템(걸음마 편)
NAVER Engineering688 visualizações
서비스 운영을 위한 디자인시스템 프로젝트 por NAVER Engineering
서비스 운영을 위한 디자인시스템 프로젝트서비스 운영을 위한 디자인시스템 프로젝트
서비스 운영을 위한 디자인시스템 프로젝트
NAVER Engineering1K visualizações
BPL(Banksalad Product Language) 무야호 por NAVER Engineering
BPL(Banksalad Product Language) 무야호BPL(Banksalad Product Language) 무야호
BPL(Banksalad Product Language) 무야호
NAVER Engineering499 visualizações
이번 생에 디자인 시스템은 처음이라 por NAVER Engineering
이번 생에 디자인 시스템은 처음이라이번 생에 디자인 시스템은 처음이라
이번 생에 디자인 시스템은 처음이라
NAVER Engineering855 visualizações
날고 있는 여러 비행기 넘나 들며 정비하기 por NAVER Engineering
날고 있는 여러 비행기 넘나 들며 정비하기날고 있는 여러 비행기 넘나 들며 정비하기
날고 있는 여러 비행기 넘나 들며 정비하기
NAVER Engineering2.9K visualizações
쏘카프레임 구축 배경과 과정 por NAVER Engineering
 쏘카프레임 구축 배경과 과정 쏘카프레임 구축 배경과 과정
쏘카프레임 구축 배경과 과정
NAVER Engineering3.2K visualizações
플랫폼 디자이너 없이 디자인 시스템을 구축하는 프로덕트 디자이너의 우당탕탕 고통 연대기 por NAVER Engineering
플랫폼 디자이너 없이 디자인 시스템을 구축하는 프로덕트 디자이너의 우당탕탕 고통 연대기플랫폼 디자이너 없이 디자인 시스템을 구축하는 프로덕트 디자이너의 우당탕탕 고통 연대기
플랫폼 디자이너 없이 디자인 시스템을 구축하는 프로덕트 디자이너의 우당탕탕 고통 연대기
NAVER Engineering1.9K visualizações
200820 NAVER TECH CONCERT 15_Code Review is Horse(코드리뷰는 말이야)(feat.Latte) por NAVER Engineering
200820 NAVER TECH CONCERT 15_Code Review is Horse(코드리뷰는 말이야)(feat.Latte)200820 NAVER TECH CONCERT 15_Code Review is Horse(코드리뷰는 말이야)(feat.Latte)
200820 NAVER TECH CONCERT 15_Code Review is Horse(코드리뷰는 말이야)(feat.Latte)
NAVER Engineering1.6K visualizações
200819 NAVER TECH CONCERT 03_화려한 코루틴이 내 앱을 감싸네! 코루틴으로 작성해보는 깔끔한 비동기 코드 por NAVER Engineering
200819 NAVER TECH CONCERT 03_화려한 코루틴이 내 앱을 감싸네! 코루틴으로 작성해보는 깔끔한 비동기 코드200819 NAVER TECH CONCERT 03_화려한 코루틴이 내 앱을 감싸네! 코루틴으로 작성해보는 깔끔한 비동기 코드
200819 NAVER TECH CONCERT 03_화려한 코루틴이 내 앱을 감싸네! 코루틴으로 작성해보는 깔끔한 비동기 코드
NAVER Engineering1.1K visualizações
200819 NAVER TECH CONCERT 10_맥북에서도 아이맥프로에서 빌드하는 것처럼 빌드 속도 빠르게 하기 por NAVER Engineering
200819 NAVER TECH CONCERT 10_맥북에서도 아이맥프로에서 빌드하는 것처럼 빌드 속도 빠르게 하기200819 NAVER TECH CONCERT 10_맥북에서도 아이맥프로에서 빌드하는 것처럼 빌드 속도 빠르게 하기
200819 NAVER TECH CONCERT 10_맥북에서도 아이맥프로에서 빌드하는 것처럼 빌드 속도 빠르게 하기
NAVER Engineering1K visualizações
200819 NAVER TECH CONCERT 08_성능을 고민하는 슬기로운 개발자 생활 por NAVER Engineering
200819 NAVER TECH CONCERT 08_성능을 고민하는 슬기로운 개발자 생활200819 NAVER TECH CONCERT 08_성능을 고민하는 슬기로운 개발자 생활
200819 NAVER TECH CONCERT 08_성능을 고민하는 슬기로운 개발자 생활
NAVER Engineering751 visualizações
200819 NAVER TECH CONCERT 05_모르면 손해보는 Android 디버깅/분석 꿀팁 대방출 por NAVER Engineering
200819 NAVER TECH CONCERT 05_모르면 손해보는 Android 디버깅/분석 꿀팁 대방출200819 NAVER TECH CONCERT 05_모르면 손해보는 Android 디버깅/분석 꿀팁 대방출
200819 NAVER TECH CONCERT 05_모르면 손해보는 Android 디버깅/분석 꿀팁 대방출
NAVER Engineering706 visualizações
200819 NAVER TECH CONCERT 09_Case.xcodeproj - 좋은 동료로 거듭나기 위한 노하우 por NAVER Engineering
200819 NAVER TECH CONCERT 09_Case.xcodeproj - 좋은 동료로 거듭나기 위한 노하우200819 NAVER TECH CONCERT 09_Case.xcodeproj - 좋은 동료로 거듭나기 위한 노하우
200819 NAVER TECH CONCERT 09_Case.xcodeproj - 좋은 동료로 거듭나기 위한 노하우
NAVER Engineering453 visualizações
200820 NAVER TECH CONCERT 14_야 너두 할 수 있어. 비전공자, COBOL 개발자를 거쳐 네이버에서 FE 개발하게 된... por NAVER Engineering
200820 NAVER TECH CONCERT 14_야 너두 할 수 있어. 비전공자, COBOL 개발자를 거쳐 네이버에서 FE 개발하게 된...200820 NAVER TECH CONCERT 14_야 너두 할 수 있어. 비전공자, COBOL 개발자를 거쳐 네이버에서 FE 개발하게 된...
200820 NAVER TECH CONCERT 14_야 너두 할 수 있어. 비전공자, COBOL 개발자를 거쳐 네이버에서 FE 개발하게 된...
NAVER Engineering487 visualizações
200820 NAVER TECH CONCERT 13_네이버에서 오픈 소스 개발을 통해 성장하는 방법 por NAVER Engineering
200820 NAVER TECH CONCERT 13_네이버에서 오픈 소스 개발을 통해 성장하는 방법200820 NAVER TECH CONCERT 13_네이버에서 오픈 소스 개발을 통해 성장하는 방법
200820 NAVER TECH CONCERT 13_네이버에서 오픈 소스 개발을 통해 성장하는 방법
NAVER Engineering428 visualizações
200820 NAVER TECH CONCERT 12_상반기 네이버 인턴을 돌아보며 por NAVER Engineering
200820 NAVER TECH CONCERT 12_상반기 네이버 인턴을 돌아보며200820 NAVER TECH CONCERT 12_상반기 네이버 인턴을 돌아보며
200820 NAVER TECH CONCERT 12_상반기 네이버 인턴을 돌아보며
NAVER Engineering389 visualizações
200820 NAVER TECH CONCERT 11_빠르게 성장하는 슈퍼루키로 거듭나기 por NAVER Engineering
200820 NAVER TECH CONCERT 11_빠르게 성장하는 슈퍼루키로 거듭나기200820 NAVER TECH CONCERT 11_빠르게 성장하는 슈퍼루키로 거듭나기
200820 NAVER TECH CONCERT 11_빠르게 성장하는 슈퍼루키로 거듭나기
NAVER Engineering515 visualizações
200819 NAVER TECH CONCERT 07_신입 iOS 개발자 개발업무 적응기 por NAVER Engineering
200819 NAVER TECH CONCERT 07_신입 iOS 개발자 개발업무 적응기200819 NAVER TECH CONCERT 07_신입 iOS 개발자 개발업무 적응기
200819 NAVER TECH CONCERT 07_신입 iOS 개발자 개발업무 적응기
NAVER Engineering459 visualizações

[16]Obfuscation 101 : 난독화, 프로가드, R8, 트랜스포머 API

  • 3. Session 6 l 카카오뱅크 김용욱
  • 4. Obfuscation 101 KakaoBank | Leonardo YongUk Kim
  • 5. 이 발표에서 다루지 않는 것. 1. ProGuard 사용법 2. 난독화 도구 벤치마크 3. 앱을 해킹하는 방법 4. ProGuard 유즈 케이스 5. 소스 코드에 기반한 분석 6. 복잡한 난독화 이론 7. 안드로이드 10 이름 8. 구글, 네이버, 라인, 카카오, 카뱅 입사
  • 6. 이 발표에서 다루는 것. 1. 안드로이드 코드의 특징 2. 바이트 코드에 대한 간략한 이해 3. Transform API 4. 난독화와 앱 보호에 대한 간략한 이론 5. ProGuard와 R8의 역할
  • 7. 안드로이드 코드의 특징 1. 이식에 좋은 바이트 코드를 사용. 2. 동적 컴파일에 기반한 성능 향상. 3. 의존성도 동적으로 로드. 4. 암호화되지 않은 클래스와 리소스로 구성. 5. 공간 효율적인 데이터 포맷.
  • 8. 안드로이드 코드의 특징 1. 이식에 좋은 바이트 코드를 사용. 2. 동적 컴파일에 기반한 성능 향상. 3. 의존성도 동적으로 로드. 4. 암호화되지 않은 클래스와 리소스로 구성. 5. 공간 효율적인 데이터 포맷.
  • 9. 이식에 좋은 바이트 코드 자바 Bytecode 1. 스택 기반의 VM을 사용. 스택에서 입력 받고 결과도 스택으로. 2. 32비트 스택 하나의 요소가 대부분의 타입을 커버. 3. char을 사용해도 기본적으로는 메모리에 이점이 없다. 4. 64비트가 필요한 자료형(double, long)을 사용할 경우 스택 두 요소. 5. 256개의 연산 (실제로는 예약어가 많음.) 6. 스택 기반은 검증하기 쉬움. 7. 레지스터 기반의 실제 기기와 차이가 있어 성능상 단점.
  • 10. 스택 기반 VM 스택 변수 Frame 상수 풀 상수의 종류를 구분하지 않는 것이 특징. (안드로이드는 분리함.)
  • 11. 스택 기반 VM 1 + 2 = ? 1 스택에 1과 2를 넣는다. istore_1 // 1을 스택에 넣는다. int 상수 -1(m1)부터 5(0)까지는 별도의 istore 명령을 제공한다.
  • 12. 스택 기반 VM 1 + 2 = ? 1 2 스택에 1과 2를 넣는다. istore_1 // 1을 스택에 넣는다. istore_2 // 2를 스택에 넣는다. int를 위한 연산
  • 13. 스택 기반 VM 1 + 2 = ? 1 2 스택에서 값을 빼고 합산한다. istore_1 // 1을 스택에 넣는다. istore_2 // 2를 스택에 넣는다. iadd // 두 개의 수를 더해 스택에 넣는다. 사칙연산도 Java는 타입을 따진다. (닷넷은 따지지 않음.)
  • 14. 스택 기반 VM 1 + 2 = ? 3 합산된 3을 스택에 넣는다. istore_1 // 1을 스택에 넣는다. istore_2 // 2를 스택에 넣는다. iadd // 두 개의 수를 더해 스택에 넣는다.
  • 15. 스택 기반 VM 1 + 2 = ? istore_1 명령을 나열해보자. istore_2 iadd
  • 16. 스택 기반 VM 1 + 2 = ? 0x04 실제 opcodes로 바꾸어 보자. 0x05 0x60 낯설어도 x86, ARM 기계어보다 단순. https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html istore_1 istore_2 iadd
  • 17. 이식에 좋은 바이트 코드 달빅 Bytecode 1. 레지스터 기반의 VM을 사용. 2. 64K 개의 레지스터 하지만 주로 앞의 256개, 가끔은 16개만 사용. 3. 32비트 레지스터 하나의 요소가 대부분의 타입을 커버. 4. char을 사용해도 기본적으로는 메모리에 이점이 없다. 5. 64비트가 필요한 자료형(double, long)을 사용할 경우 레지스터 2개. 6. 200여개의 연산. 7. 실제 하드웨어와 매핑에 이점. 성능 상의 이점. 메모리를 덜 씀. 8. 상대적으로 검증이 어려움.
  • 18. 달빅 bytecode 1+2 = ? const/4 v2, 0x1 // 레지스터 v1에 4비트 상수 0x1을 넣습니다. const/4 v3, 0x2 // 레지스터 v2에 4비트 상수 0x2를 넣습니다. add-int v0, v2, v3 // 레지스터 v1과 v2의 합을 v0에 저장한다. const/4 0x1, v2 0x12 0x12 http://pallergabor.uw.hu/androidblog/dalvik_opcodes.html
  • 19. 달빅 bytecode 1+2 = ? const/4 v2, 0x1 // 레지스터 v1에 4비트 상수 0x1을 넣습니다. const/4 v3, 0x2 // 레지스터 v2에 4비트 상수 0x2를 넣습니다. add-int v0, v2, v3 // 레지스터 v1과 v2의 합을 v0에 저장한다. add-int v0 0x90 0x00 v2 v3 0x02 0x03
  • 20. 달빅 bytecode 조금 더 작은 바이너리 const/4 v2, 0x1 // 레지스터 v1에 4비트 상수 0x1을 넣습니다. const/4 v3, 0x2 // 레지스터 v2에 4비트 상수 0x2를 넣습니다. add-int/2addr v2, v3 // 레지스터 v2에 v3를 더한다. add- int/2addr v3, v2 0xB0 0x32
  • 21. 자바와 달빅 bytecode가 다른데? javac desugar dex .java .class .class .dex
  • 22. 자바와 달빅 bytecode가 다른데? javac desugar dex .java .class .class .dex JAVA 8 기능을 사용하는 코드를 JAVA7 바이트 코드로 변형.
  • 23. 자바와 달빅 bytecode가 다른데? javac desugar dex .java .class .class .dex .class 파일을 dex(dx, d8)을 통해 .dex 파일로 변환
  • 24. DX와 D8은 무엇인가요? dex .class .dex .class 파일을 dex(dx, d8)을 통해 .dex 파일로 변환 DX - 안드로이드 3.0 전. D8 - 안드로이드 3.0 이후. gradle.properties에서 android.enableD8 = true | false D8은 써드파티 툴에서 문제가 될 수 있다. - 새로운 DEX를 지원하지 못하는 문제.
  • 28. 한번에 달빅 바이너리로 빌드하면 안되요? Jack & Jill .dex 구글은 Jack & Jill을 통해 한번에 빌드하는 것을 시도 .java
  • 29. 우리에겐 꿈과 희망이 없어. 렌더스크립트에 몰빵했던 투자했던 1인으로 구글의 신기술은 비판적으로 수용하게 됩니다…
  • 30. 구글은 결국 써드파티를 위한 API을 열었습니다. javac desugar 3rd party transformers dex 바이트 코드를 써드파티도 조작할 수 있다. Transform API
  • 31. 구글은 결국 써드파티를 위한 API을 열었습니다. Transformer .class .class 안드로이드 빌드 툴에서 제공하는 클래스 파일 변조 표준. 프로가드 등의 도구들도 Transform API에 의존적. 다만 여전히 Transform API를 사용하지 못하거나 이해하지 못하는 도구들이 존재.
  • 32. 구글은 결국 써드파티를 위한 API을 열었습니다. Transform을 상속받고 transform을 구현합니다. 입력으로 클래스 파일을 받아 출력으로 클래스를 내보내면 됩니다. (실제로는 다른 파일에 접근 가능하지만 대부분은 클래스에)
  • 33. 구글은 결국 써드파티를 위한 API을 열었습니다. ProGuard .class .class Realm Transformer .class .class Desugar .class .class 구글 코드들 조차도 표준화된 API를 따릅니다.
  • 34. 구글도 Transform API를 사용한다. 1. FixStackFramesTransform – 스택을 검증하기 위해 바이트 코드 재계산. 2. DesugarTransform – Java 8 코드의 기능을 Java 7으로 변경. (람다!) 3. StripDebugSymbolTransform – 네이티브 라이브러리 디버그 심볼 제거. 4. ProGuardTransform – 프로가드 적용 트랜스포머. 5. R8Transform – D8에 난독화를 추가한 R8을 적용. (신기능) 6. JarMergingTransform – 여러 Jar를 합쳐줌. 7. DexMergerTransform - 여러 Dex를 합침. ... https://android.googlesource.com/platform/tools/base/+/studio-master- dev/build-system/gradle- core/src/main/java/com/android/build/gradle/internal/transforms/
  • 35. 여러분도 Transformer를 만들고 싶다면 바이트 코드를 한땀 한땀 작성합시다.
  • 36. 여러분도 Transformer를 만들고 싶다면 ASM BCEL Javassist AspectJ Low Level High Level 바이트 코드를 손으로 한땀 한땀 조작 도구를 사용합시다. 구글은 ASM을 좋아하는 것 같으며, Realm은 Javassist를 선택했고, Jake Wharton이 AspectJ를 쓴 걸 봤어요.
  • 37. 안드로이드 코드의 특징 1. 이식에 좋은 바이트 코드를 사용. 2. 동적 컴파일에 기반한 성능 향상. 3. 의존성도 동적으로 로드. 4. 암호화되지 않은 클래스와 리소스로 구성. 5. 공간 효율적인 데이터 포맷.
  • 38. 동적 컴파일에 기반한 성능 향상 인터프리터 vs JIT vs AOT 1. 아무런 정보가 없었을 때 인터프리터로 해석. (느림) 2. 일정 횟수 이상 수행된 메서드만 컴파일해 JIT 코드 캐쉬에 저장. 3. 메서드 수행 시 JIT 코드 캐쉬에 있다면 인터프리터 대신 캐쉬 사용. 4. 프로파일링 정보에 기반해서 JIT 코드 캐쉬를 업데이트. 5. 주기적으로 dex2aot 데몬이 코드를 빌드해서 oat 파일을 생성. 6. 다른 앱에 의해 사용되면 전체 빌드하고 아니면 프로파일에 기반해 빌드. 7. JIT 컴파일과 AOT 컴파일이 있다면 JIT 컴파일을 사용. (동적 최적화)
  • 43. 안드로이드 코드의 특징 1. 이식에 좋은 바이트 코드를 사용. 2. 동적 컴파일에 기반한 성능 향상. 3. 의존성도 동적으로 로드. 4. 암호화되지 않은 클래스와 리소스로 구성. 5. 공간 효율적인 데이터 포맷.
  • 44. 의존성도 동적으로 로드. 1. import / export 해야하는 부분은 숨길 수 없다. 2. 메서드 명, 클래스 명, 필드 명이 공개된다. 3. 안드로이드 앱의 경우 Activity, Fragment, Service 등을 외부로 공개해야 한다. 4. 난독화 도구에 exclude/include 항목을 설정해야 함.
  • 45. 안드로이드 코드의 특징 1. 이식에 좋은 바이트 코드를 사용. 2. 동적 컴파일에 기반한 성능 향상. 3. 의존성도 동적으로 로드. 4. 암호화되지 않은 클래스와 리소스로 구성. 5. 공간 효율적인 데이터 포맷.
  • 46. 암호화되지 않은 클래스와 리소스로 구성 1. 클래스와 리소스는 압축되지 않음. 2. 기본 클래스 로더가 지원하지 않아 한계가 존재. 기본 클래스 로더가 호출되기 전에 암호화가 풀린 클래스를 가로챌 수 있음. 3. 클래스 암호화는 선호되는 보호 방법이 아님.
  • 47. 안드로이드 코드의 특징 1. 이식에 좋은 바이트 코드를 사용. 2. 동적 컴파일에 기반한 성능 향상. 3. 의존성도 동적으로 로드. 4. 암호화되지 않은 클래스와 리소스로 구성. 5. 공간 효율적인 데이터 포맷.
  • 48. 공간 효율적인 데이터 포맷 1. LEB-128을 사용하여 가변 바이트(1-5바이트)로 32비트 잘 저장. (최악의 경우에는 더 나빠질 수 있음.) 2. 부호가 있는 수를 위한 SLEB128, 부호없는 수를 위한 ULEB128과 -1만 지원하는 ULEB128p1이 있음. 3. 여러 클래스에서 상수를 공유. (Shared Constant Pool) 4. 상대 주소 사용. 5. 하지만 메서드 갯수까지 효율적으로 64K T.T
  • 49. LEB-128 (Little Endian Base-128) 10011000011101100101 7비트 단위로 나누어 그룹화. 0100110 0001110 1100101
  • 50. LEB-128 (Little Endian Base-128) 10011000011101100101 제일 앞 그룹은 0, 그 외는 앞에 1을 붙인다. 0100110 0001110 1100101 00100110 10001110 11100101
  • 51. LEB-128 (Little Endian Base-128) 10011000011101100101 1. 가변 바이트를 사용해 작은 수는 더 적은 비트로 저장. (1바이트) 2. 최악의 경우 32비트 자료형을 5바이트로 저장할 수 있다. 3. SLEB128P는 전체 값이 -1이면 0이 된다. 음수 중 -1만 지원. 0100110 0001110 1100101 00100110 10001110 11100101
  • 55. 안드로이드 코드를 어떻게 숨길까? 1. 클래스/리소스에 암호화를 적용한다. 2. 리소스 사이에 클래스를 매복시킨다. 3. 네이티브 코드로 핵심 코드를 숨긴다. 4. 서버에 핵심 코드를 숨긴다. 5. 템퍼 감지를 추가한다. 6. 클래스, 필드, 메서드 명을 변경한다. 7. 리플렉션 호출을 추가한다. 8. 노이즈 추가. 9. 제어 흐름.
  • 56. 안드로이드 코드를 어떻게 숨길까? 1. 클래스/리소스에 암호화를 적용한다. 2. 리소스 사이에 클래스를 매복시킨다. 3. 네이티브 코드로 핵심 코드를 숨긴다. 4. 서버에 핵심 코드를 숨긴다. 5. 템퍼 감지를 추가한다. 6. 클래스, 필드, 메서드 명을 변경한다. 7. 리플렉션 호출을 추가한다. 8. 노이즈 추가. 9. 제어 흐름.
  • 57. 클래스/리소스에 암호화를 적용한다. 1. 클래스를 암호화 하고 커스텀 로더를 사용하는 방법. 2. 암호화된 클래스와 로더를 가지는 패커(Packer)와 깨어진 클래스와 복구 코드를 가지는 프로텍터(Protector)로 나누어짐. 3. VM이 암호화된 클래스를 지원하지 않기 때문에 결국 해독을 하여 클래스를 전달해야 한다. 결국, 커스텀 로더가 공격의 취약점이 된다. 4. 많은 패커와 프로텍터는 ODEX 복사나 심지어 Dex2jar툴에 의해 쉽게 풀리기도 한다.
  • 58. 패커 (Packer) 클래스 로더 암호화된 코드를 해독. 암호화된 클래스 실제 코드가 암호화.
  • 59. 프로텍터 (Protector) 스텁 어플리케이션 깨어진 코드를 복구. 망가진 클래스 실제 코드가 깨어짐.
  • 60. 안드로이드 코드를 어떻게 숨길까? 1. 클래스/리소스에 암호화를 적용한다. 2. 리소스 사이에 클래스를 매복시킨다. 3. 네이티브 코드로 핵심 코드를 숨긴다. 4. 서버에 핵심 코드를 숨긴다. 5. 템퍼 감지를 추가한다. 6. 클래스, 필드, 메서드 명을 변경한다. 7. 리플렉션 호출을 추가한다. 8. 노이즈 추가. 9. 제어 흐름.
  • 61. 리소스 사이에 클래스를 매복시킨다. 1. 이미지 파일로 dex 파일을 숨기는 방법 등을 사용한다. 2. 수상하게 큰 파일을 찾거나 파일 포맷이 올바른지 검증하는 것만으로 혐의가 좁혀진다.
  • 62. 안드로이드 코드를 어떻게 숨길까? 1. 클래스/리소스에 암호화를 적용한다. 2. 리소스 사이에 클래스를 매복시킨다. 3. 네이티브 코드로 핵심 코드를 숨긴다. 4. 서버에 핵심 코드를 숨긴다. 5. 템퍼 감지를 추가한다. 6. 클래스, 필드, 메서드 명을 변경한다. 7. 리플렉션 호출을 추가한다. 8. 노이즈 추가. 9. 제어 흐름.
  • 63. 네이티브 코드로 핵심 코드를 숨긴다. 1. 성능 상의 이점이 있으며 기계어 코드가 자바 / 달빅의 바이트 코드보다 해석하기 어렵다는 장점이 있음. 하지만 인내가 있다면 결국 기계어 코드도 해석할 수 있다. 2. VM 밖은 위험하다. (온갖 하드웨어 이슈를 경험할 수 있다.) .so 파일이 간혈적으로 설치되지 않는 문제 발생. POSIX 함수가 목업이었던 B사. OpenSSL이 목업이었던 L사. 메모리 할당량을 잘못 알려주었던 S사. 재현 불가능한 이슈도 동작한다. 3. ABI 세트를 맞추기 어렵다.
  • 64. VM 밖은 위험하다. 강제로 .so 파일을 재설치하는 라이브러리 ReLinker
  • 68. 안드로이드 코드를 어떻게 숨길까? 1. 클래스/리소스에 암호화를 적용한다. 2. 리소스 사이에 클래스를 매복시킨다. 3. 네이티브 코드로 핵심 코드를 숨긴다. 4. 서버에 핵심 코드를 숨긴다. 5. 템퍼 감지를 추가한다. 6. 클래스, 필드, 메서드 명을 변경한다. 7. 리플렉션 호출을 추가한다. 8. 노이즈 추가. 9. 제어 흐름.
  • 69. 서버에 핵심 코드를 숨긴다. 1. 리얼 타임 응답성이 떨어진다. 웹 소켓 등의 상시 연결을 하더라도 로컬만큼 쾌적하기 어렵다. 응답성을 올리기 위해 로컬 데이터베이스등이 필요할 수 있다. 2. 서버가 다운된 경우 어플리케이션을 사용할 수 없다. 로컬 캐쉬를 이용해서 폴백을 구현하기 쉽지 않다.
  • 70. 안드로이드 코드를 어떻게 숨길까? 1. 클래스/리소스에 암호화를 적용한다. 2. 리소스 사이에 클래스를 매복시킨다. 3. 네이티브 코드로 핵심 코드를 숨긴다. 4. 서버에 핵심 코드를 숨긴다. 5. 템퍼 감지를 추가한다. 6. 클래스, 필드, 메서드 명을 변경한다. 7. 리플렉션 호출을 추가한다. 8. 노이즈 추가. 9. 제어 흐름.
  • 71. 템퍼 감지를 추가한다. 1. 디버거, ptrace를 발견하는 코드를 삽입한다. 2. 에뮬레이터 등에 감지한다. 3. 해당 부분을 우회하면 대부분 앱에 진입할 수 있다.
  • 72. 안드로이드 코드를 어떻게 숨길까? 1. 클래스/리소스에 암호화를 적용한다. 2. 리소스 사이에 클래스를 매복시킨다. 3. 네이티브 코드로 핵심 코드를 숨긴다. 4. 서버에 핵심 코드를 숨긴다. 5. 템퍼 감지를 추가한다. 6. 클래스, 필드, 메서드 명을 변경한다. 7. 리플렉션 호출을 추가한다. 8. 노이즈 추가. 9. 제어 흐름.
  • 73. 클래스, 필드, 메서드 등의 이름을 변경한다. 1. 무료 도구인 ProGuard 부터 대부분의 도구가 지원. 2. 짧고 간결한 이름으로 클래스, 필드, 메서드의 이름을 변경. 단 외부에서 import되거나 export되는 명칭들은 변경될 수 없음. 3. 짧고 간결한 이름의 경우 공간 복잡도도 줄이고 실행 시간에도 긍정적인 영향을 줄 수 있음. 4. 명칭이 바뀌는 것이기 때문에 공격자의 인내로 해결 가능. 5. 도구마다 고유의 네이밍 패턴이 있음. (APKiD에서 검출)
  • 75. 안드로이드 코드를 어떻게 숨길까? 1. 클래스/리소스에 암호화를 적용한다. 2. 리소스 사이에 클래스를 매복시킨다. 3. 네이티브 코드로 핵심 코드를 숨긴다. 4. 서버에 핵심 코드를 숨긴다. 5. 템퍼 감지를 추가한다. 6. 클래스, 필드, 메서드 명을 변경한다. 7. 리플렉션 호출을 추가한다. 8. 노이즈 추가. 9. 제어 흐름.
  • 76. 리플렉션 호출을 추가 1. 메서드 호출에 단계를 추가하여 해석하기 어렵게 만듬. 2. ProGuard는 지원하지 못하지만 DexGuard나 Arxan 등의 유료 도구에서 지원. 3. 리플렉션이 반복적인 패턴을 가지기 때문에 기계적으로 풀 수 있음. (예: Dex-Oracle에서 DexGuard 패턴 해제.) 4. 많은 단말에서 리플렉션 크래시가 보고됨. 리플렉션 실행이 랜덤하게 실패하는 것으로 보임. (혼탁한 안드로이드의 세계)
  • 78. 안드로이드 코드를 어떻게 숨길까? 1. 클래스/리소스에 암호화를 적용한다. 2. 리소스 사이에 클래스를 매복시킨다. 3. 네이티브 코드로 핵심 코드를 숨긴다. 4. 서버에 핵심 코드를 숨긴다. 5. 템퍼 감지를 추가한다. 6. 클래스, 필드, 메서드 명을 변경한다. 7. 리플렉션 호출을 추가한다. 8. 노이즈 추가. 9. 제어 흐름.
  • 79. 노이즈 추가 1. 메서드 외부에 아무런 영향이 없는 코드를 추가. 2. 클래스 상태 변경, I/O, 반환 값 등이 없음. 3. 디버거나 디스컴파일러가 깨지는 코드를 추가하기도 한다. 하지만 몇 줄로 망가트린 문제는 그만큼 쉽게 고쳐진다.
  • 80. 노이즈 추가 1201 3801 0300 FFFF V1에 0을 적재 다음줄 스킵 불량 코드
  • 81. 안드로이드 코드를 어떻게 숨길까? 1. 클래스/리소스에 암호화를 적용한다. 2. 리소스 사이에 클래스를 매복시킨다. 3. 네이티브 코드로 핵심 코드를 숨긴다. 4. 서버에 핵심 코드를 숨긴다. 5. 템퍼 감지를 추가한다. 6. 클래스, 필드, 메서드 명을 변경한다. 7. 리플렉션 호출을 추가한다. 8. 노이즈 추가. 9. 제어 흐름.
  • 82. 제어 흐름 1. 반복문이나 분기문을 불 필요하게 추가. 2. If문 대신 try-catch 블록을 사용하기도 함. (Replacing if null instructiions with try-catch blocks) 3. Switch 문을 중첩적인 레이블로 변경하기도 함. (Partially trapping switch statements) 4. 브랜치를 jsr 명령 (goto)로 변환하기도 함. (Converting branches to jsr instructions) 일반적으로 속도를 저하시키는 요인.
  • 83. 프로가드는 무엇을 지원할까? 1. 클래스/리소스에 암호화를 적용한다. 2. 리소스 사이에 클래스를 매복시킨다. 3. 네이티브 코드로 핵심 코드를 숨긴다. 4. 서버에 핵심 코드를 숨긴다. 5. 템퍼 감지를 추가한다. 6. 클래스, 필드, 메서드 명을 변경한다. 7. 리플렉션 호출을 추가한다. 8. 노이즈 추가. 9. 제어 흐름.
  • 84. 프로가드는 무엇을 지원할까? 1. 클래스/리소스에 암호화를 적용한다. 2. 리소스 사이에 클래스를 매복시킨다. 3. 네이티브 코드로 핵심 코드를 숨긴다. 4. 서버에 핵심 코드를 숨긴다. 5. 템퍼 감지를 추가한다. 6. 클래스, 필드, 메서드 명을 변경한다. 7. 리플렉션 호출을 추가한다. 8. 노이즈 추가. 9. 제어 흐름. 10. 사용하지 않은 클래스, 필드, 메서드 제거.
  • 86. 안드로이드 빌드에 통합된 프로가드 ProGuard Transform javac Desugar Transform ProGuard .java .class .class .class Dex .dex 제대로 통합되지 않은 툴이 많은 것을 감안하면 엄청난 장점.
  • 87. 통합되지 않은 난독화 도구들의 흐름 javac Desugar Transform .java .class .class Dex .dex Dex2jarObfuscator .class .class .class Dex .dex
  • 88. 구글의 신 병기 R8 R8javac Desugar Transform .java .class .class .dex R8 = D8 + Shrinker + Name Obfuscator https://android.googlesource.com/platform/external/r8/ ProGuard를 버리겠다는 계획. 갑자기 괜히 렌더스크립트, Jack & Jill을 불러보고 싶다.
  • 90. 성능 (Performance) 안정성 (Relability) 보안 (Security) 앱에서 아래 셋은 다 가질 수 있는 것인가? (PRS?) 방금 지어냄.