일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 |
- 덧셈 암호
- 포너블
- Writeup
- 개인정보보호교육
- 유클리드_알고리즘
- 마감임박
- pwnable.tw
- 웹 프레임워크
- 개인정보안전성
- 한국산업인력공단
- 호이스팅
- 디오판투스 알고리즘
- 백엔드입문
- arrow function
- 개인정보보호위원회
- package-lock.json
- 모듈러 연산
- 무료교육
- package.json
- 가명정보처리
- node.js
- 한국정보보호산업협회기자단
- 개인정보보호
- 한국정보보호산업협회
- function scope
- 곱셈 암호
- 동적타이핑
- 백엔드
- 확장 유클리드 알고리즘
- 국가인적자원개발컨소시엄
- Today
- Total
짱짱해커가 되고 싶은 나
[안드로이드 취약점 진단] 3-6. 루팅 탐지 및 우회 본문
루팅(Rooting)
모바일 기기에서 구동되는 안드로이드 OS 상에서 최상의 권한 (root)을 얻음으로 해당 기기의 생산자/판매자 측에서 걸어 놓은 제약을 해제하는 행위
-> 루팅을 하면? 슈퍼 유저의 권한으로 하드웨어 성능 조작, 제조사 및 통신사 기본 apk 삭제, 시스템 권한을 이용한 디바이스 조작, 디바이스 내부 민감 정보 접근 등 가능
ps. 기본적으로 금융권 apk는 루팅된 기기에서의 앱 실행을 차단, 핀테크 기술을 제공하는 역시 루팅 허용 X
루트 노출 및 우회 취약점
안드로이드
- 보안상의 이유로 루트 권한을 막아 놓음
- 애플리케이션 실행 시 각 프로그램마다 권한 부여, 독립적으로 동작
-> 순정 안드로이드는 애플리케이션에서 할 수 있는 행위 제한
루팅 체크 주요 경로
- /system/bin/su
- /system/xibn/su
- /system/app/superuser.apk
- /data/data/com.noshufou.android.su
루팅 체크 방법
- 파일 이름 기반 탐지
- su 명령어를 실행해서 확인
- which를 통해 존재 확인
- su - 명령어의 결과로 확인 (존재하면 에러X, 존재하지 않으면 에러)
(일반적으로 su 파일이나 superuser.apk 등 루팅 시 생성되는 파일 이름을 기반으로 탐지)
인시큐어뱅크 - Root Detection and Bypass
PostLogin
로그인을 하게 되면 현재 접속한 기기가 루팅되었는지를 출력해준다.
관련 코드를 보면 다음과 같이 /system/app/Superuser.apk가 존재하는지 또는 /system/bin/which 명령어를 통해 su 명령어가 존재하는지를 확인한다.
(jadx는 doesSUexist를 제대로 디컴파일 못해줘서 추가적으로 bytecode viewer를 사용했다. 근데 얘도,,좀)
private boolean doesSUexist() { //()Z
TryCatch0: L0 to L1 handled by L15: java/lang/Throwable
TryCatch1: L0 to L1 handled by L17: Type is null.
TryCatch2: L2 to L3 handled by L15: java/lang/Throwable
TryCatch3: L2 to L3 handled by L17: Type is null.
TryCatch4: L4 to L5 handled by L15: java/lang/Throwable
TryCatch5: L4 to L5 handled by L17: Type is null.
TryCatch6: L6 to L7 handled by L15: java/lang/Throwable
TryCatch7: L6 to L7 handled by L17: Type is null.
TryCatch8: L8 to L9 handled by L15: java/lang/Throwable
TryCatch9: L8 to L9 handled by L17: Type is null.
TryCatch10: L10 to L11 handled by L15: java/lang/Throwable
TryCatch11: L10 to L11 handled by L17: Type is null.
iconst_1
istore 1
aconst_null
astore 2
aconst_null
astore 3
// start TCB0, start TCB1
L0 {
invokestatic java/lang/Runtime.getRuntime()Ljava/lang/Runtime;
iconst_2
anewarray java/lang/String
dup
iconst_0
ldc "/system/xbin/which" (java.lang.String)
aastore
dup
iconst_1
ldc "su" (java.lang.String)
aastore
invokevirtual java/lang/Runtime.exec([Ljava/lang/String;)Ljava/lang/Process;
astore 4
}
// end TCB0, end TCB1
L1 {
aload 4
astore 3
aload 4
astore 2
}
// start TCB2, start TCB3
L2 {
new java/io/BufferedReader
astore 5
}
// end TCB2, end TCB3
L3 {
aload 4
astore 3
aload 4
astore 2
}
// start TCB4, start TCB5
L4 {
new java/io/InputStreamReader
astore 6
}
// end TCB4, end TCB5
L5 {
aload 4
astore 3
aload 4
astore 2
}
// start TCB6, start TCB7
L6 {
aload 6
aload 4
invokevirtual java/lang/Process.getInputStream()Ljava/io/InputStream;
invokespecial java/io/InputStreamReader.<init>(Ljava/io/InputStream;)V
}
// end TCB6, end TCB7
L7 {
aload 4
astore 3
aload 4
astore 2
}
// start TCB8, start TCB9
L8 {
aload 5
aload 6
invokespecial java/io/BufferedReader.<init>(Ljava/io/Reader;)V
}
// end TCB8, end TCB9
L9 {
aload 4
astore 3
aload 4
astore 2
}
// start TCB10, start TCB11
L10 {
aload 5
invokevirtual java/io/BufferedReader.readLine()Ljava/lang/String;
astore 6
}
// end TCB10, end TCB11
L11 {
aload 6
ifnull L13
iload 1
istore 7
aload 4
ifnull L12
aload 4
invokevirtual java/lang/Process.destroy()V
iload 1
istore 7
}
L12 {
iload 7
ireturn
}
L13 {
aload 4
ifnull L14
aload 4
invokevirtual java/lang/Process.destroy()V
}
L14 {
iconst_0
istore 7
goto L12
}
// handle TCB0, handle TCB2, handle TCB4, handle TCB6, handle TCB8, handle TCB10
L15 {
astore 2
aload 3
ifnull L16
aload 3
invokevirtual java/lang/Process.destroy()V
}
L16 {
iconst_0
istore 7
goto L12
}
// handle TCB1, handle TCB3, handle TCB5, handle TCB7, handle TCB9, handle TCB11
L17 {
astore 3
aload 2
ifnull L18
aload 2
invokevirtual java/lang/Process.destroy()V
}
L18 {
aload 3
athrow
}
}
//jadx-gui
private boolean doesSUexist() {
/*
r7 = this;
r2 = 0
r0 = 1
r1 = 0
java.lang.Runtime r3 = java.lang.Runtime.getRuntime() // Catch:{ Throwable -> 0x0039, all -> 0x0042 }
r4 = 2
java.lang.String[] r4 = new java.lang.String[r4] // Catch:{ Throwable -> 0x0039, all -> 0x0042 }
r5 = 0
java.lang.String r6 = "/system/xbin/which"
r4[r5] = r6 // Catch:{ Throwable -> 0x0039, all -> 0x0042 }
r5 = 1
java.lang.String r6 = "su"
r4[r5] = r6 // Catch:{ Throwable -> 0x0039, all -> 0x0042 }
java.lang.Process r2 = r3.exec(r4) // Catch:{ Throwable -> 0x0039, all -> 0x0042 }
java.io.BufferedReader r3 = new java.io.BufferedReader // Catch:{ Throwable -> 0x0049, all -> 0x0042 }
java.io.InputStreamReader r4 = new java.io.InputStreamReader // Catch:{ Throwable -> 0x0049, all -> 0x0042 }
java.io.InputStream r5 = r2.getInputStream() // Catch:{ Throwable -> 0x0049, all -> 0x0042 }
r4.<init>(r5) // Catch:{ Throwable -> 0x0049, all -> 0x0042 }
r3.<init>(r4) // Catch:{ Throwable -> 0x0049, all -> 0x0042 }
java.lang.String r3 = r3.readLine() // Catch:{ Throwable -> 0x0049, all -> 0x0042 }
if (r3 == 0) goto L_0x0032
if (r2 == 0) goto L_0x0031
r2.destroy()
L_0x0031:
return r0
L_0x0032:
if (r2 == 0) goto L_0x0037
r2.destroy()
L_0x0037:
r0 = r1
goto L_0x0031
L_0x0039:
r0 = move-exception
r0 = r2
L_0x003b:
if (r0 == 0) goto L_0x0040
r0.destroy()
L_0x0040:
r0 = r1
goto L_0x0031
L_0x0042:
r0 = move-exception
if (r2 == 0) goto L_0x0048
r2.destroy()
L_0x0048:
throw r0
L_0x0049:
r0 = move-exception
r0 = r2
goto L_0x003b
*/
throw new UnsupportedOperationException("Method not decompiled: com.android.insecurebankv2.PostLogin.doesSUexist():boolean");
}
//실제 코드
private boolean doesSUexist() {
Process process = null;
try {
process = Runtime.getRuntime().exec(new String[] { "/system/bin/which", "su" });
BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
if (in.readLine() != null) return true;
return false;
} catch (Throwable t) {
return false;
} finally {
if (process != null) process.destroy();
}
}
private boolean doesSuperuserApkExist(String str) {
return Boolean.valueOf(new File("/system/app/Superuser.apk").exists()).booleanValue();
}
/* access modifiers changed from: package-private */
public void showRootStatus() {
if (doesSuperuserApkExist("/system/app/Superuser.apk") || doesSUexist()) {
this.root_status.setText("Rooted Device!!");
} else {
this.root_status.setText("Device not Rooted!!");
}
}
/system/app/에 가면 SuperUser.apk가 현재 nox에는 없다.
xbin과 bin에는 which와 su 명령어가 존재한다.
/system 디렉터리에 있는 파일은 only-read 상태로 마운트 되어 있어서 수정하려면 remount를 해야한다.
remount는 adb shell에서 su 명령어로 관리자 권한을 상승시키고 remount를 진행하면 된다.
-> 이후 우회하기 위해서는 SuperUser.apk가 있다면 apk이름을 바꾸거나 su 파일의 이름을 바꿔준다.
결과적으로 루팅 검증을 위한 su 명령어를 사용하지 못하고 Device not Rooted가 뜨는 것을 확인할 수 있다.
SuperSU.apk
SuperSU.apk는 루팅된 기기에서 루트 권한을 사용하는 앱들을 편리하게 관리할 수 있게 하고, 기존에 루팅되어 있던 디바이스를 정상 디바이스로 돌려주는 unrooting 기능을 제공한다.
Superuser.apk
로직은 다음과 같다.
디바이스의 OS 정보 확인 -> 적절한 경로 리턴 -> su 명령어로 실행할 서브 프로세스 생성 -> 실제 디바이스의 /system/xbin에 su 명령어 복사 & /system/bin에 심볼릭 링크 생성
대응방안
- 루팅된 디바이스 탐지 -> 즉시 종료
- 루팅 방지 소스 코드 로직이 노출되지 않도록 문자열 난독화를 적용해 어떤 명령어와 관련 파일을 검사하는지 모르게!
- 루팅을 지속적으로 유지하는 것을 막기 위해 무결성 검증
- 디컴파일 방지 솔루션 적용
'모바일' 카테고리의 다른 글
[안드로이드 취약점 진단] 3-8. 안전하지 않은 콘텐츠 프로바이더 접근 (0) | 2022.06.22 |
---|---|
[안드로이드 취약점 진단] 3-7. 에뮬레이터 탐지 및 우회 (0) | 2022.06.19 |
[안드로이드 취약점 진단] 3-5. 액티비티 컴포넌트 취약점 (0) | 2022.06.11 |
[안드로이드 취약점 진단] 3-4. 로컬 암호화 이슈 (0) | 2022.06.11 |
[안드로이드 취약점 진단] 3-3. 취약한 인증 메커니즘 (0) | 2022.06.11 |