안녕하세요! 오늘은 '루팅 탐지 및 우회' 에 관하여 Insecure Bank앱 실습을 통해 포스팅을 해보려고합니다!
[루팅 탐지 및 우회 란?]
> 안드로이드 기반 운영체제의 경우 보안상의 이유로 루트 권한을 막아 놓았습니다. 그래서 이러한 제한을 풀거나 우회하기 위해서는 시스템 권한을 루팅으로 획득해야 합니다. 만일 루팅으로 권한을 획득할 경우 슈퍼 유저 권한으로 하드웨어 성능 조작 , 기본 애플리케이션 삭제 등 여러 기능들을 할 수 있게됩니다. 하지만 금융권 앱들의 경우 이런 루팅된 기기들에서의 앱 실행을 차단하고 있으므로 이런 차단을 우회하는 방법을 뜻합니다. 이런 루팅 탐지를 위해 앱에서 주로 체크하는 경로는 다음과 같습니다.
<금융 앱이나 게임앱에서 루팅 탐지를 위해 주로 체크하는 경로>
- /system/bin/su
- /system/xbin/su
- /system/app/superuser.apk
- /data/data/com.noshufou.android.su
1. 취약점 진단
1. 우선 Insecure Bank 앱에 로그인을 하시고 들어오시게 되면 바로 Rooted Device!! 라는 문구가 뜨게됩니다. 지금 현재 NOX기기는 루팅된 상태인데 이를 Insecure Bank 앱에서 감지했다는 뜻입니다. 그러면 어떻게 감지한 건지 앱을 디컴파일링 하여 소스코드를 살펴보겠습니다.
2. 소스코드를 보니 표시된 1번에서 doesSuperuserApkExist라는 함수로 Superuser.apk 라는 앱이 있는지 여부를 조사하고 있습니다. 또한 표시된 2번에서는 doesSUexist() 라는 함수가 보이는데 화살표를 따라가 보시면 /system/xbin/which 라는 명령어를 이용해 su 라는 명령어가 있는지 찾고있습니다. Superuser.apk 그리고 su 명령어는 기기가 루팅될 때 설치되는 부분이기 때문에 이를 탐지하고 루팅된 기기라 판단을 하는것입니다!
3. 소스코드에서 본 바와 같이 /system/xbin/which su 명령어를 입력해주니 su 명령어가 위치한 디렉토리 경로가 나왔습니다(/system/bin/su) . 이 경로는 루팅된 기기에서만 나오고 순정상태의 기기일경우 저 명령어를 입력해도 아무런 경로가 나오지 않습니다. 이점을 이용하여 루팅탐지를 하고있는 것으로 보입니다!
사용된 명령어
> nox_adb shell (녹스 기기에 명령어창으로 접근)
> /system/xbin/which su (su 라는 명령어를 가진 디렉토리 경로를 찾기)
2. 공격 과정
[ADB를 이용한 루팅탐지 우회]
1. 우선 저희가 수정해야할 파일은 두가지 입니다./system/bin/su 와Superuser.apk 의 존재유무입니다. 우선 Superuser.apk 의 이름을 바꿔주기 위하여 해당 경로로 이동합니다.
사용한 명령어
>cd /system/app
2. 해당 경로로 이동하셔서 ls 명령어로 살펴보니 여기엔 Superuser.apk 라는 파일이 없습니다?... 그러면 지금 su 명령어의 존재여부로 해당 기기가 루팅된 기기라고 탐지되고 있는것이라고 추측해볼수 있습니다.
3. /system/bin 경로로 이동하여 su 명령어를 찾았습니다. 이제 이 명령어의 이름을 sx 로 바꿔보겠습니다.
4. 밑에 사진에서 보시는 바와 같이 mv su sx 명령어로 su -> sx 로 바꾸려 하면 읽기만 가능하다는 오류가 뜹니다. 우선 system 폴더부터는 읽기만 가능하기 때문에 system 폴더의 쓰기권한을 해당 명령어로 부여해주겠습니다.
사용된 명령어
> su (관리자 권한)
> mount -o remount,rw /system (system 폴더의 읽기쓰기 권한 부여)
5. 이후 다시 이름바꾸기를 시도하니 무사히 su -> sx 로 변경한 것을 확인하실 수 있습니다. 이제 다시 Insecure Bank앱으로 가보겠습니다.
사용된 명령어
> mv su sx
6. 왜인지 아직도 루팅탐지가 되고있습니다...
7. ls -l sx 명령어로 sx 파일을 다시 살펴보니 이 명령어는 실제로 /system/xbin/su 를 가르키고 있는 심볼릭 링크였습니다... 그러면 저 경로에있는 su 명령어 까지 sx 로 바꿔보겠습니다.
8. 이제 xbin 폴더에 있던 su 까지 이름을 바꿔주었습니다. 다시 Insecure Bank 앱으로 가서 확인을 해보겠습니다.
system/xbin/su 명령어 확인 | su -> sx 로 바꾼후 확인 |
![]() |
![]() |
9. 루팅 탐지가 우회된 것을 확인하실 수 있습니다!!
[Frida CodeShare를 통한 루팅탐지 우회]
> 위에서 adb를 이용해 system폴더에 쓰기권한을 부여한 후 해당 명령어명을 바꿔서 우회하는 방법을 보여드렸습니다만...실제 어플에선 여기서 더 나아가 system 폴더에 권한을 부여했는지를 체크해서 루팅탐지를 우회하는 방법이 있는 등 여러가지 탐지 요소가 있습니다. 그래서 이런 여러가지의 탐지요소를 전부 우회할 수 있도록 Frida CodeShare 페이지에서 다른 분들이 작성한 Frida 코드를 공유해놓기 때문에 그 코드를 저희가 사용할 수 있게 해놓았습니다. 이를 사용하여 루팅탐지 우회를 좀 더 손쉽게 해보도록 하겠습니다.
1. 우선 Frida-server 를 실행시켜서 nox와 Frida 를 연동시켜줘야 합니다. 이에 대한 자세한 내용은 제가 작성한 게시물을 참고하시면 좋을 것 같습니다!
[Mobile Hacking] Nox & Frida 설치방법 (환경세팅)
안녕하세요! 이번 포스팅에서는 모바일 앱해킹에서 가장 중요하게 사용되는 Frida 설치 방법에 대하여 알려드리겠습니다. Frida 란? > 모바일 애플리케이션 및 기타 프로그램에서 사용되는 동적
jamesbexter.tistory.com
2. 저희가 사용할 Frida code는 아래 링크에 있는 코드입니다. Frida에선 친절하게도 이 codeshare 페이지에 코드를 바로 명령어창에서 사용할 수 있는 명령어를 제공합니다! 다음과 같은 명령어입니다.
> frida --codeshare [코드작성자]/[코드 이름]
https://codeshare.frida.re/@dzonerzy/fridantiroot/
Frida CodeShare
codeshare.frida.re
3. 다음과 같은 명령어를 입력하시게 되면 Frida 가 인시큐어뱅크 앱을 Frida CodeShare에 있던 코드로 후킹하기 시작합니다. 처음 시작하실땐 "처음이신가요?" 같은 문구가 뜨는데 그냥 y 를 입력하시고 진행하시면 됩니다!
4. 다시 Insecure Bank 앱으로 가서 로그인을 해보면 루팅탐지가 우회된 것을 확인하실 수 있습니다!!
[Objection을 이용한 루팅탐지 우회]
> Frida에는 좀더 동적으로 분석이 가능할 수 있는 모듈인 Objection이 있습니다. 함수가 반환이 되는지 어떤 함수가 호출이 되는지 실시간으로 바로 보면서 수정할 수 있는 편리한 모듈입니다. 이를 통해 루팅탐지 우회를 시도해보겠습니다!
시작하기에 앞서 해당명령어를 통해 objection 모듈을 설치해줍니다!
> pip install objection
1. 우선 frida-ps -Ua 명령어를 통해 현재 Nox에서 실행되고 있는 앱을 확인합니다. 저희의 타겟은 사진에서 보이는 com.android.insecurebankv2 입니다!
2. 이후 다음 명령어를 통해 Insecure Bank 앱을 objection과 연결합니다.
* objection -g [패키지명] explore → 지정된 APP과 연결한다
> objection -g com.android.insecurebankv2 explore
3. 루팅탐지는 PostLogin 액티비티에서 이루어졌으니 PostLogin 에 존재하는 함수들을 후킹하기 위하여 해당 명령어를 통해 PostLogin에 있는 함수들 리스트를 출력합니다. 가장 윗부분에 저희가 우회해야할 doesSUexist() 함수가 보입니다!
> android hooking list class_methods com.android.insecurebankv2.PostLogin
4. 그러면 이제 doesSUexist() 함수에서 반환되는 값을 확인해봅시다. 다음의 명령어를 이용하면 해당 함수만 타겟팅으로 후킹할 수 있게되며 실시간으로 함수에서 반환되는 값을 확인할 수 있습니다. 해당 명령어를 실행 후 후킹이 완료되었으면 다시 앱으로 돌아가 로그인을 해봅시다.
* anroid hooking watch [관찰한 범위] [액티비티 명/ 액티비티 안에 함수] --dump-args --dump-return
> dump_args = 함수에 들어가는 인자값
> dump-return = 함수에서 반환되는 값
> android hooking watch class_method com.android.insecurebankv2.PostLogin.doesSUexist --dump-args --dump-return
5. 앱에서 로그인하고 다시 돌아와보면 doesSUexist 함수는 true 를 반환하고있습니다. 이를 false 로 바꿔주게 되면 루팅탐지가 우회될 것 같습니다!
앱에서 로그인 () | 반환되는 값 true |
![]() |
![]() |
6. 해당 명령어를 통해 doesSUexist 반환값을 false 로 바꿔준 후 다시 로그인을 시도해봅니다.
> android hooking set return_value com.android.insecurebankv2.PostLogin.doesSUexist false
7. 로그인을 하게되면 Device not Rooted!! 가 뜨는것으로 보아 루팅탐지 우회에 성공한 것을 확인할 수 있습니다. 또한 objection 창을 보시면 리턴값이 false 로 반환된 것을 확인하실 수 있습니다!
루팅탐지 우회 성공! | objection 에서 false 리턴값 확인 |
![]() |
![]() |
3. 대응방안
> 100%막을수 있다 라는 방법은 존재하지 않지만, 현재로써 가장 좋은방안은 상용솔루션을 사용하는 것 입니다. 그러나 개발자 입장에서 이것을 막아야 한다면 루팅탐지를 우회하는 여러 갈래 길을 전부 막아야합니다. 그물을 촘촘히 깔아둬서 최대한 우회할 수 있는 구멍을 막는것이죠! 구멍을 막는 방법으로는 대표적으로 세가지가 있습니다.
- 루팅 탐지 기능 추가
- 소스코드 난독화
- 무결성 검증
1. 루팅 탐지 기능 추가
1.1) 루팅된 환경에서만 사용할 수 있는 명령어 실행결과를 통해 루팅 탐지
- su,which 같은 명령어는 루팅환경에서만 동작할 수 있기 때문에 이 점을 통해 루팅탐지를 합니다.
private boolean checkSuExists() {
Process process = null;
try {
process = Runtime.getRuntime().exec(new String[]
{"/system /xbin/which", "su"});
BufferedReader in = new BufferedReader(
new InputStreamReader(process.getInputStream()));
String line = in.readLine();
process.destroy();
return line != null;
} catch (Exception e) {
if (process != null) {
process.destroy();
}
return false;
}
}
1.2) 루팅 시 설치되는 앱의 존재 유무를 통한 루팅 탐지 방법
- 루팅 시 자동으로 설치되는 앱이 존재하는데 , 이 앱의 존재유무를 통해 루팅을 탐지합니다.
public static final String[] knownDangerousAppsPackages = {
"com.koushikdutta.rommanager",
"com.koushikdutta.rommanager.license",
"com.dimonvideo.luckypatcher",
"com.chelpus.lackypatch",
"com.ramdroid.appquarantine",
"com.ramdroid.appquarantinepro",
"com.android.vending.billing.InAppBillingService.COIN",
"com.android.vending.billing.InAppBillingService.LUCK",
"com.chelpus.luckypatcher",
"com.blackmartalpha",
"org.blackmart.market",
"com.allinone.free",
"com.repodroid.app",
"org.creeplays.hack",
"com.baseappfull.fwd",
"com.zmapp",
"com.dv.marketmod.installer",
"org.mobilism.android",
"com.android.wp.net.log",
"com.android.camera.update",
"cc.madkite.freedom",
"com.solohsu.android.edxp.manager",
"org.meowcat.edxposed.manager",
"com.xmodgame",
"com.cih.game_cih",
"com.charles.lpoqasert",
"catch_.me_.if_.you_.can_"
};
public boolean detectPotentiallyDangerousApps(String[] additionalDangerousApps) {
ArrayList<String> packages = new ArrayList<>();
// Add known dangerous apps to the list
packages.addAll(Arrays.asList(knownDangerousAppsPackages));
// Add additional dangerous apps if provided
if (additionalDangerousApps != null && additionalDangerousApps.length > 0) {
packages.addAll(Arrays.asList(additionalDangerousApps));
}
// Check if any package from the list is installed
return isAnyPackageFromListInstalled(packages);
}
1.3) Test Key 확인을 통한 루팅 탐지 방법
- 원래 Android ROM은 release-key로 빌드되는데 루팅이 되면서 개발자가 생성한 사용자 지정 키로 서명이 되면서 Test-key 값을 출력할 수 있게됩니다.
private boolean detectTestKeys() {
String buildTags = android.os.Build.TAGS;
return buildTags != null && buildTags.contains("test-keys");
}
1.4) 루팅된 기기에서만 존재하는 바이너리 파일 존재여부를 통한 루팅 탐지 방법
- 루팅 환경에서만 존재하는 바이너리 파일명 : busybox , su , magisk , superuser.apk 등...
public boolean checkForSuBinary(){
return checkForBinary("su");
}
public boolean checkForBusyBoxBinary(){
return checkForBinary("busybox");
}
public boolean checkForMagiskBinary(){
return checkForBinary("magisk");
}
public boolean checkForSuperuserApkBinary(){
return checkForBinary("Superuser.apk");
}
public static boolean checkForBinary(String filename) {
String[] pathsArray = Constants.suPaths;
boolean result = false;
for (String path : pathsArray) {
String completePath = path + filename;
File f = new File(completePath);
boolean fileExists = f.exists();
if (fileExists) {
result = true;
}
}
return result;
}
1.5) default.prop 설정값을 통한 루팅 탐지 방법
- 안드로이드 기기의 default.prop 파일의 설정값을 통해 확인하는 방법입니다.
- ro.secure = 0 이면 루팅 탐지
- ro.debuggable = 1 이면 루팅 탐지
- service.adb.root = 1 이면 루팅 탐지
private String[] propsReader() {
try {
InputStream inputstream = Runtime.getRuntime().exec("getprop").getInputStream();
if (inputstream == null) return null;
String propVal = new Scanner(inputstream).useDelimiter("\\A").next();
return propVal.split("\n");
} catch (IOException | NoSuchElementException e) {
QLog.e(e);
return null;
}
}
public boolean checkForDangerousProps() {
final Map<String, String> dangerousProps = new HashMap<>();
dangerousProps.put("ro.debuggable", "1");
dangerousProps.put("ro.secure", "0");
boolean result = false;
String[] lines = propsReader();
if (lines == null) {
return false;
}
for (String line : lines) {
for (String key : dangerousProps.keySet()) {
if (line.contains(key)) {
String badValue = dangerousProps.get(key);
badValue = "[" + badValue + "]";
if (line.contains(badValue)) {
QLog.v(key + " = " + badValue + " detected!");
result = true;
}
}
}
}
return result;
}
1.6) Google OTA 인증서를 통한 루팅 탐지
- 루팅된 기기에선 Google OTA 인증서 가 존재하지 않으므로 , 이를 통해 루팅 탐지 가능
public static boolean checkOTACerts() {
String OTAPath = "/etc/security/otacerts.zip";
File f = new File(OTAPath);
return f.exists();
}
1.7) 디렉토리의 쓰기권한을 통한 루팅 탐지 방법
- 위에 실습에서 했듯이 /system 디렉토리에 쓰기권한을 부여했는데 이를 통해 루팅탐지 가능
public static final String[] pathsThatShouldNotBeWrtiable = {
"/system",
"/system/bin",
"/system/sbin",
"/system/xbin",
"/vendor/bin",
//"/sys",
"/sbin",
"/etc",
//"/proc",
//"/dev"
}
public boolean checkForRWPaths() {
boolean result = false;
String[] lines = mountReader();
if (lines == null) {
return false;
}
int sdkVersion = android.os.Build.VERSION.SDK_INT;
for (String line : lines) {
String[] args = line.split(" ");
// Check for correct argument length based on SDK version
if ((sdkVersion <= android.os.Build.VERSION_CODES.M && args.length < 4) ||
(sdkVersion > android.os.Build.VERSION_CODES.M && args.length < 6)) {
QLog.e("Error formatting mount line: " + line);
continue;
}
// Extract mount point and options
String mountPoint;
String mountOptions;
if (sdkVersion > android.os.Build.VERSION_CODES.M) {
mountPoint = args[2];
mountOptions = args[5];
} else {
mountPoint = args[1];
mountOptions = args[3];
}
// Check if the mount point is writable
for (String pathToCheck : Const.pathsThatShouldNotBeWritable) {
if (mountPoint.equalsIgnoreCase(pathToCheck)) {
if (sdkVersion > android.os.Build.VERSION_CODES.M) {
mountOptions = mountOptions.replace("(", "").replace(")", "");
}
for (String option : mountOptions.split(",")) {
if (option.equalsIgnoreCase("rw")) {
QLog.v(pathToCheck + " path is mounted with rw permissions! " + line);
result = true;
break;
}
}
}
}
}
return result;
}
2. 소스코드 난독화
> 안드로이드 앱은 APK 파일을 디컴파일링 하여 소스소드를 직접적으로 확인할 수 있습니다. 이렇게 되면 코드의 흐름을 파악하기가 쉽고 후킹도구로 조작하기도 쉽기 때문에 루팅 탐지 우회를 위한 포인트를 알아내기 쉽습니다. 따라서 소스코드 난독화 프로그램을 이용하여 소스코드를 난독화 시켜야 공격자가 코드흐름을 따라가는데 어려움을 줄 수 있습니다.
3. 무결성 검증 기능 적용
> 만일 무결성 검증을 통해 앱이 수정되었는지 확인을 안한다면 , 앱을 리패키징 하여 아예 루팅탐지 기능이 없게 만들수 있습니다. 따라서 앱의 사인키 검증같은 무결성 검증 방식을 추가하여야 합니다.
[참고]
https://blog.naver.com/is_king/222846114250?trackingCode=rss
[InsecureBankv2] Root Detection and Bypass - objection 도구를 이용한 루팅 탐지 우회 (후킹)
Root Detection and Bypass(루팅 탐지 우회) 취약점은 어플리케이션 내에서 루팅 권한을 가진 기기의 접...
blog.naver.com
https://hagsig.tistory.com/279
[AOS 취약점 진단] 16강 - 루팅 탐지 우회 취약점 점검
안드로이드·갤럭시·AOS 애플리케이션 취약점진단/모의해킹 무료 강의 학식(hagsig) 가. 취약점 정의 - 안드로이드는 사용자가 최상위 권한(root)을 획득할 수 없도록 되어있으나, 루팅(rooting)을 통
hagsig.tistory.com
https://philosopher-chan.tistory.com/355
root detection in android device
Root Detection in Android device 의 번역본입니다. Root Access is the process of allowing users smartphones, tablets and other devices running the Android mobile operating system to attain privileged control (known as root access). 루트 어세스
philosopher-chan.tistory.com
https://stackoverflow.com/questions/44235138/root-check-for-android-device
Root check for Android device
This is an old question and asked answered by many but here I go again.. I want to check whether the device is rooted or not. I don't want my application to be installed on rooted devices. Now I went
stackoverflow.com
긴 글 읽어주셔서 감사합니다!
'모바일 앱해킹(Android) > Insecure Bank' 카테고리의 다른 글
[Insecure Bank] 취약한 웹 뷰 실행 (0) | 2024.12.05 |
---|---|
[Insecure Bank] 안전하지 않은 콘텐츠 프로바이더 접근 (0) | 2024.12.04 |
[Insecure Bank] 액티비티 컴포넌트 취약점 (0) | 2024.11.29 |
[Insecure Bank] 로컬 암호화 이슈 취약점 (0) | 2024.11.26 |
[Insecure Bank] 취약한 인증 메커니즘 취약점 (0) | 2024.11.23 |